From b077c2ece678d1cc8bb0c2b2cd705ac3fb9681fd Mon Sep 17 00:00:00 2001 From: Phillip Cohen Date: Sat, 14 Feb 2026 12:51:55 -0800 Subject: [PATCH 1/2] Fix "inside" broken with mixed quote types (#3109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When two ambiguous delimiters (like single quotes) matched, the stack truncation destroyed all entries above the match — including unrelated delimiter types (backticks) pushed between the matched pair. Use splice instead of truncation for ambiguous delimiters to preserve unrelated stack entries. --- .../textual/changeInsideMixedQuotes.yml | 28 +++++++++++++++++++ .../getSurroundingPairOccurrences.ts | 12 ++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml diff --git a/data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml b/data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml new file mode 100644 index 0000000000..89eec62bf3 --- /dev/null +++ b/data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml @@ -0,0 +1,28 @@ +languageId: plaintext +command: + version: 7 + spokenForm: change inside pair + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: interiorOnly} + - type: containingScope + scopeType: {type: surroundingPair, delimiter: any} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + aaa's `bbb 'ccc'` + `ddd` + selections: + - anchor: {line: 1, character: 1} + active: {line: 1, character: 1} + marks: {} +finalState: + documentContents: |- + aaa's `bbb 'ccc'` + `` + selections: + - anchor: {line: 1, character: 1} + active: {line: 1, character: 1} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getSurroundingPairOccurrences.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getSurroundingPairOccurrences.ts index 7f33695a9c..2f2f894c73 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getSurroundingPairOccurrences.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getSurroundingPairOccurrences.ts @@ -43,8 +43,16 @@ export function getSurroundingPairOccurrences( const openingDelimiter = openingDelimitersStack[openingDelimiterIndex]; - // Pop stack up to and including the opening delimiter - openingDelimitersStack.length = openingDelimiterIndex; + if (side === "unknown") { + // For ambiguous delimiters (eg quotes), remove only the matched + // entry so that unrelated delimiter types pushed between the + // matched pair are preserved. See #3109. + openingDelimitersStack.splice(openingDelimiterIndex, 1); + } else { + // For non-ambiguous delimiters (eg parentheses), truncate the + // stack to enforce proper nesting. + openingDelimitersStack.length = openingDelimiterIndex; + } result.push({ delimiterName: delimiterName, From 049bf93f5870e49d6eafd4a5597f88ae62fa7c71 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 15 Feb 2026 05:03:47 +0100 Subject: [PATCH 2/2] Update tests --- .../textual/changeInsideMixedQuotes.yml | 28 ------- .../surroundingPair/surroundingPair7.scope | 75 +++++++++++++++++++ 2 files changed, 75 insertions(+), 28 deletions(-) delete mode 100644 data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml create mode 100644 data/fixtures/scopes/plaintext/surroundingPair/surroundingPair7.scope diff --git a/data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml b/data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml deleted file mode 100644 index 89eec62bf3..0000000000 --- a/data/fixtures/recorded/surroundingPair/textual/changeInsideMixedQuotes.yml +++ /dev/null @@ -1,28 +0,0 @@ -languageId: plaintext -command: - version: 7 - spokenForm: change inside pair - action: - name: clearAndSetSelection - target: - type: primitive - modifiers: - - {type: interiorOnly} - - type: containingScope - scopeType: {type: surroundingPair, delimiter: any} - usePrePhraseSnapshot: true -initialState: - documentContents: |- - aaa's `bbb 'ccc'` - `ddd` - selections: - - anchor: {line: 1, character: 1} - active: {line: 1, character: 1} - marks: {} -finalState: - documentContents: |- - aaa's `bbb 'ccc'` - `` - selections: - - anchor: {line: 1, character: 1} - active: {line: 1, character: 1} diff --git a/data/fixtures/scopes/plaintext/surroundingPair/surroundingPair7.scope b/data/fixtures/scopes/plaintext/surroundingPair/surroundingPair7.scope new file mode 100644 index 0000000000..8eec1cf6d3 --- /dev/null +++ b/data/fixtures/scopes/plaintext/surroundingPair/surroundingPair7.scope @@ -0,0 +1,75 @@ +aaa's `bbb 'ccc'` +`ddd` +--- + +[#1 Content] = +[#1 Removal] = +[#1 Domain] = 0:3-0:12 + >---------< +0| aaa's `bbb 'ccc'` + +[#1 Interior: Content] = 0:4-0:10 + >------< +0| aaa's `bbb 'ccc'` +[#1 Interior: Removal] = 0:4-0:11 + >-------< +0| aaa's `bbb 'ccc'` + +[#1 Boundary L] = 0:3-0:4 + >-< +0| aaa's `bbb 'ccc'` + +[#1 Boundary R] = 0:11-0:12 + >-< +0| aaa's `bbb 'ccc'` + +[#1 Insertion delimiter] = " " + + +[#2 Content] = +[#2 Domain] = 0:6-0:17 + >-----------< +0| aaa's `bbb 'ccc'` + +[#2 Removal] = 0:5-0:17 + >------------< +0| aaa's `bbb 'ccc'` + +[#2 Leading delimiter] = 0:5-0:6 + >-< +0| aaa's `bbb 'ccc'` + +[#2 Interior] = 0:7-0:16 + >---------< +0| aaa's `bbb 'ccc'` + +[#2 Boundary L] = 0:6-0:7 + >-< +0| aaa's `bbb 'ccc'` + +[#2 Boundary R] = 0:16-0:17 + >-< +0| aaa's `bbb 'ccc'` + +[#2 Insertion delimiter] = " " + + +[#3 Content] = +[#3 Removal] = +[#3 Domain] = 1:0-1:5 + >-----< +1| `ddd` + +[#3 Interior] = 1:1-1:4 + >---< +1| `ddd` + +[#3 Boundary L] = 1:0-1:1 + >-< +1| `ddd` + +[#3 Boundary R] = 1:4-1:5 + >-< +1| `ddd` + +[#3 Insertion delimiter] = " "