summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig131
1 files changed, 17 insertions, 114 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 32a58f89..51965bd4 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -217,7 +217,8 @@ pub const Surface = struct {
cols: u16 = 0,
rows: u16 = 0,
cells: []Cell = &.{},
- cursor: ?struct { x: u16, y: u16 } = null,
+ /// bar: draw an insert-style thin cursor instead of the block
+ cursor: ?struct { x: u16, y: u16, bar: bool = false } = null,
images: [MAX_PANES]?ImagePlace = @splat(null),
nimages: usize = 0,
@@ -958,7 +959,8 @@ pub const Pardes = struct {
}
pane.tag_edit = true;
pane.tag_sel = false;
- pane.mode = .normal;
+ // a one-line tag has no use for normal mode: always insert
+ pane.mode = .insert;
pane.pending = 0;
const cc = std.math.clamp(click_col - @as(i32, @intCast(pfx.len)), 0, @as(i32, @intCast(pane.tag_tail.items.len)));
pane.tag_col = @intCast(cc);
@@ -968,6 +970,7 @@ pub const Pardes = struct {
pane.tag_edit = false;
pane.tag_sel = false;
pane.search_edit = false; // an abandoned `/` input stays as tag text
+ pane.mode = .normal; // tags are always insert; leaving one never is
}
fn tagSelBounds(pane: *Pane) struct { lo: usize, hi: usize } {
@@ -998,66 +1001,17 @@ pub const Pardes = struct {
/// tag-edit key dispatch: a modal one-line editor over the tail, sharing
/// the pane's mode. Newlines are always dropped.
- fn handleTagKey(p: *Pardes, pane: *Pane, key: Key) void {
- if (pane.mode == .insert) return p.tagInsertKey(pane, key);
- const lines = [1][]const u8{pane.tag_tail.items};
- const cur: modal.Cursor = .{ .row = 0, .col = pane.tag_col };
-
- if (pane.pending == 'g') {
- pane.pending = 0;
- const m: modal.Cursor = if (is(key, 'g')) modal.gotoFirst() else if (is(key, 'e')) modal.gotoLast(&lines) else if (is(key, 'h')) modal.lineStart(cur) else if (is(key, 'l')) modal.lineEnd(&lines, cur) else cur;
- pane.tag_col = @intCast(m.col);
- return;
- }
- if (is(key, 'g')) {
- pane.pending = 'g';
- return;
- }
- const moved: ?modal.Cursor =
- if (is(key, 'h') or is(key, Key.left)) modal.charLeft(cur) else if (is(key, 'l') or is(key, Key.right)) modal.charRight(&lines, cur) else if (is(key, 'w')) modal.nextWordStart(&lines, cur, false) else if (is(key, 'W')) modal.nextWordStart(&lines, cur, true) else if (is(key, 'b')) modal.prevWordStart(&lines, cur, false) else if (is(key, 'B')) modal.prevWordStart(&lines, cur, true) else if (is(key, 'e')) modal.nextWordEnd(&lines, cur, false) else if (is(key, 'E')) modal.nextWordEnd(&lines, cur, true) else if (is(key, '0') or is(key, Key.home)) modal.lineStart(cur) else if (is(key, '$') or is(key, Key.end)) modal.lineEnd(&lines, cur) else if (is(key, '^')) modal.firstNonWsOf(&lines, cur) else null;
- if (moved) |m| {
- pane.tag_col = @intCast(m.col);
- return;
- }
- if (is(key, 'i')) return tagEnterInsert(pane, pane.tag_col);
- if (is(key, 'a')) return tagEnterInsert(pane, @intCast(modal.charRight(&lines, cur).col));
- if (is(key, 'I')) return tagEnterInsert(pane, @intCast(modal.firstNonWsOf(&lines, cur).col));
- if (is(key, 'A')) return tagEnterInsert(pane, @intCast(pane.tag_tail.items.len));
- if (is(key, 'v')) {
- pane.tag_sel = !pane.tag_sel;
- if (pane.tag_sel) pane.tag_anchor = pane.tag_col;
- return;
- }
- if (is(key, 'x')) { // select the whole one-line tail
- const len = pane.tag_tail.items.len;
- if (len > 0) {
- pane.tag_sel = true;
- pane.tag_anchor = 0;
- pane.tag_col = @intCast(len - 1);
- }
- return;
- }
- if (is(key, 'd')) return p.tagDelete(pane, false);
- if (is(key, 'c')) return p.tagDelete(pane, true);
- if (is(key, 'y')) return p.tagYank(pane);
- if (is(key, 'p')) return p.tagPaste(pane);
- // Esc escalates: clear the selection, else hand focus back to the body
+ fn tagInsertKey(p: *Pardes, pane: *Pane, key: Key) void {
if (is(key, Key.escape)) {
- if (pane.tag_sel) pane.tag_sel = false else exitTagEdit(pane);
+ exitTagEdit(pane); // the tag is ALWAYS insert; Esc leaves it
return;
}
- }
-
- fn tagEnterInsert(pane: *Pane, col: u16) void {
- pane.tag_col = @min(col, @as(u16, @intCast(pane.tag_tail.items.len)));
- pane.tag_sel = false;
- pane.mode = .insert;
- pane.pending = 0;
- }
-
- fn tagInsertKey(p: *Pardes, pane: *Pane, key: Key) void {
- if (is(key, Key.escape)) {
- pane.mode = .normal;
+ // Enter looks / Tab executes the selection or the word at the cursor
+ if (is(key, Key.enter) or is(key, Key.tab)) {
+ if (p.tagChordText(pane)) |txt| {
+ pane.tag_sel = false;
+ p.actOnSelection(if (is(key, Key.tab)) .middle else .right, p.active, txt, null);
+ }
return;
}
if (key.text.len > 0) {
@@ -1087,50 +1041,6 @@ pub const Pardes = struct {
/// d/c: delete the selection (or the char under the cursor), yanking it;
/// c then enters insert
- fn tagDelete(p: *Pardes, pane: *Pane, change: bool) void {
- const tail = pane.tag_tail.items;
- var lo: usize = undefined;
- var count: usize = undefined;
- if (pane.tag_sel) {
- const b = tagSelBounds(pane);
- lo = b.lo;
- count = @min(b.hi + 1, tail.len) - b.lo;
- } else {
- lo = @min(@as(usize, pane.tag_col), tail.len);
- count = if (lo < tail.len) 1 else 0;
- }
- if (count > 0) {
- p.setYank(tail[lo .. lo + count]);
- pane.tag_tail.replaceRange(p.gpa, lo, count, "") catch return;
- }
- pane.tag_col = @intCast(lo);
- pane.tag_sel = false;
- if (change) pane.mode = .insert;
- }
-
- fn tagYank(p: *Pardes, pane: *Pane) void {
- const tail = pane.tag_tail.items;
- if (pane.tag_sel) {
- const b = tagSelBounds(pane);
- const hi = @min(b.hi + 1, tail.len);
- p.setYank(tail[b.lo..hi]);
- pane.tag_col = @intCast(b.lo);
- pane.tag_sel = false;
- } else p.setYank(tail); // whole tail, like a line yank
- }
-
- fn tagPaste(p: *Pardes, pane: *Pane) void {
- const y = p.yank orelse return;
- const col: usize = @min(@as(usize, pane.tag_col), pane.tag_tail.items.len);
- var at = col;
- for (y) |ch| {
- if (ch == '\n' or ch == '\r') continue;
- pane.tag_tail.insert(p.gpa, at, ch) catch break;
- at += 1;
- }
- pane.tag_col = @intCast(at);
- }
-
/// tag + '\n' + body: the full selectable pane text (row 0 = the tag)
fn paneText(p: *Pardes, pane: *Pane) ![]u8 {
const arena = p.scratch.allocator();
@@ -1272,13 +1182,6 @@ pub const Pardes = struct {
// (on the tag tail when it's focused for editing, else the body).
if (pane.mode == .normal and (is(key, Key.enter) or is(key, Key.tab))) {
const btn: Mouse.Button = if (is(key, Key.tab)) .middle else .right;
- if (pane.tag_edit) {
- if (p.tagChordText(pane)) |txt| {
- pane.tag_sel = false;
- p.actOnSelection(btn, p.active, txt, null);
- }
- return;
- }
pane.pinCursor();
if (p.currentSelText(pane)) |txt| {
pane.vsel.active = false;
@@ -1295,7 +1198,7 @@ pub const Pardes = struct {
// tag editing intercepts every other key: a modal one-line editor
// over the tail, sharing the pane's mode.
if (pane.tag_edit) {
- p.handleTagKey(pane, key);
+ p.tagInsertKey(pane, key);
return;
}
// normal-mode `p` pastes the SYSTEM clipboard: an async OSC 52 round
@@ -3564,7 +3467,7 @@ pub const Pardes = struct {
// cursor while editing the tail: prefix length + column within it
if (active and pane.tag_edit) {
const cx: usize = pfx.len + pane.tag_col;
- if (cx < tw) s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y };
+ if (cx < tw) s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y, .bar = true }; // tags are always insert
}
// image pane: the picture fills the body — petscii glyph art into the
@@ -3760,9 +3663,9 @@ pub const Pardes = struct {
else
ccol;
if (prow >= 0 and cx >= 0 and prow < r.h and cx < tw)
- s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y + @as(u16, @intCast(prow)) };
+ s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y + @as(u16, @intCast(prow)), .bar = pane.mode == .insert };
} else if (cur.y + BOX_H < r.h and cur.x < tw) {
- s.cursor = .{ .x = tx + cur.x, .y = r.y + cur.y + BOX_H };
+ s.cursor = .{ .x = tx + cur.x, .y = r.y + cur.y + BOX_H, .bar = pane.mode == .insert };
}
}