summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Dir.zig4
-rw-r--r--src/builtins.zig28
-rw-r--r--src/config.zig1
-rw-r--r--src/exec.zig11
-rw-r--r--src/ninep/ctl.zig48
5 files changed, 72 insertions, 20 deletions
diff --git a/src/Dir.zig b/src/Dir.zig
index 979cd51a..36895bda 100644
--- a/src/Dir.zig
+++ b/src/Dir.zig
@@ -408,10 +408,6 @@ test "a look at an entry is a look from the directory, .. goes up to the open pa
try tmp.dir.writeFile(testing.io, .{ .sub_path = "sub/later.txt", .data = "" });
try testing.expect(p.executeBuiltinLine(sub, "Get"));
try testing.expectEqualStrings("inner.txt later.txt\n", p.panes[sub].?.file.?.content);
- // Get anywhere else is still the acme word pardes has not.
- const failures = p.fs.failures;
- try testing.expect(p.executeBuiltinLine(0, "Get"));
- try testing.expect(p.fs.failures != failures);
}
test "an exec in a directory pane runs in its directory, and its +Errors goes there" {
diff --git a/src/builtins.zig b/src/builtins.zig
index 1172fa78..1d524514 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -1116,6 +1116,34 @@ pub const ClipReplace = struct {
// ---- panes and columns ----
+/// acme's Get: the pane's file read again, its unsaved edits refused once
+/// as Del refuses them. `Get file` loads that file into the pane instead,
+/// the pane taking its name.
+pub const Get = struct {
+ pub const takes_arg = true;
+ pub fn run(c: Ctx) void {
+ const ctl = @import("ninep/ctl.zig");
+ const f = if (c.pane.file) |*file| file else return c.p.reportFailure(c.id, "Get: only a file pane takes it");
+ // A directory pane: the directory read again (Dir.zig), as acme's
+ // Get is in a directory window.
+ if (f.listing != null) return panes.Dir.show(c.p, c.id, c.id);
+ if (!panes.Output.fileTraits(f.output).saves) return c.p.reportFailure(c.id, "Get: only a file pane takes it");
+ if (ctl.getRefused(c.p, c.pane, "Get")) |said| return c.p.reportFailure(c.id, said);
+ const typed = std.mem.trim(u8, c.arg orelse "", " \t\r\n");
+ if (typed.len > 0) {
+ var home_buf: [4096]u8 = undefined;
+ const full = std.fs.path.resolvePosix(c.p.scratch.allocator(), &.{ Pardes.paneDir(c.pane), @import("fs.zig").expandHome(typed, &home_buf) }) catch return;
+ @import("ninep/pane.zig").nameBuffer(c.p, c.id, full, false) catch return c.p.reportFailure(c.id, "Get: invalid file name");
+ }
+ var failed: anyerror = error.Unexpected;
+ if (ctl.get(c.p, c.pane, &failed) != 0) {
+ var said: [320]u8 = undefined;
+ const path = c.pane.file.?.path;
+ c.p.reportFailure(c.id, std.fmt.bufPrint(&said, "Get: {s}: {s}", .{ path[0..@min(path.len, 256)], if (failed == error.FileNotFound) "no such file" else "cannot be read" }) catch "Get: cannot be read");
+ }
+ }
+};
+
/// Write the pane's text to its file, or to the file its argument names.
pub const Save = struct {
pub const takes_arg = true;
diff --git a/src/config.zig b/src/config.zig
index d80e2a02..673e91ed 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -58,6 +58,7 @@ pub const leader_path = paths: {
.Repl = null,
.Kill = null,
.Exit = "q",
+ .Get = null,
.ClipYank = "y",
.ClipYankMain = "Y",
.ClipPaste = "p",
diff --git a/src/exec.zig b/src/exec.zig
index 92fb6244..30a1a894 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -943,21 +943,20 @@ pub fn namesBuiltin(txt: []const u8) bool {
pub const acme_words = [_]struct { acme: []const u8, pardes: ?[]const u8, hint: []const u8 = "" }{
.{ .acme = "Put", .pardes = "Save" },
.{ .acme = "Delete", .pardes = "Del" },
- .{ .acme = "Get", .pardes = null, .hint = ": write get to the pane's ctl" },
.{ .acme = "Putall", .pardes = null, .hint = ": Save each pane" },
.{ .acme = "Snarf", .pardes = null, .hint = ": y yanks the selection" },
.{ .acme = "Cut", .pardes = null, .hint = ": d deletes the selection into a register" },
.{ .acme = "Paste", .pardes = null, .hint = ": p puts a register" },
.{ .acme = "Zerox", .pardes = null },
.{ .acme = "Sort", .pardes = null },
- .{ .acme = "Load", .pardes = null, .hint = ": Restore loads a dump" },
+ .{ .acme = "Load", .pardes = "Restore" },
.{ .acme = "ID", .pardes = null },
.{ .acme = "Send", .pardes = null },
.{ .acme = "Tab", .pardes = null },
.{ .acme = "Indent", .pardes = null },
.{ .acme = "Local", .pardes = null },
.{ .acme = "Incl", .pardes = null },
- .{ .acme = "Abort", .pardes = null },
+ .{ .acme = "Abort", .pardes = null, .hint = ": a debugging word that crashes acme" },
};
pub fn executeBuiltinLine(p: *Pardes, id: usize, txt: []const u8) bool {
@@ -968,12 +967,6 @@ pub fn executeBuiltinLine(p: *Pardes, id: usize, txt: []const u8) bool {
var renamed: [1024 + 16]u8 = undefined;
const cmd = for (acme_words) |w| {
if (!std.mem.eql(u8, raw[0..word_end], w.acme)) continue;
- // Get in a directory pane, its tag or its text: the directory read
- // again (Dir.zig), as acme's Get is in a directory window.
- if (std.mem.eql(u8, w.acme, "Get")) if (p.panes[id]) |pane| if (pane.file) |f| if (f.listing != null) {
- panes.Dir.show(p, id, id);
- return true;
- };
const now = w.pardes orelse {
var said: [128]u8 = undefined;
p.reportFailure(id, std.fmt.bufPrint(&said, "invalid: acme's {s} is not a pardes builtin{s}", .{ w.acme, w.hint }) catch "invalid: an acme word pardes has not");
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 0f4e181c..269e9346 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -972,7 +972,7 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
if (std.mem.eql(u8, line, "get")) {
if (!apply) continue;
// Quoting the line, as a builtin's refusal (Save's) does.
- if (getRefused(p, pane)) |said_in_ename| {
+ if (getRefused(p, pane, "get")) |said_in_ename| {
// refuse writes ename, where the words are: copy them out.
var said_buf: [320]u8 = undefined;
const said = said_buf[0..said_in_ename.len];
@@ -1127,7 +1127,9 @@ const e_locked = tree.e_in_use ++ ": another open of this ctl holds the lock";
/// acme's get asks winclean first (exec.c:513): unsaved text is warned
/// about once, and the same get again, nothing edited since, discards it.
-fn getRefused(p: *Pardes, pane: *Pane) ?[]const u8 {
+/// Asked once over unsaved edits, by `word` (ctl's `get`, the Get builtin):
+/// the words of the refusal, in ename; null to go ahead.
+pub fn getRefused(p: *Pardes, pane: *Pane, word: []const u8) ?[]const u8 {
const f = pane_files.fileOf(pane) orelse return null;
if (!panes.Output.fileTraits(f.output).saves or f.revision == f.saved_revision) return null;
// Dirty by a rename alone is no text to lose: only edits are asked about.
@@ -1140,15 +1142,16 @@ fn getRefused(p: *Pardes, pane: *Pane) ?[]const u8 {
// write's err record logs it, not a msg too.
// And logged `unsaved <serial> <name>`, as the other refusals are.
tree.events.noteLog(p, .unsaved, pane);
- const listed = std.fmt.allocPrint(p.gpa, "{s}: Modified\n\nget again to discard\n", .{f.path}) catch null;
+ const listed = std.fmt.allocPrint(p.gpa, "{s}: Modified\n\n{s} again to discard\n", .{ f.path, word }) catch null;
if (listed) |content| panes.Output.openUnsaved(p, id, content);
+ var notice: [64]u8 = undefined;
p.fs.unlogged = true;
- p.setMessage(id, "1 unsaved pane \u{2014} get again to discard");
+ p.setMessage(id, std.fmt.bufPrint(&notice, "1 unsaved pane \u{2014} {s} again to discard", .{word}) catch "1 unsaved pane");
p.fs.unlogged = false;
- return std.fmt.bufPrint(&p.fs.ename, "{s}: Modified (get again to discard)", .{f.path}) catch "Modified (get again to discard)";
+ return std.fmt.bufPrint(&p.fs.ename, "{s}: Modified ({s} again to discard)", .{ f.path, word }) catch "Modified (get again to discard)";
}
-fn get(p: *Pardes, pane: *Pane, failed: *anyerror) u16 {
+pub fn get(p: *Pardes, pane: *Pane, failed: *anyerror) u16 {
const f = pane_files.fileOf(pane) orelse return 0;
// A directory pane: the directory read again (Dir.zig).
if (f.listing != null) {
@@ -1775,6 +1778,37 @@ test "get reloads the pane from disk and reports a missing file without losing d
try testing.expect(pane_files.dirtyOf(pane));
}
+test "Get reads the pane's file again, refusing unsaved edits once, and Get file loads that file into the pane" {
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "note.txt", .data = "from disk\n" });
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "other.txt", .data = "the other\n" });
+ var directory_buf: [4096]u8 = undefined;
+ const directory = directory_buf[0..try tmp.dir.realPath(testing.io, &directory_buf)];
+ const p = try withFile(testing.allocator, "in memory\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const pane = p.panes[0].?;
+ var name: [std.fs.max_path_bytes + 8]u8 = undefined;
+ _ = wr(p, Node.of(serial, .name), try std.fmt.bufPrint(&name, "{s}/note.txt\n", .{directory}));
+ try testing.expect(p.executeBuiltinLine(0, "Get"));
+ try testing.expectEqualStrings("from disk\n", pane.file.?.content);
+ try testing.expect(!pane_files.dirtyOf(pane));
+ // Edited: asked once, then read again.
+ _ = wr(p, Node.of(serial, .body), "unsaved\n");
+ const failures = p.fs.failures;
+ try testing.expect(p.executeBuiltinLine(0, "Get"));
+ try testing.expect(p.fs.failures != failures);
+ try testing.expectEqualStrings("from disk\nunsaved\n", pane.file.?.content);
+ try testing.expect(p.executeBuiltinLine(0, "Get"));
+ try testing.expectEqualStrings("from disk\n", pane.file.?.content);
+ // Get file: that file, by a name from the pane's directory.
+ try testing.expect(p.executeBuiltinLine(0, "Get other.txt"));
+ try testing.expectEqualStrings("the other\n", pane.file.?.content);
+ try testing.expect(std.mem.endsWith(u8, pane.file.?.path, "/other.txt"));
+ try testing.expect(!pane_files.dirtyOf(pane));
+}
+
test "a body write to a terminal is typed input, not a paste, even to a program that asked for bracketed paste" {
const p = try Pardes.init(testing.allocator, .{ .cols = 90, .rows = 30 });
defer p.deinit();
@@ -3157,7 +3191,7 @@ test "acme's words run as pardes's (Put is Save, Delete a Del that does not ask)
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
const serial = serialOf(p);
- for ([_][]const u8{ "Snarf\n", "Putall\n", "Zerox\n", "Get\n", "Tab\n", "Indent\n", "Local\n", "Incl\n", "Abort\n" }) |line| {
+ for ([_][]const u8{ "Snarf\n", "Putall\n", "Zerox\n", "Tab\n", "Indent\n", "Local\n", "Incl\n", "Abort\n" }) |line| {
for ([_]u64{ Node.of(serial, .ctl), Node.of(serial, .exec) }) |node| {
const r = wr(p, node, line);
try testing.expectEqual(E.INVAL, r.errno());