Work around snippet edits doubling up extra indentation

We can't tell vscode to not add in the extra indentation, so we instead opt to remove it from the edits themselves, and then let vscode add it back in.
This commit is contained in:
DropDemBits 2024-02-15 18:39:17 -05:00
parent d846586bc9
commit bcf14e27ce
No known key found for this signature in database
GPG key ID: 7FE02A6C1EDFA075

View file

@ -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)];
}