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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 263fd9cae12708ea5be41993083794357068d8af592bc9b7684f650cd9eb094d
|
|
4
|
+
data.tar.gz: 14d542c5978a50e0095acd951a418e43445208a7907024df7eb4dff8ead2e499
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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-
|
|
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:
|
|
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:
|
|
24
|
+
Max: 46
|
|
19
25
|
|
|
20
|
-
# Offense count:
|
|
26
|
+
# Offense count: 8
|
|
21
27
|
# Configuration parameters: CountComments, CountAsOne.
|
|
22
28
|
Metrics/ClassLength:
|
|
23
|
-
Max:
|
|
29
|
+
Max: 435
|
|
24
30
|
|
|
25
|
-
# Offense count:
|
|
31
|
+
# Offense count: 8
|
|
26
32
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
27
33
|
Metrics/CyclomaticComplexity:
|
|
28
34
|
Max: 26
|
|
29
35
|
|
|
30
|
-
# Offense count:
|
|
36
|
+
# Offense count: 83
|
|
31
37
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
32
38
|
Metrics/MethodLength:
|
|
33
|
-
Max:
|
|
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:
|
|
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:
|
|
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,64 @@
|
|
|
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
|
+
|
|
52
|
+
## [0.2.0] - 2025-12-12
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- Insert mode LSP completion support:
|
|
56
|
+
- Auto-trigger completion after `.`, `@`, and `::` characters via `InsertCompletion` autocmd
|
|
57
|
+
- Uses `textEdit` for precise text replacement (e.g., `@user` replaces properly)
|
|
58
|
+
- `force_reopen` ensures LSP has latest buffer content before completion
|
|
59
|
+
- Completion items include `label`, `kind`, `detail`, `documentation`, and `text_edit`
|
|
60
|
+
- `Ctrl+Space` keymap in Insert mode to manually trigger LSP completion
|
|
61
|
+
|
|
3
62
|
## [0.1.1] - 2025-12-11
|
|
4
63
|
|
|
5
64
|
### Changed
|
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
|
|
8
|
-
- **Go to Definition**: Jump to symbol definition (
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
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
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
| `
|
|
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
|
data/lib/mui/lsp/client.rb
CHANGED
|
@@ -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
|
},
|
|
@@ -84,8 +84,30 @@ module Mui
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def display_completions(items)
|
|
87
|
-
#
|
|
88
|
-
|
|
87
|
+
# Use Mui's insert completion API if available
|
|
88
|
+
if @editor.respond_to?(:start_insert_completion)
|
|
89
|
+
prefix = get_current_prefix
|
|
90
|
+
@editor.start_insert_completion(items, prefix:)
|
|
91
|
+
else
|
|
92
|
+
# Fallback: show message
|
|
93
|
+
show_message_fallback(items)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def get_current_prefix
|
|
98
|
+
window = @editor.window
|
|
99
|
+
buffer = window.buffer
|
|
100
|
+
line = buffer.line(window.cursor_row)
|
|
101
|
+
col = window.cursor_col
|
|
102
|
+
|
|
103
|
+
# Find word start
|
|
104
|
+
start_col = col
|
|
105
|
+
start_col -= 1 while start_col.positive? && line[start_col - 1] =~ /\w/
|
|
106
|
+
|
|
107
|
+
line[start_col...col] || ""
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def show_message_fallback(items)
|
|
89
111
|
count = items.length
|
|
90
112
|
|
|
91
113
|
# Build a summary message
|
|
@@ -94,13 +116,6 @@ module Mui
|
|
|
94
116
|
summary += ", ..." if count > 3
|
|
95
117
|
|
|
96
118
|
@editor.message = "#{count} completion#{"s" unless count == 1}: #{summary}"
|
|
97
|
-
|
|
98
|
-
# Store items for potential insertion
|
|
99
|
-
store_completions(items)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def store_completions(items)
|
|
103
|
-
@editor.instance_variable_set(:@lsp_completions, items)
|
|
104
119
|
end
|
|
105
120
|
|
|
106
121
|
def kind_to_string(kind)
|
|
@@ -83,15 +83,22 @@ module Mui
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def show_location_list(locations)
|
|
86
|
-
#
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
40
|
-
|
|
35
|
+
line = location.range.start.line
|
|
36
|
+
character = location.range.start.character
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
46
|
+
# Jump to position
|
|
47
|
+
window = @editor.window
|
|
48
|
+
return unless window
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
window.cursor_row = line
|
|
51
|
+
window.cursor_col = character
|
|
52
|
+
window.ensure_cursor_visible
|
|
51
53
|
|
|
52
|
-
|
|
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
|
|
58
|
-
# Store
|
|
59
|
-
|
|
60
|
-
@editor.instance_variable_set(:@
|
|
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
|