@@ -13,7 +13,7 @@ export async function applySnippetWorkspaceEdit(
13
13
const [ uri , edits ] = unwrapUndefinable ( editEntries [ 0 ] ) ;
14
14
const editor = await editorFromUri ( uri ) ;
15
15
if ( editor ) {
16
- edit . set ( uri , edits ) ;
16
+ edit . set ( uri , removeLeadingWhitespace ( editor , edits ) ) ;
17
17
await vscode . workspace . applyEdit ( edit ) ;
18
18
}
19
19
return ;
@@ -48,7 +48,8 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef
48
48
49
49
export async function applySnippetTextEdits ( editor : vscode . TextEditor , edits : vscode . TextEdit [ ] ) {
50
50
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 ) ) ;
52
53
await vscode . workspace . applyEdit ( edit ) ;
53
54
}
54
55
@@ -74,3 +75,69 @@ function toSnippetTextEdits(
74
75
}
75
76
} ) ;
76
77
}
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