summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig127
1 files changed, 124 insertions, 3 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 5ba95be3..9821839f 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1391,6 +1391,100 @@ test "a spawn is for the pane it was made for, not whatever took the slot since"
try std.testing.expectEqualStrings("second", Count.command[0.."second".len]);
}
+test "a REPL bound for a language takes the exec made on a body in it; tags, Exec by name and command words still run" {
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .shells = 3, .cols = 100, .rows = 30 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const file = try p.setTestFile("print(1)\nls\n");
+ gpa.free(file.file.?.path);
+ file.file.?.path = try gpa.dupe(u8, "/x.py");
+ const Sent = struct {
+ var to: [MAX_PANES][128]u8 = undefined;
+ var len: [MAX_PANES]usize = @splat(0);
+ var spawned: bool = false;
+ fn drain(pp: *Pardes) void {
+ len = @splat(0);
+ spawned = false;
+ while (pp.nextEffect()) |effect| switch (effect) {
+ .write => |w| {
+ @memcpy(to[w.pane][len[w.pane]..][0..w.bytes.slice().len], w.bytes.slice());
+ len[w.pane] += w.bytes.slice().len;
+ },
+ .spawn => spawned = true,
+ else => {},
+ };
+ }
+ fn said(pane: usize) []const u8 {
+ return to[pane][0..len[pane]];
+ }
+ };
+ // Only a terminal is a REPL; a language's name is the syntax table's.
+ try std.testing.expect(p.executeBuiltinLine(0, "Repl python"));
+ try std.testing.expect(file.repl == null);
+ try std.testing.expect(p.executeBuiltinLine(1, "Repl pithon"));
+ try std.testing.expect(p.panes[1].?.repl == null);
+ try std.testing.expect(std.mem.indexOf(u8, p.panes[1].?.msg[0..p.panes[1].?.msg_len], "python") != null);
+ try std.testing.expect(p.executeBuiltinLine(1, "Repl python"));
+ try std.testing.expect(p.executeBuiltinLine(1, "Repl python")); // again: no change
+ var idbuf: [48]u8 = undefined;
+ try std.testing.expectEqualStrings("python-a", exec.replId(&idbuf, p.panes[1].?));
+ try std.testing.expect(std.mem.endsWith(u8, try tagline.tagPrefix(p, p.panes[1].?), " python-a"));
+ Sent.drain(p);
+
+ // A click on the file's body, a single word: typed into the REPL.
+ p.exec_from_body = true;
+ try std.testing.expectEqual(@as(?usize, 1), exec.execute(p, 0, "ls"));
+ Sent.drain(p);
+ try std.testing.expectEqualStrings("ls\r", Sent.said(1));
+ try std.testing.expect(!Sent.spawned);
+ try std.testing.expect(file.msg_sent);
+ try std.testing.expectEqualStrings("\u{2192} python-a", file.msg[0..file.msg_len]);
+ // Its own body too.
+ p.exec_from_body = true;
+ try std.testing.expectEqual(@as(?usize, 1), exec.execute(p, 1, "x = 1"));
+ Sent.drain(p);
+ try std.testing.expectEqualStrings("x = 1\r", Sent.said(1));
+
+ // Escapes: a tag's word, Exec by name, a command word. Each a command.
+ _ = exec.execute(p, 0, "ls");
+ Sent.drain(p);
+ try std.testing.expect(Sent.spawned and Sent.len[1] == 0);
+ try std.testing.expect(p.executeBuiltinLine(0, "Exec ls"));
+ Sent.drain(p);
+ try std.testing.expect(Sent.spawned and Sent.len[1] == 0);
+ p.exec_from_body = true;
+ _ = exec.execute(p, 0, "@`ls`");
+ Sent.drain(p);
+ try std.testing.expect(Sent.spawned and Sent.len[1] == 0);
+
+ // Two bound: it asks, one key answers, Esc sends nowhere.
+ try std.testing.expect(p.executeBuiltinLine(2, "Repl python"));
+ try std.testing.expectEqualStrings("python-b", exec.replId(&idbuf, p.panes[2].?));
+ Sent.drain(p);
+ p.exec_from_body = true;
+ try std.testing.expectEqual(@as(?usize, null), exec.execute(p, 0, "print(2)"));
+ try std.testing.expectEqual(Pane.Prompt.repl_choice, file.prompt);
+ try std.testing.expect(std.mem.indexOf(u8, file.promptText().?, "a b") != null);
+ p.update(.{ .key = .{ .cp = 'b' } });
+ Sent.drain(p);
+ try std.testing.expectEqualStrings("print(2)\r", Sent.said(2));
+ try std.testing.expect(Sent.len[1] == 0 and file.prompt == .none);
+ p.exec_from_body = true;
+ _ = exec.execute(p, 0, "print(3)");
+ p.update(.{ .key = .{ .cp = Key.escape } });
+ Sent.drain(p);
+ try std.testing.expect(Sent.len[1] == 0 and Sent.len[2] == 0);
+
+ // Unbound, its letter is free again; bare, it says what it is.
+ try std.testing.expect(p.executeBuiltinLine(1, "Repl -"));
+ try std.testing.expect(p.panes[1].?.repl == null);
+ try std.testing.expect(p.executeBuiltinLine(1, "Repl python"));
+ try std.testing.expectEqualStrings("python-a", exec.replId(&idbuf, p.panes[1].?));
+ try std.testing.expect(p.executeBuiltinLine(2, "Repl"));
+ try std.testing.expectEqualStrings("Repl: python-b", p.panes[2].?.msg[0..p.panes[2].?.msg_len]);
+}
+
test "Tty+fish, one word a tag can hold, opens a terminal on that shell" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
@@ -3929,6 +4023,13 @@ pub const Pardes = struct {
chord_arg: ?[]u8 = null,
/// how many `execute` calls are on the stack — see max_exec_depth
exec_depth: u8 = 0,
+ /// The exec under way was made by a gesture on a pane's body -- a
+ /// middle click, the execute key -- and so may go to a REPL bound for
+ /// the text's language; one run by name, `Exec ...`, never does.
+ exec_from_body: bool = false,
+ /// An exec waiting on the REPL question (`Pane.Prompt.repl_choice`):
+ /// the pane it came from, the language, and the text, owned.
+ repl_ask: ?struct { from: u32, lang: u8, text: []u8 } = null,
/// A key press is being handled: someone is at the keyboard to answer,
/// so a builtin may ask rather than guess. Nothing else sets it -- not
/// a click, a 9P write, a startup line or a pane's shell exiting.
@@ -4138,6 +4239,7 @@ pub const Pardes = struct {
pub fn deinit(p: *Pardes) void {
look.cancelLookHover(p);
+ if (p.repl_ask) |ask| p.gpa.free(ask.text);
for (0..MAX_PANES) |id| p.dropPendingWrite(id);
for (&p.panes) |*slot| if (slot.*) |pane| {
p.teardownPane(pane);
@@ -5015,6 +5117,18 @@ pub const Pardes = struct {
// pane or a header edit is in: that key is its answer.
for (p.panes, 0..) |slot, id| {
const asked = slot orelse continue;
+ if (asked.prompt == .repl_choice) {
+ asked.prompt = .none;
+ const ask = p.repl_ask orelse return;
+ p.repl_ask = null;
+ defer p.gpa.free(ask.text);
+ // Its letter sends it there; anything else takes it back.
+ if (key.ctrl or key.alt or key.cp > 'z' or key.cp < 'a') return;
+ for (p.panes, 0..) |target, to| if (target) |repl| if (repl.repl) |r|
+ if (r.lang == ask.lang and r.letter == key.cp and exec.replLive(repl))
+ return exec.sendToRepl(p, id, to, ask.text);
+ return;
+ }
if (asked.prompt != .del_side) continue;
asked.prompt = .none;
if (hit(key, config.del_above)) exec.runBuiltin(p, .Del, id, "", "k");
@@ -5126,7 +5240,7 @@ pub const Pardes = struct {
look.submitSearch(p, p.active)
else if (normal.selRegexArmed(pane)) |_| if (target) |answered|
normal.applySelRegex(p, pane, answered, "", false),
- .none, .del_side, .name => unreachable,
+ .none, .del_side, .repl_choice, .name => unreachable,
}
pane.closePrompt();
if (target) |answered| {
@@ -5164,6 +5278,9 @@ pub const Pardes = struct {
return;
}
edit.pinPaneCursor(p, &pane.body);
+ // The execute key on a body is the exec a bound REPL may take.
+ p.exec_from_body = cmd == config.exec_cmd;
+ defer p.exec_from_body = false;
const explicit = (p.native_images and pane.hasPdfSelection()) or
(pane.body.vsel.active and pane.body.vsel.explicit) or pane.body.msel.active;
if (explicit) {
@@ -5999,12 +6116,16 @@ pub const Pardes = struct {
// A message coming or going blends toward the page it sits
// on; a grid cannot move it by less than a row.
var style = msg_style;
+ // Where an exec was sent to a REPL reads in the tag's name
+ // tint, set apart from what the editor says.
+ const ink = if (kind == .message and pane.msg_sent) chrome.tag_name_fg else chrome.tag_fg;
+ if (kind == .message and pane.msg_sent) style.fg = .{ .rgb = ink };
if (Messages.noticeLife(pane, i)) |life| {
// No page colour to fade to (a terminal's own default):
// the text dissolves into its chip instead.
const alpha = Messages.messageMotion(p, life).alpha;
const page = th.bg orelse chrome.tag_bg;
- style.fg = .{ .rgb = Messages.blendRgb(chrome.tag_fg, page, alpha) };
+ style.fg = .{ .rgb = Messages.blendRgb(ink, page, alpha) };
style.bg = .{ .rgb = Messages.blendRgb(chrome.tag_bg, page, alpha) };
}
s.fill(cx, row, chip, 1, .{ .bg = style.bg, .font_role = .tagline });
@@ -6014,7 +6135,7 @@ pub const Pardes = struct {
// the detached wire will encode -- which drops every frame for
// as long as the prompt is up.
const printed = Messages.printRight(s, cx, row, chip -| 1, text, style);
- if (kind != .prompt or id != p.active or pane.prompt == .del_side) continue;
+ if (kind != .prompt or id != p.active or pane.prompt == .del_side or pane.prompt == .repl_choice) continue;
// The cursor follows the text to wherever it landed; a caret
// in the part a narrow band dropped has nowhere to be.
const prompt_col = panes.File.displayWidth(text[0..@min(@as(usize, @intCast(@max(0, pane.input.cur_col))), text.len)]);