summaryrefslogtreecommitdiff
path: root/src/exec.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/exec.zig')
-rw-r--r--src/exec.zig101
1 files changed, 101 insertions, 0 deletions
diff --git a/src/exec.zig b/src/exec.zig
index 8b058a12..1b82d2a8 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -16,6 +16,7 @@ const colors = @import("colors.zig");
const themes = colors.themes;
const builtins = @import("builtins.zig");
const config = @import("config.zig");
+const grammars = @import("grammar_manifest.zig").all;
const pdf_enabled = panes.Pdf.enabled;
const font_picker = pardes.font_picker;
const hosted = pardes.hosted;
@@ -369,6 +370,96 @@ pub fn replaceStillborn(p: *Pardes) void {
if (was_active) p.active = new_id;
}
+// ---- REPLs ----
+//
+// A terminal is bound as a language's REPL by `Repl python` (builtins.zig).
+// An exec made by a gesture on the body of a file in that language -- or on
+// the REPL's own body -- is sent to it as input rather than run: all of it,
+// a word like `ls` too. The tag's words, `Exec` by name and a command word
+// `@`...`` still run as commands.
+
+/// A file pane's language, as grammar_manifest names it, by its index.
+pub fn langOf(pane: *const Pane) ?u8 {
+ const f = pane.file orelse return null;
+ for (grammars, 0..) |g, i| for (g.exts) |ext| {
+ if (std.mem.endsWith(u8, f.path, ext)) return @intCast(i);
+ };
+ return null;
+}
+
+/// A bound REPL whose program can still take input: a command pane's is
+/// gone once its command is done.
+pub fn replLive(pane: *const Pane) bool {
+ return pane.repl != null and pane.isTerminal() and !(pane.command != null and pane.command_done);
+}
+
+/// `python-a`.
+pub fn replId(buf: []u8, pane: *const Pane) []const u8 {
+ const r = pane.repl orelse return "";
+ return std.fmt.bufPrint(buf, "{s}-{c}", .{ grammars[r.lang].name, r.letter }) catch "";
+}
+
+const Routed = union(enum) { none, sent: usize, asked };
+
+/// Where an exec from `from`'s body goes when a REPL is bound for it: the
+/// one there is, or a question when there are several.
+fn routeToRepl(p: *Pardes, from: usize, text: []const u8) Routed {
+ const src = p.panes[from] orelse return .none;
+ if (src.repl != null and replLive(src)) {
+ sendToRepl(p, from, from, text);
+ return .{ .sent = from };
+ }
+ const lang = langOf(src) orelse return .none;
+ var bound: [26]usize = undefined;
+ var n: usize = 0;
+ for (p.panes, 0..) |slot, i| if (slot) |pane| if (pane.repl) |r| if (r.lang == lang and replLive(pane) and n < bound.len) {
+ bound[n] = i;
+ n += 1;
+ };
+ if (n == 0) return .none;
+ if (n == 1) {
+ sendToRepl(p, from, bound[0], text);
+ return .{ .sent = bound[0] };
+ }
+ // Several: asked on the pane's notice band, as Del asks, one key
+ // answering; nothing is remembered of the answer.
+ const owned = p.gpa.dupe(u8, text) catch return .none;
+ if (p.repl_ask) |old| p.gpa.free(old.text);
+ p.repl_ask = .{ .from = src.serial, .lang = lang, .text = owned };
+ var w = std.Io.Writer.fixed(&src.question);
+ w.print("send to {s}:", .{grammars[lang].name}) catch {};
+ for (bound[0..n]) |i| w.print(" {c}", .{p.panes[i].?.repl.?.letter}) catch {};
+ w.writeAll(" (Esc: none)") catch {};
+ src.question_len = @intCast(w.buffered().len);
+ src.prompt = .repl_choice;
+ p.active = from;
+ return .asked;
+}
+
+/// Types `text` into the REPL in `to`, bracketed if its program asked for
+/// that, then Enter; says so on `from`'s message row and in the log.
+pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void {
+ const dst = p.panes[to] orelse return;
+ const src = p.panes[from] orelse return;
+ var idbuf: [48]u8 = undefined;
+ const id = replId(&idbuf, dst);
+ edit.typeToTty(p, to, dst, text);
+ p.emitWrite(to, "\r");
+ // Line by line, a blank line inside a Python block ends the block.
+ if (!panes.Terminal.bracketedPaste(dst) and !dst.repl_warned and std.mem.startsWith(u8, id, "python") and
+ std.mem.indexOfScalar(u8, text, '\n') != null)
+ {
+ dst.repl_warned = true;
+ var warn: [128]u8 = undefined;
+ p.setMessage(from, std.fmt.bufPrint(&warn, "Repl: {s} takes no bracketed paste; a blank line in a block ends it", .{id}) catch "Repl: no bracketed paste");
+ }
+ var said: [64]u8 = undefined;
+ p.setMessage(from, std.fmt.bufPrint(&said, "\u{2192} {s}", .{id}) catch "sent");
+ src.msg_sent = true;
+ var rec: [96]u8 = undefined;
+ pardes.ctlfs.events.notePath(p, "send", std.fmt.bufPrint(&rec, "{d} {d} {s}", .{ src.serial, dst.serial, id }) catch return);
+}
+
/// The longest line a command pane runs.
pub const command_max = 1024;
@@ -381,6 +472,11 @@ const max_exec_depth = 8;
/// done, and which Kill signals. Answers the pane it went to.
pub fn execute(p: *Pardes, id: usize, txt: []const u8) ?usize {
const pane = p.panes[id] orelse return null;
+ // Made by a gesture on a body, it may be a bound REPL's; run by name
+ // (`Exec ...`, which runs through here again) or written in a command
+ // word, it never is.
+ const gesture = p.exec_from_body and config.commandWord(std.mem.trim(u8, txt, " \t\r\n")) == null;
+ p.exec_from_body = false;
const cmd = commandText(txt);
if (cmd.len == 0) return null;
if (p.exec_depth == 0) p.noteHaptic(.exec);
@@ -388,6 +484,11 @@ pub fn execute(p: *Pardes, id: usize, txt: []const u8) ?usize {
if (p.exec_depth >= max_exec_depth) return null;
p.exec_depth += 1;
defer p.exec_depth -= 1;
+ if (gesture) switch (routeToRepl(p, id, cmd)) {
+ .none => {},
+ .sent => |to| return to,
+ .asked => return null,
+ };
if (!takesCommandLine(p, id)) return runCommand(p, id, cmd);
panes.Terminal.padOutputBelowEdits(p, id);
panes.Terminal.noteCommand(pane, cmd);