Fix insertSnippet() destroying active snippet session tabstops #322008
Open
almayor wants to merge 4 commits into
Open
Fix insertSnippet() destroying active snippet session tabstops #322008almayor wants to merge 4 commits into
almayor wants to merge 4 commits into
Conversation
editor.insertSnippet() previously routed every expansion through SnippetController2.apply(ISnippetEdit[]), whose _doInsert() guard unconditionally cancels any active snippet session before inserting. This destroyed the outer snippet's remaining tabstops whenever a new snippet was inserted while a session was active (e.g. auto-expanding a snippet inside another snippet's placeholder) - a regression introduced in VS Code 1.86 and tracked as microsoft#214757. Route single contiguous snippet edits through the string-based insert() path instead, which merges (nests) the new snippet into the active placeholder via the existing OneSnippet.merge() infrastructure, preserving the outer session. Multi-range edits, which cannot be merged into a single active placeholder, still fall back to apply(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTkA2Uv3LgJZWrtA32tq33
Fix snippet nesting by using insert API for single-range edits
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses a regression (since 1.86) where extension calls to TextEditor.insertSnippet() during an active snippet session can cancel the outer session and destroy remaining tabstops. The approach is to route single-range snippet insertions through the merge-capable SnippetController2.insert(...) path instead of the edits-array apply(...) path that cancels active sessions.
Changes:
- Refactors
MainThreadTextEditor.insertSnippetto build a sharedinsertOptionsobject for snippet insertion. - Routes single-range insertions through
snippetController.insert(template, ...)after setting the editor selection to the target range. - Keeps multi-range insertions on the
snippetController.apply(edits, ...)path.
This was referenced Jun 18, 2026
Author
|
@microsoft-github-policy-service agree |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Related: #322010
Since VS Code 1.86, calling
editor.insertSnippet()from an extension while a snippet session is active destroys the outer session's remaining tabstops. This is a regression — it worked correctly in 1.85.2 and earlier.This breaks extensions like HyperSnips, LTeX, and others that programmatically insert snippets, making nested snippet expansion unusable.
Root cause
In 1.86,
mainThreadEditor.tswas refactored to routeinsertSnippet()calls throughsnippetController.apply(edits[])instead ofsnippetController.insert(template). Theapply()path callsthis.cancel()when a session is active:The
insert()path instead callsthis._session.merge(template, opts), which properly nests the new snippet inside the active placeholder. The merge infrastructure (OneSnippet.merge(),_nestingLevel,_renormalizePlaceholderIndices()) is fully implemented and works — the regression was the API bridge switching code paths.Fix
Route single-edit
insertSnippet()calls back throughsnippetController.insert()(the merge path), falling back toapply()only for multi-range edits.Note on issue #214757
This bug was reported in #214757, which was closed as a duplicate of #63129. However, #63129 is about snippet auto-indentation (fixed in #234858), not tabstop destruction — they are unrelated issues. The bug remains present through current versions.
Fixes #214757
Test
editor.insertSnippet()during editing\int_{$1}^{$2} $3 d$4)$1, trigger another snippet insertion via the extension$2(currently it doesn't; the session is destroyed)This fix was researched and developed with the assistance of Claude Code (Claude Opus 4.6).