diff options
Diffstat (limited to 'editors/code/src')
-rw-r--r-- | editors/code/src/client.ts | 106 | ||||
-rw-r--r-- | editors/code/src/commands.ts | 19 | ||||
-rw-r--r-- | editors/code/src/config.ts | 13 | ||||
-rw-r--r-- | editors/code/src/lsp_ext.ts | 18 | ||||
-rw-r--r-- | editors/code/src/main.ts | 1 | ||||
-rw-r--r-- | editors/code/src/run.ts | 2 |
6 files changed, 116 insertions, 43 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index d64f9a3f9..65ad573d8 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -1,8 +1,25 @@ | |||
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'; | ||
9 | |||
10 | function renderCommand(cmd: ra.CommandLink) { | ||
11 | return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; | ||
12 | } | ||
13 | |||
14 | function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { | ||
15 | const text = actions.map(group => | ||
16 | (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ') | ||
17 | ).join('___'); | ||
18 | |||
19 | const result = new vscode.MarkdownString(text); | ||
20 | result.isTrusted = true; | ||
21 | return result; | ||
22 | } | ||
6 | 23 | ||
7 | export function createClient(serverPath: string, cwd: string): lc.LanguageClient { | 24 | export function createClient(serverPath: string, cwd: string): lc.LanguageClient { |
8 | // '.' Is the fallback if no folder is open | 25 | // '.' Is the fallback if no folder is open |
@@ -32,6 +49,25 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
32 | if (res === undefined) throw new Error('busy'); | 49 | if (res === undefined) throw new Error('busy'); |
33 | return res; | 50 | return res; |
34 | }, | 51 | }, |
52 | async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) { | ||
53 | return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then( | ||
54 | (result) => { | ||
55 | const hover = client.protocol2CodeConverter.asHover(result); | ||
56 | if (hover) { | ||
57 | const actions = (<any>result).actions; | ||
58 | if (actions) { | ||
59 | hover.contents.push(renderHoverActions(actions)); | ||
60 | } | ||
61 | } | ||
62 | return hover; | ||
63 | }, | ||
64 | (error) => { | ||
65 | client.logFailedRequest(lc.HoverRequest.type, error); | ||
66 | return Promise.resolve(null); | ||
67 | }); | ||
68 | }, | ||
69 | // Using custom handling of CodeActions where each code action is resloved lazily | ||
70 | // 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) { | 71 | async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { |
36 | const params: lc.CodeActionParams = { | 72 | const params: lc.CodeActionParams = { |
37 | textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), | 73 | textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), |
@@ -43,32 +79,36 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
43 | const result: (vscode.CodeAction | vscode.Command)[] = []; | 79 | const result: (vscode.CodeAction | vscode.Command)[] = []; |
44 | const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>(); | 80 | const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>(); |
45 | for (const item of values) { | 81 | for (const item of values) { |
82 | // In our case we expect to get code edits only from diagnostics | ||
46 | if (lc.CodeAction.is(item)) { | 83 | if (lc.CodeAction.is(item)) { |
84 | assert(!item.command, "We don't expect to receive commands in CodeActions"); | ||
47 | const action = client.protocol2CodeConverter.asCodeAction(item); | 85 | const action = client.protocol2CodeConverter.asCodeAction(item); |
48 | const group = actionGroup(item); | 86 | result.push(action); |
49 | if (isSnippetEdit(item) || group) { | 87 | continue; |
50 | action.command = { | 88 | } |
51 | command: "rust-analyzer.applySnippetWorkspaceEdit", | 89 | assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here"); |
52 | title: "", | 90 | const action = new vscode.CodeAction(item.title); |
53 | arguments: [action.edit], | 91 | const group = (item as any).group; |
54 | }; | 92 | const id = (item as any).id; |
55 | action.edit = undefined; | 93 | const resolveParams: ra.ResolveCodeActionParams = { |
56 | } | 94 | id: id, |
57 | 95 | codeActionParams: params | |
58 | if (group) { | 96 | }; |
59 | let entry = groups.get(group); | 97 | action.command = { |
60 | if (!entry) { | 98 | command: "rust-analyzer.resolveCodeAction", |
61 | entry = { index: result.length, items: [] }; | 99 | title: item.title, |
62 | groups.set(group, entry); | 100 | arguments: [resolveParams], |
63 | result.push(action); | 101 | }; |
64 | } | 102 | if (group) { |
65 | entry.items.push(action); | 103 | let entry = groups.get(group); |
66 | } else { | 104 | if (!entry) { |
105 | entry = { index: result.length, items: [] }; | ||
106 | groups.set(group, entry); | ||
67 | result.push(action); | 107 | result.push(action); |
68 | } | 108 | } |
109 | entry.items.push(action); | ||
69 | } else { | 110 | } else { |
70 | const command = client.protocol2CodeConverter.asCommand(item); | 111 | result.push(action); |
71 | result.push(command); | ||
72 | } | 112 | } |
73 | } | 113 | } |
74 | for (const [group, { index, items }] of groups) { | 114 | for (const [group, { index, items }] of groups) { |
@@ -80,7 +120,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
80 | command: "rust-analyzer.applyActionGroup", | 120 | command: "rust-analyzer.applyActionGroup", |
81 | title: "", | 121 | title: "", |
82 | arguments: [items.map((item) => { | 122 | arguments: [items.map((item) => { |
83 | return { label: item.title, edit: item.command!!.arguments!![0] }; | 123 | return { label: item.title, arguments: item.command!!.arguments!![0] }; |
84 | })], | 124 | })], |
85 | }; | 125 | }; |
86 | result[index] = action; | 126 | result[index] = action; |
@@ -119,24 +159,18 @@ class ExperimentalFeatures implements lc.StaticFeature { | |||
119 | const caps: any = capabilities.experimental ?? {}; | 159 | const caps: any = capabilities.experimental ?? {}; |
120 | caps.snippetTextEdit = true; | 160 | caps.snippetTextEdit = true; |
121 | caps.codeActionGroup = true; | 161 | caps.codeActionGroup = true; |
162 | caps.resolveCodeAction = true; | ||
163 | caps.hoverActions = true; | ||
122 | capabilities.experimental = caps; | 164 | capabilities.experimental = caps; |
123 | } | 165 | } |
124 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { | 166 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { |
125 | } | 167 | } |
126 | } | 168 | } |
127 | 169 | ||
128 | function isSnippetEdit(action: lc.CodeAction): boolean { | 170 | function isCodeActionWithoutEditsAndCommands(value: any): boolean { |
129 | const documentChanges = action.edit?.documentChanges ?? []; | 171 | const candidate: lc.CodeAction = value; |
130 | for (const edit of documentChanges) { | 172 | return candidate && Is.string(candidate.title) && |
131 | if (lc.TextDocumentEdit.is(edit)) { | 173 | (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) && |
132 | if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) { | 174 | (candidate.kind === void 0 || Is.string(candidate.kind)) && |
133 | return true; | 175 | (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 | } | 176 | } |
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 534d2a984..3e9c3aa0e 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts | |||
@@ -343,10 +343,25 @@ export function showReferences(ctx: Ctx): Cmd { | |||
343 | } | 343 | } |
344 | 344 | ||
345 | export function applyActionGroup(_ctx: Ctx): Cmd { | 345 | export function applyActionGroup(_ctx: Ctx): Cmd { |
346 | return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => { | 346 | return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => { |
347 | const selectedAction = await vscode.window.showQuickPick(actions); | 347 | const selectedAction = await vscode.window.showQuickPick(actions); |
348 | if (!selectedAction) return; | 348 | if (!selectedAction) return; |
349 | await applySnippetWorkspaceEdit(selectedAction.edit); | 349 | vscode.commands.executeCommand( |
350 | 'rust-analyzer.resolveCodeAction', | ||
351 | selectedAction.arguments, | ||
352 | ); | ||
353 | }; | ||
354 | } | ||
355 | |||
356 | export function resolveCodeAction(ctx: Ctx): Cmd { | ||
357 | const client = ctx.client; | ||
358 | return async (params: ra.ResolveCodeActionParams) => { | ||
359 | const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); | ||
360 | if (!item) { | ||
361 | return; | ||
362 | } | ||
363 | const edit = client.protocol2CodeConverter.asWorkspaceEdit(item); | ||
364 | await applySnippetWorkspaceEdit(edit); | ||
350 | }; | 365 | }; |
351 | } | 366 | } |
352 | 367 | ||
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index e8abf8284..d8f0037d4 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -16,10 +16,8 @@ export class Config { | |||
16 | "files", | 16 | "files", |
17 | "highlighting", | 17 | "highlighting", |
18 | "updates.channel", | 18 | "updates.channel", |
19 | "lens.enable", | 19 | "lens", // works as lens.* |
20 | "lens.run", | 20 | "hoverActions", // works as hoverActions.* |
21 | "lens.debug", | ||
22 | "lens.implementations", | ||
23 | ] | 21 | ] |
24 | .map(opt => `${this.rootSection}.${opt}`); | 22 | .map(opt => `${this.rootSection}.${opt}`); |
25 | 23 | ||
@@ -132,4 +130,11 @@ export class Config { | |||
132 | implementations: this.get<boolean>("lens.implementations"), | 130 | implementations: this.get<boolean>("lens.implementations"), |
133 | }; | 131 | }; |
134 | } | 132 | } |
133 | |||
134 | get hoverActions() { | ||
135 | return { | ||
136 | enable: this.get<boolean>("hoverActions.enable"), | ||
137 | implementations: this.get<boolean>("hoverActions.implementations"), | ||
138 | }; | ||
139 | } | ||
135 | } | 140 | } |
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index c51acfccb..e16ea799c 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts | |||
@@ -33,6 +33,12 @@ export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position | |||
33 | 33 | ||
34 | export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule"); | 34 | export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule"); |
35 | 35 | ||
36 | export interface ResolveCodeActionParams { | ||
37 | id: string; | ||
38 | codeActionParams: lc.CodeActionParams; | ||
39 | } | ||
40 | export const resolveCodeAction = new lc.RequestType<ResolveCodeActionParams, lc.WorkspaceEdit, unknown>('experimental/resolveCodeAction'); | ||
41 | |||
36 | export interface JoinLinesParams { | 42 | export interface JoinLinesParams { |
37 | textDocument: lc.TextDocumentIdentifier; | 43 | textDocument: lc.TextDocumentIdentifier; |
38 | ranges: lc.Range[]; | 44 | ranges: lc.Range[]; |
@@ -84,3 +90,15 @@ export interface SsrParams { | |||
84 | parseOnly: boolean; | 90 | parseOnly: boolean; |
85 | } | 91 | } |
86 | export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr'); | 92 | export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr'); |
93 | |||
94 | export interface CommandLink extends lc.Command { | ||
95 | /** | ||
96 | * A tooltip for the command, when represented in the UI. | ||
97 | */ | ||
98 | tooltip?: string; | ||
99 | } | ||
100 | |||
101 | export interface CommandLinkGroup { | ||
102 | title?: string; | ||
103 | commands: CommandLink[]; | ||
104 | } | ||
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index b7337621c..a92c676fa 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -98,6 +98,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
98 | ctx.registerCommand('debugSingle', commands.debugSingle); | 98 | ctx.registerCommand('debugSingle', commands.debugSingle); |
99 | ctx.registerCommand('showReferences', commands.showReferences); | 99 | ctx.registerCommand('showReferences', commands.showReferences); |
100 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); | 100 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); |
101 | ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); | ||
101 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); | 102 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); |
102 | 103 | ||
103 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); | 104 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); |
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 5c790741f..bb060cfe1 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts | |||
@@ -110,7 +110,7 @@ export function createTask(runnable: ra.Runnable): vscode.Task { | |||
110 | switch (runnable.kind) { | 110 | switch (runnable.kind) { |
111 | case "cargo": command = toolchain.getPathForExecutable("cargo"); | 111 | case "cargo": command = toolchain.getPathForExecutable("cargo"); |
112 | } | 112 | } |
113 | const args = runnable.args.cargoArgs; | 113 | const args = [...runnable.args.cargoArgs]; // should be a copy! |
114 | if (runnable.args.executableArgs.length > 0) { | 114 | if (runnable.args.executableArgs.length > 0) { |
115 | args.push('--', ...runnable.args.executableArgs); | 115 | args.push('--', ...runnable.args.executableArgs); |
116 | } | 116 | } |