Skip to content

Commit

Permalink
feat(cmds): support snippet text edit (#252)
Browse files Browse the repository at this point in the history
* feat(cmds): support snippet text edit

rust-lang/rust-analyzer#4494

* feat(cmds): improve snippet text edit

* feat(cmds): snippet textedit
  • Loading branch information
keeneyetact committed May 25, 2020
1 parent ab9bec9 commit 1d89e2d
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 3 deletions.
46 changes: 45 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
import { Executable, LanguageClient, LanguageClientOptions, ServerOptions, Uri, workspace } from 'coc.nvim';
import { Executable, LanguageClient, LanguageClientOptions, ServerOptions, StaticFeature, Uri, workspace } from 'coc.nvim';
import { ClientCapabilities, CodeAction, CodeActionParams, CodeActionRequest, Command, InsertTextFormat, TextDocumentEdit } from 'vscode-languageserver-protocol';

class SnippetTextEditFeature implements StaticFeature {
fillClientCapabilities(capabilities: ClientCapabilities): void {
const caps: any = capabilities.experimental ?? {};
caps.snippetTextEdit = true;
capabilities.experimental = caps;
}
initialize(): void {}
}

function isSnippetEdit(action: CodeAction): boolean {
const documentChanges = action.edit?.documentChanges ?? [];
for (const edit of documentChanges) {
if (TextDocumentEdit.is(edit)) {
if (edit.edits.some((indel) => (indel as any).insertTextFormat === InsertTextFormat.Snippet)) {
return true;
}
}
}
return false;
}

export function createClient(bin: string): LanguageClient {
let folder = '.';
Expand All @@ -24,6 +46,26 @@ export function createClient(bin: string): LanguageClient {
position.character = character - 1;
return help;
},
provideCodeActions(document, range, context, token) {
const params: CodeActionParams = {
textDocument: { uri: document.uri },
range,
context,
};
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return client.sendRequest(CodeActionRequest.type, params, token).then((values) => {
if (values === null) return undefined;
const result: (CodeAction | Command)[] = [];
for (const item of values) {
if (CodeAction.is(item) && isSnippetEdit(item)) {
item.command = Command.create('', 'rust-analyzer.applySnippetWorkspaceEdit', item.edit);
item.edit = undefined;
}
result.push(item);
}
return result;
});
},
},
outputChannel,
};
Expand Down Expand Up @@ -51,5 +93,7 @@ export function createClient(bin: string): LanguageClient {
},
};
client.registerProposedFeatures();
client.registerFeature(new SnippetTextEditFeature());

return client;
}
66 changes: 65 additions & 1 deletion src/cmds/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { commands, Uri, workspace } from 'coc.nvim';
import { Location, Position } from 'vscode-languageserver-protocol';
import { Location, Position, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol';
import { Cmd, Ctx } from '../ctx';
import * as ra from '../rust-analyzer-api';
import * as sourceChange from '../source_change';
Expand Down Expand Up @@ -67,3 +67,67 @@ export function toggleInlayHints(ctx: Ctx) {
}
};
}

function parseSnippet(snip: string): [string, [number, number]] | undefined {
const m = snip.match(/\$(0|\{0:([^}]*)\})/);
if (!m) return undefined;
const placeholder = m[2] ?? '';
const range: [number, number] = [m.index!!, placeholder.length];
const insert = snip.replace(m[0], placeholder);
return [insert, range];
}

function countLines(text: string): number {
return (text.match(/\n/g) || []).length;
}

export async function applySnippetWorkspaceEdit(edit: WorkspaceEdit) {
if (!edit.documentChanges?.length) {
return;
}

let selection: Range | undefined = undefined;
let lineDelta = 0;
const change = edit.documentChanges[0];
if (TextDocumentEdit.is(change)) {
for (const indel of change.edits) {
const wsEdit: WorkspaceEdit = {};
const parsed = parseSnippet(indel.newText);
if (parsed) {
const [newText, [placeholderStart, placeholderLength]] = parsed;
const prefix = newText.substr(0, placeholderStart);
const lastNewline = prefix.lastIndexOf('\n');

const startLine = indel.range.start.line + lineDelta + countLines(prefix);
const startColumn = lastNewline === -1 ? indel.range.start.character + placeholderStart : prefix.length - lastNewline - 1;
const endColumn = startColumn + placeholderLength;
selection = Range.create(startLine, startColumn, startLine, endColumn);

const newChange = TextDocumentEdit.create(change.textDocument, [TextEdit.replace(indel.range, newText)]);
wsEdit.documentChanges = [newChange];
} else {
lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
wsEdit.documentChanges = [change];
}

await workspace.applyEdit(wsEdit);
}

if (selection) {
const current = await workspace.document;
if (current.uri !== change.textDocument.uri) {
await workspace.loadFile(change.textDocument.uri);
await workspace.jumpTo(change.textDocument.uri);
// FIXME
return;
}
await workspace.selectRange(selection);
}
}
}

export function applySnippetWorkspaceEditCommand(): Cmd {
return async (edit: WorkspaceEdit) => {
await applySnippetWorkspaceEdit(edit);
};
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function activate(context: ExtensionContext): Promise<void> {

ctx.registerCommand('analyzerStatus', cmds.analyzerStatus);
ctx.registerCommand('applySourceChange', cmds.applySourceChange);
ctx.registerCommand('applySnippetWorkspaceEdit', cmds.applySnippetWorkspaceEditCommand);
ctx.registerCommand('selectAndApplySourceChange', cmds.selectAndApplySourceChange);
ctx.registerCommand('collectGarbage', cmds.collectGarbage);
ctx.registerCommand('expandMacro', cmds.expandMacro);
Expand Down
2 changes: 1 addition & 1 deletion src/source_change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ export async function applySourceChange(change: SourceChange) {
} else if (toReveal) {
const uri = toReveal.textDocument.uri;
const position = toReveal.position;
workspace.jumpTo(uri, position);
await workspace.jumpTo(uri, position);
}
}

0 comments on commit 1d89e2d

Please sign in to comment.