diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 99 |
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; } |
