rubik 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ATTRIBUTION.md +1 -1
- data/CHANGELOG.md +54 -0
- data/lib/rubik/adapter/process.rb +11 -8
- data/lib/rubik/ast/go_extractor.rb +9 -2
- data/lib/rubik/ast/rust_extractor.rb +9 -2
- data/lib/rubik/ast/typescript_extractor.rb +13 -7
- data/lib/rubik/buffer/manager.rb +78 -66
- data/lib/rubik/config/defaults.rb +1 -0
- data/lib/rubik/context/git_lister.rb +3 -1
- data/lib/rubik/llm/anthropic.rb +5 -2
- data/lib/rubik/llm/openai_compatible.rb +83 -68
- data/lib/rubik/lsp/client.rb +320 -96
- data/lib/rubik/lsp/manager.rb +83 -12
- data/lib/rubik/lsp/server_command.rb +102 -0
- data/lib/rubik/orchestrator/lsp_feedback.rb +20 -4
- data/lib/rubik/orchestrator/subagent/spawn_transaction.rb +60 -31
- data/lib/rubik/repl/commands/agent_context.rb +126 -104
- data/lib/rubik/repl/commands/agent_execution.rb +83 -68
- data/lib/rubik/repl/commands/agent_models.rb +112 -87
- data/lib/rubik/repl/commands/buffer.rb +122 -88
- data/lib/rubik/repl/commands/mcp.rb +120 -92
- data/lib/rubik/repl/commands/session.rb +90 -59
- data/lib/rubik/repl/commands/workspace.rb +110 -94
- data/lib/rubik/repl/shell.rb +10 -1
- data/lib/rubik/runtime/discovery.rb +12 -6
- data/lib/rubik/storage/vakanuvis/distill_gate.rb +6 -2
- data/lib/rubik/storage/vakanuvis/gate.rb +37 -16
- data/lib/rubik/storage/vakanuvis/harness.rb +36 -21
- data/lib/rubik/storage/vakanuvis/memory_steward.rb +12 -6
- data/lib/rubik/tools/builtins/lifecycle.rb +34 -7
- data/lib/rubik/tools/builtins/lsp.rb +36 -8
- data/lib/rubik/version.rb +1 -1
- metadata +2 -1
data/lib/rubik/lsp/client.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "open3"
|
|
4
4
|
require "json"
|
|
5
5
|
require "uri"
|
|
6
|
+
require_relative "server_command"
|
|
6
7
|
require_relative "../platform"
|
|
7
8
|
require_relative "../log"
|
|
8
9
|
|
|
@@ -34,18 +35,20 @@ module Rubik
|
|
|
34
35
|
"vue" => "vue-language-server --stdio"
|
|
35
36
|
}.freeze
|
|
36
37
|
|
|
37
|
-
attr_reader :language, :server_cmd, :root_dir, :running, :diagnostics, :server_capabilities
|
|
38
|
+
attr_reader :language, :server_cmd, :root_dir, :running, :diagnostics, :server_capabilities,
|
|
39
|
+
:sync_capabilities
|
|
38
40
|
|
|
39
41
|
def initialize(language, root_dir: Dir.pwd, server_cmd: nil)
|
|
40
42
|
@language = language.to_s.downcase
|
|
41
43
|
@root_dir = Rubik::Platform.expand_path(root_dir)
|
|
42
44
|
cmd = server_cmd || SERVERS[@language]
|
|
43
|
-
if cmd.to_s.start_with?("typeprof", "typescript-language-server") && !cmd.to_s.include?("--stdio")
|
|
44
|
-
cmd = "#{cmd} --stdio"
|
|
45
|
-
elsif cmd.to_s.start_with?("solargraph") && !cmd.to_s.include?("stdio")
|
|
46
|
-
cmd = "#{cmd} stdio"
|
|
47
|
-
end
|
|
48
45
|
@server_cmd = cmd
|
|
46
|
+
@server_argv = ServerCommand.argv(cmd)
|
|
47
|
+
if ["typeprof", "typescript-language-server"].include?(File.basename(@server_argv.first.to_s)) && !@server_argv.include?("--stdio")
|
|
48
|
+
@server_argv << "--stdio"
|
|
49
|
+
elsif File.basename(@server_argv.first.to_s) == "solargraph" && !@server_argv.include?("stdio")
|
|
50
|
+
@server_argv << "stdio"
|
|
51
|
+
end
|
|
49
52
|
@running = false
|
|
50
53
|
@crashed = false
|
|
51
54
|
@req_id = 0
|
|
@@ -56,43 +59,59 @@ module Rubik
|
|
|
56
59
|
@reader_thr = nil
|
|
57
60
|
@stderr_drain = nil
|
|
58
61
|
@mutex = Mutex.new
|
|
62
|
+
@document_mutex = Mutex.new
|
|
63
|
+
@write_mutex = Mutex.new
|
|
59
64
|
@cv = ConditionVariable.new
|
|
60
65
|
@responses = {}
|
|
61
66
|
@pending_requests = {}
|
|
62
67
|
@reader_failure = nil
|
|
63
68
|
@diagnostics = {}
|
|
64
69
|
@open_documents = {}
|
|
70
|
+
@document_contents = {}
|
|
71
|
+
@diagnostic_states = {}
|
|
65
72
|
@version_counter = 0
|
|
66
73
|
@server_capabilities = {}
|
|
74
|
+
@sync_capabilities = {
|
|
75
|
+
source: :not_advertised, open_close: false, change: 0,
|
|
76
|
+
save: false, include_text: false
|
|
77
|
+
}.freeze
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
def binary_name
|
|
70
|
-
@
|
|
81
|
+
@server_argv.first
|
|
71
82
|
end
|
|
72
83
|
|
|
73
84
|
def available?
|
|
74
85
|
bin = binary_name
|
|
75
86
|
return false if bin.nil? || bin.empty?
|
|
76
87
|
|
|
77
|
-
!
|
|
88
|
+
!resolved_binary(bin).nil?
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def start
|
|
81
92
|
return false unless available?
|
|
82
93
|
|
|
83
|
-
@
|
|
94
|
+
@document_mutex.synchronize do
|
|
84
95
|
@req_id = 0
|
|
96
|
+
@open_documents.clear
|
|
97
|
+
@document_contents.clear
|
|
98
|
+
@version_counter = 0
|
|
99
|
+
@mutex.synchronize do
|
|
100
|
+
@diagnostics.clear
|
|
101
|
+
@diagnostic_states.clear
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
@mutex.synchronize do
|
|
85
105
|
@responses.clear
|
|
86
106
|
@pending_requests.clear
|
|
87
107
|
@reader_failure = nil
|
|
88
|
-
@open_documents.clear
|
|
89
|
-
@diagnostics.clear
|
|
90
|
-
@version_counter = 0
|
|
91
108
|
end
|
|
92
109
|
|
|
93
|
-
cmd_parts = @
|
|
110
|
+
cmd_parts = @server_argv.dup
|
|
94
111
|
bin = cmd_parts.first
|
|
95
|
-
resolved_bin =
|
|
112
|
+
resolved_bin = resolved_binary(bin)
|
|
113
|
+
return false unless resolved_bin
|
|
114
|
+
|
|
96
115
|
cmd_parts[0] = resolved_bin if resolved_bin
|
|
97
116
|
|
|
98
117
|
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*cmd_parts, chdir: @root_dir)
|
|
@@ -118,8 +137,14 @@ module Rubik
|
|
|
118
137
|
capabilities: {
|
|
119
138
|
textDocument: {
|
|
120
139
|
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
121
|
-
|
|
122
|
-
|
|
140
|
+
synchronization: {
|
|
141
|
+
dynamicRegistration: false,
|
|
142
|
+
willSave: false,
|
|
143
|
+
willSaveWaitUntil: false,
|
|
144
|
+
didSave: true
|
|
145
|
+
},
|
|
146
|
+
definition: { dynamicRegistration: false },
|
|
147
|
+
references: { dynamicRegistration: false },
|
|
123
148
|
publishDiagnostics: { relatedInformation: true }
|
|
124
149
|
}
|
|
125
150
|
}
|
|
@@ -131,7 +156,11 @@ module Rubik
|
|
|
131
156
|
end
|
|
132
157
|
|
|
133
158
|
@server_capabilities = init_res.is_a?(Hash) ? (init_res["capabilities"] || {}) : {}
|
|
134
|
-
|
|
159
|
+
@sync_capabilities = normalize_sync_capabilities(@server_capabilities["textDocumentSync"])
|
|
160
|
+
unless send_notification("initialized", {})
|
|
161
|
+
stop
|
|
162
|
+
return false
|
|
163
|
+
end
|
|
135
164
|
@running = true
|
|
136
165
|
init_res
|
|
137
166
|
rescue StandardError
|
|
@@ -246,23 +275,41 @@ module Rubik
|
|
|
246
275
|
end
|
|
247
276
|
|
|
248
277
|
def did_open(file_path, content, language_id = @language)
|
|
249
|
-
return false unless alive?
|
|
278
|
+
return false unless alive? && @sync_capabilities[:open_close]
|
|
250
279
|
|
|
251
280
|
abs_path = normalized_document_path(file_path)
|
|
252
|
-
@
|
|
253
|
-
@
|
|
254
|
-
|
|
255
|
-
end
|
|
281
|
+
@document_mutex.synchronize do
|
|
282
|
+
if @open_documents.key?(abs_path)
|
|
283
|
+
return true if @document_contents[abs_path] == content
|
|
256
284
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
285
|
+
return send_document_change_unlocked(abs_path, content)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
version = next_document_version
|
|
289
|
+
@open_documents[abs_path] = version
|
|
290
|
+
@document_contents[abs_path] = content
|
|
291
|
+
old_state = @mutex.synchronize do
|
|
292
|
+
previous = @diagnostic_states[abs_path]
|
|
293
|
+
@diagnostic_states[abs_path] = pending_diagnostics(version)
|
|
294
|
+
previous
|
|
295
|
+
end
|
|
296
|
+
sent = send_notification("textDocument/didOpen", {
|
|
297
|
+
textDocument: {
|
|
298
|
+
uri: document_uri(abs_path),
|
|
299
|
+
languageId: language_id.to_s,
|
|
300
|
+
version: version,
|
|
301
|
+
text: content
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
unless sent
|
|
305
|
+
@open_documents.delete(abs_path)
|
|
306
|
+
@document_contents.delete(abs_path)
|
|
307
|
+
@mutex.synchronize do
|
|
308
|
+
old_state ? @diagnostic_states[abs_path] = old_state : @diagnostic_states.delete(abs_path)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
sent
|
|
312
|
+
end
|
|
266
313
|
rescue StandardError
|
|
267
314
|
false
|
|
268
315
|
end
|
|
@@ -271,44 +318,50 @@ module Rubik
|
|
|
271
318
|
return false unless alive?
|
|
272
319
|
|
|
273
320
|
abs_path = normalized_document_path(file_path)
|
|
274
|
-
|
|
275
|
-
@version_counter += 1
|
|
276
|
-
@open_documents[abs_path] = @version_counter
|
|
277
|
-
@diagnostics.delete(abs_path)
|
|
278
|
-
@diagnostics.delete(file_path)
|
|
279
|
-
@diagnostics.delete_if { |k, _| Rubik::Platform.path_equal?(k, abs_path) }
|
|
280
|
-
@version_counter
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
send_notification("textDocument/didChange", {
|
|
284
|
-
textDocument: {
|
|
285
|
-
uri: document_uri(abs_path),
|
|
286
|
-
version: version
|
|
287
|
-
},
|
|
288
|
-
contentChanges: [
|
|
289
|
-
{ text: content }
|
|
290
|
-
]
|
|
291
|
-
})
|
|
292
|
-
true
|
|
321
|
+
@document_mutex.synchronize { send_document_change_unlocked(abs_path, content) }
|
|
293
322
|
rescue StandardError
|
|
294
323
|
false
|
|
295
324
|
end
|
|
296
325
|
|
|
297
326
|
def did_close(file_path)
|
|
298
|
-
return false unless @running
|
|
327
|
+
return false unless @running && @sync_capabilities[:open_close]
|
|
299
328
|
|
|
300
329
|
abs_path = normalized_document_path(file_path)
|
|
301
|
-
|
|
302
|
-
|
|
330
|
+
@document_mutex.synchronize do
|
|
331
|
+
version = @open_documents[abs_path]
|
|
332
|
+
return false unless version
|
|
333
|
+
|
|
334
|
+
content = @document_contents[abs_path]
|
|
335
|
+
state, diagnostics = @mutex.synchronize do
|
|
336
|
+
[@diagnostic_states.delete(abs_path), @diagnostics.delete(abs_path)]
|
|
337
|
+
end
|
|
303
338
|
@open_documents.delete(abs_path)
|
|
304
|
-
@
|
|
305
|
-
|
|
339
|
+
@document_contents.delete(abs_path)
|
|
340
|
+
sent = send_notification("textDocument/didClose", {
|
|
341
|
+
textDocument: { uri: document_uri(abs_path) }
|
|
342
|
+
})
|
|
343
|
+
unless sent
|
|
344
|
+
@open_documents[abs_path] = version
|
|
345
|
+
@document_contents[abs_path] = content
|
|
346
|
+
@mutex.synchronize do
|
|
347
|
+
@diagnostic_states[abs_path] = state if state
|
|
348
|
+
@diagnostics[abs_path] = diagnostics if diagnostics
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
sent
|
|
306
352
|
end
|
|
353
|
+
rescue StandardError
|
|
354
|
+
false
|
|
355
|
+
end
|
|
307
356
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
357
|
+
def did_save(file_path, content = nil)
|
|
358
|
+
return false unless alive? && @sync_capabilities[:save]
|
|
359
|
+
return false if @sync_capabilities[:include_text] && content.nil?
|
|
360
|
+
|
|
361
|
+
abs_path = normalized_document_path(file_path)
|
|
362
|
+
params = { textDocument: { uri: document_uri(abs_path) } }
|
|
363
|
+
params[:text] = content if @sync_capabilities[:include_text] && !content.nil?
|
|
364
|
+
send_notification("textDocument/didSave", params)
|
|
312
365
|
rescue StandardError
|
|
313
366
|
false
|
|
314
367
|
end
|
|
@@ -317,34 +370,54 @@ module Rubik
|
|
|
317
370
|
return false if file_path.nil? || file_path.to_s.strip.empty?
|
|
318
371
|
|
|
319
372
|
abs_path = normalized_document_path(file_path)
|
|
320
|
-
|
|
321
|
-
@mutex.synchronize do
|
|
322
|
-
@open_documents.key?(abs_path) ||
|
|
323
|
-
@open_documents.key?(raw_abs) ||
|
|
324
|
-
@open_documents.keys.any? { |k| Rubik::Platform.path_equal?(k, abs_path) }
|
|
325
|
-
end
|
|
373
|
+
@document_mutex.synchronize { @open_documents.key?(abs_path) }
|
|
326
374
|
rescue ArgumentError
|
|
327
375
|
false
|
|
328
376
|
end
|
|
329
377
|
|
|
330
378
|
def diagnostics_for(file_path)
|
|
379
|
+
diagnostics_snapshot_for(file_path)[:diagnostics]
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def all_diagnostics
|
|
383
|
+
@mutex.synchronize do
|
|
384
|
+
@diagnostics.dup
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def diagnostics_snapshot_for(file_path)
|
|
389
|
+
abs_path = normalized_document_path(file_path)
|
|
390
|
+
@mutex.synchronize { diagnostics_snapshot_unlocked(abs_path) }
|
|
391
|
+
rescue ArgumentError, TypeError
|
|
392
|
+
{ state: :unavailable, diagnostics: [], document_version: nil, published_version: nil }
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def wait_for_diagnostics(file_path, timeout: 0.2)
|
|
331
396
|
abs_path = normalized_document_path(file_path)
|
|
332
|
-
|
|
333
|
-
root_norm = Rubik::Platform.normalize_path_raw(@root_dir)
|
|
334
|
-
rel_path = raw_abs.start_with?("#{root_norm}/") ? raw_abs.delete_prefix("#{root_norm}/") : file_path.to_s.tr("\\", "/")
|
|
397
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
335
398
|
@mutex.synchronize do
|
|
336
|
-
@
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
@
|
|
341
|
-
|
|
399
|
+
while @diagnostic_states[abs_path]&.dig(:state) == :pending
|
|
400
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
401
|
+
break if remaining <= 0
|
|
402
|
+
|
|
403
|
+
@cv.wait(@mutex, remaining)
|
|
404
|
+
end
|
|
405
|
+
diagnostics_snapshot_unlocked(abs_path)
|
|
342
406
|
end
|
|
407
|
+
rescue ArgumentError, TypeError
|
|
408
|
+
{ state: :unavailable, diagnostics: [], document_version: nil, published_version: nil }
|
|
343
409
|
end
|
|
344
410
|
|
|
345
|
-
def
|
|
411
|
+
def all_diagnostic_snapshots
|
|
346
412
|
@mutex.synchronize do
|
|
347
|
-
@
|
|
413
|
+
@diagnostic_states.each_with_object({}) do |(path, state), result|
|
|
414
|
+
result[path] = {
|
|
415
|
+
state: state[:state],
|
|
416
|
+
diagnostics: (@diagnostics[path] || []).dup,
|
|
417
|
+
document_version: state[:document_version],
|
|
418
|
+
published_version: state[:published_version]
|
|
419
|
+
}
|
|
420
|
+
end
|
|
348
421
|
end
|
|
349
422
|
end
|
|
350
423
|
|
|
@@ -417,16 +490,22 @@ module Rubik
|
|
|
417
490
|
uri = params["uri"].to_s
|
|
418
491
|
raw_path = Rubik::Platform.from_file_uri(uri)
|
|
419
492
|
norm_path = normalized_document_path(raw_path)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
@
|
|
428
|
-
|
|
429
|
-
|
|
493
|
+
diags = params["diagnostics"].is_a?(Array) ? params["diagnostics"] : []
|
|
494
|
+
published_version = params["version"]
|
|
495
|
+
|
|
496
|
+
@document_mutex.synchronize do
|
|
497
|
+
current_version = @open_documents[norm_path]
|
|
498
|
+
next if published_version.is_a?(Numeric) && current_version && published_version < current_version
|
|
499
|
+
|
|
500
|
+
@mutex.synchronize do
|
|
501
|
+
@diagnostics[norm_path] = diags
|
|
502
|
+
@diagnostic_states[norm_path] = {
|
|
503
|
+
state: published_version.is_a?(Numeric) ? :fresh : :unversioned,
|
|
504
|
+
document_version: current_version,
|
|
505
|
+
published_version: published_version
|
|
506
|
+
}
|
|
507
|
+
@cv.broadcast
|
|
508
|
+
end
|
|
430
509
|
end
|
|
431
510
|
end
|
|
432
511
|
end
|
|
@@ -443,7 +522,13 @@ module Rubik
|
|
|
443
522
|
method: method,
|
|
444
523
|
params: params
|
|
445
524
|
}
|
|
446
|
-
write_message(msg)
|
|
525
|
+
unless write_message(msg)
|
|
526
|
+
@mutex.synchronize do
|
|
527
|
+
@pending_requests.delete(req_id)
|
|
528
|
+
@responses.delete(req_id)
|
|
529
|
+
end
|
|
530
|
+
raise RequestError.new(:transport_failure, @reader_failure || "LSP request write failed")
|
|
531
|
+
end
|
|
447
532
|
wait_for_response(req_id, timeout: timeout)
|
|
448
533
|
end
|
|
449
534
|
|
|
@@ -501,10 +586,133 @@ module Rubik
|
|
|
501
586
|
end
|
|
502
587
|
|
|
503
588
|
def normalized_document_path(file_path)
|
|
589
|
+
raise ArgumentError, "file_path cannot be nil or empty" if file_path.nil? || file_path.to_s.strip.empty?
|
|
590
|
+
|
|
591
|
+
return normalize_windows_document_path(file_path) if windows_document_path?(file_path) || windows_document_path?(@root_dir)
|
|
592
|
+
|
|
504
593
|
path = Rubik::Platform.expand_path(file_path, @root_dir)
|
|
505
594
|
Rubik::Platform.normalize_path(path)
|
|
506
595
|
end
|
|
507
596
|
|
|
597
|
+
def windows_document_path?(path)
|
|
598
|
+
path.to_s.match?(%r{\A[A-Za-z]:[\\/]}) || path.to_s.start_with?("\\\\")
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def normalize_windows_document_path(file_path)
|
|
602
|
+
raw = file_path.to_s.tr("\\", "/")
|
|
603
|
+
root = @root_dir.to_s.tr("\\", "/")
|
|
604
|
+
combined = if raw.match?(%r{\A[A-Za-z]:/}) || raw.start_with?("//")
|
|
605
|
+
raw
|
|
606
|
+
else
|
|
607
|
+
"#{root}/#{raw}"
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
prefix = combined.match(%r{\A([A-Za-z]:|//[^/]+/[^/]+)})&.[](1)
|
|
611
|
+
segments = combined.delete_prefix(prefix.to_s).split("/").each_with_object([]) do |segment, result|
|
|
612
|
+
next if segment.empty? || segment == "."
|
|
613
|
+
|
|
614
|
+
segment == ".." ? result.pop : result << segment
|
|
615
|
+
end
|
|
616
|
+
[prefix, *segments].compact.reject(&:empty?).join("/").downcase
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def next_document_version
|
|
620
|
+
@version_counter += 1
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
def resolved_binary(bin)
|
|
624
|
+
if bin.include?("/") || (Rubik::Platform.windows? && bin.include?("\\"))
|
|
625
|
+
path = File.expand_path(bin, @root_dir)
|
|
626
|
+
return path if Rubik::Platform.executable_file?(path)
|
|
627
|
+
|
|
628
|
+
return nil
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
Rubik::Platform.which(bin)
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def pending_diagnostics(version)
|
|
635
|
+
{
|
|
636
|
+
state: :pending,
|
|
637
|
+
document_version: version,
|
|
638
|
+
published_version: nil
|
|
639
|
+
}
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def diagnostics_snapshot_unlocked(abs_path)
|
|
643
|
+
state = @diagnostic_states[abs_path]
|
|
644
|
+
{
|
|
645
|
+
state: state ? state[:state] : :unavailable,
|
|
646
|
+
diagnostics: (@diagnostics[abs_path] || []).dup,
|
|
647
|
+
document_version: state && state[:document_version],
|
|
648
|
+
published_version: state && state[:published_version]
|
|
649
|
+
}
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def send_document_change_unlocked(abs_path, content)
|
|
653
|
+
return false unless @sync_capabilities[:change].to_i.positive?
|
|
654
|
+
|
|
655
|
+
previous_version = @open_documents[abs_path]
|
|
656
|
+
return false unless previous_version
|
|
657
|
+
return true if @document_contents[abs_path] == content
|
|
658
|
+
|
|
659
|
+
previous_content = @document_contents[abs_path]
|
|
660
|
+
previous_state = @mutex.synchronize { @diagnostic_states[abs_path] }
|
|
661
|
+
version = next_document_version
|
|
662
|
+
@open_documents[abs_path] = version
|
|
663
|
+
@document_contents[abs_path] = content
|
|
664
|
+
@mutex.synchronize { @diagnostic_states[abs_path] = pending_diagnostics(version) }
|
|
665
|
+
|
|
666
|
+
sent = send_notification("textDocument/didChange", {
|
|
667
|
+
textDocument: {
|
|
668
|
+
uri: document_uri(abs_path),
|
|
669
|
+
version: version
|
|
670
|
+
},
|
|
671
|
+
# A text-only event is a full-document replacement,
|
|
672
|
+
# which is valid for both Full and Incremental sync.
|
|
673
|
+
contentChanges: [{ text: content }]
|
|
674
|
+
})
|
|
675
|
+
unless sent
|
|
676
|
+
@open_documents[abs_path] = previous_version
|
|
677
|
+
@document_contents[abs_path] = previous_content
|
|
678
|
+
@mutex.synchronize do
|
|
679
|
+
previous_state ? @diagnostic_states[abs_path] = previous_state : @diagnostic_states.delete(abs_path)
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
sent
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def normalize_sync_capabilities(value)
|
|
686
|
+
case value
|
|
687
|
+
when Numeric
|
|
688
|
+
change = value.to_i.clamp(0, 2)
|
|
689
|
+
{
|
|
690
|
+
source: :legacy_numeric,
|
|
691
|
+
open_close: change.positive?,
|
|
692
|
+
change: change,
|
|
693
|
+
save: false,
|
|
694
|
+
include_text: false
|
|
695
|
+
}.freeze
|
|
696
|
+
when Hash
|
|
697
|
+
save = value["save"]
|
|
698
|
+
{
|
|
699
|
+
source: :options,
|
|
700
|
+
open_close: value["openClose"] == true,
|
|
701
|
+
change: (value["change"] || 0).to_i.clamp(0, 2),
|
|
702
|
+
save: !!save,
|
|
703
|
+
include_text: save.is_a?(Hash) && save["includeText"] == true
|
|
704
|
+
}.freeze
|
|
705
|
+
else
|
|
706
|
+
{
|
|
707
|
+
source: :not_advertised,
|
|
708
|
+
open_close: false,
|
|
709
|
+
change: 0,
|
|
710
|
+
save: false,
|
|
711
|
+
include_text: false
|
|
712
|
+
}.freeze
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
508
716
|
def document_uri(file_path)
|
|
509
717
|
Rubik::Platform.to_file_uri(normalized_document_path(file_path))
|
|
510
718
|
end
|
|
@@ -515,7 +723,12 @@ module Rubik
|
|
|
515
723
|
params = msg["params"]
|
|
516
724
|
items = params.is_a?(Hash) && params["items"].is_a?(Array) ? params["items"] : []
|
|
517
725
|
write_message({ jsonrpc: "2.0", id: msg["id"], result: items.map { {} } })
|
|
518
|
-
when "client/registerCapability", "client/unregisterCapability"
|
|
726
|
+
when "client/registerCapability", "client/unregisterCapability"
|
|
727
|
+
write_message({
|
|
728
|
+
jsonrpc: "2.0", id: msg["id"],
|
|
729
|
+
error: { code: -32_602, message: "Dynamic capability registration is not supported" }
|
|
730
|
+
})
|
|
731
|
+
when "window/workDoneProgress/create"
|
|
519
732
|
write_message({ jsonrpc: "2.0", id: msg["id"], result: nil })
|
|
520
733
|
else
|
|
521
734
|
write_message({
|
|
@@ -538,16 +751,27 @@ module Rubik
|
|
|
538
751
|
def write_message(msg_hash)
|
|
539
752
|
json = JSON.dump(msg_hash)
|
|
540
753
|
header = "Content-Length: #{json.bytesize}\r\n\r\n"
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
754
|
+
bytes = header + json
|
|
755
|
+
@write_mutex.synchronize do
|
|
756
|
+
offset = 0
|
|
757
|
+
while offset < bytes.bytesize
|
|
758
|
+
written = @stdin.write(bytes.byteslice(offset, bytes.bytesize - offset))
|
|
759
|
+
raise IOError, "LSP transport made no write progress" unless written&.positive?
|
|
760
|
+
|
|
761
|
+
offset += written
|
|
762
|
+
end
|
|
547
763
|
@stdin.flush
|
|
548
|
-
rescue IOError, SystemCallError
|
|
549
|
-
nil
|
|
550
764
|
end
|
|
765
|
+
true
|
|
766
|
+
rescue IOError, SystemCallError, NoMethodError => e
|
|
767
|
+
message = "LSP transport write failed: #{e.class}: #{e.message}"
|
|
768
|
+
@mutex.synchronize do
|
|
769
|
+
@reader_failure ||= message
|
|
770
|
+
@running = false
|
|
771
|
+
@crashed = true if @wait_thr
|
|
772
|
+
@cv.broadcast
|
|
773
|
+
end
|
|
774
|
+
false
|
|
551
775
|
end
|
|
552
776
|
end
|
|
553
777
|
end
|