summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/File.zig2
-rw-r--r--src/Text.zig30
-rw-r--r--src/edit.zig37
-rw-r--r--src/normal.zig4
4 files changed, 39 insertions, 34 deletions
diff --git a/src/File.zig b/src/File.zig
index 262f3ebb..af538385 100644
--- a/src/File.zig
+++ b/src/File.zig
@@ -655,7 +655,7 @@ pub fn cursorLines(arena: std.mem.Allocator, pane: *Pane, f: *State) ![]const []
/// Edit-buffer fragments and other temporary text retain modal's scan path.
fn contentIndex(t: *Text, text: []const u8) ?[]const usize {
if (t.what != .body) return null;
- const pane = t.pane();
+ const pane = t.pane() orelse return null;
const f = if (pane.file) |*file| file else return null;
if (text.ptr != f.content.ptr or text.len != f.content.len) return null;
return lineIndex(pane.gpa, f) catch null;
diff --git a/src/Text.zig b/src/Text.zig
index d4b6d048..61f238ae 100644
--- a/src/Text.zig
+++ b/src/Text.zig
@@ -76,13 +76,14 @@ ed_redo: [Terminal.history_max]Terminal.Snapshot = undefined,
ed_redo_len: usize = 0,
/// The window this text belongs to (acme's t->w). A column's or the
-/// workspace's tag belongs to no pane; nothing asks it for one.
-pub fn pane(t: *Text) *Pane {
+/// workspace's tag belongs to none, and the optional makes every caller
+/// say what it does then.
+pub fn pane(t: *Text) ?*Pane {
return switch (t.what) {
.body => @alignCast(@fieldParentPtr("body", t)),
.tag => @alignCast(@fieldParentPtr("tag", t)),
.input => @alignCast(@fieldParentPtr("input", t)),
- .column, .workspace => unreachable,
+ .column, .workspace => null,
};
}
@@ -98,7 +99,7 @@ pub fn deinit(t: *Text, gpa: std.mem.Allocator) void {
/// the tagline follows its caret where it is drawn, and a prompt's answer is
/// drawn whole.
pub fn show(t: *Text) void {
- if (t.what == .body) t.pane().ensureCursorVisible();
+ if (t.what == .body) if (t.pane()) |w| w.ensureCursorVisible();
}
pub fn toModalCursor(t: *Text) modal.Cursor {
@@ -526,8 +527,9 @@ pub fn visualMove(
pub fn halfPageMove(t: *Text, text: []const u8, range: modal.Selection, down: bool) void {
// a view to move through is a body's; a tag or an answer is all shown
if (t.what != .body) return;
- const half: i32 = @max(1, @divTrunc(@as(i32, t.pane().rows), 2));
- t.pane().scrollBy(if (down) half else -half);
+ const w = t.pane() orelse return;
+ const half: i32 = @max(1, @divTrunc(@as(i32, w.rows), 2));
+ w.scrollBy(if (down) half else -half);
verticalMove(t, text, range, down, @intCast(half));
}
@@ -536,9 +538,10 @@ pub fn halfPageMove(t: *Text, text: []const u8, range: modal.Selection, down: bo
pub fn scrollViewMove(t: *Text, text: []const u8, range: modal.Selection, delta: i32) void {
// a view to move through is a body's; a tag or an answer is all shown
if (t.what != .body) return;
- const margin: i32 = @min(config.scroll_off, @divTrunc(@as(i32, t.pane().rows) - 1, 2));
- t.pane().scrollBy(delta);
- const top = t.pane().scroll();
+ const w = t.pane() orelse return;
+ const margin: i32 = @min(config.scroll_off, @divTrunc(@as(i32, w.rows) - 1, 2));
+ w.scrollBy(delta);
+ const top = w.scroll();
const last_row: i32 = @intCast(File.textLineCount(t, text) - 1);
const cur = modal.selectionCursor(text, range);
if (delta > 0) {
@@ -548,7 +551,7 @@ pub fn scrollViewMove(t: *Text, text: []const u8, range: modal.Selection, delta:
const anchor = if (t.select) range.anchor else head;
setRange(t, text, 0, .{ .anchor = anchor, .head = head }, false);
} else {
- const snap: i32 = @max(0, @min(top + @as(i32, t.pane().rows) - margin - 1, last_row));
+ const snap: i32 = @max(0, @min(top + @as(i32, w.rows) - margin - 1, last_row));
const head = File.textLineStart(t, text, @intCast(snap));
if (head >= cur) return;
const anchor = if (t.select) range.anchor else head;
@@ -560,10 +563,11 @@ pub fn scrollViewMove(t: *Text, text: []const u8, range: modal.Selection, delta:
pub fn gotoWindow(t: *Text, text: []const u8, range: modal.Selection, which: enum { top, center, bottom }, cnt: usize) void {
// a view to move through is a body's; a tag or an answer is all shown
if (t.what != .body) return;
- const margin: i32 = @min(config.scroll_off, @divTrunc(@as(i32, t.pane().rows) - 1, 2));
- const top = t.pane().scroll();
+ const w = t.pane() orelse return;
+ const margin: i32 = @min(config.scroll_off, @divTrunc(@as(i32, w.rows) - 1, 2));
+ const top = w.scroll();
const last_row: i32 = @intCast(File.textLineCount(t, text) - 1);
- const last_vis: i32 = @min(@as(i32, t.pane().rows) - 1, last_row - top);
+ const last_vis: i32 = @min(@as(i32, w.rows) - 1, last_row - top);
const n: i32 = @intCast(cnt - 1);
var vline: i32 = switch (which) {
.top => top + margin + n,
diff --git a/src/edit.zig b/src/edit.zig
index 42032168..837a22c6 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -315,7 +315,7 @@ pub fn paneCursorLines(p: *Pardes, t: *Text) ![]const []const u8 {
for (lines) |*line| line.* = it.next().?;
return lines;
}
- const pane = t.pane();
+ const pane = t.pane() orelse return &.{};
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);
@@ -325,8 +325,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 {
- if (t.what == .body and t.pane().file != null)
- return panes.File.byteAtRowDisplay(t.pane(), row, from_raw, display_col);
+ if (t.what == .body) if (t.pane()) |pane| if (pane.file != null)
+ return panes.File.byteAtRowDisplay(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)
@@ -347,7 +347,7 @@ pub fn pinPaneCursor(p: *Pardes, t: *Text) void {
t.cur_pinned = true;
return;
}
- const pane = t.pane();
+ const pane = t.pane() orelse return;
pane.pinCursor();
if (pane.file == null and !pane.hasPdf())
t.cur_col = paneByteAtDisplay(p, t, t.cur_row, 0, t.cur_col);
@@ -356,11 +356,11 @@ pub fn pinPaneCursor(p: *Pardes, t: *Text) void {
pub fn flatSurface(p: *Pardes, t: *Text) ![]const u8 {
switch (t.what) {
.body => {},
- .tag => return tagline.curTail(t.pane()),
+ .tag => return if (t.pane()) |pane| tagline.curTail(pane) else "",
.input => return t.own orelse "",
.column, .workspace => return t.own orelse tagline.headerDefault(p, t.what),
}
- const pane = t.pane();
+ const pane = t.pane() orelse return "";
if (pane.file) |f| return f.content;
if (pane.hasPdf()) {
if (comptime pdf_enabled) return pane.pdf.?.ensureText(p.pdf_gpa);
@@ -372,7 +372,7 @@ 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();
+ const pane = t.pane() orelse return 0;
if (pane.file == null) return 0;
return panes.File.wrapWidth(pane, p.settings.wrap);
}
@@ -385,7 +385,7 @@ fn editText(p: *Pardes, t: *Text, lo: i32, hi: i32, col: i32) ?panes.EditText {
if (t.what != .body and t.what != .input and t.own == null)
t.own = p.gpa.dupe(u8, flatSurface(p, t) catch return null) catch return null;
if (t.what != .body) return .{ .text = t.own orelse &.{}, .row0 = 0 };
- const pane = t.pane();
+ const pane = t.pane() orelse return null;
if (pane.file) |f| return .{ .text = f.content, .row0 = 0 };
if (pane.image != null or pane.hasPdf()) return null;
return panes.Terminal.editText(p, pane, lo, hi, col);
@@ -406,7 +406,7 @@ fn setEditText(p: *Pardes, t: *Text, new: []u8) void {
t.own = new;
return;
}
- const pane = t.pane();
+ const pane = t.pane() orelse return p.gpa.free(new);
if (pane.file) |*f| return panes.File.setContent(p, f, new);
panes.Terminal.setEditText(p, pane, new);
}
@@ -417,7 +417,7 @@ pub const InsertAt = enum { at, append, line_start, line_end, open_below, open_a
/// 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 pane = t.pane() orelse return false;
const file = if (pane.file) |*file| file else return false;
const old_row = t.cur_row;
const old_col = t.cur_col;
@@ -429,7 +429,7 @@ pub fn clampFileCursor(t: *Text) bool {
}
pub fn enterInsert(p: *Pardes, t: *Text, where: InsertAt, cnt: usize) void {
- if (t.what == .body and t.pane().hasPdf()) return;
+ if (t.what == .body) if (t.pane()) |pane| if (pane.hasPdf()) return;
pinPaneCursor(p, t);
_ = clampFileCursor(t);
// snapshot once per insert session (WITH the pre-insert selection) so
@@ -563,7 +563,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
const flat = flatSurface(p, t) catch return;
const range = t.primaryRange(flat, 0);
if (t.what != .body) return;
- const step: i32 = @intCast(@max(1, t.pane().rows));
+ const step: i32 = @intCast(@max(1, (t.pane() orelse return).rows));
t.scrollViewMove(flat, range, if (key.cp == Key.page_down) step else -step);
return;
},
@@ -710,7 +710,7 @@ pub fn insertKey(p: *Pardes, t: *Text, key: Key) void {
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: {
if (t.what != .body) break :dot;
- const f = t.pane().file orelse break :dot;
+ const f = (t.pane() orelse break :dot).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, "");
@@ -849,7 +849,7 @@ pub fn normalDelete(p: *Pardes, t: *Text, yank: bool) void {
pub fn normalYank(p: *Pardes, t: *Text) void {
t.select = false;
- if (t.what == .body) if (t.pane().rawPointerText(t.pane().sel[sel_slot])) |text| {
+ if (t.what == .body) if (t.pane()) |pane| if (pane.rawPointerText(pane.sel[sel_slot])) |text| {
setYank(p, text);
return;
};
@@ -1324,7 +1324,8 @@ pub fn normalToggleComment(p: *Pardes, t: *Text) void {
}
next = @max(next, end);
}
- const ext = if (t.what != .body) "" else if (t.pane().file) |f| std.fs.path.extension(f.path) else "";
+ const file = if (t.what != .body) null else if (t.pane()) |pane| pane.file else null;
+ const ext = if (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)) {
@@ -1567,7 +1568,7 @@ pub fn pushUndo(p: *Pardes, t: *Text) void {
// a prompt's answer is typed once and gone; it keeps no history
.input => return,
}
- const pane = t.pane();
+ const pane = t.pane() orelse return;
if (pane.file != null) return panes.File.pushUndo(p, pane);
panes.Terminal.pushUndo(p, pane);
}
@@ -1575,7 +1576,7 @@ pub fn pushUndo(p: *Pardes, t: *Text) void {
pub fn doUndo(p: *Pardes, t: *Text) void {
if (t.what == .tag or t.what == .column or t.what == .workspace) return stepOwn(p, t, true);
if (t.what != .body) return;
- const pane = t.pane();
+ const pane = t.pane() orelse return;
if (pane.file != null) return panes.File.undo(p, pane);
panes.Terminal.undo(p, pane);
}
@@ -1583,7 +1584,7 @@ pub fn doUndo(p: *Pardes, t: *Text) void {
pub fn doRedo(p: *Pardes, t: *Text) void {
if (t.what == .tag or t.what == .column or t.what == .workspace) return stepOwn(p, t, false);
if (t.what != .body) return;
- const pane = t.pane();
+ const pane = t.pane() orelse return;
if (pane.file != null) return panes.File.redo(p, pane);
panes.Terminal.redo(p, pane);
}
diff --git a/src/normal.zig b/src/normal.zig
index 29d6d620..c7e75d70 100644
--- a/src/normal.zig
+++ b/src/normal.zig
@@ -175,7 +175,7 @@ const Replay = union(enum) {
pub fn replaySels(p: *Pardes, t: *Text, what: Replay) void {
const id = p.active;
// a header belongs to no pane and cannot go away under the replay
- const owner: ?*Pane = if (t.what == .column or t.what == .workspace) null else t.pane();
+ const owner = t.pane();
const serial = if (owner) |pane| pane.serial else 0;
// the whole selection in text coordinates, document order. t.sels
// is already ordered, so this only slots the primary into place.
@@ -292,7 +292,7 @@ pub fn handleNormal(p: *Pardes, t: *Text, key: Key) void {
fn executeNormalAction(p: *Pardes, t: *Text, semantic: modal.Normal.Action) void {
// What reaches past the text -- a builtin, a search, a language query --
// acts for the pane the text belongs to; a header's is the active one.
- const pane = if (t.what == .column or t.what == .workspace) p.panes[p.active] orelse return else t.pane();
+ const pane = t.pane() orelse p.panes[p.active] orelse return;
const raw_selection = t.what == .body and pane.rawPointerText(pane.sel[sel_slot]) != null;
const before = .{ t.cur_row, t.cur_col, t.vsel, t.msel, t.select, t.nsel, t.mode };
defer if (raw_selection and !std.meta.eql(before, .{ t.cur_row, t.cur_col, t.vsel, t.msel, t.select, t.nsel, t.mode })) {