Skip to content

Commit bcf14e2

Browse files
committed
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.
1 parent d846586 commit bcf14e2

File tree

1 file changed

+69
-2
lines changed

1 file changed

+69
-2
lines changed

editors/code/src/snippets.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export async function applySnippetWorkspaceEdit(
1313
const [uri, edits] = unwrapUndefinable(editEntries[0]);
1414
const editor = await editorFromUri(uri);
1515
if (editor) {
16-
edit.set(uri, edits);
16+
edit.set(uri, removeLeadingWhitespace(editor, edits));
1717
await vscode.workspace.applyEdit(edit);
1818
}
1919
return;
@@ -48,7 +48,8 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef
4848

4949
export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
5050
const edit = new vscode.WorkspaceEdit();
51-
edit.set(editor.document.uri, toSnippetTextEdits(edits));
51+
const snippetEdits = toSnippetTextEdits(edits);
52+
edit.set(editor.document.uri, removeLeadingWhitespace(editor, snippetEdits));
5253
await vscode.workspace.applyEdit(edit);
5354
}
5455

@@ -74,3 +75,69 @@ function toSnippetTextEdits(
7475
}
7576
});
7677
}
78+
79+
/**
80+
* Removes the leading whitespace from snippet edits, so as to not double up
81+
* on indentation.
82+
*
83+
* Snippet edits by default adjust any multi-line snippets to match the
84+
* indentation of the line to insert at. Unfortunately, we (the server) also
85+
* include the required indentation to match what we line insert at, so we end
86+
* up doubling up the indentation. Since there isn't any way to tell vscode to
87+
* not fixup indentation for us, we instead opt to remove the indentation and
88+
* then let vscode add it back in.
89+
*
90+
* This assumes that the source snippet text edits have the required
91+
* indentation, but that's okay as even without this workaround and the problem
92+
* to workaround, those snippet edits would already be inserting at the wrong
93+
* indentation.
94+
*/
95+
function removeLeadingWhitespace(
96+
editor: vscode.TextEditor,
97+
edits: (vscode.TextEdit | vscode.SnippetTextEdit)[],
98+
) {
99+
return edits.map((edit) => {
100+
if (edit instanceof vscode.SnippetTextEdit) {
101+
const snippetEdit: vscode.SnippetTextEdit = edit;
102+
const firstLineEnd = snippetEdit.snippet.value.indexOf("\n");
103+
104+
if (firstLineEnd !== -1) {
105+
// Is a multi-line snippet, remove the indentation which
106+
// would be added back in by vscode.
107+
const startLine = editor.document.lineAt(snippetEdit.range.start.line);
108+
const leadingWhitespace = getLeadingWhitespace(
109+
startLine.text,
110+
0,
111+
startLine.firstNonWhitespaceCharacterIndex,
112+
);
113+
114+
const [firstLine, rest] = splitAt(snippetEdit.snippet.value, firstLineEnd + 1);
115+
const unindentedLines = rest
116+
.split("\n")
117+
.map((line) => line.replace(leadingWhitespace, ""))
118+
.join("\n");
119+
120+
snippetEdit.snippet.value = firstLine + unindentedLines;
121+
}
122+
123+
return snippetEdit;
124+
} else {
125+
return edit;
126+
}
127+
});
128+
}
129+
130+
// based on https://github.com/microsoft/vscode/blob/main/src/vs/base/common/strings.ts#L284
131+
function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string {
132+
for (let i = start; i < end; i++) {
133+
const chCode = str.charCodeAt(i);
134+
if (chCode !== " ".charCodeAt(0) && chCode !== " ".charCodeAt(0)) {
135+
return str.substring(start, i);
136+
}
137+
}
138+
return str.substring(start, end);
139+
}
140+
141+
function splitAt(str: string, index: number): [string, string] {
142+
return [str.substring(0, index), str.substring(index)];
143+
}

0 commit comments

Comments
 (0)