about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--editors/code/src/snippets.ts71
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)];
+}