summaryrefslogtreecommitdiff
path: root/src/file_pane.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-09 23:43:06 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit0d66575a3c888498c7929e2ec345133628c5d63d (patch)
tree3be80051fd505f9675b36f9b570c46a955ca3445 /src/file_pane.zig
parent4b5e793f48f6e721c39ab6ddcf755604df73b643 (diff)
downloadpardes-0d66575a3c888498c7929e2ec345133628c5d63d.tar.gz
pardes-0d66575a3c888498c7929e2ec345133628c5d63d.zip
fix tab rendering - waybe wreckless
Diffstat (limited to 'src/file_pane.zig')
-rw-r--r--src/file_pane.zig69
1 files changed, 56 insertions, 13 deletions
diff --git a/src/file_pane.zig b/src/file_pane.zig
index c0a2350d..b0921bd0 100644
--- a/src/file_pane.zig
+++ b/src/file_pane.zig
@@ -17,6 +17,39 @@ const tracy = @import("tracy.zig");
const SYNTAX_CONTEXT_AFTER_ROWS: usize = 2;
+pub fn byteDisplayWidth(byte: u8) usize {
+ return if (byte == '\t') config.tab_width else 1;
+}
+
+pub fn displayWidth(text: []const u8) usize {
+ var width: usize = 0;
+ for (text) |byte| width +|= byteDisplayWidth(byte);
+ return width;
+}
+
+/// Source byte at a zero-based display column. Every cell occupied by a tab
+/// maps back to that one tab byte.
+pub fn byteAtDisplay(text: []const u8, display_col: usize) usize {
+ var col: usize = 0;
+ for (text, 0..) |byte, i| {
+ const next = col +| byteDisplayWidth(byte);
+ if (display_col < next) return i;
+ col = next;
+ }
+ return text.len;
+}
+
+fn fitEnd(text: []const u8, start: usize, width: usize) usize {
+ var end = start;
+ var used: usize = 0;
+ while (end < text.len) : (end += 1) {
+ const next = used +| byteDisplayWidth(text[end]);
+ if (next > width) return if (end == start) start + 1 else end;
+ used = next;
+ }
+ return end;
+}
+
pub fn lineCount(content: []const u8) usize {
return std.mem.count(u8, content, "\n") + 1;
}
@@ -258,16 +291,17 @@ fn fillBody(dst: ?[]u8, pane: *Pane, f: *File, width: usize, record_wrap: bool)
// panicking — see test/snapshots/badutf.snap. It cannot overflow
// the pane either: a UTF-8 sequence is never fewer bytes than the
// cells it draws in.
- const take = if (width == 0) text.len - at else @min(width, text.len - at);
+ const end = if (width == 0) text.len else fitEnd(text, at, width);
+ const take = end - at;
const cut = if (pane.hscroll > 0 and width == 0)
@min(@as(usize, @intCast(pane.hscroll)), take)
else
0;
- const shown = text[at + cut .. at + take];
+ const shown = text[at + cut .. end];
if (dst) |out| @memcpy(out[written..][0..shown.len], shown);
written += shown.len;
- if (width > 0 and at + take < text.len) {
- at += take;
+ if (width > 0 and end < text.len) {
+ at = end;
} else {
abs += 1;
at = 0;
@@ -364,16 +398,25 @@ pub fn recolorSyntax(p: *Pardes, pane: *Pane, f: *File, r: pardes.Rect, tx: u16,
line.len;
}
var c: usize = 0;
- while (hs + c < limit and config.PREFIX_W + c < tw) : (c += 1) {
+ var screen_c: usize = 0;
+ while (hs + c < limit and config.PREFIX_W + screen_c < tw) : (c += 1) {
+ const cells = byteDisplayWidth(line[hs + c]);
const idx = base + hs + c;
- if (idx < f.highlight_start) continue;
- const hidx = idx - f.highlight_start;
- if (hidx >= f.highlights.len) continue;
- const ss = synStyle(p, @enumFromInt(f.highlights[hidx])) orelse continue;
- const cell = s.at(tx + @as(u16, @intCast(config.PREFIX_W + c)), body_y + vr);
- if (cell.default) continue;
- cell.style.fg = .{ .rgb = ss.fg };
- cell.style.bold = ss.bold;
+ if (idx >= f.highlight_start) {
+ const hidx = idx - f.highlight_start;
+ if (hidx < f.highlights.len) {
+ if (synStyle(p, @enumFromInt(f.highlights[hidx]))) |ss| {
+ var fill: usize = 0;
+ while (fill < cells and config.PREFIX_W + screen_c + fill < tw) : (fill += 1) {
+ const cell = s.at(tx + @as(u16, @intCast(config.PREFIX_W + screen_c + fill)), body_y + vr);
+ if (cell.default) continue;
+ cell.style.fg = .{ .rgb = ss.fg };
+ cell.style.bold = ss.bold;
+ }
+ }
+ }
+ }
+ screen_c += cells;
}
}
}