summaryrefslogtreecommitdiff
path: root/src/exec.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/exec.zig')
-rw-r--r--src/exec.zig63
1 files changed, 43 insertions, 20 deletions
diff --git a/src/exec.zig b/src/exec.zig
index 1fc38b9c..cf4e01ab 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -395,6 +395,22 @@ pub fn isDirectory(path: []const u8) bool {
return stat.kind == .directory;
}
+/// Whether `path` is a plain file on this host, the turn out while it asks.
+pub fn isFile(path: []const u8) bool {
+ return kindOf(path) == .file;
+}
+
+/// What `path` is on this host, null when it is not there; the turn out
+/// while it asks.
+pub fn kindOf(path: []const u8) ?std.Io.File.Kind {
+ var buf: [4096]u8 = undefined;
+ const z = std.fmt.bufPrintSentinel(&buf, "{s}", .{path}, 0) catch return null;
+ pardes.turn.yield();
+ defer pardes.turn.back();
+ const stat = std.Io.Dir.cwd().statFile(std.Io.Threaded.global_single_threaded.io(), z, .{}) catch return null;
+ return stat.kind;
+}
+
/// With `BootShell replace`, a doc landing in `col` closes a lone pristine
/// shell there, the boot's placeholder, and absorbVWeight hands its space to
/// the doc. With `keep`, the default, the shell stays like any other pane.
@@ -641,6 +657,14 @@ pub fn executeFrom(p: *Pardes, id: usize, txt: []const u8, from_body: bool) ?usi
.sent => |to| return to,
.asked => return null,
};
+ // A builtin or setting this build lacks (Fonts on a terminal) is said,
+ // never run as a shell command.
+ const word = cmd[0 .. std.mem.indexOfAny(u8, cmd, " \t") orelse cmd.len];
+ if (builtins.absentWhy(word) orelse if (config.Runtime.find(word) != null) @as([]const u8, "GUI-only") else null) |why| {
+ var said: [96]u8 = undefined;
+ p.reportFailure(id, std.fmt.bufPrint(&said, "{s} is {s}, invalid here", .{ word, why }) catch "GUI-only, invalid here");
+ return null;
+ }
if (!takesCommandLine(p, id)) return runCommand(p, id, cmd);
panes.Terminal.padOutputBelowEdits(p, id);
panes.Terminal.noteCommand(pane, cmd);
@@ -824,20 +848,29 @@ pub fn applyStartupConfig(p: *Pardes) void {
}
test "an init file's ThemeSel runs as Themes and says it was renamed" {
- const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 });
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24, .startup_config = "ThemeSel\n" });
defer p.deinit();
- p.opts.startup_config = "ThemeSel\n";
- applyStartupConfig(p);
var listed = false;
for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output) |o| {
listed = listed or std.meta.eql(o.from, panes.Output.Origin{ .cmd = .Themes });
};
try std.testing.expect(listed);
+ // In the session's directory, not `/`.
+ for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output != null)
+ try std.testing.expectEqualStrings(p.sessionDir(), std.fs.path.dirname(f.path).?);
var said = false;
for (p.panes) |slot| if (slot) |pane| {
said = said or std.mem.eql(u8, pane.msg[0..pane.msg_len], "ThemeSel: renamed to Themes");
};
try std.testing.expect(said);
+ // The panes there at boot are the log's first records, before what the
+ // init file said.
+ const th = @import("ninep/testing.zig");
+ const log = @intFromEnum(pardes.ctlfs.TopFile.log);
+ const h = th.call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle;
+ defer _ = th.call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
+ const text = th.call(p, .{ .tag = 2, .op = .read, .node = log, .handle = h, .size = 1 << 16 }).bytes;
+ try std.testing.expectStringStartsWith(text, "new ");
}
test "an init file's retired ColumnTags line is said and ignored" {
@@ -861,23 +894,16 @@ pub fn runBuiltinFrom(p: *Pardes, b: Builtin, id: usize, txt: []const u8, arg: ?
const pane = p.panes[id] orelse return;
// A builtin says its own name before it runs, so a tagline click is
// legible even when what it did is invisible. Whatever the builtin
- // then says for itself replaces the announcement.
- // Its log record stands only if it does not fail (events.dropFailedAnnouncement).
- var announced: ?u64 = null;
+ // then says for itself replaces the announcement. It is the message
+ // row's only, never /log's: a msg record is something said.
if (p.announce and p.settings.verbose and builtins.registry.announces(b)) {
+ p.fs.unlogged = true;
p.setMessage(id, @tagName(b));
+ p.fs.unlogged = false;
pane.msg_announcement = true;
- announced = pardes.ctlfs.events.newestSaying(p, @tagName(b));
- p.fs.announced_seq = announced;
- const word = @tagName(b)[0..@min(@tagName(b).len, p.fs.announced_word.len)];
- @memcpy(p.fs.announced_word[0..word.len], word);
- p.fs.announced_word_len = @intCast(word.len);
}
- const failures = p.fs.failures;
const c: builtins.Ctx = .{ .p = p, .pane = pane, .id = id, .txt = txt, .arg = arg, .from_body = from_body };
builtins.registry.dispatch(b, c);
- if (announced != null and p.fs.failures != failures and p.fs.announced_seq == announced)
- pardes.ctlfs.events.dropFailedAnnouncement(p, "", true);
}
pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[]const u8) void {
@@ -888,15 +914,12 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[]
switch (setting.action) {
.theme => {
const want = std.mem.trim(u8, arg orelse return, " \t\r\n");
- for (themes, 0..) |t, i| if (std.mem.eql(u8, t.name, want)) {
- colors.setThemeIndex(p, i);
- return;
- };
+ if (colors.themeIndex(want)) |i| return colors.setThemeIndex(p, i);
// A config line is not answered on screen (applyStartupConfig
// runs with announce off); a click or a ctl write is.
if (!p.announce) return;
- var near: [96]u8 = undefined;
- var text: [200]u8 = undefined;
+ var near: [170]u8 = undefined;
+ var text: [256]u8 = undefined;
return p.reportFailure(p.active, std.fmt.bufPrint(&text, "Theme: no theme \"{s}\"; like it: {s}; Themes lists all", .{ want[0..@min(want.len, 40)], colors.themesNear(&near, want) }) catch "Theme: no such theme");
},
.font => {