summaryrefslogtreecommitdiff
path: root/src/edit.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/edit.zig')
-rw-r--r--src/edit.zig116
1 files changed, 75 insertions, 41 deletions
diff --git a/src/edit.zig b/src/edit.zig
index f1b381d6..5249a64c 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -84,12 +84,23 @@ pub fn applyPaste(p: *Pardes, bytes: []const u8) void {
const pane = p.panes[id] orelse return;
if (req) |r| if (pane.serial != r.serial) return;
p.active = id;
+ if (pane.prompt != .none and pane.prompt != .del_side) {
+ // an answer is one line: a newline in it would only be submitting it
+ const line = p.scratch.allocator().alloc(u8, bytes.len) catch return;
+ var n: usize = 0;
+ for (bytes) |byte| if (byte != '\n' and byte != '\r') {
+ line[n] = byte;
+ n += 1;
+ };
+ if (n > 0) insertKey(p, &pane.input, .{ .cp = line[0], .text = line[0..n] });
+ if (normal.selRegexArmed(pane)) |a| normal.applySelRegex(p, pane, a.pat, a.split);
+ return;
+ }
if (pane.tag_edit) {
const text = p.tagText(p.scratch.allocator(), pane) catch return;
const range: @TypeOf(Pardes.tagSelBounds(pane)) = if (pane.tag_sel) Pardes.tagSelBounds(pane) else .{ .lo = pane.tag_col, .hi = pane.tag_col };
const hi = if (pane.tag_sel) modal.nextGrapheme(text, range.hi) else range.hi;
_ = p.replaceTagText(pane, range.lo, hi, bytes);
- if (normal.selRegexArmed(pane)) |a| normal.applySelRegex(p, pane, a.pat, a.split);
return;
}
if (pane.isTerminal() and pane.body.mode == .tty) return typeToTty(p, id, pane, bytes);
@@ -274,7 +285,7 @@ pub fn exitInsert(p: *Pardes, t: *Text) void {
t.cur_col = @intCast(bc.col);
t.vsel = .{ .active = a_off != back, .row = aa.row, .col = aa.col, .explicit = false };
t.cur_pinned = true;
- t.pane().ensureCursorVisible();
+ t.show();
for (t.sels[0..t.nsel]) |*s| {
const sgap = modal.offsetAt(text, .{ .row = @intCast(@max(0, s.row)), .col = @intCast(@max(0, s.col)) });
const b2 = if (back == gap) sgap else modal.prevGrapheme(text, sgap);
@@ -288,8 +299,15 @@ pub fn exitInsert(p: *Pardes, t: *Text) void {
}
pub fn paneCursorLines(p: *Pardes, t: *Text) ![]const []const u8 {
- const pane = t.pane();
const arena = p.scratch.allocator();
+ if (t.what != .body) {
+ const text = t.own orelse "";
+ const lines = try arena.alloc([]const u8, std.mem.count(u8, text, "\n") + 1);
+ var it = std.mem.splitScalar(u8, text, '\n');
+ for (lines) |*line| line.* = it.next().?;
+ return lines;
+ }
+ const pane = t.pane();
if (pane.file) |*f| return panes.File.cursorLines(arena, pane, f);
if (pane.hasPdf()) {
if (comptime pdf_enabled) return panes.Pdf.textLines(&pane.pdf.?, p.pdf_gpa, arena);
@@ -299,9 +317,8 @@ pub fn paneCursorLines(p: *Pardes, t: *Text) ![]const []const u8 {
}
pub fn paneByteAtDisplay(p: *Pardes, t: *Text, row: i32, from_raw: i32, display_col: i32) i32 {
- const pane = t.pane();
- if (pane.file != null)
- return panes.File.byteAtRowDisplay(pane, row, from_raw, display_col);
+ if (t.what == .body and t.pane().file != null)
+ return panes.File.byteAtRowDisplay(t.pane(), row, from_raw, display_col);
const lines = paneCursorLines(p, t) catch return @max(0, from_raw + display_col);
const local = row;
if (local < 0 or @as(usize, @intCast(local)) >= lines.len)
@@ -316,14 +333,20 @@ pub fn paneByteAtDisplay(p: *Pardes, t: *Text, row: i32, from_raw: i32, display_
/// Freeze the live terminal cursor into the modal coordinate space. The
/// emulator reports screen cells; editing state stores UTF-8 byte offsets.
pub fn pinPaneCursor(p: *Pardes, t: *Text) void {
- const pane = t.pane();
if (t.cur_pinned) return;
+ // Only a body has a live cursor of someone else's to freeze.
+ if (t.what != .body) {
+ t.cur_pinned = true;
+ return;
+ }
+ const pane = t.pane();
pane.pinCursor();
if (pane.file == null and !pane.hasPdf())
t.cur_col = paneByteAtDisplay(p, t, t.cur_row, 0, t.cur_col);
}
pub fn flatSurface(p: *Pardes, t: *Text) ![]const u8 {
+ if (t.what != .body) return t.own orelse "";
const pane = t.pane();
if (pane.file) |f| return f.content;
if (pane.hasPdf()) {
@@ -335,13 +358,16 @@ pub fn flatSurface(p: *Pardes, t: *Text) ![]const u8 {
}
pub fn paneWrapWidth(p: *const Pardes, t: *Text) usize {
+ if (t.what != .body) return 0;
const pane = t.pane();
if (pane.file == null) return 0;
return panes.File.wrapWidth(pane, p.settings.wrap);
}
-/// Route shared edit operations to a file's content or a terminal overlay.
+/// Route shared edit operations to the characters `t` edits: its own, or
+/// for a body its file's content or its terminal's overlay.
fn editText(p: *Pardes, t: *Text, lo: i32, hi: i32, col: i32) ?panes.EditText {
+ if (t.what != .body) return .{ .text = t.own orelse &.{}, .row0 = 0 };
const pane = t.pane();
if (pane.file) |f| return .{ .text = f.content, .row0 = 0 };
if (pane.image != null or pane.hasPdf()) return null;
@@ -358,6 +384,11 @@ fn editTextEol(p: *Pardes, t: *Text, b: Bounds) ?panes.EditText {
/// install a rewritten editable text (frees the old one)
fn setEditText(p: *Pardes, t: *Text, new: []u8) void {
+ if (t.what != .body) {
+ if (t.own) |old| p.gpa.free(old);
+ t.own = new;
+ return;
+ }
const pane = t.pane();
if (pane.file) |*f| return panes.File.setContent(p, f, new);
panes.Terminal.setEditText(p, pane, new);
@@ -368,6 +399,7 @@ pub const InsertAt = enum { at, append, line_start, line_end, open_below, open_a
/// Files insert only inside existing rows and at grapheme boundaries.
/// Terminal overlays can materialize virtual rows/columns, so keep those.
pub fn clampFileCursor(t: *Text) bool {
+ if (t.what != .body) return false;
const pane = t.pane();
const file = if (pane.file) |*file| file else return false;
const old_row = t.cur_row;
@@ -380,8 +412,7 @@ pub fn clampFileCursor(t: *Text) bool {
}
pub fn enterInsert(p: *Pardes, t: *Text, where: InsertAt, cnt: usize) void {
- const pane = t.pane();
- if (pane.hasPdf()) return;
+ if (t.what == .body and t.pane().hasPdf()) return;
pinPaneCursor(p, t);
_ = clampFileCursor(t);
// snapshot once per insert session (WITH the pre-insert selection) so
@@ -478,7 +509,7 @@ pub fn enterInsert(p: *Pardes, t: *Text, where: InsertAt, cnt: usize) void {
t.mode = .insert;
t.msel.active = false;
t.normal.clear();
- pane.ensureCursorVisible();
+ t.show();
}
pub fn handleInsert(p: *Pardes, t: *Text, key: Key) void {
@@ -487,7 +518,6 @@ pub fn handleInsert(p: *Pardes, t: *Text, key: Key) void {
}
pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
- const pane = t.pane();
// helix aliases: normalize to the base key and re-dispatch
if (hit(key, config.insert_backspace_alias)) return insertKey(p, t, .{ .cp = Key.backspace });
if (hit(key, config.insert_enter_alias)) return insertKey(p, t, .{ .cp = Key.enter });
@@ -507,7 +537,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
else => cur0,
};
t.fromModalCursor(nc);
- pane.ensureCursorVisible();
+ t.show();
return;
},
Key.page_up, Key.page_down => {
@@ -515,14 +545,15 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
// scroll + cursor snap as normal mode
const flat = flatSurface(p, t) catch return;
const range = t.primaryRange(flat, 0);
- const step: i32 = @intCast(@max(1, pane.rows));
+ if (t.what != .body) return;
+ const step: i32 = @intCast(@max(1, t.pane().rows));
t.scrollViewMove(flat, range, if (key.cp == Key.page_down) step else -step);
return;
},
Key.home => {
t.cur_col = 0;
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
return;
},
Key.end => {
@@ -531,7 +562,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
const cur0 = t.toModalCursor();
t.cur_col = @intCast(panes.File.textLine(t, text, cur0.row).len);
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
return;
},
else => {},
@@ -549,7 +580,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
} else t.cur_col += 1;
}
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
return;
}
// helix insert-mode kills (word/line; deleteSpan is exclusive)
@@ -569,7 +600,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
t.cur_row = @as(i32, @intCast(fc.row)) + e2.row0;
t.cur_col = @intCast(fc.col);
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
return;
}
if (hit(key, config.delete_word_forward)) {
@@ -592,7 +623,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
setEditText(p, t, new);
t.cur_col = 0;
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
return;
}
if (hit(key, config.kill_to_line_end)) {
@@ -615,7 +646,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
t.cur_row += 1;
t.cur_col = @intCast(indent);
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
},
Key.backspace => {
if (t.cur_col > 0) {
@@ -625,7 +656,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
setEditText(p, t, new);
t.cur_col = @intCast(prev);
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
return;
}
// at col 0: eat the newline above (join up) — a terminal grows
@@ -640,7 +671,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
t.cur_row -= 1;
t.cur_col = @intCast(prevlen);
t.cur_pinned = true;
- pane.ensureCursorVisible();
+ t.show();
},
Key.delete => {
// delete forward; at line end eat the newline (join), which on
@@ -661,7 +692,8 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
Key.tab => {
const ln = modal.lineSlice(text, c.row);
if (!p.multi_on and c.col > 0 and c.col <= ln.len and ln[c.col - 1] == '.') dot: {
- const f = pane.file orelse break :dot;
+ if (t.what != .body) break :dot;
+ const f = t.pane().file orelse break :dot;
if (f.output != null or !lsp.speaks(f.path)) break :dot;
const seq = p.lsp_seq;
p.lspRequest(p.active, .completion, "");
@@ -684,7 +716,7 @@ pub fn insertTab(p: *Pardes, t: *Text) void {
setEditText(p, t, new);
t.cur_col += @intCast(pad);
t.cur_pinned = true;
- t.pane().ensureCursorVisible();
+ t.show();
}
const Bounds = struct { lo_row: i32, lo_col: i32, hi_row: i32, hi_col: i32 };
@@ -779,7 +811,7 @@ pub fn normalDelete(p: *Pardes, t: *Text, yank: bool) void {
t.cur_col = 0;
t.msel.active = false;
t.cur_pinned = true;
- t.pane().ensureCursorVisible();
+ t.show();
} else {
pushUndo(p, t);
const eb = editTextEol(p, t, .{ .lo_row = t.cur_row, .lo_col = t.cur_col, .hi_row = t.cur_row, .hi_col = t.cur_col }) orelse return;
@@ -799,12 +831,11 @@ pub fn normalDelete(p: *Pardes, t: *Text, yank: bool) void {
}
pub fn normalYank(p: *Pardes, t: *Text) void {
- const pane = t.pane();
t.select = false;
- if (pane.rawPointerText(pane.sel[sel_slot])) |text| {
+ if (t.what == .body) if (t.pane().rawPointerText(t.pane().sel[sel_slot])) |text| {
setYank(p, text);
return;
- }
+ };
if (t.vsel.active) {
setYank(p, vselText(p, t));
return;
@@ -882,11 +913,10 @@ pub fn pasteText(p: *Pardes, t: *Text, y0: []const u8, before: bool, count: usiz
}
t.msel.active = false;
t.cur_pinned = true;
- t.pane().ensureCursorVisible();
+ t.show();
}
pub fn normalChange(p: *Pardes, t: *Text) void {
- const pane = t.pane();
t.select = false;
t.sticky_col = -1;
if (t.vsel.active) {
@@ -913,7 +943,7 @@ pub fn normalChange(p: *Pardes, t: *Text) void {
t.cur_pinned = true;
t.mode = .insert;
t.normal.clear();
- pane.ensureCursorVisible();
+ t.show();
return;
}
setYank(p, vselText(p, t));
@@ -974,7 +1004,7 @@ pub fn normalChange(p: *Pardes, t: *Text) void {
t.cur_pinned = true;
t.mode = .insert;
t.normal.clear();
- pane.ensureCursorVisible();
+ t.show();
} else {
pushUndo(p, t);
const eb = editTextEol(p, t, .{ .lo_row = t.cur_row, .lo_col = t.cur_col, .hi_row = t.cur_row, .hi_col = t.cur_col }) orelse return;
@@ -1075,7 +1105,7 @@ fn replaceWithText(p: *Pardes, t: *Text, y: []const u8) void {
}
t.cur_pinned = true;
t.sticky_col = -1;
- t.pane().ensureCursorVisible();
+ t.show();
}
/// `~` / `` ` `` / ``Alt-` ``: case-map the selection (or the cursor char),
@@ -1174,7 +1204,7 @@ pub fn normalJoin(p: *Pardes, t: *Text) void {
t.msel.active = false;
t.cur_pinned = true;
t.sticky_col = -1;
- t.pane().ensureCursorVisible();
+ t.show();
}
pub fn normalIndent(p: *Pardes, t: *Text, cnt: usize, add: bool) void {
@@ -1242,7 +1272,7 @@ pub fn normalIndent(p: *Pardes, t: *Text, cnt: usize, add: bool) void {
t.msel.active = false;
t.cur_pinned = true;
t.sticky_col = -1;
- t.pane().ensureCursorVisible();
+ t.show();
}
pub fn normalToggleComment(p: *Pardes, t: *Text) void {
@@ -1277,7 +1307,7 @@ pub fn normalToggleComment(p: *Pardes, t: *Text) void {
}
next = @max(next, end);
}
- const ext = if (t.pane().file) |f| std.fs.path.extension(f.path) else "";
+ const ext = if (t.what != .body) "" else if (t.pane().file) |f| std.fs.path.extension(f.path) else "";
var token: []const u8 = config.comment_token_default;
lang: for (config.comment_tokens) |row| {
for (row.exts) |e| if (std.ascii.eqlIgnoreCase(ext, e)) {
@@ -1359,7 +1389,7 @@ pub fn normalAdjustNumber(p: *Pardes, t: *Text, delta: i64) void {
t.cur_col = @intCast(cc.col);
t.cur_pinned = true;
t.sticky_col = -1;
- t.pane().ensureCursorVisible();
+ t.show();
}
/// `]Space` / `[Space`: add blank lines below/above, the cursor staying
@@ -1381,7 +1411,7 @@ pub fn addNewline(p: *Pardes, t: *Text, below: bool, cnt: usize) void {
t.vsel.row += @intCast(cnt);
}
t.cur_pinned = true;
- t.pane().ensureCursorVisible();
+ t.show();
}
/// pair mapping for m-mode: either bracket names the pair; anything else
@@ -1450,7 +1480,7 @@ pub fn surroundAdd(p: *Pardes, t: *Text, ch: u21) void {
t.cur_col = @intCast(close_col + @as(usize, if (r.a.row == r.b.row) 1 else 0));
t.cur_pinned = true;
t.sticky_col = -1;
- t.pane().ensureCursorVisible();
+ t.show();
}
/// `md<ch>`: delete the enclosing pair's chars; the cursor maps through
@@ -1487,7 +1517,7 @@ pub fn surroundDelete(p: *Pardes, t: *Text, ch: u21) void {
t.vsel.active = false;
t.msel.active = false;
t.cur_pinned = true;
- t.pane().ensureCursorVisible();
+ t.show();
}
/// `mr<from><to>`: swap the enclosing <from> pair's chars for <to>'s
@@ -1512,20 +1542,24 @@ pub fn surroundReplace(p: *Pardes, t: *Text, from: u21, to: u21) void {
}
pub fn pushUndo(p: *Pardes, t: *Text) void {
- const pane = t.pane();
// one keystroke, one undo step — even when it edited at ten cursors.
if (p.multi_on and !p.multi_first) return;
+ // a prompt's answer is typed once and gone; it keeps no history
+ if (t.what != .body) return;
+ const pane = t.pane();
if (pane.file != null) return panes.File.pushUndo(p, pane);
panes.Terminal.pushUndo(p, pane);
}
pub fn doUndo(p: *Pardes, t: *Text) void {
+ if (t.what != .body) return;
const pane = t.pane();
if (pane.file != null) return panes.File.undo(p, pane);
panes.Terminal.undo(p, pane);
}
pub fn doRedo(p: *Pardes, t: *Text) void {
+ if (t.what != .body) return;
const pane = t.pane();
if (pane.file != null) return panes.File.redo(p, pane);
panes.Terminal.redo(p, pane);