@@ -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
4949export 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