diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CHANGELOG.md | 20 | ||||
| -rw-r--r-- | src/File.zig | 4 | ||||
| -rw-r--r-- | src/Messages.zig | 2 | ||||
| -rw-r--r-- | src/Output.zig | 2 | ||||
| -rw-r--r-- | src/builtins.zig | 32 | ||||
| -rw-r--r-- | src/config.zig | 11 | ||||
| -rw-r--r-- | src/crash.zig | 2 | ||||
| -rw-r--r-- | src/draw.zig | 26 | ||||
| -rw-r--r-- | src/fs.zig | 19 | ||||
| -rw-r--r-- | src/mouse.zig | 161 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 6 | ||||
| -rw-r--r-- | src/pardes.zig | 12 | ||||
| -rw-r--r-- | src/tutor.txt | 4 |
13 files changed, 245 insertions, 56 deletions
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index a2f493df..42f2eb6f 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,12 +1,30 @@ # Changelog -## Unreleased +## 0.0.6 +- A middle or right sweep in a column tag or the workspace tag runs or looks + up the swept text on release, highlighted while it is held, as in a pane's + tag and as in acme; a press used to act on the word under it at once. +- `Config` (`SPC f c`) opens the startup file, `init`, in a pane, or goes + to its pane when it is open. With no file yet, it opens an empty pane of + that name, and Save writes it, making the directory first. The settings + report `Config` used to open is `DumpConfig`, in a `+DumpConfig` pane. +- The workspace tag reads `… Dump Themes Config Debug Exit`: `Themes` in + place of `NextColor`, which stays a builtin, and `Config` beside it. A + workspace tag you edited, and one a dump kept, stays as it is. +- F1-F25, Insert and the keypad reach the GUI's terminal panes too: one key + map serves the tty, the GUI and the board. +- A session restored from a dump is built as a fresh one is: its column and + workspace tags hold several selections again, and 9P mtimes are real. +- Zig files use `zls` from `PATH` as a language server like any other; the + in-process ZLS backend is gone, and the binary is about 9 MB smaller. - Dumps and init files from before 0.0.4 no longer load their old tag formats: a restored pane's tag tail is read only from `tag_tail`, and an old terminal's `TTY` tag or raw output tail is not replayed. `ThemeSel`, `FontSel` and `ColumnTags` are no longer recognised in init files or dumps (`Themes` and `Fonts` are the words). +- The GUI always embeds its shader sources; the prebuilt GLSL copies are + gone. `lspprobe` is `zig build lspbench -- probe`. ## 0.0.5 diff --git a/src/File.zig b/src/File.zig index 8babb27d..e8a9e2f8 100644 --- a/src/File.zig +++ b/src/File.zig @@ -58,6 +58,10 @@ pub const State = struct { /// as an undo to the save point is in acme. saved_hash: ?u64 = null, watch_after_save: bool = false, + /// Save makes the file's directory, and any above it, first: `Config`'s + /// pane for an init file that is not there yet, whose directory may not + /// be either. + make_dirs: bool = false, /// The file changed on disk under unsaved edits, which were kept (see /// `changed`): its hash, so the same change is said once, until a Save /// or get settles it. A Save warns once before overwriting it, as acme's diff --git a/src/Messages.zig b/src/Messages.zig index 7dcb22ed..d1478ac7 100644 --- a/src/Messages.zig +++ b/src/Messages.zig @@ -782,7 +782,7 @@ test "a message falls in, lingers past the input that dismisses it, and dissolve try std.testing.expect(p.executeBuiltinLine(p.active, "MessageDissolve 0")); try std.testing.expectEqual(@as(u16, 3), messageFrames(p.settings.message_fall_ms)); try std.testing.expectEqual(@as(u16, 1), messageFrames(p.settings.message_dissolve_ms)); - // The Config report reads back as configuration. + // The DumpConfig report reads back as configuration. try std.testing.expect(p.executeBuiltinLine(p.active, "MessageAnimation")); try std.testing.expect(!p.settings.message_animation); try std.testing.expect(p.executeBuiltinLine(p.active, "Msg plain")); diff --git a/src/Output.zig b/src/Output.zig index 5bc1f9fc..0581be41 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -827,7 +827,7 @@ pub fn openConfig(p: *Pardes, id: usize) !void { .state = &p.settings, }); const content = try out.toOwnedSlice(); - return openRead(p, id, .{ .cmd = .Config }, "", content); + return openRead(p, id, .{ .cmd = .DumpConfig }, "", content); } /// The message-row log, oldest first — the lines that were said in passing and diff --git a/src/builtins.zig b/src/builtins.zig index c549b17f..c40b2e00 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -1283,10 +1283,38 @@ pub const Help = struct { } }; -/// Open the settings in effect, each a word to click. +/// Open the startup file, `init`, in a pane: its pane when it is open, else +/// the file, else a pane of that name for Save to make it and its directory. pub const Config = struct { pub const scope: Scope = .session; - pub const output: OutputTraits = .{ .name = config.config_buffer }; + pub fn run(c: Ctx) void { + const path = c.p.opts.startup_config_path orelse + return c.p.reportFailure(c.id, "Config: no startup file here: no configuration directory"); + var real_buf: [4096]u8 = undefined; + const real = if (pardes.filesystem.resolveOs(path, &real_buf)) |r| r.path else null; + for (c.p.panes, 0..) |slot, id| { + const pane = slot orelse continue; + const f = pane.file orelse continue; + if (f.output != null) continue; + if (!std.mem.eql(u8, f.path, path) and !(if (real) |r| std.mem.eql(u8, f.path, r) else false)) continue; + return look.focusPaneLine(c.p, id, .{}, .keep); + } + if (real != null) return look.lookAt(c.p, c.id, path); + // Not there yet: a pane named for it, which Save makes. + const free = c.p.freeSlot() orelse return c.p.reportError(c.id, "Config", error.NoPaneSlots); + c.p.newScratchFor(c.id, .scratch); + const pane = c.p.panes[free] orelse return; + @import("ninep/pane.zig").nameBuffer(c.p, free, path, false) catch |err| return c.p.reportError(c.id, "Config", err); + pane.file.?.make_dirs = true; + c.p.active = free; + } +}; + +/// Open the settings in effect, each a word to click, and the startup +/// file's path. +pub const DumpConfig = struct { + pub const scope: Scope = .session; + pub const output: OutputTraits = .{ .name = config.dump_config_buffer }; pub fn run(c: Ctx) void { panes.Output.openConfig(c.p, c.id) catch |err| c.p.reportError(c.id, "config", err); } diff --git a/src/config.zig b/src/config.zig index a8b4c24e..a70ae708 100644 --- a/src/config.zig +++ b/src/config.zig @@ -68,6 +68,7 @@ pub const leader_path = paths: { .Recent = "fr", .Grep = "fg", .Config = "fc", + .DumpConfig = null, .LocationsConfig = null, .Tutor = "ht", .Changelog = "hc", @@ -266,7 +267,7 @@ pub const child_term = "xterm-256color"; pub const child_colorterm = "truecolor"; pub const child_term_program = "pardes"; -pub const topbar_str = "Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit"; +pub const topbar_str = "Newcol Joincol Find Grep Help Changelog Tutor Dump Themes Config Debug Exit"; pub const pane_builtins_str = "Tty Collapse Del"; pub const pdf_pane_builtins_str = "Tty PdfSections PdfTint Collapse Del"; @@ -452,7 +453,7 @@ pub const pipe_marker = " |"; pub const search_buffer = "+Search"; pub const help_buffer = "+Help"; -pub const config_buffer = "+Config"; +pub const dump_config_buffer = "+DumpConfig"; pub const effect_code_buffer = "+EffectCode"; pub const jumps_buffer = "+Jumps"; pub const recent_buffer = "+Recent"; @@ -968,7 +969,7 @@ pub const Runtime = struct { } }; - // The builtin registry and Config report share this command table. + // The builtin registry and DumpConfig report share this command table. pub const settings = [_]Setting{ .{ .word = "Colors", .action = .{ .toggle = .colors }, .doc = "Syntax colours in text panes, on or off." }, .{ .word = "Wrap", .action = .{ .toggle = .wrap }, .doc = "Long lines wrap onto the next row, on or off." }, @@ -1291,7 +1292,7 @@ pub const Runtime = struct { /// Each line reads as the command that would set it: the word, then the /// value you would type after it. No colons, no units — `WindowOpacity 70` - /// is both what Config prints and what you write back. + /// is both what DumpConfig prints and what you write back. pub fn writeReport(out: *std.Io.Writer, context: ReportContext) !void { const state = context.state; var wrote_transition = false; @@ -1650,7 +1651,7 @@ pub const Runtime = struct { try std.testing.expect(FontSpec.parse(bad) == null); } - test "Config report observes every simple setting and all live context" { + test "DumpConfig report observes every simple setting and all live context" { var state: Runtime = .{}; // Every setting, every context line: the report outgrows 4K. var storage: [8192]u8 = undefined; diff --git a/src/crash.zig b/src/crash.zig index 10d38591..99822aad 100644 --- a/src/crash.zig +++ b/src/crash.zig @@ -23,7 +23,7 @@ const std = @import("std"); const builtin = @import("builtin"); const pardes = @import("pardes.zig"); -/// Beside `init`, so `Config` opens the directory that holds both. +/// The crash log's name, beside `init` in the configuration directory. pub const name = "crashes"; /// The config directory `config.User.load` resolved, COPIED rather than diff --git a/src/draw.zig b/src/draw.zig index ad24153f..0ea0b35e 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -1109,8 +1109,9 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, // The word under the pointer, a Look or Exec target: on a header even // while it is edited, on a notice, and on the grid's copy of either // (joined from this); not on a column a carried column's pointer is - // aiming at a place over. - const hovered = p.pointer_inside and !(kind == .column and p.drag == .column_move); + // aiming at a place over; nor under a held middle or right sweep, + // which paints what it sweeps instead. + const hovered = p.pointer_inside and !(kind == .column and p.drag == .column_move) and p.header_sweep == null; if (hovered) { const tag_hit = p.pointer_tag_hit; const col: ?usize = if (tag_hit) |pointer| @@ -1141,6 +1142,27 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, const asking = if (p.panes[p.active]) |active| active.prompt != .none else false; if (!asking and t.cur_row == line + p.header_top and caret < columns) layer.cursor = .{ .x = @intCast(caret), .y = line, .bar = t.mode == .insert }; } + // A middle or right sweep held over this header, tinted by its + // button as a pane tag's is. + if (p.header_sweep) |sweep| if (sweep.column(p)) |column| { + const mine = switch (kind) { + .workspace => column == null, + .column => column == id, + .pane, .notice, .body => false, + }; + const swept = sweep.span(text.len); + const start = modal.lineStartOffset(text, index + top); + const end = start + shown.len; + if (mine and swept.lo < swept.hi and swept.hi > start and swept.lo < end) { + const button = @intFromEnum(sweep.button); + var col = panes.File.rawDisplayCol(shown, @max(swept.lo, start) - start) -| scroll; + const stop = panes.File.rawDisplayCol(shown, @min(swept.hi, end) - start) -| scroll; + while (col < stop and col < columns) : (col += 1) { + surface.at(@intCast(col), 0).style.bg = .{ .rgb = body_layer.buttonTints(p.theme())[button] }; + surface.at(@intCast(col), 0).style.fg = .{ .rgb = body_layer.buttonInks(p.theme())[button] }; + } + } + } else |_| {}; } } @@ -637,6 +637,25 @@ test "a path with a part over 255 bytes is NameTooLong, never a panic" { try std.testing.expectError(error.NameTooLong, statPath(std.testing.io, long, .{})); } +/// Make `dir` and each directory above it that is missing, as `mkdir -p` +/// does: one already there is no failure. A failure shows at the open +/// that follows. +pub fn makeDirs(dir: []const u8) void { + if (comptime !platform_has_fs) return; + var z: [4096:0]u8 = undefined; + if (dir.len == 0 or dir.len >= z.len) return; + @memcpy(z[0..dir.len], dir); + z[dir.len] = 0; + pardes.turn.yield(); + defer pardes.turn.back(); + for (z[1..dir.len], 1..) |c, i| if (c == '/') { + z[i] = 0; + _ = libc.mkdir(z[0..i :0], 0o755); + z[i] = '/'; + }; + _ = libc.mkdir(z[0..dir.len :0], 0o755); +} + pub fn localPath(path: []const u8) ?[]const u8 { if (std.mem.eql(u8, path, "/n/os")) return "/"; if (std.mem.startsWith(u8, path, "/n/os/")) return path[5..]; diff --git a/src/mouse.zig b/src/mouse.zig index cb0378de..2280020d 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -70,6 +70,84 @@ pub const Drag = union(enum) { } }; +/// A middle or right sweep over a column's tag or the workspace's, held as +/// a pane tag's is: painted in its button's tint while the button is down, +/// and run (or looked for) in that tag's context when it comes up. `anchor` +/// and `cur` are byte offsets into the header's text. +pub const HeaderSweep = struct { + /// The column's serial; null for the workspace's tag. + serial: ?u32, + button: Mouse.Button, + anchor: usize, + cur: usize, + + /// The column index the sweep is over, or null for the workspace's + /// tag; `error.Gone` once its column has been closed. + pub fn column(s: HeaderSweep, p: *const Pardes) error{Gone}!?usize { + const serial = s.serial orelse return null; + return layout.columnBySerial(p, serial) orelse error.Gone; + } + + /// The swept bytes, empty for a click that has not moved. + pub fn span(s: HeaderSweep, len: usize) struct { lo: usize, hi: usize } { + return .{ .lo = @min(s.anchor, s.cur, len), .hi = @min(@max(s.anchor, s.cur), len) }; + } +}; + +/// What a pointer at (mcol, mrow) is over in a column's tag, or the +/// workspace's for null: the header's text, the line, and the byte offset +/// in that text. `clamp` holds a held sweep to the header's rows and text, +/// as a pane tag's sweep stays on its tag. +fn headerPoint(p: *Pardes, column: ?usize, mcol: u16, mrow: u16, clamp: bool) ?struct { t: *panes.Text, text: []const u8, line: u16, at: usize } { + const t = if (column) |c| &p.col_tags[c] else &p.global_tag; + const x = if (column) |c| p.col_x[c] + TAG_TEXT_INSET else 0; + const focused = p.header_focus and p.header_column == column; + const text = edit.flatSurface(p, t) catch return null; + const base = if (column != null) p.topBarHeight() else 0; + const rows = if (column != null) p.columnBarHeight() else p.topBarHeight(); + const row: u16 = if (clamp) @min(mrow -| base, rows -| 1) else mrow -| base; + const top: usize = if (focused) p.header_top else 0; + const lines = modal.lineCount(text); + const line: u16 = if (clamp) @intCast(@min(row + top, lines -| 1)) else @intCast(row + top); + if (line > 0 and line >= lines) return null; + const bar = modal.lineSlice(text, line); + const scroll = if (focused) p.header_scroll else 0; + const kind: Layer.Kind = if (column != null) .column else .workspace; + const at = @min(bar.len, panes.File.rawAtDisplay(bar, (Layer.columnAt(p, kind, column orelse 0, p.pointer_tag_hit, clamp, row) orelse (mcol -| x)) + scroll)); + return .{ .t = t, .text = text, .line = line, .at = modal.lineStartOffset(text, line) + at }; +} + +/// A header sweep's button came up: a click runs the word under it, or the +/// header's selection when it is on that; a sweep runs what it swept. +fn headerSweepRun(p: *Pardes, sweep: HeaderSweep) void { + const column = sweep.column(p) catch return; + const t = if (column) |c| &p.col_tags[c] else &p.global_tag; + const text = edit.flatSurface(p, t) catch return; + const swept = sweep.span(text.len); + const selected = if (swept.lo < swept.hi) text[swept.lo..swept.hi] else click: { + const line = std.mem.count(u8, text[0..swept.lo], "\n"); + const start = modal.lineStartOffset(text, line); + const bar = modal.lineSlice(text, line); + const at = swept.lo - start; + const focused = p.header_focus and p.header_column == column; + const on = if (focused) tagline.headerSelection(t, text, line) else null; + break :click if (on) |sp| (if (at >= sp.lo and at < sp.hi) bar[sp.lo..sp.hi] else wordAtCol(bar, at)) else wordAtCol(bar, at); + }; + const word = p.scratch.allocator().dupe(u8, selected) catch return; + tagline.exitHeader(p); + // A column tag's command acts on that column, empty or not. + p.exec_column = column; + defer p.exec_column = null; + p.exec_header = true; + defer p.exec_header = false; + if (word.len == 0) return; + if (sweep.button == config.exec_button) { + const named = std.meta.stringToEnum(Builtin, word); + const held = if (named) |b| (if (builtins.registry.takesArg(b)) exec.heldSelection(p, p.active) else null) else exec.heldSelection(p, p.active); + _ = exec.execute(p, p.active, exec.withArg(p, word, held)); + } else exec.runBuiltin(p, config.look_cmd, p.active, "", word); +} + fn clampBorderCol(lx: u16, lw: u16, rw: u16, mcol: u16) u16 { const lo = lx + config.MINW; const hi = lx +| lw +| rw -| config.MINW; @@ -255,7 +333,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { const previous_tag_hit = p.pointer_tag_hit; p.pointer_tag_hit = if (m.tag_hit) |point| p.reprojectTagHit(point) else null; if (m.tag_hit != null and p.pointer_tag_hit == null and m.kind == .press) return; - if (m.tag_hit == null and (m.kind == .drag or m.kind == .release) and (p.drag == .select or p.header_drag)) { + if (m.tag_hit == null and (m.kind == .drag or m.kind == .release) and (p.drag == .select or p.header_drag or p.header_sweep != null)) { if (previous_tag_hit) |previous| { var continued = previous; continued.pixel_x = (@as(f32, @floatFromInt(raw_col)) + 0.5) * @as(f32, @floatFromInt(previous.metrics.body_w)); @@ -263,7 +341,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { p.pointer_tag_hit = continued; } } - if ((m.kind == .drag or m.kind == .release) and (p.drag == .select or p.header_drag)) { + if ((m.kind == .drag or m.kind == .release) and (p.drag == .select or p.header_drag or p.header_sweep != null)) { if (previous_tag_hit) |previous| if (p.pointer_tag_hit) |*current| { current.kind = previous.kind; current.id = previous.id; @@ -289,7 +367,13 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { p.raw_hover_intent = false; const mapped = p.presentation.pointer(p.screen_w, p.screen_h, raw_col, raw_row) orelse { look.cancelLookHover(p); - if (m.kind == .release) dragRelease(p, m.button); + if (m.kind == .release) { + if (p.header_sweep) |sweep| if (sweep.button == m.button) { + p.header_sweep = null; + return headerSweepRun(p, sweep); + }; + dragRelease(p, m.button); + } return; }; const mcol = mapped.col; @@ -309,6 +393,32 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { p.chord_arg = null; } } + if (p.header_sweep) |*sweep| { + if (m.kind == .press and switch (m.button) { + .left, .middle, .right => true, + else => false, + }) { + // acme 2-3 / 3-2: the other of execute and look cancels the + // sweep. The select button is inert: a header's command already + // takes the held selection as its argument, which is what a 2-1 + // chord would give it. + if (m.button != config.select_button) p.header_sweep = null; + return; + } + if (m.button == sweep.button and (m.kind == .drag or m.kind == .release)) { + const column = sweep.column(p) catch { + p.header_sweep = null; + return; + }; + if (headerPoint(p, column, mcol, mrow, true)) |point| sweep.cur = point.at; + if (m.kind == .release) { + const done = sweep.*; + p.header_sweep = null; + headerSweepRun(p, done); + } + return; + } + } if (mrow >= p.topBarHeight() and mrow < p.topBarHeight() + p.columnBarHeight() and m.kind == .press) { for (0..p.ncol) |column| { if (mcol < p.col_x[column] or mcol >= p.col_x[column] + @min(config.GUTTER, p.col_w[column])) continue; @@ -360,43 +470,26 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { }; if (column == null or mcol < x) return; } - const t = if (column) |c| &p.col_tags[c] else &p.global_tag; - const focused = p.header_focus and p.header_column == column; - const text = edit.flatSurface(p, t) catch return; - const row: u16 = @intCast(mrow - if (column != null) p.topBarHeight() else 0); - const line: u16 = @intCast(row + if (focused) p.header_top else 0); - if (line > 0 and line >= modal.lineCount(text)) return; - const bar = modal.lineSlice(text, line); - const scroll = if (focused) p.header_scroll else 0; - const at = @min(bar.len, panes.File.rawAtDisplay(bar, (Layer.columnAt(p, if (column != null) .column else .workspace, column orelse 0, p.pointer_tag_hit, p.header_drag, row) orelse (mcol -| x)) + scroll)); + const point = headerPoint(p, column, mcol, mrow, false) orelse return; if (m.button == config.select_button) { + const t = point.t; + const at = point.at - modal.lineStartOffset(point.text, point.line); // A clicked header is typed straight into, as a clicked tag is. - if (!focused) tagline.enterHeader(p, column); + if (!(p.header_focus and p.header_column == column)) tagline.enterHeader(p, column); t.mode = .insert; - t.cur_row = line; + t.cur_row = point.line; t.cur_col = @intCast(at); - t.vsel = .{ .active = false, .row = line, .col = @intCast(at) }; + t.vsel = .{ .active = false, .row = point.line, .col = @intCast(at) }; p.header_drag = true; } else { - const span = if (focused) tagline.headerSelection(t, text, line) else null; - const selected = if (span) |sp| - (if (at >= sp.lo and at < sp.hi) bar[sp.lo..sp.hi] else wordAtCol(bar, at)) - else - wordAtCol(bar, at); - const word = p.scratch.allocator().dupe(u8, selected) catch return; - tagline.exitHeader(p); - // A column tag's command acts on that column, empty or not. - p.exec_column = column; - defer p.exec_column = null; - p.exec_header = true; - defer p.exec_header = false; - if (word.len > 0) { - if (m.button == config.exec_button) { - const named = std.meta.stringToEnum(Builtin, word); - const held = if (named) |b| (if (builtins.registry.takesArg(b)) exec.heldSelection(p, p.active) else null) else exec.heldSelection(p, p.active); - _ = exec.execute(p, p.active, exec.withArg(p, word, held)); - } else exec.runBuiltin(p, config.look_cmd, p.active, "", word); - } + // Held, as a pane tag's sweep is: it runs when the button + // comes up (headerSweepRun). + p.header_sweep = .{ + .serial = if (column) |c| p.col_serial[c] else null, + .button = m.button, + .anchor = point.at, + .cur = point.at, + }; } return; } diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 060e0d6c..5ed03c58 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -485,7 +485,7 @@ const e_prompt = "control message needs its argument"; /// Every setting, one a line, in the words a write of it takes, so that the /// file read back is the configuration, as webfs's ctl reads (webfs(4)). /// What is not a setting -- platform, themes, where the config came from -- -/// is /status's or the Config window's. +/// is /status's or the DumpConfig window's. fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void { try writeSettingsOf(p, &p.settings, w); try w.writeAll("LocationsConfig "); @@ -523,7 +523,7 @@ pub fn writeSettingsOf(p: *Pardes, s: *const config.Runtime, w: *std.Io.Writer) inline else => |field| try w.print("{s} {s}\n", .{ word, @tagName(@field(s, @tagName(field))) }), }, .message_ms => |which| try w.print("{s} {d}\n", .{ word, @field(s, "message_" ++ @tagName(which) ++ "_ms") }), - // The directory in effect, as Config reports it; bare only when + // The directory in effect, as DumpConfig reports it; bare only when // there is none (no home to put the default in). .dump_dir => { var buf: [900]u8 = undefined; @@ -1165,7 +1165,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi try testing.expectEqual(@as(u64, settings.len), call(p, .{ .tag = 1, .op = .getattr, .node = root_ctl }).reply.attr.size); try testing.expect(std.mem.indexOf(u8, settings, "Verbose on\n") != null); try testing.expect(std.mem.indexOf(u8, settings, "LocationsConfig ") != null); - // Settings only: nothing /status or the Config window says. + // Settings only: nothing /status or the DumpConfig window says. try testing.expect(std.mem.indexOf(u8, settings, "Platform") == null); try testing.expect(std.mem.indexOf(u8, settings, "requested") == null); diff --git a/src/pardes.zig b/src/pardes.zig index 151fe7b0..a63568db 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1199,7 +1199,7 @@ test "acme placement: New and 9P's new take the bottom half of the last pane" { try std.testing.expect(p.rects[made].y > was.y + BOX_H); } -test "Placement pardes brings back the old rules, and says so in Config" { +test "Placement pardes brings back the old rules, and says so in DumpConfig" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 40 }); defer p.deinit(); try std.testing.expect(p.executeBuiltinLine(p.active, "Placement pardes")); @@ -3336,7 +3336,7 @@ test "s in a header selects in the header's own text" { var ranges: [Text.max_selections]modal.Selection = undefined; const text = try edit.flatSurface(p, &p.global_tag); const got = p.global_tag.ranges(text, 0, &ranges); - try std.testing.expectEqual(@as(usize, 3), got.n); // Newcol, Joincol, NextColor + try std.testing.expectEqual(@as(usize, 2), got.n); // Newcol, Joincol try std.testing.expectEqual(@as(u16, 0), pane.body.nsel); } @@ -4317,6 +4317,8 @@ pub const Pardes = struct { header_column: ?usize = null, /// A mouse sweep is extending the focused header's selection. header_drag: bool = false, + /// A middle or right sweep held over a column's or the workspace's tag. + header_sweep: ?mouse.HeaderSweep = null, header_scroll: usize = 0, /// The focused header's first line on screen, which follows its caret /// when it has more lines than rows. @@ -5102,7 +5104,7 @@ pub const Pardes = struct { } /// Resolve the request already handed to the GUI host. Requested and - /// effective tuples deliberately remain separate so Config can show both. + /// effective tuples deliberately remain separate so DumpConfig can show both. pub fn acknowledgeFont( p: *Pardes, effective_name: []const u8, @@ -5437,6 +5439,8 @@ pub const Pardes = struct { const was = f.saved_revision; const failures = p.fs.failures; f.saved_revision = f.revision; + if (f.make_dirs) if (filesystem.localPath(f.path)) |local| + filesystem.makeDirs(std.fs.path.dirname(local) orelse "/"); p.hostWriteFile(sf.pane, f.path, f.content); const saved_pane = p.panes[sf.pane] orelse return; if (saved_pane.serial != serial) return; @@ -6562,7 +6566,7 @@ pub const Pardes = struct { } /// A native post pass proved unavailable: the chain is emptied, which - /// Config then reports. + /// DumpConfig then reports. pub fn disableSceneEffects(p: *Pardes) void { p.settings.post.clear(); } diff --git a/src/tutor.txt b/src/tutor.txt index c59deeae..f8aa7aef 100644 --- a/src/tutor.txt +++ b/src/tutor.txt @@ -65,8 +65,8 @@ Every pane has Collapse: hide its body, then execute again to expand it. The top bar is: - Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor - Debug Exit + Newcol Joincol Find Grep Help Changelog Tutor Dump Themes + Config Debug Exit Reach it from the keyboard with `k` off the topmost tagline; `j` comes back down. |
