Renderers: linear per-row measuring in ImGui; one tab width; Qt drawing fixes
ImGui measured every span, highlight and the cursor from column 0 of the line (and rescanned the line for each column conversion), which is quadratic in line length on minified files. Columns now come from one table per line and pixel offsets from the nearest position already measured. Output is pixel-identical to before (compared under Xvfb, with syntax colouring, tabs, UTF-8 and search highlights). kte::kTabWidth replaces the literal 8s, and Qt's 4: Qt expanded tabs to 4 columns while the command layer scrolled by 8, misplacing the cursor and horizontal scroll on lines with tabs. MeasureChar and DisplayColumns (TermWidth.h) are the command layer's column model, now shared, and Qt draws by it instead of one column per byte, which shifted everything after a multi-byte character. Qt also: - drew operators in the dark-mode background colour, so they vanished; - computed the editor's column count three ways (different glyph, padding and status-row accounting) from painting, and positioned text with a rounded cell width, so the cursor drifted off the glyphs on long lines. One TextLayout now serves painting, resizing, font changes and wheel scrolling, with a fractional cell width; runs are drawn only where visible; - drew each non-ASCII character at its cell, since a fallback-font glyph wider than a cell pushed the rest of the run out of place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
+7
-31
@@ -140,33 +140,6 @@ line_view(const std::string &l)
|
||||
}
|
||||
|
||||
|
||||
// Keep buffer viewport offsets so that the cursor stays within the visible
|
||||
// window based on the editor's current dimensions. The bottom row is reserved
|
||||
// for the status line.
|
||||
// Decode the character at line[i]: its byte length and display width in cells
|
||||
// (tabs expand to the next multiple of tabw from column rx). Measures the way
|
||||
// TerminalRenderer draws: invalid bytes are one cell each.
|
||||
static void
|
||||
measure_char(std::string_view line, std::size_t i, std::size_t rx, std::size_t tabw, std::size_t &len,
|
||||
std::size_t &width)
|
||||
{
|
||||
std::mbstate_t state{};
|
||||
wchar_t wch = 0;
|
||||
const auto rc = std::mbrtowc(&wch, line.data() + i, line.size() - i, &state);
|
||||
if (rc == static_cast<std::size_t>(-1) || rc == static_cast<std::size_t>(-2) || rc == 0) {
|
||||
// Invalid byte or NUL: the renderer draws the byte value itself.
|
||||
wch = static_cast<unsigned char>(line[i]);
|
||||
len = 1;
|
||||
} else {
|
||||
len = rc;
|
||||
}
|
||||
if (wch == L'\t')
|
||||
width = tabw - (rx % tabw);
|
||||
else
|
||||
width = static_cast<std::size_t>(kte::CellWidth(wch));
|
||||
}
|
||||
|
||||
|
||||
// Display column of byte offset curx, in terminal cells.
|
||||
static std::size_t
|
||||
compute_render_x(std::string_view line, const std::size_t curx, const std::size_t tabw)
|
||||
@@ -175,7 +148,7 @@ compute_render_x(std::string_view line, const std::size_t curx, const std::size_
|
||||
std::size_t i = 0;
|
||||
while (i < curx && i < line.size()) {
|
||||
std::size_t len = 1, width = 1;
|
||||
measure_char(line, i, rx, tabw, len, width);
|
||||
kte::MeasureChar(line, i, rx, len, width, tabw);
|
||||
rx += width;
|
||||
i += len;
|
||||
}
|
||||
@@ -219,6 +192,9 @@ clamp_cursor_to_buffer(Buffer &buf)
|
||||
}
|
||||
|
||||
|
||||
// Keep buffer viewport offsets so that the cursor stays within the visible
|
||||
// window based on the editor's current dimensions. The bottom row is reserved
|
||||
// for the status line.
|
||||
static void
|
||||
ensure_cursor_visible(const Editor &ed, Buffer &buf)
|
||||
{
|
||||
@@ -256,7 +232,7 @@ ensure_cursor_visible(const Editor &ed, Buffer &buf)
|
||||
if (cury < total_rows) {
|
||||
// GetLineView would materialize the whole buffer after an edit; copying
|
||||
// the one line is cheaper.
|
||||
rx = compute_render_x(buf.GetLineString(cury), curx, 8);
|
||||
rx = compute_render_x(buf.GetLineString(cury), curx, kte::kTabWidth);
|
||||
}
|
||||
if (rx < coloffs) {
|
||||
coloffs = rx;
|
||||
@@ -802,7 +778,7 @@ inverse_render_to_source_col(const std::string &line, std::size_t rx_target, std
|
||||
if (i >= line.size() || rx >= rx_target)
|
||||
break;
|
||||
std::size_t len = 1, width = 1;
|
||||
measure_char(line, i, rx, tabw, len, width);
|
||||
kte::MeasureChar(line, i, rx, len, width, tabw);
|
||||
rx += width;
|
||||
i += len;
|
||||
}
|
||||
@@ -854,7 +830,7 @@ cmd_move_cursor_to(CommandContext &ctx)
|
||||
by = lines2.size() - 1;
|
||||
std::string line2 = static_cast<std::string>(lines2[by]);
|
||||
std::size_t rx_target = bco + vx;
|
||||
std::size_t sx = inverse_render_to_source_col(line2, rx_target, 8);
|
||||
std::size_t sx = inverse_render_to_source_col(line2, rx_target, kte::kTabWidth);
|
||||
row = by;
|
||||
col = sx;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user