mui-lsp 0.1.1 → 0.3.0
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/.rubocop_todo.yml +28 -10
- data/CHANGELOG.md +59 -0
- data/README.md +67 -12
- data/lib/mui/lsp/client.rb +23 -0
- data/lib/mui/lsp/handlers/completion.rb +24 -9
- data/lib/mui/lsp/handlers/definition.rb +13 -6
- data/lib/mui/lsp/handlers/formatting.rb +99 -0
- data/lib/mui/lsp/handlers/references.rb +44 -31
- data/lib/mui/lsp/handlers/type_definition.rb +106 -0
- data/lib/mui/lsp/handlers.rb +2 -0
- data/lib/mui/lsp/manager.rb +235 -10
- data/lib/mui/lsp/plugin.rb +189 -60
- data/lib/mui/lsp/server_config.rb +22 -0
- data/lib/mui/lsp/text_document_sync.rb +5 -3
- data/lib/mui/lsp/version.rb +1 -1
- metadata +4 -2
data/lib/mui/lsp/plugin.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Mui
|
|
|
6
6
|
module Lsp
|
|
7
7
|
# Main plugin class for mui-lsp
|
|
8
8
|
# Registers commands and keymaps for LSP integration
|
|
9
|
-
class Plugin < Mui::Plugin
|
|
9
|
+
class Plugin < Mui::Plugin # rubocop:disable Metrics/ClassLength
|
|
10
10
|
name "lsp"
|
|
11
11
|
|
|
12
12
|
def setup
|
|
@@ -44,6 +44,11 @@ module Mui
|
|
|
44
44
|
handle_lsp_definition(ctx)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# :LspTypeDefinition - Go to type definition
|
|
48
|
+
command(:LspTypeDefinition) do |ctx, _args|
|
|
49
|
+
handle_lsp_type_definition(ctx)
|
|
50
|
+
end
|
|
51
|
+
|
|
47
52
|
# :LspReferences - Show references
|
|
48
53
|
command(:LspReferences) do |ctx, _args|
|
|
49
54
|
handle_lsp_references(ctx)
|
|
@@ -78,6 +83,11 @@ module Mui
|
|
|
78
83
|
command(:LspOpen) do |ctx, _args|
|
|
79
84
|
handle_lsp_open(ctx)
|
|
80
85
|
end
|
|
86
|
+
|
|
87
|
+
# :LspFormat - Format current file
|
|
88
|
+
command(:LspFormat) do |ctx, _args|
|
|
89
|
+
handle_lsp_format(ctx)
|
|
90
|
+
end
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
def register_keymaps
|
|
@@ -87,75 +97,84 @@ module Mui
|
|
|
87
97
|
true
|
|
88
98
|
end
|
|
89
99
|
|
|
90
|
-
#
|
|
91
|
-
keymap(:normal, "
|
|
92
|
-
|
|
100
|
+
# <Space>df - Go to definition
|
|
101
|
+
keymap(:normal, "<Space>df") do |ctx|
|
|
102
|
+
handle_lsp_definition(ctx)
|
|
93
103
|
true
|
|
94
104
|
end
|
|
95
105
|
|
|
96
|
-
#
|
|
97
|
-
keymap(:normal, "
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
handle_lsp_definition(ctx)
|
|
101
|
-
true
|
|
102
|
-
else
|
|
103
|
-
false # Let default 'd' handle it
|
|
104
|
-
end
|
|
106
|
+
# <Space>tf - Go to type definition (toggle between .rb and .rbs)
|
|
107
|
+
keymap(:normal, "<Space>tf") do |ctx|
|
|
108
|
+
handle_lsp_jump_to_type_file(ctx)
|
|
109
|
+
true
|
|
105
110
|
end
|
|
106
111
|
|
|
107
|
-
#
|
|
108
|
-
keymap(:normal, "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
handle_lsp_references(ctx)
|
|
112
|
-
true
|
|
113
|
-
else
|
|
114
|
-
false # Let default 'r' handle it
|
|
115
|
-
end
|
|
112
|
+
# <Space>rf - Find references
|
|
113
|
+
keymap(:normal, "<Space>rf") do |ctx|
|
|
114
|
+
handle_lsp_references(ctx)
|
|
115
|
+
true
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
#
|
|
119
|
-
keymap(:normal, "
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
handle_lsp_hover(ctx)
|
|
123
|
-
true
|
|
124
|
-
else
|
|
125
|
-
false # Let default 'h' handle it
|
|
126
|
-
end
|
|
118
|
+
# <Space>hf - Show hover (alternative to K)
|
|
119
|
+
keymap(:normal, "<Space>hf") do |ctx|
|
|
120
|
+
handle_lsp_hover(ctx)
|
|
121
|
+
true
|
|
127
122
|
end
|
|
128
123
|
|
|
129
|
-
#
|
|
130
|
-
keymap(:normal, "
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
handle_lsp_completion(ctx)
|
|
134
|
-
true
|
|
135
|
-
else
|
|
136
|
-
false # Let default 'c' handle it
|
|
137
|
-
end
|
|
124
|
+
# <Space>cf - Show completion
|
|
125
|
+
keymap(:normal, "<Space>cf") do |ctx|
|
|
126
|
+
handle_lsp_completion(ctx)
|
|
127
|
+
true
|
|
138
128
|
end
|
|
139
129
|
|
|
140
|
-
#
|
|
141
|
-
keymap(:normal, "
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
handle_lsp_diagnostic_show(ctx)
|
|
145
|
-
true
|
|
146
|
-
else
|
|
147
|
-
false # Let default 'e' handle it
|
|
148
|
-
end
|
|
130
|
+
# <Space>ef - Show diagnostic at cursor
|
|
131
|
+
keymap(:normal, "<Space>ef") do |ctx|
|
|
132
|
+
handle_lsp_diagnostic_show(ctx)
|
|
133
|
+
true
|
|
149
134
|
end
|
|
150
135
|
|
|
151
|
-
#
|
|
152
|
-
keymap(:normal, "
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
136
|
+
# <Space>ff - Format current file
|
|
137
|
+
keymap(:normal, "<Space>ff") do |ctx|
|
|
138
|
+
handle_lsp_format(ctx)
|
|
139
|
+
true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Insert mode: Ctrl+Space - Trigger LSP completion
|
|
143
|
+
keymap(:insert, "\x00") do |ctx|
|
|
144
|
+
handle_lsp_completion(ctx)
|
|
145
|
+
true
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Picker navigation: Leader+Enter - open selected
|
|
149
|
+
keymap(:normal, "<Leader><CR>") do |ctx|
|
|
150
|
+
next unless picker_active?(ctx.editor)
|
|
151
|
+
|
|
152
|
+
handle_picker_select(ctx, new_tab: false)
|
|
153
|
+
true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Picker navigation: Ctrl+t - open in new tab
|
|
157
|
+
keymap(:normal, "<C-t>") do |ctx|
|
|
158
|
+
next unless picker_active?(ctx.editor)
|
|
159
|
+
|
|
160
|
+
handle_picker_select(ctx, new_tab: true)
|
|
161
|
+
true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Picker navigation: Leader+q - close picker
|
|
165
|
+
keymap(:normal, "<Leader>q") do |ctx|
|
|
166
|
+
next unless picker_active?(ctx.editor)
|
|
167
|
+
|
|
168
|
+
close_picker(ctx.editor)
|
|
169
|
+
true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Picker navigation: Leader+Esc - close picker
|
|
173
|
+
keymap(:normal, "<Leader><Esc>") do |ctx|
|
|
174
|
+
next unless picker_active?(ctx.editor)
|
|
175
|
+
|
|
176
|
+
close_picker(ctx.editor)
|
|
177
|
+
true
|
|
159
178
|
end
|
|
160
179
|
end
|
|
161
180
|
|
|
@@ -194,6 +213,11 @@ module Mui
|
|
|
194
213
|
|
|
195
214
|
get_manager(ctx.editor).did_close(file_path: file_path)
|
|
196
215
|
end
|
|
216
|
+
|
|
217
|
+
# Hook into insert completion trigger (. and @ characters)
|
|
218
|
+
autocmd(:InsertCompletion) do |ctx|
|
|
219
|
+
handle_lsp_completion(ctx)
|
|
220
|
+
end
|
|
197
221
|
end
|
|
198
222
|
|
|
199
223
|
def setup_default_servers
|
|
@@ -272,6 +296,18 @@ module Mui
|
|
|
272
296
|
get_manager(ctx.editor).definition(file_path: file_path, line: line, character: character)
|
|
273
297
|
end
|
|
274
298
|
|
|
299
|
+
def handle_lsp_jump_to_type_file(ctx)
|
|
300
|
+
file_path = ctx.buffer.file_path
|
|
301
|
+
unless file_path
|
|
302
|
+
ctx.set_message("LSP: no file path")
|
|
303
|
+
return
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
line = ctx.window.cursor_row
|
|
307
|
+
character = ctx.window.cursor_col
|
|
308
|
+
get_manager(ctx.editor).jump_to_type_file(file_path: file_path, line: line, character: character)
|
|
309
|
+
end
|
|
310
|
+
|
|
275
311
|
def handle_lsp_references(ctx)
|
|
276
312
|
file_path = ctx.buffer.file_path
|
|
277
313
|
unless file_path
|
|
@@ -291,9 +327,15 @@ module Mui
|
|
|
291
327
|
return
|
|
292
328
|
end
|
|
293
329
|
|
|
330
|
+
mgr = get_manager(ctx.editor)
|
|
331
|
+
text = ctx.buffer.lines.join("\n")
|
|
332
|
+
|
|
333
|
+
# Force re-open document to ensure LSP has latest content
|
|
334
|
+
mgr.force_reopen(file_path: file_path, text: text)
|
|
335
|
+
|
|
294
336
|
line = ctx.window.cursor_row
|
|
295
337
|
character = ctx.window.cursor_col
|
|
296
|
-
|
|
338
|
+
mgr.completion(file_path: file_path, line: line, character: character)
|
|
297
339
|
end
|
|
298
340
|
|
|
299
341
|
def handle_lsp_diagnostics(ctx)
|
|
@@ -439,6 +481,22 @@ module Mui
|
|
|
439
481
|
ctx.set_message("LSP: opened #{File.basename(file_path)}")
|
|
440
482
|
end
|
|
441
483
|
|
|
484
|
+
def handle_lsp_format(ctx)
|
|
485
|
+
file_path = ctx.buffer.file_path
|
|
486
|
+
unless file_path
|
|
487
|
+
ctx.set_message("LSP: no file path")
|
|
488
|
+
return
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
mgr = get_manager(ctx.editor)
|
|
492
|
+
text = ctx.buffer.lines.join("\n")
|
|
493
|
+
|
|
494
|
+
# Sync document before formatting
|
|
495
|
+
mgr.sync_now(file_path: file_path, text: text)
|
|
496
|
+
|
|
497
|
+
mgr.format(file_path: file_path)
|
|
498
|
+
end
|
|
499
|
+
|
|
442
500
|
public
|
|
443
501
|
|
|
444
502
|
def get_manager(editor)
|
|
@@ -461,14 +519,80 @@ module Mui
|
|
|
461
519
|
ServerConfig.rubocop_lsp(auto_start: true)
|
|
462
520
|
when :kanayago
|
|
463
521
|
ServerConfig.kanayago(auto_start: true)
|
|
522
|
+
when :steep
|
|
523
|
+
ServerConfig.steep(auto_start: true)
|
|
464
524
|
else
|
|
465
|
-
raise ArgumentError, "Unknown server: #{name}. Use :solargraph, :ruby_lsp, :rubocop, or :
|
|
525
|
+
raise ArgumentError, "Unknown server: #{name}. Use :solargraph, :ruby_lsp, :rubocop, :kanayago, or :steep"
|
|
466
526
|
end
|
|
467
527
|
register_server(config)
|
|
468
528
|
end
|
|
469
529
|
|
|
470
530
|
private
|
|
471
531
|
|
|
532
|
+
# Picker helpers
|
|
533
|
+
|
|
534
|
+
def picker_active?(editor)
|
|
535
|
+
# Check if current buffer is the picker buffer
|
|
536
|
+
editor.buffer&.file_path == "[LSP Picker]"
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def handle_picker_select(ctx, new_tab:)
|
|
540
|
+
locations = ctx.editor.instance_variable_get(:@lsp_picker_locations)
|
|
541
|
+
return unless locations
|
|
542
|
+
|
|
543
|
+
# Get selected index from cursor position (line 3+ are the items, 0-indexed)
|
|
544
|
+
cursor_row = ctx.window.cursor_row
|
|
545
|
+
index = cursor_row - 2 # Skip header lines
|
|
546
|
+
return if index.negative? || index >= locations.length
|
|
547
|
+
|
|
548
|
+
location = locations[index]
|
|
549
|
+
close_picker(ctx.editor)
|
|
550
|
+
jump_to_location(ctx, location, new_tab: new_tab)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def close_picker(editor)
|
|
554
|
+
editor.instance_variable_set(:@lsp_picker_locations, nil)
|
|
555
|
+
editor.instance_variable_set(:@lsp_picker_type, nil)
|
|
556
|
+
# Close the picker window
|
|
557
|
+
editor.window_manager.close_current_window
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def jump_to_location(ctx, location, new_tab:)
|
|
561
|
+
file_path = location.file_path
|
|
562
|
+
unless file_path
|
|
563
|
+
ctx.set_message("Cannot open: #{location.uri}")
|
|
564
|
+
return
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
line = location.range.start.line
|
|
568
|
+
character = location.range.start.character
|
|
569
|
+
|
|
570
|
+
if new_tab
|
|
571
|
+
# Open in new tab (same as :tabnew command)
|
|
572
|
+
tab_manager = ctx.editor.tab_manager
|
|
573
|
+
new_tab_obj = tab_manager.add
|
|
574
|
+
new_buffer = Mui::Buffer.new
|
|
575
|
+
new_buffer.load(file_path)
|
|
576
|
+
new_buffer.undo_manager = Mui::UndoManager.new
|
|
577
|
+
new_tab_obj.window_manager.add_window(new_buffer)
|
|
578
|
+
else
|
|
579
|
+
# Open in current window
|
|
580
|
+
current_buffer = ctx.buffer
|
|
581
|
+
if current_buffer.file_path != file_path
|
|
582
|
+
new_buffer = Mui::Buffer.new
|
|
583
|
+
new_buffer.load(file_path)
|
|
584
|
+
ctx.editor.window.buffer = new_buffer
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
window = ctx.editor.window
|
|
589
|
+
window.cursor_row = line
|
|
590
|
+
window.cursor_col = character
|
|
591
|
+
window.ensure_cursor_visible
|
|
592
|
+
|
|
593
|
+
ctx.set_message("#{File.basename(file_path)}:#{line + 1}")
|
|
594
|
+
end
|
|
595
|
+
|
|
472
596
|
def create_manager(editor)
|
|
473
597
|
mgr = Manager.new(editor: editor)
|
|
474
598
|
# Register configured servers
|
|
@@ -509,8 +633,13 @@ module Mui
|
|
|
509
633
|
ServerConfig.rubocop_lsp(auto_start: true)
|
|
510
634
|
when :kanayago
|
|
511
635
|
ServerConfig.kanayago(auto_start: true)
|
|
636
|
+
when :typeprof
|
|
637
|
+
ServerConfig.typeprof(auto_start: true)
|
|
638
|
+
when :steep
|
|
639
|
+
ServerConfig.steep(auto_start: true)
|
|
512
640
|
else
|
|
513
|
-
raise ArgumentError,
|
|
641
|
+
raise ArgumentError,
|
|
642
|
+
"Unknown server: #{name}. Use :solargraph, :ruby_lsp, :rubocop, :kanayago, :typeprof, or :steep"
|
|
514
643
|
end
|
|
515
644
|
@server_configs << config
|
|
516
645
|
end
|
|
@@ -26,6 +26,8 @@ module Mui
|
|
|
26
26
|
case ext
|
|
27
27
|
when ".rb", ".rake", ".gemspec", ".ru"
|
|
28
28
|
"ruby"
|
|
29
|
+
when ".rbs"
|
|
30
|
+
"rbs"
|
|
29
31
|
when ".js"
|
|
30
32
|
"javascript"
|
|
31
33
|
when ".ts"
|
|
@@ -99,6 +101,26 @@ module Mui
|
|
|
99
101
|
)
|
|
100
102
|
end
|
|
101
103
|
|
|
104
|
+
def typeprof(auto_start: false)
|
|
105
|
+
new(
|
|
106
|
+
name: "typeprof",
|
|
107
|
+
command: "typeprof --lsp --stdio",
|
|
108
|
+
language_ids: ["ruby"],
|
|
109
|
+
file_patterns: ["**/*.rb", "**/*.rake", "**/Gemfile", "**/Rakefile", "**/*.gemspec"],
|
|
110
|
+
auto_start: auto_start
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def steep(auto_start: false)
|
|
115
|
+
new(
|
|
116
|
+
name: "steep",
|
|
117
|
+
command: "steep langserver",
|
|
118
|
+
language_ids: %w[ruby rbs],
|
|
119
|
+
file_patterns: ["**/*.rb", "**/*.rbs", "**/*.rake", "**/Gemfile", "**/Rakefile", "**/*.gemspec"],
|
|
120
|
+
auto_start: auto_start
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
102
124
|
def custom(name:, command:, language_ids:, file_patterns:, auto_start: true, sync_on_change: true)
|
|
103
125
|
new(
|
|
104
126
|
name: name,
|
|
@@ -8,6 +8,8 @@ module Mui
|
|
|
8
8
|
class TextDocumentSync
|
|
9
9
|
DEFAULT_DEBOUNCE_MS = 300
|
|
10
10
|
|
|
11
|
+
attr_reader :client
|
|
12
|
+
|
|
11
13
|
def initialize(client:, server_config:, debounce_ms: DEFAULT_DEBOUNCE_MS)
|
|
12
14
|
@client = client
|
|
13
15
|
@server_config = server_config
|
|
@@ -38,9 +40,9 @@ module Mui
|
|
|
38
40
|
)
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
def did_change(uri:, text:, debounce: true)
|
|
42
|
-
# Skip if sync_on_change is disabled for this server
|
|
43
|
-
return unless @server_config.sync_on_change
|
|
43
|
+
def did_change(uri:, text:, debounce: true, force: false)
|
|
44
|
+
# Skip if sync_on_change is disabled for this server (unless forced)
|
|
45
|
+
return unless force || @server_config.sync_on_change
|
|
44
46
|
|
|
45
47
|
@mutex.synchronize do
|
|
46
48
|
return unless @open_documents.key?(uri)
|
data/lib/mui/lsp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mui-lsp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- S-H-GAMELINKS
|
|
@@ -43,8 +43,10 @@ files:
|
|
|
43
43
|
- lib/mui/lsp/handlers/completion.rb
|
|
44
44
|
- lib/mui/lsp/handlers/definition.rb
|
|
45
45
|
- lib/mui/lsp/handlers/diagnostics.rb
|
|
46
|
+
- lib/mui/lsp/handlers/formatting.rb
|
|
46
47
|
- lib/mui/lsp/handlers/hover.rb
|
|
47
48
|
- lib/mui/lsp/handlers/references.rb
|
|
49
|
+
- lib/mui/lsp/handlers/type_definition.rb
|
|
48
50
|
- lib/mui/lsp/highlighters/diagnostic_highlighter.rb
|
|
49
51
|
- lib/mui/lsp/json_rpc_io.rb
|
|
50
52
|
- lib/mui/lsp/manager.rb
|
|
@@ -83,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
83
85
|
- !ruby/object:Gem::Version
|
|
84
86
|
version: '0'
|
|
85
87
|
requirements: []
|
|
86
|
-
rubygems_version:
|
|
88
|
+
rubygems_version: 4.0.2
|
|
87
89
|
specification_version: 4
|
|
88
90
|
summary: LSP (Language Server Protocol) plugin for Mui editor
|
|
89
91
|
test_files: []
|