rubik 0.3.1 → 0.3.2
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 +50 -0
- data/lib/rubik/ast/cache_store.rb +4 -2
- data/lib/rubik/ast/graph.rb +49 -94
- data/lib/rubik/ast/graph_extensions.rb +202 -0
- data/lib/rubik/ast/ruby_extractor.rb +8 -3
- data/lib/rubik/ast/symbol.rb +7 -0
- data/lib/rubik/ast/treesitter/base_walker.rb +4 -3
- data/lib/rubik/ast/treesitter/c_walker.rb +10 -8
- data/lib/rubik/ast/treesitter/go_walker.rb +4 -2
- data/lib/rubik/ast/treesitter/rust_walker.rb +9 -7
- data/lib/rubik/ast/treesitter/typescript_walker.rb +7 -5
- data/lib/rubik/ast/treesitter/zig_walker.rb +44 -6
- data/lib/rubik/lsp/client.rb +102 -24
- data/lib/rubik/lsp/manager.rb +136 -83
- data/lib/rubik/orchestrator/tool_circuit_breaker.rb +15 -5
- data/lib/rubik/prompt/token_estimator.rb +15 -3
- data/lib/rubik/repl/ast_warmup.rb +35 -4
- data/lib/rubik/repl/shell.rb +1 -1
- data/lib/rubik/tools/builtins/symbols.rb +56 -20
- data/lib/rubik/tools/symbol_tool_support.rb +113 -0
- data/lib/rubik/version.rb +1 -1
- metadata +3 -1
|
@@ -16,7 +16,9 @@ module Rubik
|
|
|
16
16
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
|
-
def walk_children(node, symbols, impl_type)
|
|
19
|
+
def walk_children(node, symbols, impl_type, depth: 0)
|
|
20
|
+
return if depth > 50
|
|
21
|
+
|
|
20
22
|
(0...node.named_child_count).each do |i|
|
|
21
23
|
child = node.named_child(i)
|
|
22
24
|
case child.kind
|
|
@@ -25,13 +27,13 @@ module Rubik
|
|
|
25
27
|
when "enum_item"
|
|
26
28
|
walk_enum(child, symbols)
|
|
27
29
|
when "impl_item"
|
|
28
|
-
walk_impl(child, symbols)
|
|
30
|
+
walk_impl(child, symbols, depth: depth + 1)
|
|
29
31
|
when "function_item"
|
|
30
32
|
walk_function(child, symbols, impl_type)
|
|
31
33
|
when "use_declaration"
|
|
32
34
|
walk_use(child, symbols)
|
|
33
35
|
else
|
|
34
|
-
walk_children(child, symbols, impl_type)
|
|
36
|
+
walk_children(child, symbols, impl_type, depth: depth + 1)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
end
|
|
@@ -64,12 +66,12 @@ module Rubik
|
|
|
64
66
|
)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
def walk_impl(node, symbols)
|
|
69
|
+
def walk_impl(node, symbols, depth: 0)
|
|
68
70
|
type_node = node.child_by_field_name("type")
|
|
69
71
|
impl_name = type_node ? text_for(type_node).strip : nil
|
|
70
72
|
|
|
71
73
|
body = node.child_by_field_name("body")
|
|
72
|
-
walk_children(body, symbols, impl_name) if body
|
|
74
|
+
walk_children(body, symbols, impl_name, depth: depth + 1) if body
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
def walk_function(node, symbols, impl_type)
|
|
@@ -110,13 +112,13 @@ module Rubik
|
|
|
110
112
|
if n.kind == "call_expression"
|
|
111
113
|
fn = n.child_by_field_name("function")
|
|
112
114
|
if fn
|
|
113
|
-
fn_text = text_for(fn).
|
|
115
|
+
fn_text = text_for(fn).strip
|
|
114
116
|
calls << fn_text if fn_text && !fn_text.empty?
|
|
115
117
|
end
|
|
116
118
|
elsif n.kind == "macro_invocation"
|
|
117
119
|
macro = n.child_by_field_name("macro") || n.named_child(0)
|
|
118
120
|
if macro
|
|
119
|
-
macro_text = text_for(macro).sub(/!\z/, "").
|
|
121
|
+
macro_text = text_for(macro).sub(/!\z/, "").strip
|
|
120
122
|
calls << macro_text if macro_text && !macro_text.empty?
|
|
121
123
|
end
|
|
122
124
|
end
|
|
@@ -16,12 +16,14 @@ module Rubik
|
|
|
16
16
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
|
-
def walk_children(node, symbols, current_class)
|
|
19
|
+
def walk_children(node, symbols, current_class, depth: 0)
|
|
20
|
+
return if depth > 50
|
|
21
|
+
|
|
20
22
|
(0...node.named_child_count).each do |i|
|
|
21
23
|
child = node.named_child(i)
|
|
22
24
|
case child.kind
|
|
23
25
|
when "export_statement"
|
|
24
|
-
walk_export(child, symbols, current_class)
|
|
26
|
+
walk_export(child, symbols, current_class, depth: depth + 1)
|
|
25
27
|
when "interface_declaration"
|
|
26
28
|
walk_interface(child, symbols)
|
|
27
29
|
when "enum_declaration"
|
|
@@ -35,12 +37,12 @@ module Rubik
|
|
|
35
37
|
when "import_statement"
|
|
36
38
|
walk_import(child, symbols)
|
|
37
39
|
else
|
|
38
|
-
walk_children(child, symbols, current_class)
|
|
40
|
+
walk_children(child, symbols, current_class, depth: depth + 1)
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
def walk_export(node, symbols, current_class)
|
|
45
|
+
def walk_export(node, symbols, current_class, depth:)
|
|
44
46
|
(0...node.named_child_count).each do |i|
|
|
45
47
|
child = node.named_child(i)
|
|
46
48
|
case child.kind
|
|
@@ -49,7 +51,7 @@ module Rubik
|
|
|
49
51
|
when "class_declaration" then walk_class(child, symbols)
|
|
50
52
|
when "function_declaration" then walk_function(child, symbols)
|
|
51
53
|
when "lexical_declaration", "variable_declaration" then walk_variable_declaration(child, symbols)
|
|
52
|
-
else walk_children(child, symbols, current_class)
|
|
54
|
+
else walk_children(child, symbols, current_class, depth: depth + 1)
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
end
|
|
@@ -19,14 +19,50 @@ module Rubik
|
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# Zig represents a method receiver as the prefix of a SuffixExpr while
|
|
23
|
+
# FieldOrFnCall contains only the trailing `.method()` node. Preserve
|
|
24
|
+
# the full expression so unresolved receiver dispatch is not flattened
|
|
25
|
+
# into a call to every same-named function.
|
|
26
|
+
def extract_calls(node, call_kinds = ["call", "call_expression", "invocation"])
|
|
27
|
+
return super unless call_kinds.include?("FieldOrFnCall")
|
|
28
|
+
|
|
29
|
+
calls = []
|
|
30
|
+
stack = [node]
|
|
31
|
+
while (current = stack.pop)
|
|
32
|
+
if current.kind == "FieldOrFnCall"
|
|
33
|
+
args_node = (0...current.named_child_count).map { |i| current.named_child(i) }
|
|
34
|
+
.find { |child| child.kind == "FnCallArguments" }
|
|
35
|
+
if args_node
|
|
36
|
+
suffix = current.parent
|
|
37
|
+
start_byte = suffix&.kind == "SuffixExpr" ? suffix.start_byte : current.start_byte
|
|
38
|
+
fn_text = (@code.byteslice(start_byte...args_node.start_byte) || "").strip
|
|
39
|
+
calls << fn_text unless fn_text.empty?
|
|
40
|
+
end
|
|
41
|
+
elsif current.kind == "SuffixExpr" &&
|
|
42
|
+
current.named_child_count >= 2 &&
|
|
43
|
+
current.named_child(1)&.kind == "FnCallArguments"
|
|
44
|
+
fn_text = text_for(current.named_child(0)).strip
|
|
45
|
+
calls << fn_text unless fn_text.empty?
|
|
46
|
+
elsif call_kinds.include?(current.kind)
|
|
47
|
+
fn = current.child_by_field_name("function") || current.child_by_field_name("name") || current.named_child(0)
|
|
48
|
+
fn_text = text_for(fn).strip if fn
|
|
49
|
+
calls << fn_text if fn_text && !fn_text.empty?
|
|
50
|
+
end
|
|
51
|
+
(0...current.named_child_count).reverse_each { |i| stack.push(current.named_child(i)) }
|
|
52
|
+
end
|
|
53
|
+
calls.uniq
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def walk_children(node, symbols, depth: 0)
|
|
57
|
+
return if depth > 50
|
|
58
|
+
|
|
23
59
|
(0...node.named_child_count).each do |i|
|
|
24
60
|
child = node.named_child(i)
|
|
25
61
|
case child.kind
|
|
26
62
|
when "Decl"
|
|
27
63
|
walk_decl(child, symbols)
|
|
28
64
|
else
|
|
29
|
-
walk_children(child, symbols)
|
|
65
|
+
walk_children(child, symbols, depth: depth + 1)
|
|
30
66
|
end
|
|
31
67
|
end
|
|
32
68
|
end
|
|
@@ -82,11 +118,12 @@ module Rubik
|
|
|
82
118
|
# Locates the node that owns this Decl's function definition. Zig
|
|
83
119
|
# exposes it under several kind names across grammar versions, so we
|
|
84
120
|
# accept every one we know about rather than only "FnProto".
|
|
85
|
-
def find_function(node)
|
|
121
|
+
def find_function(node, depth: 0)
|
|
122
|
+
return if depth > 50
|
|
86
123
|
return node if FN_KINDS.include?(node.kind)
|
|
87
124
|
|
|
88
125
|
(0...node.named_child_count).each do |i|
|
|
89
|
-
found = find_function(node.named_child(i))
|
|
126
|
+
found = find_function(node.named_child(i), depth: depth + 1)
|
|
90
127
|
return found if found
|
|
91
128
|
end
|
|
92
129
|
nil
|
|
@@ -94,11 +131,12 @@ module Rubik
|
|
|
94
131
|
|
|
95
132
|
# Walks the subtree for the first identifier node — used as a fallback
|
|
96
133
|
# when the grammar does not expose the function name as a named field.
|
|
97
|
-
def find_identifier(node)
|
|
134
|
+
def find_identifier(node, depth: 0)
|
|
135
|
+
return if depth > 50
|
|
98
136
|
return node if node.kind == "identifier"
|
|
99
137
|
|
|
100
138
|
(0...node.named_child_count).each do |i|
|
|
101
|
-
found = find_identifier(node.named_child(i))
|
|
139
|
+
found = find_identifier(node.named_child(i), depth: depth + 1)
|
|
102
140
|
return found if found
|
|
103
141
|
end
|
|
104
142
|
nil
|
data/lib/rubik/lsp/client.rb
CHANGED
|
@@ -9,6 +9,15 @@ require_relative "../log"
|
|
|
9
9
|
module Rubik
|
|
10
10
|
module LSP
|
|
11
11
|
class Client
|
|
12
|
+
class RequestError < StandardError
|
|
13
|
+
attr_reader :reason
|
|
14
|
+
|
|
15
|
+
def initialize(reason, message)
|
|
16
|
+
@reason = reason
|
|
17
|
+
super(message)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
SERVERS = {
|
|
13
22
|
"go" => "gopls",
|
|
14
23
|
"rust" => "rust-analyzer",
|
|
@@ -31,13 +40,14 @@ module Rubik
|
|
|
31
40
|
@language = language.to_s.downcase
|
|
32
41
|
@root_dir = Rubik::Platform.expand_path(root_dir)
|
|
33
42
|
cmd = server_cmd || SERVERS[@language]
|
|
34
|
-
if
|
|
43
|
+
if cmd.to_s.start_with?("typeprof", "typescript-language-server") && !cmd.to_s.include?("--stdio")
|
|
35
44
|
cmd = "#{cmd} --stdio"
|
|
36
45
|
elsif cmd.to_s.start_with?("solargraph") && !cmd.to_s.include?("stdio")
|
|
37
46
|
cmd = "#{cmd} stdio"
|
|
38
47
|
end
|
|
39
48
|
@server_cmd = cmd
|
|
40
49
|
@running = false
|
|
50
|
+
@crashed = false
|
|
41
51
|
@req_id = 0
|
|
42
52
|
@stdin = nil
|
|
43
53
|
@stdout = nil
|
|
@@ -49,6 +59,7 @@ module Rubik
|
|
|
49
59
|
@cv = ConditionVariable.new
|
|
50
60
|
@responses = {}
|
|
51
61
|
@pending_requests = {}
|
|
62
|
+
@reader_failure = nil
|
|
52
63
|
@diagnostics = {}
|
|
53
64
|
@open_documents = {}
|
|
54
65
|
@version_counter = 0
|
|
@@ -73,6 +84,7 @@ module Rubik
|
|
|
73
84
|
@req_id = 0
|
|
74
85
|
@responses.clear
|
|
75
86
|
@pending_requests.clear
|
|
87
|
+
@reader_failure = nil
|
|
76
88
|
@open_documents.clear
|
|
77
89
|
@diagnostics.clear
|
|
78
90
|
@version_counter = 0
|
|
@@ -92,6 +104,7 @@ module Rubik
|
|
|
92
104
|
@stdin.binmode
|
|
93
105
|
@stdout.binmode
|
|
94
106
|
@running = false
|
|
107
|
+
@crashed = false
|
|
95
108
|
@reader_alive = false
|
|
96
109
|
|
|
97
110
|
start_reader_thread
|
|
@@ -138,6 +151,10 @@ module Rubik
|
|
|
138
151
|
true
|
|
139
152
|
end
|
|
140
153
|
|
|
154
|
+
def crashed?
|
|
155
|
+
defined?(@crashed) && @crashed == true
|
|
156
|
+
end
|
|
157
|
+
|
|
141
158
|
def reconnect!
|
|
142
159
|
stop if @running
|
|
143
160
|
start
|
|
@@ -154,6 +171,7 @@ module Rubik
|
|
|
154
171
|
end
|
|
155
172
|
|
|
156
173
|
@running = false
|
|
174
|
+
@crashed = false
|
|
157
175
|
@reader_alive = false
|
|
158
176
|
# Drain first: its thread may be blocked in gets on @stderr.
|
|
159
177
|
@stderr_drain&.stop
|
|
@@ -190,9 +208,8 @@ module Rubik
|
|
|
190
208
|
def definition(file_path, line, character)
|
|
191
209
|
return { ok: false, error: "LSP server not running" } unless alive?
|
|
192
210
|
|
|
193
|
-
abs_path = File.expand_path(file_path, @root_dir)
|
|
194
211
|
res = send_request("textDocument/definition", {
|
|
195
|
-
textDocument: { uri:
|
|
212
|
+
textDocument: { uri: document_uri(file_path) },
|
|
196
213
|
position: { line: line, character: character }
|
|
197
214
|
})
|
|
198
215
|
|
|
@@ -204,9 +221,8 @@ module Rubik
|
|
|
204
221
|
def references(file_path, line, character)
|
|
205
222
|
return { ok: false, error: "LSP server not running" } unless alive?
|
|
206
223
|
|
|
207
|
-
abs_path = File.expand_path(file_path, @root_dir)
|
|
208
224
|
res = send_request("textDocument/references", {
|
|
209
|
-
textDocument: { uri:
|
|
225
|
+
textDocument: { uri: document_uri(file_path) },
|
|
210
226
|
position: { line: line, character: character },
|
|
211
227
|
context: { includeDeclaration: true }
|
|
212
228
|
})
|
|
@@ -219,9 +235,8 @@ module Rubik
|
|
|
219
235
|
def hover(file_path, line, character)
|
|
220
236
|
return { ok: false, error: "LSP server not running" } unless alive?
|
|
221
237
|
|
|
222
|
-
abs_path = File.expand_path(file_path, @root_dir)
|
|
223
238
|
res = send_request("textDocument/hover", {
|
|
224
|
-
textDocument: { uri:
|
|
239
|
+
textDocument: { uri: document_uri(file_path) },
|
|
225
240
|
position: { line: line, character: character }
|
|
226
241
|
})
|
|
227
242
|
|
|
@@ -233,7 +248,7 @@ module Rubik
|
|
|
233
248
|
def did_open(file_path, content, language_id = @language)
|
|
234
249
|
return false unless alive?
|
|
235
250
|
|
|
236
|
-
abs_path =
|
|
251
|
+
abs_path = normalized_document_path(file_path)
|
|
237
252
|
@mutex.synchronize do
|
|
238
253
|
@version_counter += 1
|
|
239
254
|
@open_documents[abs_path] = @version_counter
|
|
@@ -241,7 +256,7 @@ module Rubik
|
|
|
241
256
|
|
|
242
257
|
send_notification("textDocument/didOpen", {
|
|
243
258
|
textDocument: {
|
|
244
|
-
uri:
|
|
259
|
+
uri: document_uri(abs_path),
|
|
245
260
|
languageId: language_id.to_s,
|
|
246
261
|
version: @version_counter,
|
|
247
262
|
text: content
|
|
@@ -255,7 +270,7 @@ module Rubik
|
|
|
255
270
|
def did_change(file_path, content)
|
|
256
271
|
return false unless alive?
|
|
257
272
|
|
|
258
|
-
abs_path =
|
|
273
|
+
abs_path = normalized_document_path(file_path)
|
|
259
274
|
version = @mutex.synchronize do
|
|
260
275
|
@version_counter += 1
|
|
261
276
|
@open_documents[abs_path] = @version_counter
|
|
@@ -267,7 +282,7 @@ module Rubik
|
|
|
267
282
|
|
|
268
283
|
send_notification("textDocument/didChange", {
|
|
269
284
|
textDocument: {
|
|
270
|
-
uri:
|
|
285
|
+
uri: document_uri(abs_path),
|
|
271
286
|
version: version
|
|
272
287
|
},
|
|
273
288
|
contentChanges: [
|
|
@@ -282,11 +297,16 @@ module Rubik
|
|
|
282
297
|
def did_close(file_path)
|
|
283
298
|
return false unless @running
|
|
284
299
|
|
|
285
|
-
abs_path =
|
|
286
|
-
|
|
300
|
+
abs_path = normalized_document_path(file_path)
|
|
301
|
+
raw_abs = Rubik::Platform.expand_path(file_path, @root_dir)
|
|
302
|
+
@mutex.synchronize do
|
|
303
|
+
@open_documents.delete(abs_path)
|
|
304
|
+
@open_documents.delete(raw_abs)
|
|
305
|
+
@open_documents.delete_if { |k, _| Rubik::Platform.path_equal?(k, abs_path) }
|
|
306
|
+
end
|
|
287
307
|
|
|
288
308
|
send_notification("textDocument/didClose", {
|
|
289
|
-
textDocument: { uri:
|
|
309
|
+
textDocument: { uri: document_uri(abs_path) }
|
|
290
310
|
})
|
|
291
311
|
true
|
|
292
312
|
rescue StandardError
|
|
@@ -296,18 +316,25 @@ module Rubik
|
|
|
296
316
|
def document_open?(file_path)
|
|
297
317
|
return false if file_path.nil? || file_path.to_s.strip.empty?
|
|
298
318
|
|
|
299
|
-
abs_path =
|
|
300
|
-
|
|
319
|
+
abs_path = normalized_document_path(file_path)
|
|
320
|
+
raw_abs = Rubik::Platform.expand_path(file_path, @root_dir)
|
|
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
|
|
301
326
|
rescue ArgumentError
|
|
302
327
|
false
|
|
303
328
|
end
|
|
304
329
|
|
|
305
330
|
def diagnostics_for(file_path)
|
|
306
|
-
abs_path =
|
|
331
|
+
abs_path = normalized_document_path(file_path)
|
|
332
|
+
raw_abs = Rubik::Platform.normalize_path_raw(Rubik::Platform.expand_path(file_path, @root_dir))
|
|
307
333
|
root_norm = Rubik::Platform.normalize_path_raw(@root_dir)
|
|
308
|
-
rel_path =
|
|
334
|
+
rel_path = raw_abs.start_with?("#{root_norm}/") ? raw_abs.delete_prefix("#{root_norm}/") : file_path.to_s.tr("\\", "/")
|
|
309
335
|
@mutex.synchronize do
|
|
310
336
|
@diagnostics[abs_path] ||
|
|
337
|
+
@diagnostics[raw_abs] ||
|
|
311
338
|
@diagnostics[rel_path] ||
|
|
312
339
|
@diagnostics[file_path] ||
|
|
313
340
|
@diagnostics.find { |k, _| Rubik::Platform.path_equal?(k, abs_path) }&.last ||
|
|
@@ -358,13 +385,20 @@ module Rubik
|
|
|
358
385
|
end
|
|
359
386
|
end
|
|
360
387
|
ensure
|
|
361
|
-
@
|
|
362
|
-
|
|
388
|
+
@mutex.synchronize do
|
|
389
|
+
@crashed = true if @running
|
|
390
|
+
@reader_alive = false
|
|
391
|
+
@running = false
|
|
392
|
+
@reader_failure ||= "LSP response reader stopped before completing the request"
|
|
393
|
+
@cv.broadcast
|
|
394
|
+
end
|
|
363
395
|
end
|
|
364
396
|
end
|
|
365
397
|
|
|
366
398
|
def handle_incoming_message(msg)
|
|
367
|
-
if msg
|
|
399
|
+
if msg.key?("method") && msg.key?("id")
|
|
400
|
+
handle_server_request(msg)
|
|
401
|
+
elsif msg.key?("id")
|
|
368
402
|
req_id = msg["id"]
|
|
369
403
|
@mutex.synchronize do
|
|
370
404
|
# Discard late response if request is neither pending nor already tracked
|
|
@@ -382,12 +416,14 @@ module Rubik
|
|
|
382
416
|
params = msg["params"] || {}
|
|
383
417
|
uri = params["uri"].to_s
|
|
384
418
|
raw_path = Rubik::Platform.from_file_uri(uri)
|
|
419
|
+
norm_path = normalized_document_path(raw_path)
|
|
385
420
|
abs_path = Rubik::Platform.normalize_path_raw(Rubik::Platform.expand_path(raw_path, @root_dir))
|
|
386
421
|
root_norm = Rubik::Platform.normalize_path_raw(@root_dir)
|
|
387
422
|
rel_path = abs_path.start_with?("#{root_norm}/") ? abs_path.delete_prefix("#{root_norm}/") : abs_path
|
|
388
423
|
diags = params["diagnostics"] || []
|
|
389
424
|
|
|
390
425
|
@mutex.synchronize do
|
|
426
|
+
@diagnostics[norm_path] = diags
|
|
391
427
|
@diagnostics[abs_path] = diags
|
|
392
428
|
@diagnostics[rel_path] = diags
|
|
393
429
|
@diagnostics[raw_path] = diags
|
|
@@ -413,15 +449,28 @@ module Rubik
|
|
|
413
449
|
|
|
414
450
|
def wait_for_response(req_id, timeout: 5)
|
|
415
451
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
416
|
-
res = nil
|
|
417
452
|
timed_out = false
|
|
453
|
+
reader_failure = nil
|
|
418
454
|
|
|
419
455
|
@mutex.synchronize do
|
|
420
456
|
loop do
|
|
421
457
|
if @responses.key?(req_id)
|
|
422
|
-
|
|
458
|
+
response = @responses.delete(req_id)
|
|
423
459
|
@pending_requests.delete(req_id)
|
|
424
|
-
|
|
460
|
+
if response.key?("error")
|
|
461
|
+
error = response["error"] || {}
|
|
462
|
+
raise RequestError.new(:server_error, error["message"] || "LSP server returned an error")
|
|
463
|
+
end
|
|
464
|
+
return response["result"] if response.key?("result")
|
|
465
|
+
|
|
466
|
+
raise RequestError.new(:invalid_response, "LSP response did not contain a result")
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
if @reader_failure
|
|
470
|
+
reader_failure = @reader_failure
|
|
471
|
+
@responses.delete(req_id)
|
|
472
|
+
@pending_requests.delete(req_id)
|
|
473
|
+
break
|
|
425
474
|
end
|
|
426
475
|
|
|
427
476
|
remaining = timeout - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time)
|
|
@@ -443,11 +492,40 @@ module Rubik
|
|
|
443
492
|
rescue StandardError
|
|
444
493
|
nil
|
|
445
494
|
end
|
|
495
|
+
raise RequestError.new(:timeout, "LSP request timed out after #{timeout} seconds")
|
|
446
496
|
end
|
|
447
497
|
|
|
498
|
+
raise RequestError.new(:reader_failure, reader_failure) if reader_failure
|
|
499
|
+
|
|
448
500
|
nil
|
|
449
501
|
end
|
|
450
502
|
|
|
503
|
+
def normalized_document_path(file_path)
|
|
504
|
+
path = Rubik::Platform.expand_path(file_path, @root_dir)
|
|
505
|
+
Rubik::Platform.normalize_path(path)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def document_uri(file_path)
|
|
509
|
+
Rubik::Platform.to_file_uri(normalized_document_path(file_path))
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def handle_server_request(msg)
|
|
513
|
+
case msg["method"]
|
|
514
|
+
when "workspace/configuration"
|
|
515
|
+
params = msg["params"]
|
|
516
|
+
items = params.is_a?(Hash) && params["items"].is_a?(Array) ? params["items"] : []
|
|
517
|
+
write_message({ jsonrpc: "2.0", id: msg["id"], result: items.map { {} } })
|
|
518
|
+
when "client/registerCapability", "client/unregisterCapability", "window/workDoneProgress/create"
|
|
519
|
+
write_message({ jsonrpc: "2.0", id: msg["id"], result: nil })
|
|
520
|
+
else
|
|
521
|
+
write_message({
|
|
522
|
+
jsonrpc: "2.0",
|
|
523
|
+
id: msg["id"],
|
|
524
|
+
error: { code: -32_601, message: "Method not found: #{msg['method']}" }
|
|
525
|
+
})
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
451
529
|
def send_notification(method, params)
|
|
452
530
|
msg = {
|
|
453
531
|
jsonrpc: "2.0",
|