diff options
author | Mikhail Rakhmanov <[email protected]> | 2020-06-02 21:21:48 +0100 |
---|---|---|
committer | Mikhail Rakhmanov <[email protected]> | 2020-06-02 22:10:53 +0100 |
commit | 57cd936c5262c3b43626618be42d7a72f71c3539 (patch) | |
tree | a21852bb596fea5d1d355b3ad70f1f8e985ef3bd /editors/code/src/client.ts | |
parent | 61e8f392191037acefddc5793e814f93d01b114a (diff) |
Preliminary implementation of lazy CodeAssits
Diffstat (limited to 'editors/code/src/client.ts')
-rw-r--r-- | editors/code/src/client.ts | 76 |
1 files changed, 40 insertions, 36 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index d64f9a3f9..a25091f79 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -1,8 +1,11 @@ | |||
1 | import * as lc from 'vscode-languageclient'; | 1 | import * as lc from 'vscode-languageclient'; |
2 | import * as vscode from 'vscode'; | 2 | import * as vscode from 'vscode'; |
3 | import * as ra from '../src/lsp_ext'; | ||
4 | import * as Is from 'vscode-languageclient/lib/utils/is'; | ||
3 | 5 | ||
4 | import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; | 6 | import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; |
5 | import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; | 7 | import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; |
8 | import { assert } from './util'; | ||
6 | 9 | ||
7 | export function createClient(serverPath: string, cwd: string): lc.LanguageClient { | 10 | export function createClient(serverPath: string, cwd: string): lc.LanguageClient { |
8 | // '.' Is the fallback if no folder is open | 11 | // '.' Is the fallback if no folder is open |
@@ -32,6 +35,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
32 | if (res === undefined) throw new Error('busy'); | 35 | if (res === undefined) throw new Error('busy'); |
33 | return res; | 36 | return res; |
34 | }, | 37 | }, |
38 | // Using custom handling of CodeActions where each code action is resloved lazily | ||
39 | // That's why we are not waiting for any command or edits | ||
35 | async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { | 40 | async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { |
36 | const params: lc.CodeActionParams = { | 41 | const params: lc.CodeActionParams = { |
37 | textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), | 42 | textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), |
@@ -43,32 +48,38 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
43 | const result: (vscode.CodeAction | vscode.Command)[] = []; | 48 | const result: (vscode.CodeAction | vscode.Command)[] = []; |
44 | const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>(); | 49 | const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>(); |
45 | for (const item of values) { | 50 | for (const item of values) { |
51 | // In our case we expect to get code edits only from diagnostics | ||
46 | if (lc.CodeAction.is(item)) { | 52 | if (lc.CodeAction.is(item)) { |
53 | assert(!item.command, "We don't expect to receive commands in CodeActions"); | ||
47 | const action = client.protocol2CodeConverter.asCodeAction(item); | 54 | const action = client.protocol2CodeConverter.asCodeAction(item); |
48 | const group = actionGroup(item); | 55 | result.push(action); |
49 | if (isSnippetEdit(item) || group) { | 56 | continue; |
50 | action.command = { | 57 | } |
51 | command: "rust-analyzer.applySnippetWorkspaceEdit", | 58 | assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here"); |
52 | title: "", | 59 | const action = new vscode.CodeAction(item.title); |
53 | arguments: [action.edit], | 60 | const group = (item as any).group; |
54 | }; | 61 | const id = (item as any).id; |
55 | action.edit = undefined; | 62 | const resolveParams: ra.ResolveCodeActionParams = { |
56 | } | 63 | id: id, |
57 | 64 | // TODO: delete after discussions if needed | |
58 | if (group) { | 65 | label: item.title, |
59 | let entry = groups.get(group); | 66 | codeActionParams: params |
60 | if (!entry) { | 67 | }; |
61 | entry = { index: result.length, items: [] }; | 68 | action.command = { |
62 | groups.set(group, entry); | 69 | command: "rust-analyzer.resolveCodeAction", |
63 | result.push(action); | 70 | title: item.title, |
64 | } | 71 | arguments: [resolveParams], |
65 | entry.items.push(action); | 72 | }; |
66 | } else { | 73 | if (group) { |
74 | let entry = groups.get(group); | ||
75 | if (!entry) { | ||
76 | entry = { index: result.length, items: [] }; | ||
77 | groups.set(group, entry); | ||
67 | result.push(action); | 78 | result.push(action); |
68 | } | 79 | } |
80 | entry.items.push(action); | ||
69 | } else { | 81 | } else { |
70 | const command = client.protocol2CodeConverter.asCommand(item); | 82 | result.push(action); |
71 | result.push(command); | ||
72 | } | 83 | } |
73 | } | 84 | } |
74 | for (const [group, { index, items }] of groups) { | 85 | for (const [group, { index, items }] of groups) { |
@@ -80,7 +91,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
80 | command: "rust-analyzer.applyActionGroup", | 91 | command: "rust-analyzer.applyActionGroup", |
81 | title: "", | 92 | title: "", |
82 | arguments: [items.map((item) => { | 93 | arguments: [items.map((item) => { |
83 | return { label: item.title, edit: item.command!!.arguments!![0] }; | 94 | return { label: item.title, arguments: item.command!!.arguments!![0] }; |
84 | })], | 95 | })], |
85 | }; | 96 | }; |
86 | result[index] = action; | 97 | result[index] = action; |
@@ -119,24 +130,17 @@ class ExperimentalFeatures implements lc.StaticFeature { | |||
119 | const caps: any = capabilities.experimental ?? {}; | 130 | const caps: any = capabilities.experimental ?? {}; |
120 | caps.snippetTextEdit = true; | 131 | caps.snippetTextEdit = true; |
121 | caps.codeActionGroup = true; | 132 | caps.codeActionGroup = true; |
133 | caps.resolveCodeAction = true; | ||
122 | capabilities.experimental = caps; | 134 | capabilities.experimental = caps; |
123 | } | 135 | } |
124 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { | 136 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { |
125 | } | 137 | } |
126 | } | 138 | } |
127 | 139 | ||
128 | function isSnippetEdit(action: lc.CodeAction): boolean { | 140 | function isCodeActionWithoutEditsAndCommands(value: any): boolean { |
129 | const documentChanges = action.edit?.documentChanges ?? []; | 141 | const candidate: lc.CodeAction = value; |
130 | for (const edit of documentChanges) { | 142 | return candidate && Is.string(candidate.title) && |
131 | if (lc.TextDocumentEdit.is(edit)) { | 143 | (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) && |
132 | if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) { | 144 | (candidate.kind === void 0 || Is.string(candidate.kind)) && |
133 | return true; | 145 | (candidate.edit === void 0 && candidate.command === void 0); |
134 | } | ||
135 | } | ||
136 | } | ||
137 | return false; | ||
138 | } | ||
139 | |||
140 | function actionGroup(action: lc.CodeAction): string | undefined { | ||
141 | return (action as any).group; | ||
142 | } | 146 | } |