Draw non-ASCII text whole in the GUIs (B7); one span sanitizer

Highlighters emit one span per byte of a non-ASCII character, and the
ImGui and Qt renderers decoded each span on its own, so every such
character was drawn as replacement glyphs (reproduced under Xvfb:
"héllo" showed as "h??llo"). kte::SanitizeSpans (Highlight.h) widens
spans to whole UTF-8 characters and removes overlaps (the earlier span
wins), and replaces the three renderers' copies of the clamp/sort code.

The terminal renderer's token lookup rescanned the spans from the first
for every byte drawn; it now walks them with a cursor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 12:19:59 -07:00
committed by Kyle Isom
parent d490245f30
commit c5df5035fa
5 changed files with 95 additions and 87 deletions
+5 -27
View File
@@ -310,30 +310,8 @@ protected:
HasHighlighter()) {
const kte::LineHighlight &lh = buf->Highlighter()->GetLine(
*buf, static_cast<int>(i), buf->Version());
struct SSpan {
std::size_t s;
std::size_t e;
kte::TokenKind k;
};
std::vector<SSpan> spans;
spans.reserve(lh.spans.size());
const std::size_t line_len = line.size();
for (const auto &sp: lh.spans) {
int s_raw = sp.col_start;
int e_raw = sp.col_end;
if (e_raw < s_raw)
std::swap(e_raw, s_raw);
std::size_t s = static_cast<std::size_t>(std::max(
0, std::min(s_raw, (int) line_len)));
std::size_t e = static_cast<std::size_t>(std::max(
(int) s, std::min(e_raw, (int) line_len)));
if (s < e)
spans.push_back({s, e, sp.kind});
}
std::sort(spans.begin(), spans.end(),
[](const SSpan &a, const SSpan &b) {
return a.s < b.s;
});
std::vector<kte::HighlightSpan> spans; // clamped, ordered, whole characters
kte::SanitizeSpans(line, lh.spans, spans);
auto colorFor = [](kte::TokenKind k) -> QColor {
// GUITheme provides colors via ImGui vector; avoid direct dependency types
@@ -344,10 +322,10 @@ protected:
std::size_t pos = 0;
for (const auto &sp: spans) {
const std::size_t s = std::max(sp.s, pos);
const std::size_t s = std::max(static_cast<std::size_t>(sp.col_start), pos);
draw_seg(pos, s, fg);
draw_seg(s, sp.e, colorFor(sp.k));
pos = std::max(pos, sp.e);
draw_seg(s, static_cast<std::size_t>(sp.col_end), colorFor(sp.kind));
pos = std::max(pos, static_cast<std::size_t>(sp.col_end));
}
draw_seg(pos, line.size(), fg);
} else {