summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/builtins.zig35
-rw-r--r--src/config.zig4
-rw-r--r--src/dump.zig11
-rw-r--r--src/exec.zig3
-rw-r--r--src/look.zig50
-rw-r--r--src/ninep/ctl.zig2
-rw-r--r--src/pardes.zig13
7 files changed, 114 insertions, 4 deletions
diff --git a/src/builtins.zig b/src/builtins.zig
index 8fcc3c01..780d0bb7 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -1159,6 +1159,41 @@ pub const Putall = struct {
}
};
+/// acme's Incl, for the session: `Incl dir ...` adds directories a look at a
+/// name found nowhere else is tried in (`stdio.h`, `<foo.h>`), bare lists
+/// them, `Incl -` empties the list.
+pub const Incl = struct {
+ pub const scope: Scope = .session;
+ pub const takes_arg = true;
+ pub fn run(c: Ctx) void {
+ const p = c.p;
+ const typed = std.mem.trim(u8, c.arg orelse "", " \t\r\n");
+ if (std.mem.eql(u8, typed, "-")) {
+ for (p.incl.items) |dir| p.gpa.free(dir);
+ p.incl.clearRetainingCapacity();
+ return p.setMessage(c.id, "Incl: none");
+ }
+ var words = std.mem.tokenizeAny(u8, typed, " \t");
+ while (words.next()) |dir_word| {
+ var home_buf: [4096]u8 = undefined;
+ const full = std.fs.path.resolvePosix(p.scratch.allocator(), &.{ Pardes.paneDir(c.pane), @import("fs.zig").expandHome(dir_word, &home_buf) }) catch return;
+ if (!std.fs.path.isAbsolute(full)) return p.reportFailure(c.id, "Incl: invalid directory");
+ for (p.incl.items) |dir| {
+ if (std.mem.eql(u8, dir, full)) break;
+ } else {
+ const copy = p.gpa.dupe(u8, full) catch return;
+ p.incl.append(p.gpa, copy) catch return p.gpa.free(copy);
+ }
+ }
+ if (typed.len > 0) return;
+ var list: std.Io.Writer.Allocating = .init(p.scratch.allocator());
+ list.writer.writeAll("Incl:") catch return;
+ for (p.incl.items) |dir| list.writer.print(" {s}", .{dir}) catch return;
+ if (p.incl.items.len == 0) list.writer.writeAll(" none") catch return;
+ p.setMessage(c.id, list.written());
+ }
+};
+
/// 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 640f3cd0..375b4c69 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -60,6 +60,7 @@ pub const leader_path = paths: {
.Exit = "q",
.Get = null,
.Putall = null,
+ .Incl = null,
.ClipYank = "y",
.ClipYankMain = "Y",
.ClipPaste = "p",
@@ -400,6 +401,9 @@ pub fn defaultShell() []const u8 {
// Minimum file gutter width, including the space after the line number.
pub const PREFIX_W: u16 = 5;
+/// acme's Incl's C defaults: where a look at `stdio.h` finds it.
+pub const default_incl = [_][]const u8{ "/usr/include", "/usr/local/include" };
+
pub const tab_width: u16 = 4;
comptime {
if (tab_width == 0) @compileError("config.tab_width must be greater than zero");
diff --git a/src/dump.zig b/src/dump.zig
index 005770e1..b64441d3 100644
--- a/src/dump.zig
+++ b/src/dump.zig
@@ -302,6 +302,8 @@ pub const State = struct {
/// config are kept on their own, and the font is the frontend's. Absent
/// in older dumps.
settings: []const []const u8 = &.{},
+ /// Incl's directories, in order; null in older dumps (the defaults).
+ incl: ?[]const []const u8 = null,
};
/// A header is a text like any other: it may hold newlines, but not a
@@ -798,6 +800,7 @@ pub fn dumpState(p: *Pardes) !void {
.panes = dump_panes[0..panes_len],
.mounts = mounts[0..p.fs.mounts.items.len],
.settings = try changedSettings(p, arena),
+ .incl = p.incl.items,
};
try validate(state);
var out: std.Io.Writer.Allocating = .init(p.gpa);
@@ -902,6 +905,14 @@ fn initDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8, previo
p.locations_config = st.locations_config;
p.settings.tree_context_tag_style = st.tree_context_tag_style;
applySettings(p, st.settings);
+ if (st.incl) |dirs| {
+ for (p.incl.items) |dir| p.gpa.free(dir);
+ p.incl.clearRetainingCapacity();
+ for (dirs) |dir| if (dir.len > 0 and dir[0] == '/' and std.mem.indexOfScalar(u8, dir, 0) == null) {
+ const copy = p.gpa.dupe(u8, dir) catch continue;
+ p.incl.append(p.gpa, copy) catch p.gpa.free(copy);
+ };
+ }
// A dump naming a ThemeFile asks the host to load it again: until it
// has, and for good if it fails, the theme this session showed goes on
// (a failed ThemeFile changes nothing, a Restore's included), unless
diff --git a/src/exec.zig b/src/exec.zig
index c465bda8..4bdb2584 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -954,9 +954,8 @@ pub const acme_words = [_]struct { acme: []const u8, pardes: ?[]const u8, hint:
.{ .acme = "ID", .pardes = null },
.{ .acme = "Send", .pardes = null },
.{ .acme = "Tab", .pardes = null },
- .{ .acme = "Indent", .pardes = null },
+ .{ .acme = "Indent", .pardes = null, .hint = ": Enter, o and O keep the indent always" },
.{ .acme = "Local", .pardes = null },
- .{ .acme = "Incl", .pardes = null },
.{ .acme = "Abort", .pardes = null, .hint = ": a debugging word that crashes acme" },
};
diff --git a/src/look.zig b/src/look.zig
index a2aa5be0..3d02802a 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1141,6 +1141,13 @@ fn resolveLookTarget(p: *Pardes, id: usize, txt: []const u8, realbuf: *[4096]u8)
// `.`, `..`, `./x` are this pane's directory's, not another's.
if (found != .none or pl.path.len == 0 or pl.path[0] == '/' or filesystem.dotRelative(pl.path)) break;
}
+ // Found nowhere else, a relative name is tried in Incl's directories,
+ // as acme's includename tries its (look.c:500).
+ if (found == .none and pl.path.len > 0 and pl.path[0] != '/' and !filesystem.dotRelative(pl.path))
+ for (p.incl.items) |dir| {
+ found = resolve(p, txt, dir, realbuf);
+ if (found != .none) break;
+ };
return found;
}
@@ -1734,7 +1741,9 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
const txt = p.scratch.allocator().dupe(u8, looked) catch return;
// A trailing `:` (`a.txt:2:3:`, as a compiler's message ends) is no
// part of the place, as a click's expansion leaves it off.
- const trimmed = std.mem.trimEnd(u8, std.mem.trim(u8, txt, " \t\r\n"), ":");
+ const bare = std.mem.trimEnd(u8, std.mem.trim(u8, txt, " \t\r\n"), ":");
+ // `<stdio.h>` written whole: the name inside, as a click on it takes.
+ const trimmed = if (bare.len > 2 and bare[0] == '<' and bare[bare.len - 1] == '>') bare[1 .. bare.len - 1] else bare;
// acme's `file:addr` and `:addr` (look.c:450-543): an address after
// the colon, evaluated in that file from its dot (openfile, look.c:675).
// `file:3:0`, and `file:0:0` too: columns count from 1, as addr's do,
@@ -2586,6 +2595,45 @@ test "a tag word looked at by a click and by Enter is one record to an event rea
try std.testing.expect(p.panes[0] != null);
}
+test "Incl's directories find a name found nowhere else, <name> too; bare Incl lists them, Incl - empties them, and a dump keeps them" {
+ if (!platform_has_fs) return;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.createDirPath(std.testing.io, "inc");
+ try tmp.dir.createDirPath(std.testing.io, "src");
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "inc/pardes_incl_probe.h", .data = "#define X 1\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const root = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)];
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 });
+ defer p.deinit();
+ // C's two by default.
+ try std.testing.expectEqualStrings("/usr/include", p.incl.items[0]);
+ const pane = try p.setTestFile("#include <pardes_incl_probe.h>\n");
+ p.gpa.free(pane.file.?.path);
+ pane.file.?.path = try std.fs.path.join(p.gpa, &.{ root, "src", "main.c" });
+ var line: [4200]u8 = undefined;
+ try std.testing.expect(p.executeBuiltinLine(0, try std.fmt.bufPrint(&line, "Incl {s}/inc", .{root})));
+ for ([_][]const u8{ "pardes_incl_probe.h", "<pardes_incl_probe.h>" }) |word| {
+ p.active = 0;
+ p.look_missed = false;
+ lookAt(p, 0, word);
+ try std.testing.expect(!p.look_missed);
+ try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "/inc/pardes_incl_probe.h"));
+ }
+ // Bare, it lists them.
+ try std.testing.expect(p.executeBuiltinLine(0, "Incl"));
+ try std.testing.expect(std.mem.indexOf(u8, pane.msg[0..pane.msg_len], "/inc") != null);
+ // A dump keeps them.
+ try pardes.dump.dumpState(p);
+ const restored = try pardes.dump.restore(p, p.dump_out.?, "/tmp/pardes.dump.zon");
+ defer restored.deinit();
+ try std.testing.expectEqual(p.incl.items.len, restored.incl.items.len);
+ try std.testing.expect(std.mem.endsWith(u8, restored.incl.items[restored.incl.items.len - 1], "/inc"));
+ // Emptied, the name is found nowhere.
+ try std.testing.expect(p.executeBuiltinLine(0, "Incl -"));
+ try std.testing.expectEqual(@as(usize, 0), p.incl.items.len);
+}
+
test "Look outside a diff pane expands and looks as ever" {
const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 });
defer p.deinit();
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 028ddf55..d0b1defb 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -3210,7 +3210,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", "Zerox\n", "Tab\n", "Indent\n", "Local\n", "Incl\n", "Abort\n" }) |line| {
+ for ([_][]const u8{ "Snarf\n", "Zerox\n", "Tab\n", "Indent\n", "Local\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());
diff --git a/src/pardes.zig b/src/pardes.zig
index 425a0993..36a37293 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4324,6 +4324,10 @@ pub const Pardes = struct {
/// The files opened lately, closed ones too (recent.zig): `Recent`,
/// /recent, and the jumplist's way back to a closed pane.
recent: recent_files.List = .{},
+ /// acme's Incl: the directories a look at a name that is nowhere else
+ /// (`stdio.h`, `<foo.h>`) is tried in, in order. The session's, seeded
+ /// with C's (/usr/include, /usr/local/include), kept in a dump.
+ incl: std.ArrayList([]u8) = .empty,
/// The kept list was read (or there was none): only then is it written,
/// so a session never writes over one it did not read.
recent_loaded: bool = false,
@@ -4709,6 +4713,13 @@ pub const Pardes = struct {
}
p.sync();
p.loadRecent();
+ for (config.default_incl) |dir| {
+ const copy = try gpa.dupe(u8, dir);
+ p.incl.append(gpa, copy) catch |err| {
+ gpa.free(copy);
+ return err;
+ };
+ }
exec.applyStartupConfig(p);
colors.finishThemeInitialization(p);
p.sync();
@@ -4816,6 +4827,8 @@ pub const Pardes = struct {
for (p.panes) |slot| if (slot) |pane| p.noteClosed(pane);
p.saveRecent();
p.recent.deinit(p.recentGpa());
+ for (p.incl.items) |dir| p.gpa.free(dir);
+ p.incl.deinit(p.gpa);
p.fs.batch.bytes.deinit(p.gpa);
if (p.stand_in) |pane| p.teardownPane(pane);
look.cancelLookHover(p);