summaryrefslogtreecommitdiff
path: root/src/config.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-31 01:18:53 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:08 -0300
commit8a7112f08e0ce93a37b177e3aa83637e1691b451 (patch)
tree4a5fe4b0da18cf98cd91950a218ad4a6d324b0cc /src/config.zig
parenta77e342eb7fa4add700a96a661226649e8c4763f (diff)
downloadpardes-8a7112f08e0ce93a37b177e3aa83637e1691b451.tar.gz
pardes-8a7112f08e0ce93a37b177e3aa83637e1691b451.zip
Look and Exec are builtins like any other
Their only special feature is now the keys they are assigned. `Look main.zig` typed in a tag is the same look a right click is; config.look_cmd/exec_cmd point the two buttons and Enter/Tab at them, so retargeting Enter to Grep is one line. actOnSelection — a hand-written cascade with the builtin dispatch nested inside it — is gone, and the `button` parameter it threaded through five KEYBOARD call sites went with it. New syntax, spelled once in config.zig: @`ls -la` names a command to run rather than a file to open. Word expansion takes the quoted run whole, the way acme does for its own </|/> words, so a click inside one does not hand Look the fragment `ls`. And it nests: @`Look .` unwraps, re-enters the dispatcher, and looks at the directory. Guarded at depth 8, which is unreachable today (every re-entry strips a word or a delimiter pair, so the string strictly shrinks) and exists for a future syntax that does not shrink. leader_path is now optional: a builtin may have no SPC path when its shortcut is a key and a mouse button. New golden cmdword; the other 59 unmoved.
Diffstat (limited to 'src/config.zig')
-rw-r--r--src/config.zig62
1 files changed, 58 insertions, 4 deletions
diff --git a/src/config.zig b/src/config.zig
index 65ac397b..394f9ca3 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -59,10 +59,16 @@ pub const leader: []const Chord = &.{.{ .cp = ' ' }};
pub const leader_help: u8 = '?';
/// SPC leader: ONE key path per builtin, the whole remapping surface. An
-/// EnumArray, so a new enum field is a compile error until it has a path.
-/// Groups are just shared first letters (f files, h docs, c columns, t
-/// toggles, s session, l language, w windows).
-pub const leader_path = std.EnumArray(Builtin, []const u8).init(.{
+/// EnumArray, so a new enum field is a compile error until someone has DECIDED
+/// its path. Groups are just shared first letters (f files, h docs, c columns,
+/// t toggles, s session, l language, w windows).
+///
+/// `null` = this builtin's shortcut is not a leader path. Look and Exec are
+/// the two: their shortcuts are Enter/Tab and the two mouse buttons below, and
+/// a third spelling under SPC would be a key that does nothing you cannot
+/// already do with the key your hand is on. The option is the honest type —
+/// "every builtin has a leader path" was only ever true by accident.
+pub const leader_path = std.EnumArray(Builtin, ?[]const u8).init(.{
.Help = &[_]u8{leader_help},
// The whole LANGUAGE group lives under `l`, and pardes's own builtins keep
// the letters they always had — `SPC d` is Del, `SPC k` is Kill.
@@ -117,6 +123,9 @@ pub const leader_path = std.EnumArray(Builtin, []const u8).init(.{
.Up = "wk",
.Right = "wl",
.Toggleterm = "wt",
+ // the two acme verbs: keys and buttons, no leader path — see above
+ .Look = null,
+ .Exec = null,
});
// ---- the acme chords ----
@@ -134,6 +143,15 @@ pub const exec_button: Mouse.Button = .middle;
/// sweep, focus, place the cursor, and the left half of every acme chord
pub const select_button: Mouse.Button = .left;
+/// ...and WHAT those four run. Look and Exec are ORDINARY builtins — the same
+/// kind of thing Save and Grep are, executable by name wherever text lives
+/// (`Look main.zig` in a tag does what a right click on `main.zig` does) — so
+/// the four bindings above are bindings like any other, and these two lines
+/// are the whole of what makes them special. Point `look_cmd` at `.Grep` and
+/// Enter greps.
+pub const look_cmd: Builtin = .Look;
+pub const exec_cmd: Builtin = .Exec;
+
// ---- windows ----
/// helix's window prefix. It stays despite `SPC w` covering the same four
@@ -278,6 +296,42 @@ pub fn isFileChar(c: u8) bool {
};
}
+/// `` @`ls -la` `` — a word that names a COMMAND to run rather than a file to
+/// open. Both halves are named here because they are a CHOICE: the `@` marks
+/// it as ours (it is already a file char, so it can never split a path) and
+/// the backquotes hold a command line with spaces in it, which is the whole
+/// point — a file-ish word cannot. Respell them here and nowhere else.
+pub const cmd_open = "@`";
+pub const cmd_close: u8 = '`';
+
+/// The command inside `` @`...` ``, or null when `w` is not one.
+pub fn commandWord(w: []const u8) ?[]const u8 {
+ if (w.len <= cmd_open.len or !std.mem.startsWith(u8, w, cmd_open)) return null;
+ if (w[w.len - 1] != cmd_close) return null;
+ return w[cmd_open.len .. w.len - 1];
+}
+
+/// The bounds of the word at `col` in `line` — THE expansion a no-drag
+/// look/execute click and the tag chord both use.
+///
+/// A `` @`...` `` run is taken WHOLE and wins outright: a backtick is not a
+/// file char, so the plain scan below would stop dead inside one and hand a
+/// look the fragment `ls` out of `` @`ls -la` ``. Acme does exactly this for
+/// its own `<`/`|`/`>` command words. Otherwise it is the file-ish word.
+pub fn wordBounds(line: []const u8, col: usize) struct { lo: usize, hi: usize } {
+ var i: usize = 0;
+ while (std.mem.indexOfPos(u8, line, i, cmd_open)) |o| {
+ const close = std.mem.indexOfScalarPos(u8, line, o + cmd_open.len, cmd_close) orelse break;
+ if (col >= o and col <= close) return .{ .lo = o, .hi = close + 1 };
+ i = close + 1;
+ }
+ var lo = col;
+ while (lo > 0 and isFileChar(line[lo - 1])) lo -= 1;
+ var hi = col;
+ while (hi < line.len and isFileChar(line[hi])) hi += 1;
+ return .{ .lo = lo, .hi = hi };
+}
+
/// separates a path from its LINE and COL: `main.zig:100:7`. Must be a member
/// of isFileChar or the suffix would not be part of the word in the first
/// place.