| import * as vscode from "vscode"; | 
 |  | 
 | import { assert, unwrapUndefinable } from "./util"; | 
 |  | 
 | export type SnippetTextDocumentEdit = [vscode.Uri, (vscode.TextEdit | vscode.SnippetTextEdit)[]]; | 
 |  | 
 | export async function applySnippetWorkspaceEdit( | 
 |     edit: vscode.WorkspaceEdit, | 
 |     editEntries: SnippetTextDocumentEdit[], | 
 | ) { | 
 |     if (editEntries.length === 1) { | 
 |         const [uri, edits] = unwrapUndefinable(editEntries[0]); | 
 |         const editor = await editorFromUri(uri); | 
 |         if (editor) { | 
 |             edit.set(uri, removeLeadingWhitespace(editor, edits)); | 
 |             await vscode.workspace.applyEdit(edit); | 
 |         } | 
 |         return; | 
 |     } | 
 |     for (const [uri, edits] of editEntries) { | 
 |         const editor = await editorFromUri(uri); | 
 |         if (editor) { | 
 |             await editor.edit((builder) => { | 
 |                 for (const indel of edits) { | 
 |                     assert( | 
 |                         !(indel instanceof vscode.SnippetTextEdit), | 
 |                         `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`, | 
 |                     ); | 
 |                     builder.replace(indel.range, indel.newText); | 
 |                 } | 
 |             }); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undefined> { | 
 |     if (vscode.window.activeTextEditor?.document.uri !== uri) { | 
 |         // `vscode.window.visibleTextEditors` only contains editors whose contents are being displayed | 
 |         await vscode.window.showTextDocument(uri, {}); | 
 |     } | 
 |     return vscode.window.visibleTextEditors.find( | 
 |         (it) => it.document.uri.toString() === uri.toString(), | 
 |     ); | 
 | } | 
 |  | 
 | export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) { | 
 |     const edit = new vscode.WorkspaceEdit(); | 
 |     const snippetEdits = toSnippetTextEdits(edits); | 
 |     edit.set(editor.document.uri, removeLeadingWhitespace(editor, snippetEdits)); | 
 |     await vscode.workspace.applyEdit(edit); | 
 | } | 
 |  | 
 | function hasSnippet(snip: string): boolean { | 
 |     const m = snip.match(/\$\d+|\{\d+:[^}]*\}/); | 
 |     return m != null; | 
 | } | 
 |  | 
 | function toSnippetTextEdits( | 
 |     edits: vscode.TextEdit[], | 
 | ): (vscode.TextEdit | vscode.SnippetTextEdit)[] { | 
 |     return edits.map((textEdit) => { | 
 |         // Note: text edits without any snippets are returned as-is instead of | 
 |         // being wrapped in a SnippetTextEdit, as otherwise it would be | 
 |         // treated as if it had a tab stop at the end. | 
 |         if (hasSnippet(textEdit.newText)) { | 
 |             return new vscode.SnippetTextEdit( | 
 |                 textEdit.range, | 
 |                 new vscode.SnippetString(textEdit.newText), | 
 |             ); | 
 |         } else { | 
 |             return textEdit; | 
 |         } | 
 |     }); | 
 | } | 
 |  | 
 | /** | 
 |  * 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)]; | 
 | } |