Skip to content

Commit e43fffc

Browse files
committed
feat(cmds): support snippet text edit
rust-lang/rust-analyzer#4494
1 parent 8109959 commit e43fffc

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

src/client.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
1-
import { Executable, LanguageClient, LanguageClientOptions, ServerOptions, Uri, workspace } from 'coc.nvim';
1+
import { Executable, LanguageClient, LanguageClientOptions, ServerOptions, StaticFeature, Uri, workspace } from 'coc.nvim';
2+
import { ClientCapabilities, CodeAction, CodeActionParams, CodeActionRequest, Command, InsertTextFormat, TextDocumentEdit } from 'vscode-languageserver-protocol';
3+
4+
class SnippetTextEditFeature implements StaticFeature {
5+
fillClientCapabilities(capabilities: ClientCapabilities): void {
6+
const caps: any = capabilities.experimental ?? {};
7+
caps.snippetTextEdit = true;
8+
capabilities.experimental = caps;
9+
}
10+
initialize(): void {}
11+
}
12+
13+
function isSnippetEdit(action: CodeAction): boolean {
14+
const documentChanges = action.edit?.documentChanges ?? [];
15+
for (const edit of documentChanges) {
16+
if (TextDocumentEdit.is(edit)) {
17+
if (edit.edits.some((indel) => (indel as any).insertTextFormat === InsertTextFormat.Snippet)) {
18+
return true;
19+
}
20+
}
21+
}
22+
return false;
23+
}
224

325
export function createClient(bin: string): LanguageClient {
426
let folder = '.';
@@ -24,6 +46,26 @@ export function createClient(bin: string): LanguageClient {
2446
position.character = character - 1;
2547
return help;
2648
},
49+
provideCodeActions(document, range, context, token) {
50+
const params: CodeActionParams = {
51+
textDocument: { uri: document.uri },
52+
range,
53+
context,
54+
};
55+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
56+
return client.sendRequest(CodeActionRequest.type, params, token).then((values) => {
57+
if (values === null) return undefined;
58+
const result: (CodeAction | Command)[] = [];
59+
for (const item of values) {
60+
if (CodeAction.is(item) && isSnippetEdit(item)) {
61+
item.command = Command.create('', 'rust-analyzer.applySnippetWorkspaceEdit', item.edit);
62+
item.edit = undefined;
63+
}
64+
result.push(item);
65+
}
66+
return result;
67+
});
68+
},
2769
},
2870
outputChannel,
2971
};
@@ -51,5 +93,7 @@ export function createClient(bin: string): LanguageClient {
5193
},
5294
};
5395
client.registerProposedFeatures();
96+
client.registerFeature(new SnippetTextEditFeature());
97+
5498
return client;
5599
}

src/cmds/index.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { commands, Uri, workspace } from 'coc.nvim';
2-
import { Location, Position } from 'vscode-languageserver-protocol';
2+
import { Location, Position, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol';
33
import { Cmd, Ctx } from '../ctx';
44
import * as ra from '../rust-analyzer-api';
55
import * as sourceChange from '../source_change';
@@ -67,3 +67,32 @@ export function toggleInlayHints(ctx: Ctx) {
6767
}
6868
};
6969
}
70+
71+
export function applySnippetWorkspaceEdit(): Cmd {
72+
return async (edit: WorkspaceEdit) => {
73+
if (edit.documentChanges && edit.documentChanges.length) {
74+
let editWithSnippet: TextEdit | undefined = undefined;
75+
let lineDelta = 0;
76+
77+
const edits = (edit.documentChanges as TextDocumentEdit[])[0].edits;
78+
for (const indel of edits) {
79+
const isSnippet = indel.newText.indexOf('$0') !== -1 || indel.newText.indexOf('${') !== -1;
80+
if (isSnippet) {
81+
editWithSnippet = indel;
82+
} else {
83+
if (!editWithSnippet) {
84+
lineDelta = (indel.newText.match(/\n/g) || []).length - (indel.range.end.line - indel.range.start.line);
85+
}
86+
TextEdit.replace(indel.range, indel.newText);
87+
}
88+
}
89+
90+
if (editWithSnippet) {
91+
const snip = editWithSnippet as TextEdit;
92+
const range = Range.create(snip.range.start.line + lineDelta, snip.range.start.character, snip.range.end.line + lineDelta, snip.range.end.character);
93+
snip.range = range;
94+
await commands.executeCommand('editor.action.insertSnippet', snip);
95+
}
96+
}
97+
};
98+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export async function activate(context: ExtensionContext): Promise<void> {
3838

3939
ctx.registerCommand('analyzerStatus', cmds.analyzerStatus);
4040
ctx.registerCommand('applySourceChange', cmds.applySourceChange);
41+
ctx.registerCommand('applySnippetWorkspaceEdit', cmds.applySnippetWorkspaceEdit);
4142
ctx.registerCommand('selectAndApplySourceChange', cmds.selectAndApplySourceChange);
4243
ctx.registerCommand('collectGarbage', cmds.collectGarbage);
4344
ctx.registerCommand('expandMacro', cmds.expandMacro);

0 commit comments

Comments
 (0)