aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/package.json8
-rw-r--r--editors/code/src/client.ts41
-rw-r--r--editors/code/src/commands/index.ts89
-rw-r--r--editors/code/src/commands/join_lines.ts12
-rw-r--r--editors/code/src/commands/on_enter.ts5
-rw-r--r--editors/code/src/commands/ssr.ts6
-rw-r--r--editors/code/src/main.ts4
-rw-r--r--editors/code/src/rust-analyzer-api.ts8
8 files changed, 113 insertions, 60 deletions
diff --git a/editors/code/package.json b/editors/code/package.json
index d899f60e3..78f647baa 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -313,22 +313,22 @@
313 "rust-analyzer.inlayHints.enable": { 313 "rust-analyzer.inlayHints.enable": {
314 "type": "boolean", 314 "type": "boolean",
315 "default": true, 315 "default": true,
316 "description": "Disable all inlay hints" 316 "description": "Whether to show inlay hints"
317 }, 317 },
318 "rust-analyzer.inlayHints.typeHints": { 318 "rust-analyzer.inlayHints.typeHints": {
319 "type": "boolean", 319 "type": "boolean",
320 "default": true, 320 "default": true,
321 "description": "Whether to show inlay type hints" 321 "description": "Whether to show inlay type hints for variables."
322 }, 322 },
323 "rust-analyzer.inlayHints.chainingHints": { 323 "rust-analyzer.inlayHints.chainingHints": {
324 "type": "boolean", 324 "type": "boolean",
325 "default": true, 325 "default": true,
326 "description": "Whether to show inlay type hints for method chains" 326 "description": "Whether to show inlay type hints for method chains."
327 }, 327 },
328 "rust-analyzer.inlayHints.parameterHints": { 328 "rust-analyzer.inlayHints.parameterHints": {
329 "type": "boolean", 329 "type": "boolean",
330 "default": true, 330 "default": true,
331 "description": "Whether to show function parameter name inlay hints at the call site" 331 "description": "Whether to show function parameter name inlay hints at the call site."
332 }, 332 },
333 "rust-analyzer.inlayHints.maxLength": { 333 "rust-analyzer.inlayHints.maxLength": {
334 "type": [ 334 "type": [
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index fac1a0be3..d64f9a3f9 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -41,10 +41,12 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
41 return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => { 41 return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => {
42 if (values === null) return undefined; 42 if (values === null) return undefined;
43 const result: (vscode.CodeAction | vscode.Command)[] = []; 43 const result: (vscode.CodeAction | vscode.Command)[] = [];
44 const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>();
44 for (const item of values) { 45 for (const item of values) {
45 if (lc.CodeAction.is(item)) { 46 if (lc.CodeAction.is(item)) {
46 const action = client.protocol2CodeConverter.asCodeAction(item); 47 const action = client.protocol2CodeConverter.asCodeAction(item);
47 if (isSnippetEdit(item)) { 48 const group = actionGroup(item);
49 if (isSnippetEdit(item) || group) {
48 action.command = { 50 action.command = {
49 command: "rust-analyzer.applySnippetWorkspaceEdit", 51 command: "rust-analyzer.applySnippetWorkspaceEdit",
50 title: "", 52 title: "",
@@ -52,12 +54,38 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
52 }; 54 };
53 action.edit = undefined; 55 action.edit = undefined;
54 } 56 }
55 result.push(action); 57
58 if (group) {
59 let entry = groups.get(group);
60 if (!entry) {
61 entry = { index: result.length, items: [] };
62 groups.set(group, entry);
63 result.push(action);
64 }
65 entry.items.push(action);
66 } else {
67 result.push(action);
68 }
56 } else { 69 } else {
57 const command = client.protocol2CodeConverter.asCommand(item); 70 const command = client.protocol2CodeConverter.asCommand(item);
58 result.push(command); 71 result.push(command);
59 } 72 }
60 } 73 }
74 for (const [group, { index, items }] of groups) {
75 if (items.length === 1) {
76 result[index] = items[0];
77 } else {
78 const action = new vscode.CodeAction(group);
79 action.command = {
80 command: "rust-analyzer.applyActionGroup",
81 title: "",
82 arguments: [items.map((item) => {
83 return { label: item.title, edit: item.command!!.arguments!![0] };
84 })],
85 };
86 result[index] = action;
87 }
88 }
61 return result; 89 return result;
62 }, 90 },
63 (_error) => undefined 91 (_error) => undefined
@@ -81,15 +109,16 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
81 // implementations are still in the "proposed" category for 3.16. 109 // implementations are still in the "proposed" category for 3.16.
82 client.registerFeature(new CallHierarchyFeature(client)); 110 client.registerFeature(new CallHierarchyFeature(client));
83 client.registerFeature(new SemanticTokensFeature(client)); 111 client.registerFeature(new SemanticTokensFeature(client));
84 client.registerFeature(new SnippetTextEditFeature()); 112 client.registerFeature(new ExperimentalFeatures());
85 113
86 return client; 114 return client;
87} 115}
88 116
89class SnippetTextEditFeature implements lc.StaticFeature { 117class ExperimentalFeatures implements lc.StaticFeature {
90 fillClientCapabilities(capabilities: lc.ClientCapabilities): void { 118 fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
91 const caps: any = capabilities.experimental ?? {}; 119 const caps: any = capabilities.experimental ?? {};
92 caps.snippetTextEdit = true; 120 caps.snippetTextEdit = true;
121 caps.codeActionGroup = true;
93 capabilities.experimental = caps; 122 capabilities.experimental = caps;
94 } 123 }
95 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 124 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
@@ -107,3 +136,7 @@ function isSnippetEdit(action: lc.CodeAction): boolean {
107 } 136 }
108 return false; 137 return false;
109} 138}
139
140function actionGroup(action: lc.CodeAction): string | undefined {
141 return (action as any).group;
142}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index 0937b495c..abb53a248 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -41,48 +41,65 @@ export function applySourceChange(ctx: Ctx): Cmd {
41 }; 41 };
42} 42}
43 43
44export function selectAndApplySourceChange(ctx: Ctx): Cmd { 44export function applyActionGroup(_ctx: Ctx): Cmd {
45 return async (changes: ra.SourceChange[]) => { 45 return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => {
46 if (changes.length === 1) { 46 const selectedAction = await vscode.window.showQuickPick(actions);
47 await sourceChange.applySourceChange(ctx, changes[0]); 47 if (!selectedAction) return;
48 } else if (changes.length > 0) { 48 await applySnippetWorkspaceEdit(selectedAction.edit);
49 const selectedChange = await vscode.window.showQuickPick(changes);
50 if (!selectedChange) return;
51 await sourceChange.applySourceChange(ctx, selectedChange);
52 }
53 }; 49 };
54} 50}
55 51
56export function applySnippetWorkspaceEdit(_ctx: Ctx): Cmd { 52export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
57 return async (edit: vscode.WorkspaceEdit) => { 53 return async (edit: vscode.WorkspaceEdit) => {
58 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`); 54 await applySnippetWorkspaceEdit(edit);
59 const [uri, edits] = edit.entries()[0]; 55 };
56}
60 57
61 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString()); 58export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
62 if (!editor) return; 59 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
60 const [uri, edits] = edit.entries()[0];
63 61
64 let editWithSnippet: vscode.TextEdit | undefined = undefined; 62 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
65 let lineDelta = 0; 63 if (!editor) return;
66 await editor.edit((builder) => { 64
67 for (const indel of edits) { 65 let selection: vscode.Selection | undefined = undefined;
68 const isSnippet = indel.newText.indexOf('$0') !== -1 || indel.newText.indexOf('${') !== -1; 66 let lineDelta = 0;
69 if (isSnippet) { 67 await editor.edit((builder) => {
70 editWithSnippet = indel; 68 for (const indel of edits) {
71 } else { 69 const parsed = parseSnippet(indel.newText);
72 if (!editWithSnippet) { 70 if (parsed) {
73 lineDelta = (indel.newText.match(/\n/g) || []).length - (indel.range.end.line - indel.range.start.line); 71 const [newText, [placeholderStart, placeholderLength]] = parsed;
74 } 72 const prefix = newText.substr(0, placeholderStart);
75 builder.replace(indel.range, indel.newText); 73 const lastNewline = prefix.lastIndexOf('\n');
76 } 74
75 const startLine = indel.range.start.line + lineDelta + countLines(prefix);
76 const startColumn = lastNewline === -1 ?
77 indel.range.start.character + placeholderStart
78 : prefix.length - lastNewline - 1;
79 const endColumn = startColumn + placeholderLength;
80 selection = new vscode.Selection(
81 new vscode.Position(startLine, startColumn),
82 new vscode.Position(startLine, endColumn),
83 );
84 builder.replace(indel.range, newText);
85 } else {
86 lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
87 builder.replace(indel.range, indel.newText);
77 } 88 }
78 });
79 if (editWithSnippet) {
80 const snip = editWithSnippet as vscode.TextEdit;
81 const range = snip.range.with(
82 snip.range.start.with(snip.range.start.line + lineDelta),
83 snip.range.end.with(snip.range.end.line + lineDelta),
84 );
85 await editor.insertSnippet(new vscode.SnippetString(snip.newText), range);
86 } 89 }
87 }; 90 });
91 if (selection) editor.selection = selection;
92}
93
94function parseSnippet(snip: string): [string, [number, number]] | undefined {
95 const m = snip.match(/\$(0|\{0:([^}]*)\})/);
96 if (!m) return undefined;
97 const placeholder = m[2] ?? "";
98 const range: [number, number] = [m.index!!, placeholder.length];
99 const insert = snip.replace(m[0], placeholder);
100 return [insert, range];
101}
102
103function countLines(text: string): number {
104 return (text.match(/\n/g) || []).length;
88} 105}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
index de0614653..0bf1ee6e6 100644
--- a/editors/code/src/commands/join_lines.ts
+++ b/editors/code/src/commands/join_lines.ts
@@ -1,7 +1,7 @@
1import * as ra from '../rust-analyzer-api'; 1import * as ra from '../rust-analyzer-api';
2import * as lc from 'vscode-languageclient';
2 3
3import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd } from '../ctx';
4import { applySourceChange } from '../source_change';
5 5
6export function joinLines(ctx: Ctx): Cmd { 6export function joinLines(ctx: Ctx): Cmd {
7 return async () => { 7 return async () => {
@@ -9,10 +9,14 @@ export function joinLines(ctx: Ctx): Cmd {
9 const client = ctx.client; 9 const client = ctx.client;
10 if (!editor || !client) return; 10 if (!editor || !client) return;
11 11
12 const change = await client.sendRequest(ra.joinLines, { 12 const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
13 range: client.code2ProtocolConverter.asRange(editor.selection), 13 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
14 textDocument: { uri: editor.document.uri.toString() }, 14 textDocument: { uri: editor.document.uri.toString() },
15 }); 15 });
16 await applySourceChange(ctx, change); 16 editor.edit((builder) => {
17 client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
18 builder.replace(edit.range, edit.newText);
19 });
20 });
17 }; 21 };
18} 22}
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
index 285849db7..a7871c31e 100644
--- a/editors/code/src/commands/on_enter.ts
+++ b/editors/code/src/commands/on_enter.ts
@@ -1,8 +1,8 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api'; 2import * as ra from '../rust-analyzer-api';
3 3
4import { applySourceChange } from '../source_change';
5import { Cmd, Ctx } from '../ctx'; 4import { Cmd, Ctx } from '../ctx';
5import { applySnippetWorkspaceEdit } from '.';
6 6
7async function handleKeypress(ctx: Ctx) { 7async function handleKeypress(ctx: Ctx) {
8 const editor = ctx.activeRustEditor; 8 const editor = ctx.activeRustEditor;
@@ -21,7 +21,8 @@ async function handleKeypress(ctx: Ctx) {
21 }); 21 });
22 if (!change) return false; 22 if (!change) return false;
23 23
24 await applySourceChange(ctx, change); 24 const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change);
25 await applySnippetWorkspaceEdit(workspaceEdit);
25 return true; 26 return true;
26} 27}
27 28
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
index 4ef8cdf04..5d40a64d2 100644
--- a/editors/code/src/commands/ssr.ts
+++ b/editors/code/src/commands/ssr.ts
@@ -2,7 +2,6 @@ import * as vscode from 'vscode';
2import * as ra from "../rust-analyzer-api"; 2import * as ra from "../rust-analyzer-api";
3 3
4import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd } from '../ctx';
5import { applySourceChange } from '../source_change';
6 5
7export function ssr(ctx: Ctx): Cmd { 6export function ssr(ctx: Ctx): Cmd {
8 return async () => { 7 return async () => {
@@ -22,11 +21,10 @@ export function ssr(ctx: Ctx): Cmd {
22 } 21 }
23 }; 22 };
24 const request = await vscode.window.showInputBox(options); 23 const request = await vscode.window.showInputBox(options);
25
26 if (!request) return; 24 if (!request) return;
27 25
28 const change = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); 26 const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
29 27
30 await applySourceChange(ctx, change); 28 await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
31 }; 29 };
32} 30}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index ad3c1a192..3405634f3 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -92,8 +92,8 @@ export async function activate(context: vscode.ExtensionContext) {
92 ctx.registerCommand('debugSingle', commands.debugSingle); 92 ctx.registerCommand('debugSingle', commands.debugSingle);
93 ctx.registerCommand('showReferences', commands.showReferences); 93 ctx.registerCommand('showReferences', commands.showReferences);
94 ctx.registerCommand('applySourceChange', commands.applySourceChange); 94 ctx.registerCommand('applySourceChange', commands.applySourceChange);
95 ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEdit); 95 ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand);
96 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); 96 ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
97 97
98 ctx.pushCleanup(activateTaskProvider(workspaceFolder)); 98 ctx.pushCleanup(activateTaskProvider(workspaceFolder));
99 99
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
index 400ac3714..73f36432f 100644
--- a/editors/code/src/rust-analyzer-api.ts
+++ b/editors/code/src/rust-analyzer-api.ts
@@ -64,12 +64,12 @@ export const parentModule = request<lc.TextDocumentPositionParams, Vec<lc.Locati
64 64
65export interface JoinLinesParams { 65export interface JoinLinesParams {
66 textDocument: lc.TextDocumentIdentifier; 66 textDocument: lc.TextDocumentIdentifier;
67 range: lc.Range; 67 ranges: lc.Range[];
68} 68}
69export const joinLines = request<JoinLinesParams, SourceChange>("joinLines"); 69export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], unknown>('experimental/joinLines');
70 70
71 71
72export const onEnter = request<lc.TextDocumentPositionParams, Option<SourceChange>>("onEnter"); 72export const onEnter = request<lc.TextDocumentPositionParams, Option<lc.WorkspaceEdit>>("onEnter");
73 73
74export interface RunnablesParams { 74export interface RunnablesParams {
75 textDocument: lc.TextDocumentIdentifier; 75 textDocument: lc.TextDocumentIdentifier;
@@ -112,7 +112,7 @@ export interface SsrParams {
112 query: string; 112 query: string;
113 parseOnly: boolean; 113 parseOnly: boolean;
114} 114}
115export const ssr = request<SsrParams, SourceChange>("ssr"); 115export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, unknown>('experimental/ssr');
116 116
117 117
118export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations"); 118export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations");