summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig99
1 files changed, 75 insertions, 24 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 0950d13a..f6032ac5 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4700,10 +4700,33 @@ pub const Pardes = struct {
}
/// A file pane's path and dot, for the recent list (0 when none).
- fn recentDot(pane: *const Pane) ?struct { path: []const u8, line: u32, col: u32 } {
+ /// Whether `path` is one Recent keeps: a file on this host's disk, not
+ /// the served tree's (`/virtual/`) nor a name never saved.
+ pub fn recentKeeps(path: []const u8) bool {
+ const local = filesystem.localPath(path) orelse return false;
+ if (comptime !hosted) return true;
+ return exec.isFile(local);
+ }
+
+ /// A file pane closing: its place noted in Recent, when Recent keeps it.
+ fn noteClosed(p: *Pardes, pane: *const Pane) void {
+ const d = recentDot(pane) orelse return;
+ if (p.recent.get(d.path) == null and !recentKeeps(d.path)) return;
+ p.recent.closedSel(p.recentGpa(), d.path, d.line, d.col, d.aline, d.acol);
+ }
+
+ fn recentDot(pane: *const Pane) ?struct { path: []const u8, line: u32, col: u32, aline: u32 = 0, acol: u32 = 0 } {
const f = pane.file orelse return null;
if (f.output != null) return null;
- return .{ .path = f.path, .line = @intCast(@max(0, pane.body.cur_row) + 1), .col = @intCast(@max(0, pane.body.cur_col) + 1) };
+ // The selection's anchor too, so a reopen restores both its ends.
+ const sel = pane.body.vsel.active;
+ return .{
+ .path = f.path,
+ .line = @intCast(@max(0, pane.body.cur_row) + 1),
+ .col = @intCast(@max(0, pane.body.cur_col) + 1),
+ .aline = if (sel) @intCast(@max(0, pane.body.vsel.row) + 1) else 0,
+ .acol = if (sel) @intCast(@max(0, pane.body.vsel.col) + 1) else 0,
+ };
}
/// The recent entry a closed jump names, by its path's hash.
@@ -4714,7 +4737,7 @@ pub const Pardes = struct {
pub fn deinit(p: *Pardes) void {
// Every file open now is closed with its dot, and the list kept.
- for (p.panes) |slot| if (slot) |pane| if (recentDot(pane)) |d| p.recent.closed(p.recentGpa(), d.path, d.line, d.col);
+ for (p.panes) |slot| if (slot) |pane| p.noteClosed(pane);
p.saveRecent();
p.recent.deinit(p.recentGpa());
look.cancelLookHover(p);
@@ -4827,7 +4850,7 @@ pub const Pardes = struct {
fn retirePane(p: *Pardes, pane: *Pane) void {
// Closed, a file is remembered with its dot: Recent and a jump back
// to it reopen it there.
- if (recentDot(pane)) |d| p.recent.closed(p.recentGpa(), d.path, d.line, d.col);
+ p.noteClosed(pane);
const watched = (if (pane.file) |f| f.output == null else false) or pane.hasPdf();
for (p.panes, 0..) |slot, id| if (slot == pane) {
if (watched) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = false } });
@@ -5330,16 +5353,7 @@ pub const Pardes = struct {
const pane = p.panes[id] orelse return;
pane.shell_failed = true;
ctlfs.pty.shellGone(p, pane, false);
- if (err == error.FileNotFound or err == error.NotDir)
- p.reportFailure(id, "shell: directory missing (no such directory)")
- else
- p.reportError(id, "shell", err);
- // A 9P write that asked for the shell (pty/ctl's exec), waiting on
- // it, fails with what was said, as a failed Save's does.
- const said = pane.msg[0..pane.msg_len];
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.sayShellFailure(id, err);
// A command that never started ended: `exit 127`, as a shell says of
// a command it could not run, so a follower waiting on its exit
// hears one, its tag stops saying it runs, and Kill finds nothing.
@@ -5354,6 +5368,36 @@ pub const Pardes = struct {
}
}
+ /// A shell started again in a terminal whose shell runs (pty/ctl's
+ /// exec) did not start: the host kept the running one, so only said.
+ pub fn restartFailed(p: *Pardes, id: u8, err: anyerror) void {
+ if (p.panes[id] == null) return;
+ p.sayShellFailure(id, err);
+ }
+
+ fn sayShellFailure(p: *Pardes, id: u8, err: anyerror) void {
+ const pane = p.panes[id] orelse return;
+ if (err == error.FileNotFound or err == error.NotDir) {
+ p.reportFailure(id, "shell: directory missing (no such directory)");
+ } else if (err == error.InterpreterNotFound) {
+ // The script is there; the program its `#!` names is not.
+ var said: [320]u8 = undefined;
+ var interp: [256]u8 = undefined;
+ var buf: [std.fs.max_path_bytes]u8 = undefined;
+ const Shell = if (comptime hosted) @import("host_io.zig").Shell else void;
+ const name: ?[]const u8 = if (comptime hosted) (if (Shell.find(pane.shell orelse p.shellBin(), &buf)) |f| Shell.missingInterpreter(f, &interp) else null) else null;
+ p.reportFailure(id, if (name) |n| std.fmt.bufPrint(&said, "shell: interpreter {s} not found", .{n}) catch "shell: interpreter not found" else "shell: interpreter not found");
+ } else {
+ p.reportError(id, "shell", err);
+ }
+ // A 9P write that asked for the shell (pty/ctl's exec), waiting on
+ // it, fails with what was said, as a failed Save's does.
+ const said = pane.msg[0..pane.msg_len];
+ const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
+ @memcpy(p.fs.late_failure[0..kept.len], kept);
+ p.fs.late_failure_len = @intCast(kept.len);
+ }
+
pub fn saveFailed(p: *Pardes, id: u8, path: []const u8, err: anyerror) void {
if (p.panes[id]) |pane| if (pane.file) |*f| {
// The `-%` spelling fs.zig already uses for "make this dirty".
@@ -5381,11 +5425,15 @@ pub const Pardes = struct {
pub fn dumpFailed(p: *Pardes, path: []const u8, err: anyerror) void {
var what: [limits.host_path_cap + 8]u8 = undefined;
const operation = std.fmt.bufPrint(&what, "Dump {s}", .{path}) catch "Dump";
- // Into a DumpDir that is not there: said so, ENOENT, as a Save is.
+ // Into a DumpDir that is not there: said so, ENOENT, as a Save is;
+ // one that is there but no directory (`/dev/null`) says that.
const parent = std.fs.path.dirname(path) orelse "/";
- const no_dir = err == error.FileNotFound or err == error.NotDir or
- (if (comptime hosted) (if (filesystem.localPath(parent)) |local| !exec.isDirectory(local) else false) else false);
- if (no_dir) {
+ const kind: ?std.Io.File.Kind = if (comptime hosted) (if (filesystem.localPath(parent)) |local| exec.kindOf(local) else .directory) else .directory;
+ const no_dir = err == error.FileNotFound or err == error.NotDir or kind != .directory;
+ if (kind != null and kind != .directory) {
+ var said: [limits.host_path_cap + 48]u8 = undefined;
+ p.reportFailure(p.active, std.fmt.bufPrint(&said, "{s}: {s} is not a directory", .{ operation, parent }) catch "Dump: not a directory");
+ } else if (no_dir) {
var said: [limits.host_path_cap + 48]u8 = undefined;
p.reportFailure(p.active, std.fmt.bufPrint(&said, "{s}: no such directory", .{operation}) catch "Dump: no such directory");
} else p.reportError(p.active, operation, err);
@@ -6694,14 +6742,17 @@ pub const Pardes = struct {
// places it, and the entry is that pane from here on.
if (j.serial == 0) {
const e = p.recentByHash(j.path_hash) orelse return;
- var where: [recent_path_cap + 32]u8 = undefined;
- const loc = std.fmt.bufPrint(&where, "{s}:{d}:{d}", .{ e.path, @max(1, j.line), @max(1, j.col) }) catch return;
const from = p.active;
- look.lookAt(p, from, loc);
+ look.reopenAt(p, from, e.path, .{ .line = @max(1, j.line), .col = @max(1, j.col) });
const opened = p.panes[p.active] orelse return;
- if (p.active == from) return;
- p.jumps[@intCast(next)].pane = @intCast(p.active);
- p.jumps[@intCast(next)].serial = opened.serial;
+ const f = opened.file orelse return;
+ if (!std.mem.eql(u8, f.path, e.path)) return; // it did not open
+ // Every entry of that file is the reopened pane from here on, so
+ // a step onto another of them moves in it rather than reopening.
+ for (p.jumps[0..p.njumps]) |*k| if (k.serial == 0 and k.path_hash == j.path_hash) {
+ k.pane = @intCast(p.active);
+ k.serial = opened.serial;
+ };
p.jcur = @intCast(next);
return;
}