Change:
const sw = new StopWatch(true); const languageId = this._getLanguageIdAtPosition(model, position); <del> const snippets = await this._snippets.getSnippets(languageId); let pos = { lineNumber: position.lineNumber, column: 1 }; let lineOffsets: number[] = []; const lineContent = model.getLineContent(position.lineNumber).toLowerCase(); const endsInWhitespace = /s/.test(lineContent[position.column - 2]); while (pos.column < position.column) { let word = model.getWordAtPosition(pos); if (word) { // at a word lineOffsets.push(word.startColumn - 1); pos.column = word.endColumn + 1; if (word.endColumn < position.column && !/s/.test(lineContent[word.endColumn - 1])) { lineOffsets.push(word.endColumn - 1); } } else if (!/s/.test(lineContent[pos.column - 1])) { // at a none-whitespace character lineOffsets.push(pos.column - 1); pos.column += 1; } else { // always advance! pos.column += 1; } } </del> <ins> const snippets = new Set(await this._snippets.getSnippets(languageId)); </ins> <del> const availableSnippets = new Set<Snippet>(snippets); const suggestions: SnippetCompletion[] = []; </del> <ins> const lineContentLow = model.getLineContent(position.lineNumber).toLowerCase(); const suggestions: SnippetCompletion[] = []; </ins> const columnOffset = position.column - 1; <del> for (const start of lineOffsets) { availableSnippets.forEach(snippet => { if (isPatternInWord(lineContent, start, columnOffset, snippet.prefixLow, 0, snippet.prefixLow.length)) { const prefixPos = position.column - (1 + start); const prefixRestLen = snippet.prefixLow.length - prefixPos; const endsWithPrefixRest = compareSubstring(lineContent, snippet.prefixLow, columnOffset, (columnOffset) + prefixRestLen, prefixPos, prefixPos + prefixRestLen); const startPosition = position.delta(0, -prefixPos); let endColumn = endsWithPrefixRest === 0 ? position.column + prefixRestLen : position.column; // First check if there is anything to the right of the cursor if (columnOffset < lineContent.length) { const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageId); const standardAutoClosingPairConditionals = autoClosingPairs.autoClosingPairsCloseSingleChar.get(lineContent[columnOffset]); // If the character to the right of the cursor is a closing character of an autoclosing pair if (standardAutoClosingPairConditionals?.some(p => // and the start position is the opening character of an autoclosing pair p.open === lineContent[startPosition.column - 1] && // and the snippet prefix contains the opening and closing pair at its edges snippet.prefix.startsWith(p.open) && snippet.prefix[snippet.prefix.length - 1] === p.close)) { // Eat the character that was likely inserted because of auto-closing pairs endColumn++; } } </del> <ins> for (const snippet of snippets) { </ins> <del> const replace = Range.fromPositions(startPosition, { lineNumber: position.lineNumber, column: endColumn }); const insert = replace.setEndPosition(position.lineNumber, position.column); </del> <ins> for (let pos = Math.max(0, columnOffset - snippet.prefixLow.length); pos < lineContentLow.length; pos++) { if (!isPatternInWord(lineContentLow, pos, columnOffset, snippet.prefixLow, 0, snippet.prefixLow.length)) { continue; } </ins> <del> suggestions.push(new SnippetCompletion(snippet, { replace, insert })); availableSnippets.delete(snippet); </del> <ins> const prefixRestLen = snippet.prefixLow.length - (columnOffset - pos); const endsWithPrefixRest = compareSubstring(lineContentLow, snippet.prefixLow, columnOffset, columnOffset + prefixRestLen, columnOffset - pos); const startPosition = position.with(undefined, pos + 1); let endColumn = endsWithPrefixRest === 0 ? position.column + prefixRestLen : position.column; // First check if there is anything to the right of the cursor if (columnOffset < lineContentLow.length) { const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageId); const standardAutoClosingPairConditionals = autoClosingPairs.autoClosingPairsCloseSingleChar.get(lineContentLow[columnOffset]); // If the character to the right of the cursor is a closing character of an autoclosing pair if (standardAutoClosingPairConditionals?.some(p => // and the start position is the opening character of an autoclosing pair p.open === lineContentLow[startPosition.column - 1] && // and the snippet prefix contains the opening and closing pair at its edges snippet.prefix.startsWith(p.open) && snippet.prefix[snippet.prefix.length - 1] === p.close) ) { // Eat the character that was likely inserted because of auto-closing pairs endColumn++; } </ins> } <del> }); </del> <ins> const replace = Range.fromPositions(startPosition, { lineNumber: position.lineNumber, column: endColumn }); const insert = replace.setEndPosition(position.lineNumber, position.column); suggestions.push(new SnippetCompletion(snippet, { replace, insert })); snippets.delete(snippet); break; } </ins> } <del> if (endsInWhitespace || lineOffsets.length === 0) { // add remaing snippets when the current prefix ends in whitespace or when no // interesting positions have been found availableSnippets.forEach(snippet => { </del> <ins> const endsInWhitespace = /s/.test(lineContentLow[position.column - 2]); if (endsInWhitespace || !lineContentLow /*empty line*/) { // add remaing snippets when the current prefix ends in whitespace or when line is empty for (let snippet of snippets) { </ins> const insert = Range.fromPositions(position); <del> const replace = lineContent.indexOf(snippet.prefixLow, columnOffset) === columnOffset ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; </del> <ins> const replace = lineContentLow.indexOf(snippet.prefixLow, columnOffset) === columnOffset ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; </ins> suggestions.push(new SnippetCompletion(snippet, { replace, insert })); <del> }); </del> <ins> } </ins> }

Discussion:
Issue Type: <bBug</bI would like the prefix to trigger only when there is a leading whitespace in front of the prefix (it is supposed to be substituted with an explicit non-breaking space character ). The example of the snippet: --The prefix behaves as if there is no leading space, meaning the remains one extra whitespace before the expanded body, resulting in a double space: . VS Code version: Code 1.55.2 (, 2021-04-13T09:35:57.887Z) OS version: WindowsNT x64 10.0. <details<summarySystem Info</summary </details<details<summaryExtensions (37)</summaryExtensionVersion ------ html-snippets0.2.1 Handlebars0.4.1 rc-beautify1.2.0 git-temporal-vscode1.0.0 insert-unicode0.10.0 i30.0.1 vscode-generate-getter-setter0.5.0 gitlens11.4.1 vscode-html-css1.10.2 vscode-npm-script0.3.21 carbon-now-sh1.2.0 vscode-angular-html1.7.0 beautify1.5.0 latex-workshop8.18.0 polacode-.5.2 svg1.4.6 Angular211.0.0 solidity0.0.113 Kotlin1.7.1 rainbow-csv1.8.1 vscode-docker1.12.1 python2021.5. vscode-pylance2021.5.2 jupyter2021.5. remote-containers0.166.1 postscript1.0.1 java0.79.1 LiveServer5.6.1 bash-debug0.3.9 scala0.5.3 bash-beautify0.1.1 code-settings-sync3.4.3 code-spell-checker1.10.2 sass-indented1.8.16 html-preview-vscode0.2.5 vscode-ruby0.28.0 markdown-all-in-one3.4.0 (2 theme extensions excluded) </details<details<summaryA/B Experiments</summary</details<!-- generated by issue reporter --
I have a fix for this but it makes bigger changes in how snippet completion are computed. Pushing out to November to get more Insider usage for it, changes are in