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.
@@ -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
- # Leader key (\) - start pending mode
91
- keymap(:normal, "\\") do |_ctx|
92
- @leader_pending = true
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
- # \d - Go to definition
97
- keymap(:normal, "d") do |ctx|
98
- if @leader_pending
99
- @leader_pending = false
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
- # \r - Go to references
108
- keymap(:normal, "r") do |ctx|
109
- if @leader_pending
110
- @leader_pending = false
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
- # \h - Show hover (alternative to K)
119
- keymap(:normal, "h") do |ctx|
120
- if @leader_pending
121
- @leader_pending = false
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
- # \c - Show completion
130
- keymap(:normal, "c") do |ctx|
131
- if @leader_pending
132
- @leader_pending = false
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
- # \e - Show diagnostic at cursor
141
- keymap(:normal, "e") do |ctx|
142
- if @leader_pending
143
- @leader_pending = false
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
- # Cancel leader pending on any other key (via Escape)
152
- keymap(:normal, "\e") do |_ctx|
153
- if @leader_pending
154
- @leader_pending = false
155
- true
156
- else
157
- false
158
- end
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
- get_manager(ctx.editor).completion(file_path: file_path, line: line, character: character)
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 :kanayago"
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, "Unknown server: #{name}. Use :solargraph, :ruby_lsp, :rubocop, or :kanayago"
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Mui
4
4
  module Lsp
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
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.1.1
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: 3.6.9
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: []