This repository has been archived by the owner on Dec 15, 2022. It is now read-only.
Fix behavior when inserting content between two adjacent tab stops #281
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.
Fixes #15.
Description of the Change
The problem
Here’s a simple example of a snippet that has never quite worked right.
I’ll quote from my comment in #236 to explain how this goes wrong:
The fix
All snippet markers are getting created with
exclusive: false
. That means that they’ll expand to claim text that’s typed at their boundaries. But when inserted text is right between two adjacent markers, the one on the right always wins. The only way to force it in the direction you want is to create one marker withexclusive: true
and one withexclusive: false
.In our example,
$2
is the active tab stop, so we want its marker to claim the characters that we type. But when we hit Tab and make$3
the active tab stop, we want the opposite behavior. So we’ll have to change the settings on the markers as the active tab stop changes — or, rather, copy them to new markers with the settings we want.There’s one more edge case to consider, and I wasn’t sure how to conceptualize it until I peeked at VScode’s strategy for avoiding this problem: tab stops can reference other tab stops in their placeholder content. If
$2
has a placeholder that includes a reference to$1
, then$2
’s marker needs to be inclusive when either$1
or$2
is the active tab stop, because we need$2
’s marker to grow as we type the value for$1
. If you find this hard to wrap your brain around, you’re not the only one.Alternate Designs
The marker API, though robust, is not so rich as to give me lots of options here. The fact that this implementation is nearly identical to VScode’s is validation of the approach.
There were a few different ways I could’ve used to determine the cross-references between tab stops, their placeholders, and any tab stops those placeholders referenced. I decided to capture those references just after parsing, as we consume the syntax tree. Another option would’ve been to inspect the markers after creation and infer the relationships based on which markers were completely surrounded by other markers, but that felt like an indirect approach.
Benefits
A number of snippets that seemed like they should just work haven’t worked (or have worked incorrectly) for a long time, and now they will.
Possible Drawbacks
The fact that we have to destroy and recreate markers as tab stops move is not ideal, but I don’t think it’s a bottleneck. If it turns out to be a problem, we can make the changes necessary in text-buffer to allow the
exclusive
setting to be changed after creation on an existingDisplayMarker
.Applicable Issues
#236 is a duplicate of #15 but contains a lot of relevant discussion.