summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.zig12
-rw-r--r--src/look.zig19
-rw-r--r--src/ninep/ctl.zig23
3 files changed, 53 insertions, 1 deletions
diff --git a/src/config.zig b/src/config.zig
index e0fcab48..b7662f1a 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -26,6 +26,7 @@ const undecided: []const u8 = "<undecided>";
// null means word/chord-only. Every enabled builtin must explicitly choose a path.
pub const leader_path = paths: {
+ @setEvalBranchQuota(10_000); // a check per builtin, and they grow
var table = std.EnumArray(Builtin, ?[]const u8).initDefault(@as(?[]const u8, undecided), .{
.Help = &[_]u8{leader_help},
.Hover = "lk",
@@ -75,6 +76,7 @@ pub const leader_path = paths: {
.Joincol = "cj",
.Placement = "cp",
.BootShell = null,
+ .LookWord = null,
.Debug = "td",
.Msg = null,
.Colors = "tc",
@@ -707,12 +709,17 @@ pub const Runtime = struct {
/// has typed into stands: `keep` leaves the shell, `replace` closes it
/// (exec.evictLonePristineTty).
boot_shell: BootShell = .keep,
+ /// A plain word looked at in a pane: `search`, acme's look3, selects its
+ /// next place in that pane, wrapping; `list`, what pardes did before,
+ /// lists every place in a +Search pane (look.zig, lookWord).
+ look_word: LookWord = .search,
pub const ShaderAnimation = enum { off, on, always };
pub const Placement = enum { acme, pardes };
pub const BootShell = enum { keep, replace };
+ pub const LookWord = enum { search, list };
/// A setting that is one of a few words, each an enum field of Runtime.
- pub const Choice = enum { placement, boot_shell };
+ pub const Choice = enum { placement, boot_shell, look_word };
/// shadow: a soft drop shadow. rim: a light hairline along the top of
/// the focused pane's tag rows. auto: a shadow on a light page; on a
@@ -963,6 +970,7 @@ pub const Runtime = struct {
.{ .word = "ShaderAnimation", .action = .shader_animation, .availability = .scene_shaders },
.{ .word = "Placement", .action = .{ .choice = .placement } },
.{ .word = "BootShell", .action = .{ .choice = .boot_shell } },
+ .{ .word = "LookWord", .action = .{ .choice = .look_word } },
};
pub fn find(name: []const u8) ?Setting {
@@ -1636,6 +1644,7 @@ pub const Runtime = struct {
.choice => |which| switch (which) {
.placement => "pardes",
.boot_shell => "replace",
+ .look_word => "list",
},
.lift => "rim",
.motion => "playful",
@@ -1694,6 +1703,7 @@ pub const Runtime = struct {
.choice => |which| switch (which) {
.placement => "Placement pardes\n",
.boot_shell => "BootShell replace\n",
+ .look_word => "LookWord list\n",
},
.lift => "Lift rim\n",
.motion => "Motion playful\n",
diff --git a/src/look.zig b/src/look.zig
index 5d60e8b3..86238413 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1229,6 +1229,24 @@ fn settleLine(p: *Pardes, id: usize, path: []const u8, at: Spot, before: usize,
}
}
+/// A plain word looked at in a pane, as acme's look3 does with one that
+/// names no file (look.c:210, search): its next place in that pane after
+/// the dot, wrapping to the top, is selected there. No +Search pane
+/// (`LookWord list` lists every place in one instead).
+fn lookWord(p: *Pardes, id: usize, pane: *Pane, word: []const u8) void {
+ const ninep_pane = @import("ninep/pane.zig");
+ const f = pane.file orelse return;
+ const text = f.content;
+ const from: usize = @min(ninep_pane.dotOf(pane).q1, text.len);
+ const at = std.mem.indexOfPos(u8, text, from, word) orelse (std.mem.indexOf(u8, text[0..@min(text.len, from + word.len -| 1)], word) orelse {
+ var cut: [miss_path_cap + "…".len]u8 = undefined;
+ return missed(p, id, "no match for \"{s}\"", .{missText(word, &cut)});
+ });
+ clearNavigationSelection(p, pane);
+ ninep_pane.setDot(pane, .{ .q0 = @intCast(at), .q1 = @intCast(at + word.len) });
+ p.active = id;
+}
+
/// `file:addr` or `:addr`, acme's look.c:450-543: a colon followed by an
/// address character (addr.c:28, isaddrc) that is not a line spot this
/// parser already reads (`file:12`, `file:12:5`). Not a URL.
@@ -1315,6 +1333,7 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
switch (found) {
.none => {
if (pane.image != null or pane.body.mode == .tty) return;
+ if (p.settings.look_word == .search and pane.file != null) return lookWord(p, id, pane, trimmed);
const rows = runSearch(p, id, trimmed, .text, .cursor, false) catch |err| {
p.reportError(id, "search", err);
return;
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index fac1e5eb..f40825e3 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1588,3 +1588,26 @@ test "a file changed on disk under unsaved edits is not reloaded: it says so, ge
try testing.expect(std.mem.indexOf(u8, first.reply.ename, "modified on disk since read") != null);
try testing.expect(wr(p, Node.of(serial, .ctl), "Save\n").saved);
}
+
+test "a looked word is found in its own pane, next place after the dot, wrapping, and opens no pane" {
+ const p = try withFile(testing.allocator, "foo x foo y\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const look_node = Node.of(serial, .look);
+ const dot = Node.of(serial, .dot);
+ var count: usize = 0;
+ for (p.panes) |slot| count += @intFromBool(slot != null);
+ for ([_][]const u8{ " 0 3 ", " 6 9 ", " 0 3 " }) |want| {
+ try testing.expectEqual(Status.ok, wr(p, look_node, "foo\n").reply.status);
+ try testing.expectEqualStrings(want, rd(p, dot, 0, 64).bytes);
+ }
+ var after: usize = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expectEqual(count, after);
+ // LookWord list: the +Search listing, as before.
+ try testing.expect(p.executeBuiltinLine(0, "LookWord list"));
+ try testing.expectEqual(Status.ok, wr(p, look_node, "foo\n").reply.status);
+ after = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expect(after > count);
+}