gui: quote-pair selection spans full document, not one line

Quote matching now scans the whole block text and computes opener/closer
parity document-wide, so content between a '' "" `` pair that spans
multiple lines is selected in full. The adjacent quote (left or right of
the cursor) sets the direction; the matching end quote is found by
scanning that way.
This commit is contained in:
Levi Neely 2026-10-08 17:42:50 +02:00
parent 4b2c14943d
commit 794cecfa6c
1 changed files with 72 additions and 44 deletions

View File

@ -421,18 +421,29 @@ Column {
return ch === "'" || ch === '"' || ch === '`'
}
// Scan forward for the next occurrence of a quote char
function findQuoteForward(text, startPos, quote) {
for (var pos = startPos; pos < text.length; pos++) {
if (text.charAt(pos) === quote) return pos
// A quote at position qPos opens a pair when an even number of identical
// quote chars precede it; odd means it closes one. Quotes do not nest, so
// parity decides direction.
function quoteIsOpener(text, qPos, quote) {
var count = 0
for (var c = 0; c < qPos; c++) {
if (text.charAt(c) === quote) count++
}
return (count % 2) === 0
}
// Next identical quote char forward from qPos. -1 if none.
function findQuoteForward(text, qPos, quote) {
for (var c = qPos + 1; c < text.length; c++) {
if (text.charAt(c) === quote) return c
}
return -1
}
// Scan backward for the previous occurrence of a quote char
function findQuoteBackward(text, startPos, quote) {
for (var pos = startPos; pos >= 0; pos--) {
if (text.charAt(pos) === quote) return pos
// Previous identical quote char backward from qPos. -1 if none.
function findQuoteBackward(text, qPos, quote) {
for (var c = qPos - 1; c >= 0; c--) {
if (text.charAt(c) === quote) return c
}
return -1
}
@ -469,70 +480,87 @@ Column {
return -1 // unbalanced
}
// Select text between matching brackets on double-click
// Returns true if a bracket pair was found and selected
// Select text between a matching delimiter pair on double-click.
// Quotes ('' "" ``) are resolved first (symmetric, non-nesting, line-scoped,
// direction by parity); then brackets (nesting, document-wide). The adjacent
// delimiter can be on either side of the cursor and determines the search
// direction and the matching end delimiter.
// Returns true if a pair was found and the inner content selected.
function selectBracketPair(textEdit, mouseX, mouseY) {
var cursorPos = textEdit.positionAt(mouseX, mouseY)
var text = textEdit.getText(0, textEdit.length)
if (cursorPos < 0 || cursorPos > text.length) return false
// Check char to the right of cursor
// Char to the right and left of the click position.
var rightCh = cursorPos < text.length ? text.charAt(cursorPos) : ''
var rightMatch = matchingBracket(rightCh)
// Check char to the left of cursor
var leftCh = cursorPos > 0 ? text.charAt(cursorPos - 1) : ''
// Quote pairs first. Pick the adjacent quote (right preferred), decide
// opener/closer by parity, then scan that direction for the matching
// quote. Content between may span multiple lines; all of it is selected.
var q = ''
var qPos = -1
if (isQuote(rightCh)) {
q = rightCh
qPos = cursorPos
} else if (isQuote(leftCh)) {
q = leftCh
qPos = cursorPos - 1
}
if (q !== '') {
var qOpen = -1
var qClose = -1
if (quoteIsOpener(text, qPos, q)) {
qOpen = qPos
qClose = findQuoteForward(text, qPos, q)
} else {
qClose = qPos
qOpen = findQuoteBackward(text, qPos, q)
}
if (qOpen < 0 || qClose < 0) return false // unbalanced
textEdit.select(qOpen + 1, qClose)
return true
}
var rightMatch = matchingBracket(rightCh)
var leftMatch = matchingBracket(leftCh)
var innerStart = -1
var innerEnd = -1
// Prefer opener to the right, then closer to the left
// Prefer opener to the right, then closer to the left, then the
// remaining adjacency cases. Inner range excludes the delimiters.
if (rightMatch && rightMatch.isOpener) {
// Opener to right: find closer
// Opener to right: find closer forward.
var closerPos = findCloser(text, cursorPos + 1, rightCh, rightMatch.match)
if (closerPos >= 0) {
innerStart = cursorPos + 1
innerEnd = closerPos
}
} else if (leftMatch && !leftMatch.isOpener) {
// Closer to left: find opener
// Closer to left: find opener backward.
var openerPos = findOpener(text, cursorPos - 2, leftMatch.match, leftCh)
if (openerPos >= 0) {
innerStart = openerPos + 1
innerEnd = cursorPos - 1
}
} else if (rightMatch && !rightMatch.isOpener) {
// Closer to right: find opener backwards
var openerPos = findOpener(text, cursorPos - 1, rightMatch.match, rightCh)
if (openerPos >= 0) {
innerStart = openerPos + 1
// Closer to right: find opener backward from just before it.
var openerPos2 = findOpener(text, cursorPos - 1, rightMatch.match, rightCh)
if (openerPos2 >= 0) {
innerStart = openerPos2 + 1
innerEnd = cursorPos
}
} else if (leftMatch && leftMatch.isOpener) {
// Opener to left: find closer
var closerPos = findCloser(text, cursorPos, leftCh, leftMatch.match)
if (closerPos >= 0) {
// Opener to left: find closer forward from the click position.
var closerPos2 = findCloser(text, cursorPos, leftCh, leftMatch.match)
if (closerPos2 >= 0) {
innerStart = cursorPos
innerEnd = closerPos
}
} else if (isQuote(rightCh)) {
// Quote to right: treat as opener, find next matching quote forward
var qClose = findQuoteForward(text, cursorPos + 1, rightCh)
if (qClose >= 0) {
innerStart = cursorPos + 1
innerEnd = qClose
}
} else if (isQuote(leftCh)) {
// Quote to left: treat as closer, find previous matching quote backward
var qOpen = findQuoteBackward(text, cursorPos - 2, leftCh)
if (qOpen >= 0) {
innerStart = qOpen + 1
innerEnd = cursorPos - 1
innerEnd = closerPos2
}
}
if (innerStart >= 0 && innerEnd >= innerStart) {
textEdit.select(innerStart, innerEnd)
return true