diff options
| -rw-r--r-- | editors/code/src/snippets.ts | 71 |
1 files changed, 69 insertions, 2 deletions
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts index fb12125bcd8..b3982bdf2be 100644 --- a/editors/code/src/snippets.ts +++ b/editors/code/src/snippets.ts @@ -13,7 +13,7 @@ export async function applySnippetWorkspaceEdit( const [uri, edits] = unwrapUndefinable(editEntries[0]); const editor = await editorFromUri(uri); if (editor) { - edit.set(uri, edits); + edit.set(uri, removeLeadingWhitespace(editor, edits)); await vscode.workspace.applyEdit(edit); } return; @@ -48,7 +48,8 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) { const edit = new vscode.WorkspaceEdit(); - edit.set(editor.document.uri, toSnippetTextEdits(edits)); + const snippetEdits = toSnippetTextEdits(edits); + edit.set(editor.document.uri, removeLeadingWhitespace(editor, snippetEdits)); await vscode.workspace.applyEdit(edit); } @@ -74,3 +75,69 @@ function toSnippetTextEdits( } }); } + +/** + * Removes the leading whitespace from snippet edits, so as to not double up + * on indentation. + * + * Snippet edits by default adjust any multi-line snippets to match the + * indentation of the line to insert at. Unfortunately, we (the server) also + * include the required indentation to match what we line insert at, so we end + * up doubling up the indentation. Since there isn't any way to tell vscode to + * not fixup indentation for us, we instead opt to remove the indentation and + * then let vscode add it back in. + * + * This assumes that the source snippet text edits have the required + * indentation, but that's okay as even without this workaround and the problem + * to workaround, those snippet edits would already be inserting at the wrong + * indentation. + */ +function removeLeadingWhitespace( + editor: vscode.TextEditor, + edits: (vscode.TextEdit | vscode.SnippetTextEdit)[], +) { + return edits.map((edit) => { + if (edit instanceof vscode.SnippetTextEdit) { + const snippetEdit: vscode.SnippetTextEdit = edit; + const firstLineEnd = snippetEdit.snippet.value.indexOf("\n"); + + if (firstLineEnd !== -1) { + // Is a multi-line snippet, remove the indentation which + // would be added back in by vscode. + const startLine = editor.document.lineAt(snippetEdit.range.start.line); + const leadingWhitespace = getLeadingWhitespace( + startLine.text, + 0, + startLine.firstNonWhitespaceCharacterIndex, + ); + + const [firstLine, rest] = splitAt(snippetEdit.snippet.value, firstLineEnd + 1); + const unindentedLines = rest + .split("\n") + .map((line) => line.replace(leadingWhitespace, "")) + .join("\n"); + + snippetEdit.snippet.value = firstLine + unindentedLines; + } + + return snippetEdit; + } else { + return edit; + } + }); +} + +// based on https://github.com/microsoft/vscode/blob/main/src/vs/base/common/strings.ts#L284 +function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string { + for (let i = start; i < end; i++) { + const chCode = str.charCodeAt(i); + if (chCode !== " ".charCodeAt(0) && chCode !== " ".charCodeAt(0)) { + return str.substring(start, i); + } + } + return str.substring(start, end); +} + +function splitAt(str: string, index: number): [string, string] { + return [str.substring(0, index), str.substring(index)]; +} |
