rubik 0.3.3 → 0.3.4

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.
@@ -19,36 +19,7 @@ module Rubik
19
19
 
20
20
  def self.register(shell)
21
21
  diag_handler = lambda do |args, ctx|
22
- target_file = args.first
23
- if target_file
24
- diags = ctx.lsp_manager.diagnostics_for(target_file)
25
- if diags.empty?
26
- ctx.out.puts "No LSP diagnostics or errors for '#{target_file}'."
27
- else
28
- ctx.out.puts "=== LSP Diagnostics for #{target_file} (#{diags.size} issue(s)) ==="
29
- diags.each do |d|
30
- sev = d["severity"] == 1 ? "ERROR" : "WARN"
31
- line = (d.dig("range", "start", "line") || 0) + 1
32
- ctx.out.puts " • [#{sev}] Line #{line}: #{d['message']}"
33
- end
34
- end
35
- else
36
- all = ctx.lsp_manager.all_diagnostics
37
- if all.empty? || all.values.flatten.empty?
38
- ctx.out.puts "No active LSP diagnostics across workspace."
39
- else
40
- all.each do |fpath, diags|
41
- next if diags.empty?
42
-
43
- ctx.out.puts "\nFile: #{fpath} (#{diags.size} issue(s)):"
44
- diags.each do |d|
45
- sev = d["severity"] == 1 ? "ERROR" : "WARN"
46
- line = (d.dig("range", "start", "line") || 0) + 1
47
- ctx.out.puts " • [#{sev}] Line #{line}: #{d['message']}"
48
- end
49
- end
50
- end
51
- end
22
+ handle_diagnostics_command(args, ctx)
52
23
  end
53
24
 
54
25
  shell.register_command("diagnostics", description: "Display live LSP diagnostics across workspace or for a specific file", &diag_handler)
@@ -57,176 +28,238 @@ module Rubik
57
28
  audience: :both,
58
29
  description: "Manage Language Server Protocol (LSP) integration (status|on|off|diag|start|stop|restart|servers)",
59
30
  usage: USAGE_LSP) do |args, ctx|
60
- scope, clean_args = Base.parse_scope(args)
61
- sub = clean_args.first&.downcase || "status"
62
-
63
- case sub
64
- when "status"
65
- summary = ctx.lsp_manager.status_summary
66
- ctx.out.puts "=== LSP Integration Status ==="
67
- ctx.out.puts " • Enabled : #{summary[:enabled]}"
68
- ctx.out.puts " • Auto Diagnostics : #{summary[:auto_diagnostics]}"
69
- ctx.out.puts "\nConfigured Language Servers:"
70
- summary[:servers].each do |lang, info|
71
- status_mark = if info[:running]
72
- "🟢 RUNNING"
73
- else
74
- (info[:available] ? "⚪ AVAILABLE" : "🔴 NOT FOUND")
75
- end
76
- diags_str = info[:running] ? " (#{info[:diagnostics_count]} diagnostics)" : ""
77
- ctx.out.puts " • #{lang.ljust(8)} : #{info[:server_cmd].ljust(16)} -> #{status_mark}#{diags_str}"
78
- end
79
- when "servers"
80
- summary = ctx.lsp_manager.status_summary
81
- ctx.out.puts "=== Supported & Configured Language Servers ==="
82
- summary[:servers].each do |lang, info|
83
- status_mark = if info[:running]
84
- "🟢 RUNNING"
85
- else
86
- (info[:available] ? "⚪ AVAILABLE" : "🔴 NOT INSTALLED")
87
- end
88
- ctx.out.puts " • #{lang.ljust(12)} : #{info[:server_cmd].ljust(35)} -> #{status_mark}"
89
- end
90
- when "on", "enable"
91
- res = ctx.config.set("lsp.enabled", true, persist: scope)
92
- next if Base.report_persist_failure(ctx, res, scope)
93
-
94
- ctx.out.puts "LSP integration enabled [#{Base.scope_label(scope)}]."
95
- when "off", "disable"
96
- res = ctx.config.set("lsp.enabled", false, persist: scope)
97
- ctx.lsp_manager.stop_all
98
- next if Base.report_persist_failure(ctx, res, scope)
99
-
100
- ctx.out.puts "LSP integration disabled and running servers stopped [#{Base.scope_label(scope)}]."
101
- when "diag", "diagnostics"
102
- diag_handler.call(clean_args[1..], ctx)
103
- when "start"
104
- lang = clean_args[1]
105
- if lang.nil? || lang.empty?
106
- supported = (Rubik::LSP::Client::SERVERS.keys + (ctx.config.fetch("lsp.servers", {})&.keys || [])).map(&:to_s).uniq.sort
107
- ctx.out.puts "Usage: !lsp start <#{supported.join('|')}>"
108
- else
109
- res = ctx.lsp_manager.start_client(lang)
110
- if res[:ok]
111
- ctx.out.puts res[:message]
112
- else
113
- ctx.out.puts "Error: #{res[:error]}"
114
- ctx.out.puts "\n💡 Suggestion: #{res[:suggestion]}" if res[:suggestion]
115
- candidates = Rubik::Runtime::Discovery.lsp_candidates(lang)
116
- ctx.out.puts "Probed candidates for #{lang}: #{candidates.join(', ')}" unless candidates.empty?
117
- ctx.out.puts "Tip: Use `repair_runtime_service` (or `fix_service`) to probe or configure an alternative server."
118
- end
119
- end
120
- when "stop"
121
- lang = clean_args[1]
122
- if lang.nil? || lang.empty?
123
- ctx.lsp_manager.stop_all
124
- ctx.out.puts "Stopped all active LSP servers."
125
- else
126
- res = ctx.lsp_manager.stop_client(lang)
127
- ctx.out.puts res[:ok] ? res[:message] : "Error: #{res[:error]}"
128
- end
129
- when "restart"
130
- lang = clean_args[1]
131
- if lang.nil? || lang.empty?
132
- running_langs = ctx.lsp_manager.status_summary[:servers].select { |_, v| v[:running] }.keys
133
- if running_langs.empty?
134
- ctx.out.puts "No LSP servers are currently running."
135
- else
136
- ctx.lsp_manager.stop_all
137
- restarted = []
138
- running_langs.each do |l|
139
- res = ctx.lsp_manager.start_client(l)
140
- restarted << l if res[:ok]
141
- end
142
- ctx.out.puts "Restarted LSP servers: #{restarted.join(', ')}"
143
- end
144
- else
145
- ctx.lsp_manager.stop_client(lang)
146
- res = ctx.lsp_manager.start_client(lang)
147
- if res[:ok]
148
- ctx.out.puts "Restarted LSP server for #{lang}."
149
- else
150
- ctx.out.puts "Error: #{res[:error]}"
151
- ctx.out.puts "💡 Suggestion: #{res[:suggestion]}" if res[:suggestion]
152
- end
153
- end
154
- else
155
- ctx.out.puts USAGE_LSP
156
- end
31
+ handle_lsp_command(args, ctx, diag_handler)
157
32
  end
158
33
 
159
34
  shell.register_command("symbols", description: "Search function, class, struct definitions in AST graph",
160
35
  usage: USAGE_SYMBOLS) do |args, ctx|
161
- query = args.join(" ").strip
162
- if ctx.ast_graph.symbols.empty? && ctx.indexer_thread&.alive?
163
- begin
164
- ctx.indexer_thread.join(0.2)
165
- rescue
166
- nil
167
- end
168
- end
169
- if query.empty?
170
- total = ctx.ast_graph.symbols.size
171
- ctx.out.puts "AST Symbol Graph (#{total} symbols indexed). #{USAGE_SYMBOLS}"
172
- else
173
- tool = Rubik::Tools::SearchSymbolsTool.new(ctx.ast_graph)
174
- ctx.out.puts tool.execute(query: query)
175
- end
36
+ handle_symbols_command(args, ctx)
176
37
  end
177
38
 
178
39
  shell.register_command("outline", description: "Show semantic outline (classes/methods) of a source file",
179
40
  usage: USAGE_OUTLINE) do |args, ctx|
180
- target = args.first
181
- if target.nil? || target.empty?
182
- ctx.out.puts USAGE_OUTLINE
183
- else
184
- full_path = File.expand_path(target, ctx.buffer_manager&.root_dir || Dir.pwd)
185
- ctx.ast_graph.index_file(full_path) if File.exist?(full_path) && !ctx.ast_graph.file_symbols.key?(full_path)
186
- tool = Rubik::Tools::GetOutlineTool.new(ctx.ast_graph)
187
- ctx.out.puts tool.execute(file_path: target)
188
- end
41
+ handle_outline_command(args, ctx)
189
42
  end
190
43
 
191
44
  shell.register_command("refs", description: "Find call sites and references of a symbol across codebase",
192
45
  usage: USAGE_REFS) do |args, ctx|
193
- sym = args.first
194
- if ctx.ast_graph.symbols.empty? && ctx.indexer_thread&.alive?
195
- begin
196
- ctx.indexer_thread.join(0.2)
197
- rescue
198
- nil
46
+ handle_refs_command(args, ctx)
47
+ end
48
+
49
+ shell.register_command("ast", description: "Inspect AST outline for a file or definition for a symbol",
50
+ usage: USAGE_AST) do |args, ctx|
51
+ handle_ast_command(args, ctx)
52
+ end
53
+ end
54
+
55
+ private_class_method def self.handle_diagnostics_command(args, ctx)
56
+ target_file = args.first
57
+ if target_file
58
+ diags = ctx.lsp_manager.diagnostics_for(target_file)
59
+ if diags.empty?
60
+ ctx.out.puts "No LSP diagnostics or errors for '#{target_file}'."
61
+ else
62
+ ctx.out.puts "=== LSP Diagnostics for #{target_file} (#{diags.size} issue(s)) ==="
63
+ render_diagnostics(ctx, diags)
64
+ end
65
+ else
66
+ all = ctx.lsp_manager.all_diagnostics
67
+ if all.empty? || all.values.flatten.empty?
68
+ ctx.out.puts "No active LSP diagnostics across workspace."
69
+ else
70
+ all.each do |fpath, diags|
71
+ next if diags.empty?
72
+
73
+ ctx.out.puts "\nFile: #{fpath} (#{diags.size} issue(s)):"
74
+ render_diagnostics(ctx, diags)
199
75
  end
200
76
  end
201
- if sym.nil? || sym.empty?
202
- ctx.out.puts USAGE_REFS
77
+ end
78
+ end
79
+
80
+ private_class_method def self.render_diagnostics(ctx, diags)
81
+ diags.each do |d|
82
+ sev = d["severity"] == 1 ? "ERROR" : "WARN"
83
+ line = (d.dig("range", "start", "line") || 0) + 1
84
+ ctx.out.puts " • [#{sev}] Line #{line}: #{d['message']}"
85
+ end
86
+ end
87
+
88
+ private_class_method def self.handle_lsp_command(args, ctx, diag_handler)
89
+ scope, clean_args = Base.parse_scope(args)
90
+ sub = clean_args.first&.downcase || "status"
91
+
92
+ case sub
93
+ when "status"
94
+ handle_lsp_status(ctx)
95
+ when "servers"
96
+ handle_lsp_servers(ctx)
97
+ when "on", "enable"
98
+ res = ctx.config.set("lsp.enabled", true, persist: scope)
99
+ return if Base.report_persist_failure(ctx, res, scope)
100
+
101
+ ctx.out.puts "LSP integration enabled [#{Base.scope_label(scope)}]."
102
+ when "off", "disable"
103
+ res = ctx.config.set("lsp.enabled", false, persist: scope)
104
+ ctx.lsp_manager.stop_all
105
+ return if Base.report_persist_failure(ctx, res, scope)
106
+
107
+ ctx.out.puts "LSP integration disabled and running servers stopped [#{Base.scope_label(scope)}]."
108
+ when "diag", "diagnostics"
109
+ diag_handler.call(clean_args[1..], ctx)
110
+ when "start"
111
+ handle_lsp_start(clean_args[1], ctx)
112
+ when "stop"
113
+ handle_lsp_stop(clean_args[1], ctx)
114
+ when "restart"
115
+ handle_lsp_restart(clean_args[1], ctx)
116
+ else
117
+ ctx.out.puts USAGE_LSP
118
+ end
119
+ end
120
+
121
+ private_class_method def self.handle_lsp_status(ctx)
122
+ summary = ctx.lsp_manager.status_summary
123
+ ctx.out.puts "=== LSP Integration Status ==="
124
+ ctx.out.puts " • Enabled : #{summary[:enabled]}"
125
+ ctx.out.puts " • Auto Diagnostics : #{summary[:auto_diagnostics]}"
126
+ ctx.out.puts "\nConfigured Language Servers:"
127
+ summary[:servers].each do |lang, info|
128
+ status_mark = if info[:running]
129
+ "🟢 RUNNING"
130
+ else
131
+ (info[:available] ? "⚪ AVAILABLE" : "🔴 NOT FOUND")
132
+ end
133
+ diags_str = info[:running] ? " (#{info[:diagnostics_count]} diagnostics)" : ""
134
+ ctx.out.puts " • #{lang.ljust(8)} : #{info[:server_cmd].ljust(16)} -> #{status_mark}#{diags_str}"
135
+ end
136
+ end
137
+
138
+ private_class_method def self.handle_lsp_servers(ctx)
139
+ summary = ctx.lsp_manager.status_summary
140
+ ctx.out.puts "=== Supported & Configured Language Servers ==="
141
+ summary[:servers].each do |lang, info|
142
+ status_mark = if info[:running]
143
+ "🟢 RUNNING"
144
+ else
145
+ (info[:available] ? "⚪ AVAILABLE" : "🔴 NOT INSTALLED")
146
+ end
147
+ ctx.out.puts " • #{lang.ljust(12)} : #{info[:server_cmd].ljust(35)} -> #{status_mark}"
148
+ end
149
+ end
150
+
151
+ private_class_method def self.handle_lsp_start(lang, ctx)
152
+ if lang.nil? || lang.empty?
153
+ supported = (Rubik::LSP::Client::SERVERS.keys + (ctx.config.fetch("lsp.servers", {})&.keys || [])).map(&:to_s).uniq.sort
154
+ ctx.out.puts "Usage: !lsp start <#{supported.join('|')}>"
155
+ else
156
+ res = ctx.lsp_manager.start_client(lang)
157
+ if res[:ok]
158
+ ctx.out.puts res[:message]
203
159
  else
204
- tool = Rubik::Tools::FindReferencesTool.new(ctx.ast_graph)
205
- ctx.out.puts tool.execute(symbol_name: sym)
160
+ ctx.out.puts "Error: #{res[:error]}"
161
+ ctx.out.puts "\n💡 Suggestion: #{res[:suggestion]}" if res[:suggestion]
162
+ candidates = Rubik::Runtime::Discovery.lsp_candidates(lang)
163
+ ctx.out.puts "Probed candidates for #{lang}: #{candidates.join(', ')}" unless candidates.empty?
164
+ ctx.out.puts "Tip: Use `repair_runtime_service` (or `fix_service`) to probe or configure an alternative server."
206
165
  end
207
166
  end
167
+ end
208
168
 
209
- shell.register_command("ast", description: "Inspect AST outline for a file or definition for a symbol",
210
- usage: USAGE_AST) do |args, ctx|
211
- target = args.first
212
- if target.nil? || target.empty?
213
- ctx.out.puts USAGE_AST
214
- elsif File.exist?(target)
215
- full_path = File.expand_path(target, ctx.buffer_manager&.root_dir || Dir.pwd)
216
- ctx.ast_graph.index_file(full_path) unless ctx.ast_graph.file_symbols.key?(full_path)
217
- tool = Rubik::Tools::GetOutlineTool.new(ctx.ast_graph)
218
- ctx.out.puts tool.execute(file_path: target)
169
+ private_class_method def self.handle_lsp_stop(lang, ctx)
170
+ if lang.nil? || lang.empty?
171
+ ctx.lsp_manager.stop_all
172
+ ctx.out.puts "Stopped all active LSP servers."
173
+ else
174
+ res = ctx.lsp_manager.stop_client(lang)
175
+ ctx.out.puts res[:ok] ? res[:message] : "Error: #{res[:error]}"
176
+ end
177
+ end
178
+
179
+ private_class_method def self.handle_lsp_restart(lang, ctx)
180
+ if lang.nil? || lang.empty?
181
+ running_langs = ctx.lsp_manager.status_summary[:servers].select { |_, v| v[:running] }.keys
182
+ if running_langs.empty?
183
+ ctx.out.puts "No LSP servers are currently running."
219
184
  else
220
- if ctx.ast_graph.symbols.empty? && ctx.indexer_thread&.alive?
221
- begin
222
- ctx.indexer_thread.join(0.2)
223
- rescue
224
- nil
225
- end
185
+ ctx.lsp_manager.stop_all
186
+ restarted = []
187
+ running_langs.each do |l|
188
+ res = ctx.lsp_manager.start_client(l)
189
+ restarted << l if res[:ok]
226
190
  end
227
- tool = Rubik::Tools::GetSymbolTool.new(ctx.ast_graph)
228
- ctx.out.puts tool.execute(symbol_name: target)
191
+ ctx.out.puts "Restarted LSP servers: #{restarted.join(', ')}"
229
192
  end
193
+ else
194
+ ctx.lsp_manager.stop_client(lang)
195
+ res = ctx.lsp_manager.start_client(lang)
196
+ if res[:ok]
197
+ ctx.out.puts "Restarted LSP server for #{lang}."
198
+ else
199
+ ctx.out.puts "Error: #{res[:error]}"
200
+ ctx.out.puts "💡 Suggestion: #{res[:suggestion]}" if res[:suggestion]
201
+ end
202
+ end
203
+ end
204
+
205
+ private_class_method def self.wait_for_indexer(ctx)
206
+ return unless ctx.ast_graph.symbols.empty? && ctx.indexer_thread&.alive?
207
+
208
+ begin
209
+ ctx.indexer_thread.join(0.2)
210
+ rescue
211
+ nil
212
+ end
213
+ end
214
+
215
+ private_class_method def self.handle_symbols_command(args, ctx)
216
+ query = args.join(" ").strip
217
+ wait_for_indexer(ctx)
218
+ if query.empty?
219
+ total = ctx.ast_graph.symbols.size
220
+ ctx.out.puts "AST Symbol Graph (#{total} symbols indexed). #{USAGE_SYMBOLS}"
221
+ else
222
+ tool = Rubik::Tools::SearchSymbolsTool.new(ctx.ast_graph)
223
+ ctx.out.puts tool.execute(query: query)
224
+ end
225
+ end
226
+
227
+ private_class_method def self.handle_outline_command(args, ctx)
228
+ target = args.first
229
+ if target.nil? || target.empty?
230
+ ctx.out.puts USAGE_OUTLINE
231
+ else
232
+ full_path = File.expand_path(target, ctx.buffer_manager&.root_dir || Dir.pwd)
233
+ ctx.ast_graph.index_file(full_path) if File.exist?(full_path) && !ctx.ast_graph.file_symbols.key?(full_path)
234
+ tool = Rubik::Tools::GetOutlineTool.new(ctx.ast_graph)
235
+ ctx.out.puts tool.execute(file_path: target)
236
+ end
237
+ end
238
+
239
+ private_class_method def self.handle_refs_command(args, ctx)
240
+ sym = args.first
241
+ wait_for_indexer(ctx)
242
+ if sym.nil? || sym.empty?
243
+ ctx.out.puts USAGE_REFS
244
+ else
245
+ tool = Rubik::Tools::FindReferencesTool.new(ctx.ast_graph)
246
+ ctx.out.puts tool.execute(symbol_name: sym)
247
+ end
248
+ end
249
+
250
+ private_class_method def self.handle_ast_command(args, ctx)
251
+ target = args.first
252
+ if target.nil? || target.empty?
253
+ ctx.out.puts USAGE_AST
254
+ elsif File.exist?(target)
255
+ full_path = File.expand_path(target, ctx.buffer_manager&.root_dir || Dir.pwd)
256
+ ctx.ast_graph.index_file(full_path) unless ctx.ast_graph.file_symbols.key?(full_path)
257
+ tool = Rubik::Tools::GetOutlineTool.new(ctx.ast_graph)
258
+ ctx.out.puts tool.execute(file_path: target)
259
+ else
260
+ wait_for_indexer(ctx)
261
+ tool = Rubik::Tools::GetSymbolTool.new(ctx.ast_graph)
262
+ ctx.out.puts tool.execute(symbol_name: target)
230
263
  end
231
264
  end
232
265
  end