Found by the mutation review of #43 (review). Reported by Data, filed by Riker. This is a follow-up. It is not a regression from #43. It is a known and accepted limit of the fix that #43 shipped.
What happens
SearchCommand.snippet cuts a long match at 200 runes. Cutting on runes is already safer than cutting on UTF-16 code units: it cannot split a surrogate pair, so it cannot produce mojibake. PR #43 made that change and it is tested.
A rune is not a grapheme. A single user-visible character can be several runes:
- A family emoji is 7 runes: 4 people joined by 3 zero-width joiners.
- A flag is 2 runes: a regional indicator pair.
A match of 100 family sequences is 500 runes. The cut at rune 200 can land inside one sequence and end the snippet on a dangling zero-width joiner, or split a flag into a single regional indicator. The result renders as the wrong glyphs, not as broken text.
Why this is a low priority
No lone surrogate can survive the cut, so there is no mojibake and no invalid string. The output is always valid UTF-8. Only the glyph grouping is wrong, and only for a match longer than 200 runes that holds a multi-rune sequence at the boundary.
Suggested fix
Use package:characters and cut on Characters rather than on runes:
final graphemes = collapsed.characters;
if (graphemes.length <= _snippetLength) return collapsed;
return '${graphemes.take(_snippetLength)}…';
Note that this changes what _snippetLength counts, from runes to graphemes, so the existing boundary tests need their intent restated. Decide whether 200 should mean 200 graphemes before making the change.
Where
lib/src/cli/commands/search_command.dart — snippet
Found by the mutation review of #43 (review). Reported by Data, filed by Riker. This is a follow-up. It is not a regression from #43. It is a known and accepted limit of the fix that #43 shipped.
What happens
SearchCommand.snippetcuts a long match at 200 runes. Cutting on runes is already safer than cutting on UTF-16 code units: it cannot split a surrogate pair, so it cannot produce mojibake. PR #43 made that change and it is tested.A rune is not a grapheme. A single user-visible character can be several runes:
A match of 100 family sequences is 500 runes. The cut at rune 200 can land inside one sequence and end the snippet on a dangling zero-width joiner, or split a flag into a single regional indicator. The result renders as the wrong glyphs, not as broken text.
Why this is a low priority
No lone surrogate can survive the cut, so there is no mojibake and no invalid string. The output is always valid UTF-8. Only the glyph grouping is wrong, and only for a match longer than 200 runes that holds a multi-rune sequence at the boundary.
Suggested fix
Use
package:charactersand cut onCharactersrather than onrunes:Note that this changes what
_snippetLengthcounts, from runes to graphemes, so the existing boundary tests need their intent restated. Decide whether 200 should mean 200 graphemes before making the change.Where
lib/src/cli/commands/search_command.dart—snippet