Repository navigation
fix(workspace): select terminal search matches at the original column - #805
Conversation
|
ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Thanks. One regression to fix first, plus a correction to the description.
frontend/workspace/src/components/terminal-search.ts line 31 computes
length: origin[column + needle.length - 1]! + 1 - origin[column]!That's one short when a match ends in an astral character. Searching 😀 in x😀 gives length 1 (main gives 2), so the highlight cuts the emoji in half. Tracking an end offset per folded unit alongside the start fixes it:
for (let unit = 0; unit < folded.length; unit++) { start.push(offset); end.push(offset + char.length) }
// ...
length: end[column + needle.length - 1]! - start[column]!That passes your İ cases and gets the emoji lengths right. Please add expect(terminalMatches(["x😀"], "😀")).toEqual([{ column: 1, row: 0, length: 2 }]).
On the description: U+0130 is the only code point whose lowercase is longer, and it grows under the default locale, so Turkish locales aren't the ones most affected.
4abef4e to
92aaaa3
Compare
…ded one The search folded each line to lower case and reported the offset it found, which is an index into the folded line rather than the cell xterm selects against the original buffer. Track the start and the end of every folded code unit instead, and derive the length from the end of the last one: taking it from the start was a code unit short whenever the match ended in an astral character, so searching an emoji drew the highlight through half of it.
455ded3 to
9346b28
Compare
|
Done in 9346b28.
Note the workspace suite is green apart from four tests that also fail on |
|
All checks are green on |
The requested changes are in and verified; CI is green.
# Conflicts: # CHANGELOG.md
a4e3b36
into
synthetic-sciences:main
What
Terminal scrollback search lowercases each line to compare, then hands the offsets it finds straight to xterm:
toLocaleLowerCase()can change a string's UTF-16 length —İ(U+0130, the only code point whose lowercase is longer under the default locale) lowercases toi+ U+0307, one code unit becoming two — so an index intovalueis not an index intoline. Andlengthuses the un-foldedquery.lengthwhile the match spansneedle.lengthfolded code units.The consumer selects against the original buffer line:
Why it matters
Measured:
So every match after an
İin the same line is highlighted one cell to the right. The row and the match count stay right, which is exactly why this reads as "search works, the highlight is just a bit off" rather than as a bug.Verification
The three existing tests are all ASCII. The new ones fail before the fix:
The ASCII guard (
"abc build"folds to itself) pins the ordinary path, and the three pre-existing tests are untouched.The change
Each folded code unit carries its original span, and the match is translated back:
.map((line, row) => { - const value = line.toLocaleLowerCase() + // Folding a character can change its length (U+0130 lowercases to two + // code units), so an offset in the folded line is not the cell xterm + // selects against the original buffer. Track the original span of each + // folded code unit and translate the match back. A start-only map with + // a +1 reads one short when a match ends in an astral character, whose + // single code point spans two units, so each unit carries its own end. + let value = "" + const start: number[] = [] + const end: number[] = [] + let offset = 0 + for (const char of line) { + const folded = char.toLocaleLowerCase() + value += folded + for (let unit = 0; unit < folded.length; unit++) { + start.push(offset) + end.push(offset + char.length) + } + offset += char.length + } return Array.from({ length: value.length }, (_, column) => column) .filter((column) => value.indexOf(needle, column) === column) - .map((column) => ({ column, row, length: query.length })) + .map((column) => ({ + column: start[column], + row, + length: end[column + needle.length - 1]! - start[column]!, + })) })lengthis derived from the original line rather than from the query.indexOfguaranteescolumn + needle.length <= value.length, so the trailing lookup is always in range.Update addressing review: the first version tracked only a start offset and added one, which reads a match ending in an astral character one short — searching
😀inx😀gave length 1 and would highlight half the emoji. Each folded unit now carries its own end offset, and a regression test pinsterminalMatches(["x😀"], "😀")to[{ column: 1, row: 0, length: 2 }]. Also corrected: U+0130 is the only code point that grows, and it does so under the default locale.Frontend typecheck clean; touched files are Prettier-clean.
Fixes #804