diff options
Diffstat (limited to 'src/exec.zig')
| -rw-r--r-- | src/exec.zig | 101 |
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); |
