diff options
Diffstat (limited to 'src/exec.zig')
| -rw-r--r-- | src/exec.zig | 160 |
1 files changed, 106 insertions, 54 deletions
diff --git a/src/exec.zig b/src/exec.zig index ee7c31e9..117970d9 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -322,12 +322,7 @@ fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane { nt.greet = !v9fs; nt.v9fs_on_spawn = v9fs; nt.body.mode = .tty; - const parent = layout.splitParent(p, from); - const f = layout.findPane(p, parent).?; - layout.insert(p, f.col, f.idx + 1, free); - layout.splitBelow(p, parent, nt); - p.active = free; - p.stillborn_joiner = nt.serial; + placeNew(p, from, from, free, .shell); return nt; } @@ -357,29 +352,6 @@ pub fn evictLonePristineTty(p: *Pardes, col: usize, keep_id: usize) void { if (p.active == tty_id) p.active = keep_id; } -/// A pane that just joined a column whose only other pane is stillborn — -/// an empty scratch nobody typed into, body or tag (what Newcol makes and -/// what a closed column's last pane leaves behind) — takes the column -/// whole: the placeholder was only holding the space for it. Run at the -/// end of a step: the pane that asked for the joiner is often the -/// placeholder itself, and its caller is still holding it. -pub fn replaceStillborn(p: *Pardes) void { - const serial = p.stillborn_joiner orelse return; - p.stillborn_joiner = null; - const new_id = p.paneBySerial(serial) orelse return; - const f = layout.findPane(p, new_id) orelse return; - if (p.col_n[f.col] != 2) return; - const old_id = p.col_panes[f.col][1 - f.idx]; - const old = p.panes[old_id] orelse return; - const file = old.file orelse return; - const out = file.output orelse return; - if (out.from != .cmd or out.from.cmd != .New) return; - if (file.revision != 0 or file.content.len != 0) return; - if (old.tag.own) |own| if (!std.mem.eql(u8, own, tagline.defaultTail(old))) return; - layout.compute(p); // the joiner may have no rect yet; absorb snaps to rows - p.removePane(old_id, new_id) catch |err| return p.reportError(old_id, "close", err); -} - // ---- REPLs ---- // // A terminal is bound as a language's REPL by `Repl python` (builtins.zig). @@ -552,7 +524,7 @@ pub fn executeFrom(p: *Pardes, id: usize, txt: []const u8, from_body: bool) ?usi /// Runs `line` in a command pane for `from`'s directory: `from` itself if /// it is one whose command is done, else the directory's command pane that -/// is done, else a new one below the last pane of the rightmost column. +/// is done, else a new one, placed as placeNew places a command pane. /// A pane reused keeps what it showed: the new command runs below a line /// naming it, as acme appends to +Errors and never clears it (util.c:213). fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { @@ -607,13 +579,7 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { return null; }; echoCommand(p, pane, line); - const rc = if (p.ncol > 0) p.ncol - 1 else 0; - if (p.col_n[rc] > 0) { - const parent = layout.splitParent(p, p.col_panes[rc][p.col_n[rc] - 1]); - const f = layout.findPane(p, parent).?; - layout.splitBelow(p, parent, pane); - layout.insert(p, f.col, f.idx + 1, free); - } else layout.insert(p, rc, p.col_n[rc], free); + placeNew(p, from, from, free, .command); noteRun(p, pane, "run", line); return free; } @@ -691,7 +657,24 @@ pub fn applyStartupConfig(p: *Pardes) void { p.announce = false; defer p.announce = true; var lines = std.mem.splitScalar(u8, p.opts.startup_config orelse return, '\n'); - while (lines.next()) |line| _ = executeBuiltinLine(p, p.active, line); + while (lines.next()) |line| { + if (executeBuiltinLine(p, p.active, line)) continue; + // A setting pardes no longer has is said, once, and changes nothing. + const word = commandText(line); + if (std.mem.eql(u8, word[0 .. std.mem.indexOfAny(u8, word, " \t") orelse word.len], "ColumnTags")) + p.setMessage(p.active, "ColumnTags: column tags are always shown now; the init line is ignored"); + } +} + +test "an init file's retired ColumnTags line is said and ignored" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 }); + defer p.deinit(); + p.opts.startup_config = "ColumnTags\nWrap off\n"; + applyStartupConfig(p); + try std.testing.expect(!p.settings.wrap); + try std.testing.expect(p.columnBarHeight() > 0); + const pane = p.panes[p.active].?; + try std.testing.expectEqualStrings("ColumnTags: column tags are always shown now; the init line is ignored", pane.msg[0..pane.msg_len]); } pub fn runBuiltin(p: *Pardes, b: Builtin, id: usize, txt: []const u8, arg: ?[]const u8) void { @@ -718,7 +701,6 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[] const previous_context_style = p.settings.tree_context_tag_style; const previous_transition = p.settings.panel_transition; const previous_tagline_percent = p.settings.font.tagline_percent; - const previous_column_tags = p.settings.column_tags; switch (setting.action) { .theme => { const want = std.mem.trim(u8, arg orelse return, " \t\r\n"); @@ -768,20 +750,98 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[] p.settings.panel_transition != previous_transition; const tagline_metrics_changed = setting.action == .tagline_size and p.settings.font.tagline_percent != previous_tagline_percent; - if (p.settings.column_tags != previous_column_tags) { - if (p.header_column != null) tagline.exitHeader(p); - } // Idle frames keep no frozen grid while transitions are off; the one // just drawn is the baseline the first transition starts from. if (transition_changed and previous_transition == .off) p.presentation.capturePrevious(p.gpa, &p.surface); - if (transition_changed or tagline_metrics_changed or p.settings.column_tags != previous_column_tags) { + if (transition_changed or tagline_metrics_changed) { p.abandonPanelAnimations(); } } -pub fn placeDoc(p: *Pardes, from_id: usize, free: usize, nt: *Pane) void { - p.stillborn_joiner = nt.serial; +/// What a new pane is, and who asked: each rule places them differently. +/// `scratch` is New's, `fs` 9P's pane/new, `errors` a +Errors pane. +pub const NewPane = enum { scratch, fs, shell, doc, command, errors }; + +/// Put new pane `id` in the layout: every new pane comes through here, by +/// the `Placement` setting, and every one but a command pane takes the +/// keyboard. `from` is the pane that asked, `origin` the text it asked +/// from: null when a column tag, the workspace tag or 9P asked (acme's +/// t->w == nil). +pub fn placeNew(p: *Pardes, from: usize, origin: ?usize, id: usize, kind: NewPane) void { + const asked = if (p.exec_column != null) null else origin; + switch (p.settings.placement) { + .acme => switch (kind) { + // New: the bottom half of the last pane in its own column + // (look.c:921-923, et->col). + .scratch => layout.acmePlace(p, p.exec_column orelse columnOf(p, from), null, id), + // +Errors, a command's output: the last column (util.c:94-98). + .command, .errors => layout.acmePlace(p, p.ncol - 1, null, id), + // makenewwindow(nil) for 9P's new (acme.c:877), else (t). + .fs => layout.acmePlace(p, acmeColumn(p, from), null, id), + .shell, .doc => layout.acmePlace(p, acmeColumn(p, from), asked, id), + }, + .pardes => pardesPlace(p, from, id, kind), + } + if (kind == .command) return; + tagline.exitHeader(p); + p.active = id; + if (layout.findPane(p, id)) |f| p.setActiveColumn(f.col); +} + +/// acme's makenewwindow column (util.c:456-467): the column a command came +/// from (its tag's), else the active column, else the keyboard's pane's, +/// else the asking pane's. +fn acmeColumn(p: *Pardes, from: usize) usize { + if (p.exec_column) |c| return c; + if (p.activeColumn()) |c| return c; + if (layout.findPane(p, p.active)) |f| return f.col; + return columnOf(p, from); +} + +fn columnOf(p: *Pardes, id: usize) usize { + return if (layout.findPane(p, id)) |f| f.col else p.ncol - 1; +} + +/// The placements pardes had before it took acme's: an empty column whose +/// tag asked, or has the keyboard, is filled; a scratch goes under the +/// asking pane, a shell under it or the nearest pane with room, a document +/// beside the last one read or in a column of its own, and a command pane +/// at the foot of the last column. +fn pardesPlace(p: *Pardes, from: usize, id: usize, kind: NewPane) void { + const empty = if (p.exec_column orelse if (p.header_focus) p.header_column else null) |c| + (if (c < p.ncol and p.col_n[c] == 0) c else null) + else + null; + if (empty) |c| { + layout.insert(p, c, 0, id); + p.panes[id].?.vweight = 1; + return; + } + const nt = p.panes[id].?; + switch (kind) { + .scratch, .fs => if (layout.findPane(p, from)) |sf| { + layout.insert(p, sf.col, sf.idx + 1, id); + layout.splitBelow(p, from, nt); + }, + .shell => belowParent(p, from, id), + .doc, .errors => pardesDoc(p, from, id), + .command => { + const rc = p.ncol - 1; + if (p.col_n[rc] > 0) belowParent(p, p.col_panes[rc][p.col_n[rc] - 1], id) else layout.insert(p, rc, 0, id); + }, + } +} + +fn belowParent(p: *Pardes, from: usize, id: usize) void { + const parent = layout.splitParent(p, from); + const f = layout.findPane(p, parent).?; + layout.insert(p, f.col, f.idx + 1, id); + layout.splitBelow(p, parent, p.panes[id].?); +} + +fn pardesDoc(p: *Pardes, from_id: usize, free: usize) void { + const nt = p.panes[free].?; const doc = if (nt.file) |f| panes.Output.fileTraits(f.output).doc else true; // an image is a doc var src_id: ?usize = null; if (p.panes[from_id]) |src| if (if (src.file) |f| panes.Output.fileTraits(f.output).doc else src.image != null or src.hasPdf()) { @@ -808,22 +868,14 @@ pub fn placeDoc(p: *Pardes, from_id: usize, free: usize, nt: *Pane) void { if (!doc) if (layout.findPane(p, from_id)) |sf| { layout.insert(p, sf.col, sf.idx + 1, free); layout.splitBelow(p, from_id, nt); // NOT splitParent: no bystander pays - p.active = free; return; }; if (src_id == null and doc and p.ncol < MAX_COLS and layout.columnFitsHalves(p, from_id, 100) and layout.splitColumn(p, from_id, free, true)) - { - p.active = free; return; - } // Without room for a first-document column, split the Look origin // just like Tty. Never displace (or evict) a top-left bystander. - const src = layout.splitParent(p, src_id orelse from_id); - const sf = layout.findPane(p, src).?; - layout.insert(p, sf.col, sf.idx + 1, free); - layout.splitBelow(p, src, nt); - p.active = free; + belowParent(p, src_id orelse from_id, free); } test "the acme chords act once per selection, not once on the primary" { |
