summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig54
1 files changed, 46 insertions, 8 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index ebc54426..32a58f89 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -284,7 +284,7 @@ pub const Surface = struct {
pub const Mouse = struct {
// sel indexing relies on left/middle/right being 0/1/2
- pub const Button = enum { left, middle, right, wheel_up, wheel_down, none };
+ pub const Button = enum { left, middle, right, wheel_up, wheel_down, wheel_left, wheel_right, none };
pub const Kind = enum { press, release, motion, drag };
button: Button,
kind: Kind,
@@ -498,6 +498,12 @@ pub const Pane = struct {
cur_pinned: bool = false,
cur_row: i32 = 0,
cur_col: i32 = 0,
+ /// horizontal scroll, file panes only (terminals wrap at pty width, they
+ /// never have wider lines): content columns hidden left of the gutter.
+ /// No scrollbar — the wheel and cursor movement (with scrolloff) drive it,
+ /// the goal is just being able to read long lines. Byte columns, like the
+ /// rest of the file-pane code.
+ hscroll: i32 = 0,
cur_off: u16 = 0,
sel: [3]Sel = @splat(.{}),
edits: [MAX_EDITS]Edit = @splat(.{}),
@@ -552,6 +558,17 @@ pub const Pane = struct {
if (pane.file) |f| to = @min(to, @as(i32, @intCast(modal.lineCount(f.content) -| 1)));
pane.scrollBy(@max(0, to - last));
}
+ // the horizontal mirror, files only: keep scroll_off columns of
+ // context around the cursor (wheel-driven hscroll is exempt — it
+ // never moves the cursor, and a cursor move pulls the view back)
+ if (pane.file != null) {
+ const w: i32 = @max(1, @as(i32, pane.cols) - @as(i32, PREFIX_W));
+ const hmargin: i32 = @min(scroll_off, @divTrunc(w - 1, 2));
+ if (pane.cur_col < pane.hscroll + hmargin)
+ pane.hscroll = @max(0, pane.cur_col - hmargin)
+ else if (pane.cur_col > pane.hscroll + w - 1 - hmargin)
+ pane.hscroll = pane.cur_col - (w - 1 - hmargin);
+ }
}
/// the last navigable row: file -> last content line; terminal -> viewport bottom
@@ -1172,7 +1189,7 @@ pub const Pardes = struct {
/// coords: row 0 is the tag; file panes carry the line-number prefix)
fn cursorWordSel(pane: *Pane) Sel {
const vrow = (pane.cur_row - pane.scroll()) + @as(i32, BOX_H);
- const vcol = if (pane.file != null) pane.cur_col + @as(i32, PREFIX_W) else pane.cur_col;
+ const vcol = if (pane.file != null) pane.cur_col - pane.hscroll + @as(i32, PREFIX_W) else pane.cur_col;
return .{ .state = .done, .c0 = vcol, .c1 = vcol, .r0 = vrow, .r1 = vrow };
}
@@ -2368,6 +2385,15 @@ pub const Pardes = struct {
const id = hovered orelse return;
p.panes[id].?.scrollBy(if (m.button == .wheel_up) -1 else 1);
},
+ .wheel_left, .wheel_right => {
+ if (m.kind != .press) return;
+ const id = hovered orelse return;
+ const pane = p.panes[id].?;
+ // ponytail: no right clamp — overscroll shows blank and the
+ // next cursor move or left wheel pulls it back
+ if (pane.file != null)
+ pane.hscroll = @max(0, pane.hscroll + @as(i32, if (m.button == .wheel_right) 4 else -4));
+ },
.left => switch (m.kind) {
.press => {
// middle+left chord: a left press during a middle select-
@@ -2560,7 +2586,7 @@ pub const Pardes = struct {
const body_vis = sl.r1 - @as(i32, BOX_H);
if (body_vis >= 0 and pane.mode != .tty) {
pane.cur_row = body_vis + pane.scroll();
- pane.cur_col = if (pane.file != null) @max(0, sl.c1 - @as(i32, PREFIX_W)) else sl.c1;
+ pane.cur_col = if (pane.file != null) @max(0, sl.c1 - @as(i32, PREFIX_W)) + pane.hscroll else sl.c1;
pane.cur_off = if (pane.runAt(pane.cur_row, pane.cur_col)) |i| pane.edits[i].len else 0;
pane.cur_pinned = true;
pane.mode = .normal;
@@ -3602,13 +3628,14 @@ pub const Pardes = struct {
while (skip < f.scroll) : (skip += 1) {
if (flines.next() == null) break;
}
+ const hs: usize = @intCast(@max(0, pane.hscroll));
var vr: u16 = 0;
while (vr < body_h) : (vr += 1) {
const line = flines.next() orelse break;
const base = @intFromPtr(line.ptr) - @intFromPtr(f.content.ptr);
var c: usize = 0;
- while (c < line.len and PREFIX_W + c < tw) : (c += 1) {
- const idx = base + c;
+ while (hs + c < line.len and PREFIX_W + c < tw) : (c += 1) {
+ const idx = base + hs + c;
if (idx < f.highlight_start) continue;
const hidx = idx - f.highlight_start;
if (hidx >= f.highlights.len) continue;
@@ -3697,12 +3724,13 @@ pub const Pardes = struct {
if (pane.mode == .normal and pane.vsel.active) {
const bnd = vselBounds(pane);
const vpfx: i32 = if (pane.file != null) PREFIX_W else 0;
+ const vhs: i32 = if (pane.file != null) pane.hscroll else 0;
var ar: i32 = bnd.lo_row;
while (ar <= bnd.hi_row) : (ar += 1) {
const prow = ar - off + @as(i32, BOX_H);
if (prow < 0 or prow >= @as(i32, r.h)) continue;
- const cstart: i32 = if (ar == bnd.lo_row) bnd.lo_col + vpfx else vpfx;
- const cend: i32 = if (ar == bnd.hi_row) bnd.hi_col + vpfx else @as(i32, tw) - 1;
+ const cstart: i32 = if (ar == bnd.lo_row) bnd.lo_col - vhs + vpfx else vpfx;
+ const cend: i32 = if (ar == bnd.hi_row) bnd.hi_col - vhs + vpfx else @as(i32, tw) - 1;
var col: i32 = @max(cstart, vpfx);
while (col <= cend and col < tw) : (col += 1) {
const cell = s.at(tx + @as(u16, @intCast(col)), r.y + @as(u16, @intCast(prow)));
@@ -3726,7 +3754,7 @@ pub const Pardes = struct {
// insert mode maps the run anchor to its visual column; normal
// mode keeps cur_col already-visual.
const cx = if (pane.file != null)
- @as(i32, PREFIX_W) + ccol + @as(i32, coff)
+ @as(i32, PREFIX_W) + ccol + @as(i32, coff) - pane.hscroll
else if (pane.mode == .insert)
pane.renderedCol(crow, ccol, coff)
else
@@ -3861,6 +3889,7 @@ pub const Pardes = struct {
while (i < pane.rows) : (i += 1) {
if (i > 0) try out.append(arena, '\n');
const abs: i32 = @as(i32, @intCast(f.scroll)) + @as(i32, @intCast(i));
+ const row_start = out.items.len;
if (flines.next()) |text| {
var lbuf: [16]u8 = undefined;
// unsigned: {d} prints a leading '+' for signed ints
@@ -3871,6 +3900,15 @@ pub const Pardes = struct {
try base.appendSlice(arena, text);
try spliceRow(arena, &out, pane, abs, base.items);
} else try spliceRow(arena, &out, pane, abs, "");
+ // horizontal scroll: cut hscroll columns after the gutter,
+ // from the fully composed row (edit splices shift with it)
+ if (pane.hscroll > 0) {
+ const cut_at = row_start + PREFIX_W;
+ if (out.items.len > cut_at) {
+ const cut = @min(@as(usize, @intCast(pane.hscroll)), out.items.len - cut_at);
+ out.replaceRangeAssumeCapacity(cut_at, cut, &.{});
+ }
+ }
}
return out.items;
}