diff options
Diffstat (limited to 'docs/dev')
-rw-r--r-- | docs/dev/lsp-extensions.md | 326 |
1 files changed, 316 insertions, 10 deletions
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 7c45aef4c..dbc95be38 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -3,9 +3,13 @@ | |||
3 | This document describes LSP extensions used by rust-analyzer. | 3 | This document describes LSP extensions used by rust-analyzer. |
4 | It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ). | 4 | It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ). |
5 | We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority. | 5 | We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority. |
6 | All capabilities are enabled via `experimental` field of `ClientCapabilities`. | 6 | All capabilities are enabled via `experimental` field of `ClientCapabilities` or `ServerCapabilities`. |
7 | Requests which we hope to upstream live under `experimental/` namespace. | ||
8 | Requests, which are likely to always remain specific to `rust-analyzer` are under `rust-analyzer/` namespace. | ||
7 | 9 | ||
8 | ## `SnippetTextEdit` | 10 | ## Snippet `TextEdit` |
11 | |||
12 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/724 | ||
9 | 13 | ||
10 | **Client Capability:** `{ "snippetTextEdit": boolean }` | 14 | **Client Capability:** `{ "snippetTextEdit": boolean }` |
11 | 15 | ||
@@ -36,13 +40,96 @@ At the moment, rust-analyzer guarantees that only a single edit will have `Inser | |||
36 | * Where exactly are `SnippetTextEdit`s allowed (only in code actions at the moment)? | 40 | * Where exactly are `SnippetTextEdit`s allowed (only in code actions at the moment)? |
37 | * Can snippets span multiple files (so far, no)? | 41 | * Can snippets span multiple files (so far, no)? |
38 | 42 | ||
39 | ## `joinLines` | 43 | ## `CodeAction` Groups |
44 | |||
45 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/994 | ||
46 | |||
47 | **Client Capability:** `{ "codeActionGroup": boolean }` | ||
48 | |||
49 | If this capability is set, `CodeAction` returned from the server contain an additional field, `group`: | ||
50 | |||
51 | ```typescript | ||
52 | interface CodeAction { | ||
53 | title: string; | ||
54 | group?: string; | ||
55 | ... | ||
56 | } | ||
57 | ``` | ||
58 | |||
59 | All code-actions with the same `group` should be grouped under single (extendable) entry in lightbulb menu. | ||
60 | The set of actions `[ { title: "foo" }, { group: "frobnicate", title: "bar" }, { group: "frobnicate", title: "baz" }]` should be rendered as | ||
61 | |||
62 | ``` | ||
63 | 💡 | ||
64 | +-------------+ | ||
65 | | foo | | ||
66 | +-------------+-----+ | ||
67 | | frobnicate >| bar | | ||
68 | +-------------+-----+ | ||
69 | | baz | | ||
70 | +-----+ | ||
71 | ``` | ||
72 | |||
73 | Alternatively, selecting `frobnicate` could present a user with an additional menu to choose between `bar` and `baz`. | ||
74 | |||
75 | ### Example | ||
76 | |||
77 | ```rust | ||
78 | fn main() { | ||
79 | let x: Entry/*cursor here*/ = todo!(); | ||
80 | } | ||
81 | ``` | ||
82 | |||
83 | Invoking code action at this position will yield two code actions for importing `Entry` from either `collections::HashMap` or `collection::BTreeMap`, grouped under a single "import" group. | ||
84 | |||
85 | ### Unresolved Questions | ||
86 | |||
87 | * Is a fixed two-level structure enough? | ||
88 | * Should we devise a general way to encode custom interaction protocols for GUI refactorings? | ||
89 | |||
90 | ## Parent Module | ||
91 | |||
92 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/1002 | ||
93 | |||
94 | **Server Capability:** `{ "parentModule": boolean }` | ||
95 | |||
96 | This request is send from client to server to handle "Goto Parent Module" editor action. | ||
97 | |||
98 | **Method:** `experimental/parentModule` | ||
99 | |||
100 | **Request:** `TextDocumentPositionParams` | ||
101 | |||
102 | **Response:** `Location | Location[] | LocationLink[] | null` | ||
103 | |||
104 | |||
105 | ### Example | ||
106 | |||
107 | ```rust | ||
108 | // src/main.rs | ||
109 | mod foo; | ||
110 | // src/foo.rs | ||
111 | |||
112 | /* cursor here*/ | ||
113 | ``` | ||
114 | |||
115 | `experimental/parentModule` returns a single `Link` to the `mod foo;` declaration. | ||
116 | |||
117 | ### Unresolved Question | ||
118 | |||
119 | * An alternative would be to use a more general "gotoSuper" request, which would work for super methods, super classes and super modules. | ||
120 | This is the approach IntelliJ Rust is takeing. | ||
121 | However, experience shows that super module (which generally has a feeling of navigation between files) should be separate. | ||
122 | If you want super module, but the cursor happens to be inside an overriden function, the behavior with single "gotoSuper" request is surprising. | ||
123 | |||
124 | ## Join Lines | ||
125 | |||
126 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/992 | ||
40 | 127 | ||
41 | **Server Capability:** `{ "joinLines": boolean }` | 128 | **Server Capability:** `{ "joinLines": boolean }` |
42 | 129 | ||
43 | This request is send from client to server to handle "Join Lines" editor action. | 130 | This request is send from client to server to handle "Join Lines" editor action. |
44 | 131 | ||
45 | **Method:** `experimental/JoinLines` | 132 | **Method:** `experimental/joinLines` |
46 | 133 | ||
47 | **Request:** | 134 | **Request:** |
48 | 135 | ||
@@ -55,11 +142,7 @@ interface JoinLinesParams { | |||
55 | } | 142 | } |
56 | ``` | 143 | ``` |
57 | 144 | ||
58 | **Response:** | 145 | **Response:** `TextEdit[]` |
59 | |||
60 | ```typescript | ||
61 | TextEdit[] | ||
62 | ``` | ||
63 | 146 | ||
64 | ### Example | 147 | ### Example |
65 | 148 | ||
@@ -71,7 +154,7 @@ fn main() { | |||
71 | } | 154 | } |
72 | ``` | 155 | ``` |
73 | 156 | ||
74 | `experimental/joinLines` yields (curly braces are automagiacally removed) | 157 | `experimental/joinLines` yields (curly braces are automagically removed) |
75 | 158 | ||
76 | ```rust | 159 | ```rust |
77 | fn main() { | 160 | fn main() { |
@@ -85,6 +168,59 @@ fn main() { | |||
85 | Currently this is left to editor's discretion, but it might be useful to specify on the server via snippets. | 168 | Currently this is left to editor's discretion, but it might be useful to specify on the server via snippets. |
86 | However, it then becomes unclear how it works with multi cursor. | 169 | However, it then becomes unclear how it works with multi cursor. |
87 | 170 | ||
171 | ## On Enter | ||
172 | |||
173 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/1001 | ||
174 | |||
175 | **Server Capability:** `{ "onEnter": boolean }` | ||
176 | |||
177 | This request is send from client to server to handle <kbd>Enter</kbd> keypress. | ||
178 | |||
179 | **Method:** `experimental/onEnter` | ||
180 | |||
181 | **Request:**: `TextDocumentPositionParams` | ||
182 | |||
183 | **Response:** | ||
184 | |||
185 | ```typescript | ||
186 | SnippetTextEdit[] | ||
187 | ``` | ||
188 | |||
189 | ### Example | ||
190 | |||
191 | ```rust | ||
192 | fn main() { | ||
193 | // Some /*cursor here*/ docs | ||
194 | let x = 92; | ||
195 | } | ||
196 | ``` | ||
197 | |||
198 | `experimental/onEnter` returns the following snippet | ||
199 | |||
200 | ```rust | ||
201 | fn main() { | ||
202 | // Some | ||
203 | // $0 docs | ||
204 | let x = 92; | ||
205 | } | ||
206 | ``` | ||
207 | |||
208 | The primary goal of `onEnter` is to handle automatic indentation when opening a new line. | ||
209 | This is not yet implemented. | ||
210 | The secondary goal is to handle fixing up syntax, like continuing doc strings and comments, and escaping `\n` in string literals. | ||
211 | |||
212 | As proper cursor positioning is raison-d'etat for `onEnter`, it uses `SnippetTextEdit`. | ||
213 | |||
214 | ### Unresolved Question | ||
215 | |||
216 | * How to deal with synchronicity of the request? | ||
217 | One option is to require the client to block until the server returns the response. | ||
218 | Another option is to do a OT-style merging of edits from client and server. | ||
219 | A third option is to do a record-replay: client applies heuristic on enter immediatelly, then applies all user's keypresses. | ||
220 | When the server is ready with the response, the client rollbacks all the changes and applies the recorded actions on top of the correct response. | ||
221 | * How to deal with multiple carets? | ||
222 | * Should we extend this to arbitrary typed events and not just `onEnter`? | ||
223 | |||
88 | ## Structural Search Replace (SSR) | 224 | ## Structural Search Replace (SSR) |
89 | 225 | ||
90 | **Server Capability:** `{ "ssr": boolean }` | 226 | **Server Capability:** `{ "ssr": boolean }` |
@@ -119,3 +255,173 @@ SSR with query `foo($a:expr, $b:expr) ==>> ($a).foo($b)` will transform, eg `foo | |||
119 | 255 | ||
120 | * Probably needs search without replace mode | 256 | * Probably needs search without replace mode |
121 | * Needs a way to limit the scope to certain files. | 257 | * Needs a way to limit the scope to certain files. |
258 | |||
259 | ## Matching Brace | ||
260 | |||
261 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/999 | ||
262 | |||
263 | **Server Capability:** `{ "matchingBrace": boolean }` | ||
264 | |||
265 | This request is send from client to server to handle "Matching Brace" editor action. | ||
266 | |||
267 | **Method:** `experimental/matchingBrace` | ||
268 | |||
269 | **Request:** | ||
270 | |||
271 | ```typescript | ||
272 | interface MatchingBraceParams { | ||
273 | textDocument: TextDocumentIdentifier, | ||
274 | /// Position for each cursor | ||
275 | positions: Position[], | ||
276 | } | ||
277 | ``` | ||
278 | |||
279 | **Response:** | ||
280 | |||
281 | ```typescript | ||
282 | Position[] | ||
283 | ``` | ||
284 | |||
285 | ### Example | ||
286 | |||
287 | ```rust | ||
288 | fn main() { | ||
289 | let x: Vec<()>/*cursor here*/ = vec![] | ||
290 | } | ||
291 | ``` | ||
292 | |||
293 | `experimental/matchingBrace` yields the position of `<`. | ||
294 | In many cases, matching braces can be handled by the editor. | ||
295 | However, some cases (like disambiguating between generics and comparison operations) need a real parser. | ||
296 | Moreover, it would be cool if editors didn't need to implement even basic language parsing | ||
297 | |||
298 | ### Unresolved Question | ||
299 | |||
300 | * Should we return a a nested brace structure, to allow paredit-like actions of jump *out* of the current brace pair? | ||
301 | This is how `SelectionRange` request works. | ||
302 | * Alternatively, should we perhaps flag certain `SelectionRange`s as being brace pairs? | ||
303 | |||
304 | ## Analyzer Status | ||
305 | |||
306 | **Method:** `rust-analyzer/analyzerStatus` | ||
307 | |||
308 | **Request:** `null` | ||
309 | |||
310 | **Response:** `string` | ||
311 | |||
312 | Returns internal status message, mostly for debugging purposes. | ||
313 | |||
314 | ## Collect Garbage | ||
315 | |||
316 | **Method:** `rust-analyzer/collectGarbage` | ||
317 | |||
318 | **Request:** `null` | ||
319 | |||
320 | **Response:** `null` | ||
321 | |||
322 | Frees some caches. For internal use, and is mostly broken at the moment. | ||
323 | |||
324 | ## Syntax Tree | ||
325 | |||
326 | **Method:** `rust-analyzer/syntaxTree` | ||
327 | |||
328 | **Request:** | ||
329 | |||
330 | ```typescript | ||
331 | interface SyntaxTeeParams { | ||
332 | textDocument: TextDocumentIdentifier, | ||
333 | range?: Range, | ||
334 | } | ||
335 | ``` | ||
336 | |||
337 | **Response:** `string` | ||
338 | |||
339 | Returns textual representation of a parse tree for the file/selected region. | ||
340 | Primarily for debugging, but very useful for all people working on rust-analyzer itself. | ||
341 | |||
342 | ## Expand Macro | ||
343 | |||
344 | **Method:** `rust-analyzer/expandMacro` | ||
345 | |||
346 | **Request:** | ||
347 | |||
348 | ```typescript | ||
349 | interface ExpandMacroParams { | ||
350 | textDocument: TextDocumentIdentifier, | ||
351 | position: Position, | ||
352 | } | ||
353 | ``` | ||
354 | |||
355 | **Response:** | ||
356 | |||
357 | ```typescript | ||
358 | interface ExpandedMacro { | ||
359 | name: string, | ||
360 | expansion: string, | ||
361 | } | ||
362 | ``` | ||
363 | |||
364 | Expands macro call at a given position. | ||
365 | |||
366 | ## Inlay Hints | ||
367 | |||
368 | **Method:** `rust-analyzer/inlayHints` | ||
369 | |||
370 | This request is send from client to server to render "inlay hints" -- virtual text inserted into editor to show things like inferred types. | ||
371 | Generally, the client should re-query inlay hints after every modification. | ||
372 | Note that we plan to move this request to `experimental/inlayHints`, as it is not really Rust-specific, but the current API is not necessary the right one. | ||
373 | Upstream issue: https://github.com/microsoft/language-server-protocol/issues/956 | ||
374 | |||
375 | **Request:** | ||
376 | |||
377 | ```typescript | ||
378 | interface InlayHintsParams { | ||
379 | textDocument: TextDocumentIdentifier, | ||
380 | } | ||
381 | ``` | ||
382 | |||
383 | **Response:** `InlayHint[]` | ||
384 | |||
385 | ```typescript | ||
386 | interface InlayHint { | ||
387 | kind: "TypeHint" | "ParameterHint" | "ChainingHint", | ||
388 | range: Range, | ||
389 | label: string, | ||
390 | } | ||
391 | ``` | ||
392 | |||
393 | ## Runnables | ||
394 | |||
395 | **Method:** `rust-analyzer/runnables` | ||
396 | |||
397 | This request is send from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`). | ||
398 | Note that we plan to move this request to `experimental/runnables`, as it is not really Rust-specific, but the current API is not necessary the right one. | ||
399 | Upstream issue: https://github.com/microsoft/language-server-protocol/issues/944 | ||
400 | |||
401 | **Request:** | ||
402 | |||
403 | ```typescript | ||
404 | interface RunnablesParams { | ||
405 | textDocument: TextDocumentIdentifier; | ||
406 | /// If null, compute runnables for the whole file. | ||
407 | position?: Position; | ||
408 | } | ||
409 | ``` | ||
410 | |||
411 | **Response:** `Runnable[]` | ||
412 | |||
413 | ```typescript | ||
414 | interface Runnable { | ||
415 | /// The range this runnable is applicable for. | ||
416 | range: lc.Range; | ||
417 | /// The label to show in the UI. | ||
418 | label: string; | ||
419 | /// The following fields describe a process to spawn. | ||
420 | bin: string; | ||
421 | args: string[]; | ||
422 | /// Args for cargo after `--`. | ||
423 | extraArgs: string[]; | ||
424 | env: { [key: string]: string }; | ||
425 | cwd: string | null; | ||
426 | } | ||
427 | ``` | ||