mui-lsp 0.2.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 265b2178245c88fe87ea19d4b97323cd4bc47cc104ed89258fed1e542d6c2c57
4
- data.tar.gz: 762e6e1dcb1dc49f22bf866647b9a2c7637806e16d1f8070b46a899290db93f1
3
+ metadata.gz: 263fd9cae12708ea5be41993083794357068d8af592bc9b7684f650cd9eb094d
4
+ data.tar.gz: 14d542c5978a50e0095acd951a418e43445208a7907024df7eb4dff8ead2e499
5
5
  SHA512:
6
- metadata.gz: b20c948e1fe4171cf1a87db8d738f758375f5094b2463c64dbe3b9ed6c803fb666f5692158516d543b62acbed82cb1bfe5103a6224a4d2fb7435daaa124b14da
7
- data.tar.gz: a24ed8994311f2db029ef9a9cde459a27fe9669eff27f332f60fedc7e51838d9647a37f40e61e2b2ba8e3707d5b84c6ff51237d9725d06d1155632d25f245d1b
6
+ metadata.gz: 488392ecc54c123a1a558def0e6b8ec326f85252f0ab95bb2dc97fc40f5a864e43a60563578d0e3b817207ae5122bcb1f5350a0cb884db5ea01549a9e42bfcf1
7
+ data.tar.gz: 8fd6da70ce1e5640e35f72d3d1239d0ec2d5fb76a3e5791a8b406f0efb1b3a3369a16bed112b1ba153d8ec9e96141fdcc0e6a3483470f2faeed10951864b6e26
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-12-11 11:19:34 UTC using RuboCop version 1.81.7.
3
+ # on 2025-12-26 06:52:41 UTC using RuboCop version 1.81.7.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -12,36 +12,54 @@ Lint/DuplicateBranch:
12
12
  Exclude:
13
13
  - 'lib/mui/lsp/highlighters/diagnostic_highlighter.rb'
14
14
 
15
- # Offense count: 16
15
+ # Offense count: 1
16
+ # Configuration parameters: AllowComments.
17
+ Lint/EmptyClass:
18
+ Exclude:
19
+ - 'test/mui/lsp/handlers/test_completion.rb'
20
+
21
+ # Offense count: 25
16
22
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
17
23
  Metrics/AbcSize:
18
- Max: 44
24
+ Max: 46
19
25
 
20
- # Offense count: 6
26
+ # Offense count: 8
21
27
  # Configuration parameters: CountComments, CountAsOne.
22
28
  Metrics/ClassLength:
23
- Max: 361
29
+ Max: 435
24
30
 
25
- # Offense count: 7
31
+ # Offense count: 8
26
32
  # Configuration parameters: AllowedMethods, AllowedPatterns.
27
33
  Metrics/CyclomaticComplexity:
28
34
  Max: 26
29
35
 
30
- # Offense count: 42
36
+ # Offense count: 83
31
37
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
32
38
  Metrics/MethodLength:
33
- Max: 61
39
+ Max: 56
34
40
 
35
41
  # Offense count: 4
36
42
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
37
43
  Metrics/ParameterLists:
38
44
  Max: 6
39
45
 
40
- # Offense count: 6
46
+ # Offense count: 5
41
47
  # Configuration parameters: AllowedMethods, AllowedPatterns.
42
48
  Metrics/PerceivedComplexity:
43
49
  Max: 14
44
50
 
51
+ # Offense count: 1
52
+ Naming/AccessorMethodName:
53
+ Exclude:
54
+ - 'lib/mui/lsp/handlers/completion.rb'
55
+
56
+ # Offense count: 4
57
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
58
+ # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
59
+ Naming/MethodParameterName:
60
+ Exclude:
61
+ - 'test/fixtures/sample.rb'
62
+
45
63
  # Offense count: 2
46
64
  # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
47
65
  # AllowedMethods: call
@@ -65,7 +83,7 @@ Style/IfUnlessModifier:
65
83
  Exclude:
66
84
  - 'lib/mui/lsp/json_rpc_io.rb'
67
85
 
68
- # Offense count: 1
86
+ # Offense count: 4
69
87
  # This cop supports safe autocorrection (--autocorrect).
70
88
  # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
71
89
  # URISchemes: http, https
data/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-12-26
4
+
5
+ ### Added
6
+ - Multiple LSP result merging:
7
+ - Definition, References, and Type Definition now query all matching LSP servers
8
+ - Results from all servers are merged and deduplicated
9
+ - Enables showing both Ruby source and RBS type definitions together
10
+ - Steep pre-configured server:
11
+ - `use :steep` in `Mui.lsp` block to enable
12
+ - Requires `Steepfile` in project root
13
+ - Supports `.rb` and `.rbs` files
14
+ - Ruby/RBS file toggle (`<Space>tf`):
15
+ - In Ruby files: jumps to corresponding RBS file in `sig/` directory
16
+ - In RBS files: jumps to corresponding Ruby file in `lib/` directory
17
+ - Searches multiple path patterns (e.g., `lib/mui/config.rb` → `sig/mui/config.rbs`)
18
+ - For non-Ruby files: uses LSP `textDocument/typeDefinition` as before
19
+ - Type definition support (`textDocument/typeDefinition`):
20
+ - `:LspTypeDefinition` command to jump to type definition
21
+ - `<Space>tf` keymap for type definition
22
+ - Works with TypeProf, Sorbet, Steep and other Ruby type checkers
23
+ - Uses same picker UI as Definition when multiple candidates found
24
+ - TypeProf pre-configured server:
25
+ - `use :typeprof` in `Mui.lsp` block to enable
26
+ - Requires `typeprof.conf.jsonc` in project root (run `typeprof --init` to create)
27
+ - Format support (`textDocument/formatting`):
28
+ - `:LspFormat` command to format current file
29
+ - `<Space>ff` keymap for formatting
30
+ - Works with RuboCop LSP and other formatters
31
+ - Location picker for Definition and References:
32
+ - When multiple definitions or references are found, opens a scratch buffer picker
33
+ - `j`/`k` - Navigate up/down (native cursor movement)
34
+ - `\` + `Enter` - Open selected location in current window
35
+ - `Ctrl+t` - Open selected location in new tab
36
+ - `\q` / `\` + `Esc` - Close picker
37
+
38
+ ### Fixed
39
+ - Type definition now checks `typeDefinitionProvider` capability before sending requests
40
+ - Prevents sending requests to servers that don't support type definition (e.g., Solargraph)
41
+ - Shows helpful message when no server supports type definition
42
+
43
+ ### Changed
44
+ - Keymaps changed from leader key (`\`) to `<Space>` prefix:
45
+ - `<Space>df` - Go to definition
46
+ - `<Space>rf` - Find references
47
+ - `<Space>hf` - Show hover information
48
+ - `<Space>cf` - Show completion
49
+ - `<Space>ef` - Show diagnostic at cursor
50
+ - Removed manual `@leader_pending` state management in favor of Mui's native multi-key sequence support
51
+
3
52
  ## [0.2.0] - 2025-12-12
4
53
 
5
54
  ### Added
data/README.md CHANGED
@@ -4,10 +4,12 @@ LSP (Language Server Protocol) plugin for [Mui](https://github.com/S-H-GAMELINKS
4
4
 
5
5
  ## Features
6
6
 
7
- - **Hover**: Show documentation for symbol under cursor (`K` or `\h` or `:LspHover`)
8
- - **Go to Definition**: Jump to symbol definition (`\d` or `:LspDefinition`)
9
- - **Find References**: Show all references to symbol (`\r` or `:LspReferences`)
10
- - **Completion**: Get code completion suggestions (`\c` or `:LspCompletion`)
7
+ - **Hover**: Show documentation for symbol under cursor (`K` or `<Space>hf` or `:LspHover`)
8
+ - **Go to Definition**: Jump to symbol definition (`<Space>df` or `:LspDefinition`)
9
+ - **Go to Type Definition**: Jump to type definition or toggle between Ruby/RBS files (`<Space>tf` or `:LspTypeDefinition`)
10
+ - **Find References**: Show all references to symbol (`<Space>rf` or `:LspReferences`)
11
+ - **Completion**: Get code completion suggestions (`<Space>cf` or `:LspCompletion`)
12
+ - **Format**: Format current file with LSP server (`<Space>ff` or `:LspFormat`)
11
13
  - **Diagnostics**: Display errors and warnings from LSP server (`:LspDiagnostics`)
12
14
 
13
15
  ## Supported Language Servers
@@ -18,6 +20,8 @@ Pre-configured servers for Ruby:
18
20
  - **ruby-lsp** - Shopify's Ruby language server
19
21
  - **Kanayago** - Realtime Ruby Syntax Check server
20
22
  - **RuboCop** (LSP mode) - Ruby linter with LSP support
23
+ - **TypeProf** - Ruby type analysis and inference
24
+ - **Steep** - Ruby type checker with RBS support
21
25
 
22
26
  Custom servers can be configured for other languages.
23
27
 
@@ -59,6 +63,42 @@ Available pre-configured servers:
59
63
  - `:ruby_lsp` - ruby-lsp (Shopify's Ruby LSP)
60
64
  - `:rubocop` - RuboCop in LSP mode
61
65
  - `:kanayago` - Kanayago (Japanese Ruby LSP)
66
+ - `:typeprof` - TypeProf (Ruby type analysis)
67
+ - `:steep` - Steep (Ruby type checker with RBS)
68
+
69
+ ### TypeProf Setup
70
+
71
+ TypeProf requires a configuration file in your project root. Run:
72
+
73
+ ```bash
74
+ typeprof --init
75
+ ```
76
+
77
+ This creates `typeprof.conf.jsonc` with default settings:
78
+
79
+ ```jsonc
80
+ {
81
+ "typeprof_version": "experimental",
82
+ "rbs_dir": "sig/",
83
+ "analysis_unit_dirs": ["lib"]
84
+ }
85
+ ```
86
+
87
+ TypeProf will only analyze files in the directories specified in `analysis_unit_dirs`. Without this configuration file, TypeProf will not track documents and type definition features will not work.
88
+
89
+ ### Steep Setup
90
+
91
+ Steep requires a `Steepfile` in your project root. Create one with:
92
+
93
+ ```ruby
94
+ # Steepfile
95
+ target :lib do
96
+ signature "sig"
97
+ check "lib"
98
+ end
99
+ ```
100
+
101
+ Place your RBS type definitions in the `sig/` directory.
62
102
 
63
103
  ### Custom Server Configuration
64
104
 
@@ -126,27 +166,40 @@ To manually start a server:
126
166
  | `:LspStatus` | Show running and registered servers |
127
167
  | `:LspHover` | Show hover information |
128
168
  | `:LspDefinition` | Go to definition |
169
+ | `:LspTypeDefinition` | Go to type definition |
129
170
  | `:LspReferences` | Find all references |
130
171
  | `:LspCompletion` | Show completion menu |
131
172
  | `:LspDiagnostics` | Show diagnostics for current file |
132
173
  | `:LspDiagnosticShow` | Show diagnostic at cursor in floating window |
174
+ | `:LspFormat` | Format current file |
133
175
  | `:LspLog` | Show LSP server logs in a buffer |
134
176
  | `:LspDebug` | Show debug information |
135
177
  | `:LspOpen` | Manually notify LSP server about current file |
136
178
 
137
179
  ### Keymaps
138
180
 
139
- Leader key is `\` (backslash).
140
-
141
181
  | Key | Mode | Description |
142
182
  |-----|------|-------------|
143
183
  | `K` | Normal | Show hover information (in floating window) |
144
- | `\h` | Normal | Show hover information (alternative) |
145
- | `\d` | Normal | Go to definition |
146
- | `\r` | Normal | Find references |
147
- | `\c` | Normal | Show completion |
148
- | `\e` | Normal | Show diagnostic at cursor (in floating window) |
149
- | `Esc` | Normal | Cancel leader pending state / Close floating window |
184
+ | `<Space>df` | Normal | Go to definition |
185
+ | `<Space>tf` | Normal | Go to type definition (Ruby: toggle .rb/.rbs) |
186
+ | `<Space>rf` | Normal | Find references |
187
+ | `<Space>hf` | Normal | Show hover information (alternative to K) |
188
+ | `<Space>cf` | Normal | Show completion |
189
+ | `<Space>ef` | Normal | Show diagnostic at cursor (in floating window) |
190
+ | `<Space>ff` | Normal | Format current file |
191
+ | `Esc` | Normal | Close floating window / picker |
192
+
193
+ #### Location Picker (for Definition/References with multiple candidates)
194
+
195
+ When multiple definitions or references are found, a picker buffer opens. Use standard Vim navigation:
196
+
197
+ | Key | Description |
198
+ |-----|-------------|
199
+ | `j`/`k` | Navigate up/down (native cursor movement) |
200
+ | `\` + `Enter` | Open selected location in current window |
201
+ | `Ctrl+t` | Open selected location in new tab |
202
+ | `\q` / `\` + `Esc` | Close picker |
150
203
 
151
204
  ## Architecture
152
205
 
@@ -162,9 +215,11 @@ mui-lsp/
162
215
  base.rb # Base handler class
163
216
  hover.rb # Hover response handler
164
217
  definition.rb # Definition response handler
218
+ type_definition.rb # Type definition response handler
165
219
  references.rb # References response handler
166
220
  diagnostics.rb # Diagnostics notification handler
167
221
  completion.rb # Completion response handler
222
+ formatting.rb # Formatting response handler
168
223
  json_rpc_io.rb # JSON-RPC 2.0 over stdio
169
224
  request_manager.rb # Request ID and callback management
170
225
  server_config.rb # Server configuration presets
@@ -104,6 +104,13 @@ module Mui
104
104
  }, &callback)
105
105
  end
106
106
 
107
+ def type_definition(uri:, line:, character:, &callback)
108
+ request("textDocument/typeDefinition", {
109
+ textDocument: { uri: uri },
110
+ position: { line: line, character: character }
111
+ }, &callback)
112
+ end
113
+
107
114
  def references(uri:, line:, character:, include_declaration: true, &callback)
108
115
  request("textDocument/references", {
109
116
  textDocument: { uri: uri },
@@ -119,6 +126,16 @@ module Mui
119
126
  }, &callback)
120
127
  end
121
128
 
129
+ def formatting(uri:, tab_size: 2, insert_spaces: true, &callback)
130
+ request("textDocument/formatting", {
131
+ textDocument: { uri: uri },
132
+ options: {
133
+ tabSize: tab_size,
134
+ insertSpaces: insert_spaces
135
+ }
136
+ }, &callback)
137
+ end
138
+
122
139
  def did_open(uri:, language_id:, version:, text:)
123
140
  notify("textDocument/didOpen", {
124
141
  textDocument: {
@@ -270,7 +287,13 @@ module Mui
270
287
  definition: {
271
288
  linkSupport: false
272
289
  },
290
+ typeDefinition: {
291
+ linkSupport: false
292
+ },
273
293
  references: {},
294
+ formatting: {
295
+ dynamicRegistration: false
296
+ },
274
297
  publishDiagnostics: {
275
298
  relatedInformation: true
276
299
  },
@@ -102,7 +102,7 @@ module Mui
102
102
 
103
103
  # Find word start
104
104
  start_col = col
105
- start_col -= 1 while start_col > 0 && line[start_col - 1] =~ /\w/
105
+ start_col -= 1 while start_col.positive? && line[start_col - 1] =~ /\w/
106
106
 
107
107
  line[start_col...col] || ""
108
108
  end
@@ -83,15 +83,22 @@ module Mui
83
83
  end
84
84
 
85
85
  def show_location_list(locations)
86
- # Show a list of locations for the user to choose from
87
- items = locations.map do |loc|
86
+ # Store locations for picker navigation
87
+ @editor.instance_variable_set(:@lsp_picker_locations, locations)
88
+ @editor.instance_variable_set(:@lsp_picker_type, :definition)
89
+
90
+ # Build picker content
91
+ lines = []
92
+ locations.each_with_index do |loc, idx|
88
93
  file_path = loc.file_path || loc.uri
89
- line = loc.range.start.line + 1
90
- "#{file_path}:#{line}"
94
+ display_path = File.basename(file_path.to_s)
95
+ line_num = loc.range.start.line + 1
96
+ lines << "#{idx + 1}. #{display_path}:#{line_num}"
91
97
  end
92
98
 
93
- @editor.message = "Found #{locations.length} definitions: #{items.first}..."
94
- # TODO: Integrate with quickfix list or popup menu when available
99
+ # Open scratch buffer for picker
100
+ content = "Definitions (\\Enter:open, Ctrl+t:tab, \\q:close)\n\n#{lines.join("\n")}"
101
+ @editor.open_scratch_buffer("[LSP Picker]", content)
95
102
  end
96
103
  end
97
104
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Lsp
5
+ module Handlers
6
+ # Handler for textDocument/formatting responses
7
+ class Formatting < Base
8
+ protected
9
+
10
+ def handle_result(result)
11
+ return handle_empty unless result.is_a?(Array) && !result.empty?
12
+
13
+ apply_text_edits(result)
14
+ end
15
+
16
+ def handle_empty
17
+ @editor.message = "No formatting changes"
18
+ end
19
+
20
+ private
21
+
22
+ def apply_text_edits(edits)
23
+ buffer = @editor.buffer
24
+ return @editor.message = "No buffer" unless buffer
25
+
26
+ # Sort edits in reverse order (bottom to top) to avoid position shifts
27
+ sorted_edits = edits.sort_by do |edit|
28
+ range = edit["range"]
29
+ start_line = range["start"]["line"]
30
+ start_char = range["start"]["character"]
31
+ [-start_line, -start_char]
32
+ end
33
+
34
+ # Apply each edit
35
+ changes_count = 0
36
+ sorted_edits.each do |edit|
37
+ apply_single_edit(buffer, edit)
38
+ changes_count += 1
39
+ end
40
+
41
+ @editor.message = "Formatted (#{changes_count} change#{"s" unless changes_count == 1})"
42
+ end
43
+
44
+ def apply_single_edit(buffer, edit)
45
+ range = edit["range"]
46
+ new_text = edit["newText"]
47
+
48
+ start_line = range["start"]["line"]
49
+ start_char = range["start"]["character"]
50
+ end_line = range["end"]["line"]
51
+ end_char = range["end"]["character"]
52
+
53
+ # Get current lines
54
+ lines = buffer.lines
55
+
56
+ # Build new content
57
+ # Get text before the edit range
58
+ before_text = if start_line < lines.length
59
+ line = lines[start_line] || ""
60
+ line[0, start_char] || ""
61
+ else
62
+ ""
63
+ end
64
+
65
+ # Get text after the edit range
66
+ after_text = if end_line < lines.length
67
+ line = lines[end_line] || ""
68
+ line[end_char..] || ""
69
+ else
70
+ ""
71
+ end
72
+
73
+ # Split new_text into lines
74
+ new_lines = new_text.split("\n", -1)
75
+
76
+ new_lines = [""] if new_lines.empty?
77
+
78
+ # Combine before_text with first new line
79
+ new_lines[0] = before_text + new_lines[0]
80
+
81
+ # Combine last new line with after_text
82
+ new_lines[-1] = new_lines[-1] + after_text
83
+
84
+ # Replace lines in buffer
85
+ # Delete old lines
86
+ delete_count = end_line - start_line + 1
87
+ delete_count.times do
88
+ buffer.delete_line(start_line) if start_line < buffer.lines.length
89
+ end
90
+
91
+ # Insert new lines
92
+ new_lines.each_with_index do |line, idx|
93
+ buffer.insert_line(start_line + idx, line)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -11,7 +11,12 @@ module Mui
11
11
  return handle_empty unless result.is_a?(Array) && !result.empty?
12
12
 
13
13
  locations = result.map { |loc| Protocol::Location.from_hash(loc) }
14
- show_references(locations)
14
+
15
+ if locations.length == 1
16
+ jump_to_location(locations.first)
17
+ else
18
+ show_location_list(locations)
19
+ end
15
20
  end
16
21
 
17
22
  def handle_empty
@@ -20,44 +25,52 @@ module Mui
20
25
 
21
26
  private
22
27
 
23
- def show_references(locations)
24
- count = locations.length
25
-
26
- # Build message with first few references
27
- lines = ["Found #{count} reference#{"s" unless count == 1}"]
28
-
29
- # Group by file for display
30
- by_file = locations.group_by(&:file_path)
31
-
32
- # Display first few references
33
- displayed = 0
34
- max_display = 3
35
-
36
- by_file.each do |file_path, file_locations|
37
- break if displayed >= max_display
28
+ def jump_to_location(location)
29
+ file_path = location.file_path
30
+ unless file_path
31
+ @editor.message = "Cannot open: #{location.uri}"
32
+ return
33
+ end
38
34
 
39
- file_locations.each do |loc|
40
- break if displayed >= max_display
35
+ line = location.range.start.line
36
+ character = location.range.start.character
41
37
 
42
- line = loc.range.start.line + 1
43
- lines << " #{file_path || loc.uri}:#{line}"
44
- displayed += 1
45
- end
38
+ # Open the file in current window
39
+ current_buffer = @editor.buffer
40
+ if current_buffer.file_path != file_path
41
+ new_buffer = Mui::Buffer.new
42
+ new_buffer.load(file_path)
43
+ @editor.window.buffer = new_buffer
46
44
  end
47
45
 
48
- lines << " ... and #{count - max_display} more" if count > max_display
46
+ # Jump to position
47
+ window = @editor.window
48
+ return unless window
49
49
 
50
- @editor.message = lines.first
50
+ window.cursor_row = line
51
+ window.cursor_col = character
52
+ window.ensure_cursor_visible
51
53
 
52
- # TODO: Integrate with quickfix list when available
53
- # Store references for navigation
54
- store_references(locations)
54
+ @editor.message = "#{File.basename(file_path)}:#{line + 1}"
55
55
  end
56
56
 
57
- def store_references(locations)
58
- # Store references for potential :cnext/:cprev navigation
59
- # This could be integrated with Mui's quickfix system if available
60
- @editor.instance_variable_set(:@lsp_references, locations)
57
+ def show_location_list(locations)
58
+ # Store locations for picker navigation
59
+ @editor.instance_variable_set(:@lsp_picker_locations, locations)
60
+ @editor.instance_variable_set(:@lsp_picker_type, :references)
61
+
62
+ # Build picker content
63
+ lines = []
64
+ locations.each_with_index do |loc, idx|
65
+ file_path = loc.file_path || loc.uri
66
+ display_path = File.basename(file_path.to_s)
67
+ line_num = loc.range.start.line + 1
68
+ lines << "#{idx + 1}. #{display_path}:#{line_num}"
69
+ end
70
+
71
+ # Open scratch buffer for picker
72
+ content = "References (#{locations.length} found) (\\Enter:open, Ctrl+t:tab, \\q:close)\n\n#{lines.join("\n")}"
73
+ @editor.open_scratch_buffer("[LSP Picker]", content)
61
74
  end
62
75
  end
63
76
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Lsp
5
+ module Handlers
6
+ # Handler for textDocument/typeDefinition responses
7
+ class TypeDefinition < Base
8
+ protected
9
+
10
+ def handle_result(result)
11
+ locations = normalize_locations(result)
12
+ return handle_empty if locations.empty?
13
+
14
+ if locations.length == 1
15
+ jump_to_location(locations.first)
16
+ else
17
+ show_location_list(locations)
18
+ end
19
+ end
20
+
21
+ def handle_empty
22
+ @editor.message = "No type definition found"
23
+ end
24
+
25
+ private
26
+
27
+ def normalize_locations(result)
28
+ case result
29
+ when Array
30
+ result.map { |loc| parse_location(loc) }.compact
31
+ when Hash
32
+ location = parse_location(result)
33
+ location ? [location] : []
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ def parse_location(data)
40
+ return nil unless data
41
+
42
+ # Handle both Location and LocationLink
43
+ if data["targetUri"]
44
+ # LocationLink
45
+ Protocol::Location.new(
46
+ uri: data["targetUri"],
47
+ range: data["targetSelectionRange"] || data["targetRange"]
48
+ )
49
+ elsif data["uri"]
50
+ # Location
51
+ Protocol::Location.from_hash(data)
52
+ end
53
+ end
54
+
55
+ def jump_to_location(location)
56
+ file_path = location.file_path
57
+ unless file_path
58
+ @editor.message = "Cannot open: #{location.uri}"
59
+ return
60
+ end
61
+
62
+ line = location.range.start.line
63
+ character = location.range.start.character
64
+
65
+ # Open the file in current window
66
+ current_buffer = @editor.buffer
67
+ if current_buffer.file_path != file_path
68
+ # Need to open a different file
69
+ new_buffer = Mui::Buffer.new
70
+ new_buffer.load(file_path)
71
+ @editor.window.buffer = new_buffer
72
+ end
73
+
74
+ # Jump to position
75
+ window = @editor.window
76
+ return unless window
77
+
78
+ window.cursor_row = line
79
+ window.cursor_col = character
80
+ window.ensure_cursor_visible
81
+
82
+ @editor.message = "#{File.basename(file_path)}:#{line + 1}"
83
+ end
84
+
85
+ def show_location_list(locations)
86
+ # Store locations for picker navigation
87
+ @editor.instance_variable_set(:@lsp_picker_locations, locations)
88
+ @editor.instance_variable_set(:@lsp_picker_type, :type_definition)
89
+
90
+ # Build picker content
91
+ lines = []
92
+ locations.each_with_index do |loc, idx|
93
+ file_path = loc.file_path || loc.uri
94
+ display_path = File.basename(file_path.to_s)
95
+ line_num = loc.range.start.line + 1
96
+ lines << "#{idx + 1}. #{display_path}:#{line_num}"
97
+ end
98
+
99
+ # Open scratch buffer for picker
100
+ content = "Type Definitions (\\Enter:open, Ctrl+t:tab, \\q:close)\n\n#{lines.join("\n")}"
101
+ @editor.open_scratch_buffer("[LSP Picker]", content)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -3,6 +3,8 @@
3
3
  require_relative "handlers/base"
4
4
  require_relative "handlers/hover"
5
5
  require_relative "handlers/definition"
6
+ require_relative "handlers/type_definition"
6
7
  require_relative "handlers/references"
7
8
  require_relative "handlers/diagnostics"
8
9
  require_relative "handlers/completion"
10
+ require_relative "handlers/formatting"
@@ -86,6 +86,20 @@ module Mui
86
86
  nil
87
87
  end
88
88
 
89
+ def client_for_capability(file_path, capability)
90
+ @mutex.synchronize do
91
+ @server_configs.each do |name, config|
92
+ next unless config.handles_file?(file_path)
93
+ next unless @clients[name]&.running?
94
+
95
+ # Check if server supports the capability
96
+ capabilities = @clients[name].server_capabilities
97
+ return @clients[name] if capabilities[capability]
98
+ end
99
+ end
100
+ nil
101
+ end
102
+
89
103
  def text_sync_for(file_path)
90
104
  @mutex.synchronize do
91
105
  @server_configs.each do |name, config|
@@ -215,21 +229,106 @@ module Mui
215
229
  end
216
230
 
217
231
  def definition(file_path:, line:, character:)
218
- client = client_for(file_path)
219
- unless client
232
+ text_syncs = text_syncs_for(file_path)
233
+ if text_syncs.empty?
220
234
  @editor.message = server_unavailable_message(file_path)
221
235
  return
222
236
  end
223
237
 
224
238
  uri = TextDocumentSync.path_to_uri(file_path)
225
- handler = Handlers::Definition.new(editor: @editor, client: client)
239
+ handler = Handlers::Definition.new(editor: @editor, client: text_syncs.first.client)
240
+
241
+ # Collect results from all clients
242
+ results_mutex = Mutex.new
243
+ pending_count = text_syncs.size
244
+ all_results = []
245
+
246
+ text_syncs.each do |text_sync|
247
+ text_sync.client.definition(uri: uri, line: line, character: character) do |result, _error|
248
+ results_mutex.synchronize do
249
+ all_results << result if result
250
+ pending_count -= 1
251
+
252
+ if pending_count.zero?
253
+ merged = merge_locations(all_results)
254
+ handler.handle(merged, nil)
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
226
260
 
227
- client.definition(uri: uri, line: line, character: character) do |result, error|
228
- handler.handle(result, error)
261
+ def type_definition(file_path:, line:, character:)
262
+ # Only send to servers that support typeDefinitionProvider
263
+ text_syncs = text_syncs_for(file_path).select do |ts|
264
+ ts.client.server_capabilities["typeDefinitionProvider"]
265
+ end
266
+
267
+ if text_syncs.empty?
268
+ # Fallback message: check if any server is running but doesn't support typeDefinition
269
+ any_text_sync = text_syncs_for(file_path).first
270
+ @editor.message = if any_text_sync
271
+ "LSP: no server supports typeDefinition for this file"
272
+ else
273
+ server_unavailable_message(file_path)
274
+ end
275
+ return
276
+ end
277
+
278
+ uri = TextDocumentSync.path_to_uri(file_path)
279
+ handler = Handlers::TypeDefinition.new(editor: @editor, client: text_syncs.first.client)
280
+
281
+ # Collect results from all clients
282
+ results_mutex = Mutex.new
283
+ pending_count = text_syncs.size
284
+ all_results = []
285
+
286
+ text_syncs.each do |text_sync|
287
+ text_sync.client.type_definition(uri: uri, line: line, character: character) do |result, _error|
288
+ results_mutex.synchronize do
289
+ all_results << result if result
290
+ pending_count -= 1
291
+
292
+ if pending_count.zero?
293
+ merged = merge_locations(all_results)
294
+ handler.handle(merged, nil)
295
+ end
296
+ end
297
+ end
229
298
  end
230
299
  end
231
300
 
232
301
  def references(file_path:, line:, character:)
302
+ text_syncs = text_syncs_for(file_path)
303
+ if text_syncs.empty?
304
+ @editor.message = server_unavailable_message(file_path)
305
+ return
306
+ end
307
+
308
+ uri = TextDocumentSync.path_to_uri(file_path)
309
+ handler = Handlers::References.new(editor: @editor, client: text_syncs.first.client)
310
+
311
+ # Collect results from all clients
312
+ results_mutex = Mutex.new
313
+ pending_count = text_syncs.size
314
+ all_results = []
315
+
316
+ text_syncs.each do |text_sync|
317
+ text_sync.client.references(uri: uri, line: line, character: character) do |result, _error|
318
+ results_mutex.synchronize do
319
+ all_results << result if result
320
+ pending_count -= 1
321
+
322
+ if pending_count.zero?
323
+ merged = merge_locations(all_results)
324
+ handler.handle(merged, nil)
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
330
+
331
+ def completion(file_path:, line:, character:)
233
332
  client = client_for(file_path)
234
333
  unless client
235
334
  @editor.message = server_unavailable_message(file_path)
@@ -237,14 +336,14 @@ module Mui
237
336
  end
238
337
 
239
338
  uri = TextDocumentSync.path_to_uri(file_path)
240
- handler = Handlers::References.new(editor: @editor, client: client)
339
+ handler = Handlers::Completion.new(editor: @editor, client: client)
241
340
 
242
- client.references(uri: uri, line: line, character: character) do |result, error|
341
+ client.completion(uri: uri, line: line, character: character) do |result, error|
243
342
  handler.handle(result, error)
244
343
  end
245
344
  end
246
345
 
247
- def completion(file_path:, line:, character:)
346
+ def format(file_path:, tab_size: 2, insert_spaces: true)
248
347
  client = client_for(file_path)
249
348
  unless client
250
349
  @editor.message = server_unavailable_message(file_path)
@@ -252,13 +351,48 @@ module Mui
252
351
  end
253
352
 
254
353
  uri = TextDocumentSync.path_to_uri(file_path)
255
- handler = Handlers::Completion.new(editor: @editor, client: client)
354
+ handler = Handlers::Formatting.new(editor: @editor, client: client)
256
355
 
257
- client.completion(uri: uri, line: line, character: character) do |result, error|
356
+ client.formatting(uri: uri, tab_size: tab_size, insert_spaces: insert_spaces) do |result, error|
258
357
  handler.handle(result, error)
259
358
  end
260
359
  end
261
360
 
361
+ def jump_to_type_file(file_path:, line: nil, character: nil)
362
+ # For Ruby/RBS files, use custom toggle behavior
363
+ if file_path&.end_with?(".rb", ".rbs")
364
+ jump_to_ruby_type_file(file_path)
365
+ else
366
+ # For other languages, use LSP typeDefinition
367
+ type_definition(file_path: file_path, line: line, character: character)
368
+ end
369
+ end
370
+
371
+ private
372
+
373
+ def jump_to_ruby_type_file(file_path)
374
+ target_path = if file_path.end_with?(".rb")
375
+ find_rbs_file(file_path)
376
+ else
377
+ find_ruby_file(file_path)
378
+ end
379
+
380
+ unless target_path
381
+ ext = File.extname(file_path)
382
+ target_ext = ext == ".rb" ? ".rbs" : ".rb"
383
+ @editor.message = "No #{target_ext} file found for #{File.basename(file_path)}"
384
+ return
385
+ end
386
+
387
+ # Open the target file
388
+ new_buffer = Mui::Buffer.new
389
+ new_buffer.load(target_path)
390
+ @editor.window.buffer = new_buffer
391
+ @editor.message = "Opened #{File.basename(target_path)}"
392
+ end
393
+
394
+ public
395
+
262
396
  def running_servers
263
397
  @mutex.synchronize do
264
398
  @clients.select { |_, client| client.running? }.keys
@@ -293,6 +427,80 @@ module Mui
293
427
 
294
428
  private
295
429
 
430
+ def merge_locations(results)
431
+ # Flatten all results into a single array
432
+ merged = results.flat_map do |result|
433
+ case result
434
+ when Array then result
435
+ when Hash then [result]
436
+ else []
437
+ end
438
+ end
439
+
440
+ # Remove duplicates based on uri and range
441
+ merged.uniq do |loc|
442
+ uri = loc["uri"] || loc["targetUri"]
443
+ range = loc["range"] || loc["targetSelectionRange"]
444
+ [uri, range]
445
+ end
446
+ end
447
+
448
+ def find_rbs_file(ruby_file_path)
449
+ # Find project root
450
+ project_root = find_project_root(ruby_file_path)
451
+
452
+ # Get relative path from project root
453
+ abs_path = File.expand_path(ruby_file_path)
454
+ rel_path = abs_path.sub("#{project_root}/", "")
455
+
456
+ # Try different RBS path patterns
457
+ candidates = []
458
+
459
+ # Pattern 1: sig/relative_path.rbs (e.g., lib/mui/config.rb -> sig/lib/mui/config.rbs)
460
+ candidates << File.join(project_root, "sig", rel_path.sub(/\.rb$/, ".rbs"))
461
+
462
+ # Pattern 2: sig/without_lib.rbs (e.g., lib/mui/config.rb -> sig/mui/config.rbs)
463
+ if rel_path.start_with?("lib/")
464
+ candidates << File.join(project_root, "sig", rel_path.sub(%r{^lib/}, "").sub(/\.rb$/, ".rbs"))
465
+ end
466
+
467
+ # Pattern 3: sig/basename.rbs (e.g., lib/mui/config.rb -> sig/config.rbs)
468
+ candidates << File.join(project_root, "sig", "#{File.basename(ruby_file_path, ".rb")}.rbs")
469
+
470
+ # Return first existing file
471
+ candidates.find { |path| File.exist?(path) }
472
+ end
473
+
474
+ def find_ruby_file(rbs_file_path)
475
+ # Find project root
476
+ project_root = find_project_root(rbs_file_path)
477
+
478
+ # Get relative path from project root
479
+ abs_path = File.expand_path(rbs_file_path)
480
+ rel_path = abs_path.sub("#{project_root}/", "")
481
+
482
+ # Remove sig/ prefix if present
483
+ rel_path = rel_path.sub(%r{^sig/}, "")
484
+
485
+ # Try different Ruby path patterns
486
+ candidates = []
487
+
488
+ # Pattern 1: lib/relative_path.rb (e.g., sig/mui/config.rbs -> lib/mui/config.rb)
489
+ candidates << File.join(project_root, "lib", rel_path.sub(/\.rbs$/, ".rb"))
490
+
491
+ # Pattern 2: relative_path.rb without lib (e.g., sig/lib/mui/config.rbs -> lib/mui/config.rb)
492
+ candidates << File.join(project_root, rel_path.sub(/\.rbs$/, ".rb")) if rel_path.start_with?("lib/")
493
+
494
+ # Pattern 3: Search in lib directory for basename
495
+ basename = File.basename(rbs_file_path, ".rbs")
496
+ Dir.glob(File.join(project_root, "lib", "**", "#{basename}.rb")).each do |path|
497
+ candidates << path
498
+ end
499
+
500
+ # Return first existing file
501
+ candidates.find { |path| File.exist?(path) }
502
+ end
503
+
296
504
  def send_pending_documents(server_name)
297
505
  text_sync = @mutex.synchronize { @text_syncs[server_name] }
298
506
  config = @mutex.synchronize { @server_configs[server_name] }
@@ -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,46 @@ 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
159
140
  end
160
141
 
161
142
  # Insert mode: Ctrl+Space - Trigger LSP completion
@@ -163,6 +144,38 @@ module Mui
163
144
  handle_lsp_completion(ctx)
164
145
  true
165
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
178
+ end
166
179
  end
167
180
 
168
181
  def register_autocmds
@@ -283,6 +296,18 @@ module Mui
283
296
  get_manager(ctx.editor).definition(file_path: file_path, line: line, character: character)
284
297
  end
285
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
+
286
311
  def handle_lsp_references(ctx)
287
312
  file_path = ctx.buffer.file_path
288
313
  unless file_path
@@ -456,6 +481,22 @@ module Mui
456
481
  ctx.set_message("LSP: opened #{File.basename(file_path)}")
457
482
  end
458
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
+
459
500
  public
460
501
 
461
502
  def get_manager(editor)
@@ -478,14 +519,80 @@ module Mui
478
519
  ServerConfig.rubocop_lsp(auto_start: true)
479
520
  when :kanayago
480
521
  ServerConfig.kanayago(auto_start: true)
522
+ when :steep
523
+ ServerConfig.steep(auto_start: true)
481
524
  else
482
- 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"
483
526
  end
484
527
  register_server(config)
485
528
  end
486
529
 
487
530
  private
488
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
+
489
596
  def create_manager(editor)
490
597
  mgr = Manager.new(editor: editor)
491
598
  # Register configured servers
@@ -526,8 +633,13 @@ module Mui
526
633
  ServerConfig.rubocop_lsp(auto_start: true)
527
634
  when :kanayago
528
635
  ServerConfig.kanayago(auto_start: true)
636
+ when :typeprof
637
+ ServerConfig.typeprof(auto_start: true)
638
+ when :steep
639
+ ServerConfig.steep(auto_start: true)
529
640
  else
530
- 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"
531
643
  end
532
644
  @server_configs << config
533
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Mui
4
4
  module Lsp
5
- VERSION = "0.2.0"
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.2.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: 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: []