summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 13:39:27 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit13d0d107a06d7ac5fb5f8e86c747e54603f51b4e (patch)
tree6d404c6048736a093aacb1827081c9726d5906e0 /src/pardes.zig
parent1d19d0ec24b924d1d2ccf2cab25199c6e6865fd7 (diff)
downloadpardes-13d0d107a06d7ac5fb5f8e86c747e54603f51b4e.tar.gz
pardes-13d0d107a06d7ac5fb5f8e86c747e54603f51b4e.zip
Remember tag cursors and reserve uppercase pane navigation
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig109
1 files changed, 62 insertions, 47 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 3c0205d2..dd7fc5ed 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -7128,6 +7128,7 @@ pub const Pardes = struct {
}
fn enterTagEdit(p: *Pardes, pane: *Pane, col: i32) void {
+ if (pane.tag_edit and pane.prompt == .none) pane.tag_saved_col = pane.tag_col;
const edit0: i32 = @intCast((p.tagPrefix(pane) catch return).len);
p.seedTail(pane);
if (!pane.tag_init) return;
@@ -7141,14 +7142,18 @@ pub const Pardes = struct {
const tail = pane.tagSlice();
const lead: i32 = @intCast(tail.len - std.mem.trimStart(u8, tail, " ").len);
if (col < 0) {
- pane.tag_col = @intCast(@min(edit0 + lead, end));
const text = p.tagText(p.scratch.allocator(), pane) catch return;
- if (p.paneIdOf(pane)) |id| {
+ if (pane.tag_saved_col) |saved| {
+ pane.tag_col = @intCast(modal.graphemeStart(text, @min(saved, text.len)));
+ } else {
+ pane.tag_col = @intCast(@min(edit0 + lead, end));
+ }
+ if (pane.tag_saved_col == null) if (p.paneIdOf(pane)) |id| {
const width = @max(1, p.rects[id].w -| config.GUTTER);
// Reveal the suffix together with its commands, not merely the
// first command letter at the viewport's far right edge.
pane.tag_scroll = @intCast(panes.File.displayWidth(text) + 1 -| width);
- }
+ };
} else {
const text = p.tagText(p.scratch.allocator(), pane) catch return;
pane.tag_col = @intCast(@min(text.len, panes.File.rawAtDisplay(text, @as(usize, @intCast(col)) + pane.tag_scroll)));
@@ -7156,6 +7161,7 @@ pub const Pardes = struct {
}
fn exitTagEdit(pane: *Pane) void {
+ if (pane.tag_edit and pane.prompt == .none) pane.tag_saved_col = pane.tag_col;
if (pane.isTerminal()) {
if (pane.tag_edit) pane.mode = pane.tag_mode;
} else pane.mode = .normal;
@@ -7334,7 +7340,7 @@ pub const Pardes = struct {
const text = p.tagText(p.scratch.allocator(), pane) catch return;
const cur: usize = @min(@as(usize, pane.tag_col), text.len);
// Esc abandons the command line: back to the body, tail kept as text
- if (hit(key, config.escape)) return exitTagEdit(pane);
+ if (hit(key, config.escape) or hit(key, &.{.{ .cp = 'j' }})) return exitTagEdit(pane);
if (hit(key, config.look_key) or hit(key, config.exec_key)) {
const cmd = if (hit(key, config.look_key)) config.look_cmd else config.exec_cmd;
const txt = p.tagChordText(pane) orelse return;
@@ -7364,7 +7370,9 @@ pub const Pardes = struct {
return;
}
const dir: ?Builtin = for (config.window_keys) |wk| {
- if (hit(key, &.{wk.letter})) break wk.cmd;
+ var letter = wk.letter;
+ letter.cp = std.ascii.toUpper(@intCast(letter.cp));
+ if (hit(key, &.{letter})) break wk.cmd;
} else null;
if (dir) |d| {
const from = p.active;
@@ -7372,17 +7380,13 @@ pub const Pardes = struct {
if (p.active == from) {
if (d == .Up) {
exitTagEdit(pane);
- p.topbar_col = 0;
- p.header_column = if (p.columnBarHeight() > 0) (layout.findPane(p, from) orelse return).col else null;
- p.header_insert = false;
- p.header_anchor = null;
- p.header_drag = false;
+ p.enterHeader(if (p.columnBarHeight() > 0) (layout.findPane(p, from) orelse return).col else null);
}
return;
}
const dest = p.panes[p.active] orelse return;
exitTagEdit(pane); // the pane we left is fully out of its tag...
- p.enterTagEdit(dest, -1); // ...and the tail's start is the new park
+ p.enterTagEdit(dest, -1);
if (dest.tag_edit) dest.mode = .normal;
return;
}
@@ -7421,23 +7425,46 @@ pub const Pardes = struct {
return modal.moveWord(text, .{ .anchor = cur, .head = modal.nextGrapheme(text, cur) }, 1, t);
}
+ fn rememberHeader(p: *Pardes) void {
+ const col = p.topbar_col orelse return;
+ const line = if (p.header_column) |c| &p.col_tags[c] else &p.global_tag;
+ line.saved_col = col;
+ }
+
+ pub fn exitHeader(p: *Pardes) void {
+ p.rememberHeader();
+ p.topbar_col = null;
+ p.header_column = null;
+ p.header_anchor = null;
+ p.header_drag = false;
+ p.header_scroll = 0;
+ }
+
+ fn enterHeader(p: *Pardes, column: ?usize) void {
+ p.rememberHeader();
+ p.header_column = column;
+ const line = if (column) |c| &p.col_tags[c] else &p.global_tag;
+ const text = p.headerText();
+ p.topbar_col = @intCast(modal.graphemeStart(text, @min(line.saved_col orelse 0, text.len)));
+ p.header_insert = false;
+ p.header_anchor = null;
+ p.header_drag = false;
+ p.header_scroll = 0;
+ }
+
fn topbarKey(p: *Pardes, key: Key) void {
const bar = p.headerText();
// the line GROWS a `Restore <path>` word the moment a dump lands, so
// the column is clamped against the live line on every use
const cur: usize = @min(@as(usize, p.topbar_col orelse return), bar.len);
- if (hit(key, config.escape)) {
- p.topbar_col = null; // the active pane still has its body focus
- p.header_column = null;
- p.header_anchor = null;
- p.header_drag = false;
- p.header_scroll = 0;
+ if (hit(key, config.escape) or (!p.header_insert and hit(key, &.{.{ .cp = 'j' }}))) {
+ p.exitHeader();
return;
}
if (hit(key, config.look_key) or hit(key, config.exec_key)) {
const a = p.header_anchor orelse @as(u16, @intCast(cur));
const word = if (a != cur) bar[@min(a, cur)..@max(a, cur)] else wordAtCol(bar, cur);
- p.topbar_col = null;
+ p.exitHeader();
if (word.len > 0) {
if (!p.header_insert) {
// Preserve the workspace tag's keyboard command semantics.
@@ -7490,9 +7517,7 @@ pub const Pardes = struct {
}
if (hit(key, config.topbar_down)) {
if (p.header_column == null and p.columnBarHeight() > 0) {
- p.header_column = if (layout.findPane(p, p.active)) |f| f.col else 0;
- p.header_scroll = 0;
- p.topbar_col = 0;
+ p.enterHeader(if (layout.findPane(p, p.active)) |f| f.col else 0);
return;
}
var dest: ?usize = null;
@@ -7507,17 +7532,14 @@ pub const Pardes = struct {
}
const d = dest orelse return;
const pane = p.panes[d].?;
- p.topbar_col = null;
- p.header_column = null;
+ p.exitHeader();
p.active = d;
p.enterTagEdit(pane, -1);
if (pane.tag_edit) pane.mode = .normal;
return;
}
- if (key.cp == 'k' or key.cp == Key.up) {
- p.header_column = null;
- p.header_anchor = null;
- p.topbar_col = 0;
+ if (hit(key, config.topbar_up)) {
+ p.enterHeader(null);
return;
}
const col: ?usize = if (hit(key, config.topbar_left))
@@ -7905,7 +7927,7 @@ pub const Pardes = struct {
const pane = p.panes[id] orelse return;
if (!pane.isTerminal()) return;
p.active = id;
- p.topbar_col = null;
+ p.exitHeader();
if (pane.tag_edit) exitTagEdit(pane);
if (pane.mode == .tty) {
pane.mode = .normal;
@@ -10948,6 +10970,9 @@ pub const Pardes = struct {
if (mrow < TOPBAR_H + p.columnBarHeight() and m.kind == .press and
(m.button == config.select_button or m.button == config.exec_button or m.button == config.look_button))
{
+ if (m.button == config.select_button) if (p.panes[p.active]) |pane| {
+ if (pane.tag_edit) exitTagEdit(pane);
+ };
var column: ?usize = null;
var x: u16 = 0;
if (mrow >= TOPBAR_H) {
@@ -10962,6 +10987,7 @@ pub const Pardes = struct {
if (column == null) return;
}
if (p.header_column != column) {
+ p.rememberHeader();
p.topbar_col = null;
p.header_anchor = null;
p.header_scroll = 0;
@@ -10970,7 +10996,6 @@ pub const Pardes = struct {
const bar = p.headerText();
const at = @min(bar.len, panes.File.rawAtDisplay(bar, (mcol -| x) + p.header_scroll));
if (m.button == config.select_button) {
- if (p.panes[p.active]) |pane| exitTagEdit(pane);
p.topbar_col = @intCast(at);
p.header_anchor = @intCast(at);
p.header_insert = true;
@@ -10981,7 +11006,7 @@ pub const Pardes = struct {
break :blk if (at >= @min(a, cur) and at < @max(a, cur)) bar[@min(a, cur)..@max(a, cur)] else wordAtCol(bar, at);
} else wordAtCol(bar, at);
const word = p.scratch.allocator().dupe(u8, selected) catch return;
- p.topbar_col = null;
+ p.exitHeader();
if (word.len > 0) {
if (m.button == config.exec_button) {
const named = std.meta.stringToEnum(Builtin, word);
@@ -10993,10 +11018,7 @@ pub const Pardes = struct {
return;
}
if (m.kind == .press) {
- p.topbar_col = null;
- p.header_column = null;
- p.header_anchor = null;
- p.header_drag = false;
+ p.exitHeader();
}
switch (m.button) {
@@ -11083,6 +11105,9 @@ pub const Pardes = struct {
const id = hovered orelse return;
const r = p.rects[id];
const pane = p.panes[id] orelse return;
+ if (id != p.active) if (p.panes[p.active]) |previous| {
+ if (previous.tag_edit) exitTagEdit(previous);
+ };
const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y;
const body_y = if (p.settings.tag_bottom) r.y else r.y + BOX_H;
if (mcol < r.x + config.GUTTER) {
@@ -11992,13 +12017,7 @@ pub const Pardes = struct {
const tagline_metrics_changed = setting.action == .tagline_size and
p.settings.font.tagline_percent != previous_tagline_percent;
if (p.settings.column_tags != previous_column_tags) {
- if (p.header_column != null) {
- p.topbar_col = null;
- p.header_column = null;
- p.header_anchor = null;
- p.header_drag = false;
- p.header_scroll = 0;
- }
+ if (p.header_column != null) p.exitHeader();
}
if (transition_changed or tagline_metrics_changed or p.settings.column_tags != previous_column_tags) {
p.abandonPanelAnimations();
@@ -12474,11 +12493,7 @@ pub const Pardes = struct {
/// deferred greetings. The mirror of the prototype's loop epilogue.
fn sync(p: *Pardes) void {
if (p.header_column != null and p.columnBarHeight() == 0) {
- p.topbar_col = null;
- p.header_column = null;
- p.header_anchor = null;
- p.header_drag = false;
- p.header_scroll = 0;
+ p.exitHeader();
}
p.reapPanes();
layout.compute(p);
@@ -12486,7 +12501,7 @@ pub const Pardes = struct {
p.trackJump();
for (&p.panes, 0..) |*slot, id| {
const pane = slot.* orelse continue;
- if (pane.tag_name_len != null and (id != p.active or p.topbar_col != null))
+ if (pane.tag_edit and pane.prompt == .none and (id != p.active or p.topbar_col != null))
exitTagEdit(pane);
if (comptime terminal_panes) if (pane.terminal) |state| {
var off: u16 = 0;