about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--editors/code/src/client.ts48
-rw-r--r--editors/code/src/commands/index.ts34
-rw-r--r--editors/code/src/main.ts1
3 files changed, 81 insertions, 2 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 2067738ea6c..fac1a0be318 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -31,7 +31,39 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
                 const res = await next(document, token);
                 if (res === undefined) throw new Error('busy');
                 return res;
+            },
+            async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
+                const params: lc.CodeActionParams = {
+                    textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
+                    range: client.code2ProtocolConverter.asRange(range),
+                    context: client.code2ProtocolConverter.asCodeActionContext(context)
+                };
+                return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => {
+                    if (values === null) return undefined;
+                    const result: (vscode.CodeAction | vscode.Command)[] = [];
+                    for (const item of values) {
+                        if (lc.CodeAction.is(item)) {
+                            const action = client.protocol2CodeConverter.asCodeAction(item);
+                            if (isSnippetEdit(item)) {
+                                action.command = {
+                                    command: "rust-analyzer.applySnippetWorkspaceEdit",
+                                    title: "",
+                                    arguments: [action.edit],
+                                };
+                                action.edit = undefined;
+                            }
+                            result.push(action);
+                        } else {
+                            const command = client.protocol2CodeConverter.asCommand(item);
+                            result.push(command);
+                        }
+                    }
+                    return result;
+                },
+                    (_error) => undefined
+                );
             }
+
         } as any
     };
 
@@ -42,7 +74,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
         clientOptions,
     );
 
-    // To turn on all proposed features use: res.registerProposedFeatures();
+    // To turn on all proposed features use: client.registerProposedFeatures();
     // Here we want to enable CallHierarchyFeature and SemanticTokensFeature
     // since they are available on stable.
     // Note that while these features are stable in vscode their LSP protocol
@@ -58,8 +90,20 @@ class SnippetTextEditFeature implements lc.StaticFeature {
     fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
         const caps: any = capabilities.experimental ?? {};
         caps.snippetTextEdit = true;
-        capabilities.experimental = caps
+        capabilities.experimental = caps;
     }
     initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
     }
 }
+
+function isSnippetEdit(action: lc.CodeAction): boolean {
+    const documentChanges = action.edit?.documentChanges ?? [];
+    for (const edit of documentChanges) {
+        if (lc.TextDocumentEdit.is(edit)) {
+            if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index bdb7fc3b03b..770d11bd36f 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -4,6 +4,7 @@ import * as ra from '../rust-analyzer-api';
 
 import { Ctx, Cmd } from '../ctx';
 import * as sourceChange from '../source_change';
+import { assert } from '../util';
 
 export * from './analyzer_status';
 export * from './matching_brace';
@@ -51,3 +52,36 @@ export function selectAndApplySourceChange(ctx: Ctx): Cmd {
         }
     };
 }
+
+export function applySnippetWorkspaceEdit(_ctx: Ctx): Cmd {
+    return async (edit: vscode.WorkspaceEdit) => {
+        assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
+        const [uri, edits] = edit.entries()[0];
+
+        const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
+        if (!editor) return;
+
+        let editWithSnippet: vscode.TextEdit | undefined = undefined;
+        let lineDelta = 0;
+        await editor.edit((builder) => {
+            for (const indel of edits) {
+                if (indel.newText.indexOf('$0') !== -1) {
+                    editWithSnippet = indel;
+                } else {
+                    if (!editWithSnippet) {
+                        lineDelta = (indel.newText.match(/\n/g) || []).length - (indel.range.end.line - indel.range.start.line);
+                    }
+                    builder.replace(indel.range, indel.newText);
+                }
+            }
+        });
+        if (editWithSnippet) {
+            const snip = editWithSnippet as vscode.TextEdit;
+            const range = snip.range.with(
+                snip.range.start.with(snip.range.start.line + lineDelta),
+                snip.range.end.with(snip.range.end.line + lineDelta),
+            );
+            await editor.insertSnippet(new vscode.SnippetString(snip.newText), range);
+        }
+    };
+}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index c015460b883..ac3bb365e2f 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -91,6 +91,7 @@ export async function activate(context: vscode.ExtensionContext) {
     ctx.registerCommand('debugSingle', commands.debugSingle);
     ctx.registerCommand('showReferences', commands.showReferences);
     ctx.registerCommand('applySourceChange', commands.applySourceChange);
+    ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEdit);
     ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange);
 
     ctx.pushCleanup(activateTaskProvider(workspaceFolder));