diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/builtins.zig | 35 | ||||
| -rw-r--r-- | src/config.zig | 4 | ||||
| -rw-r--r-- | src/dump.zig | 11 | ||||
| -rw-r--r-- | src/exec.zig | 3 | ||||
| -rw-r--r-- | src/look.zig | 50 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 2 | ||||
| -rw-r--r-- | src/pardes.zig | 13 |
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); |
