diff options
36 files changed, 749 insertions, 143 deletions
diff --git a/docs/config.md b/docs/config.md index 83fbd0eb..324b54e9 100644 --- a/docs/config.md +++ b/docs/config.md @@ -315,8 +315,10 @@ name, thirteen required RGB roles (`sel_bg`, `sel_fg`, `tag_bg`, `tag_fg`, `scroll_thumb`), nullable `bg`/`fg`, and either a 16-color RGB `palette` or `null`. Optional nullable RGB roles extend this format: `tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `tag_active_name_fg`, `border`, `lineno_active`, `search_bg`, `search_fg`, -`diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, and -`diagnostic_hint`. Missing new roles use backward-compatible defaults, so +`diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, +`diagnostic_hint`, `tag_sel_bg`, `tag_rule`, `box_border` and `box_dirty`; +`sweep_bg` and `sweep_fg` take three triples, and `rule_px` and `rail_px` a +pixel count. Missing new roles use backward-compatible defaults, so previously exported files remain valid. [Theme customization](themes.md) describes each role and its fallback. RGB values are three-byte arrays, and hex literals are accepted. The original fields remain required; there is no @@ -536,6 +538,8 @@ Lift auto a shadow on a light page; on a dark one the others recede Lift off InactiveDim 30 the unfocused panes' text fades 30% toward its ground Motion smooth off, crisp, smooth, bouncy or playful +GripWidth 150 the grip's button and the scrollbar under it, percent of the + theme's rail_px (12px at a 17px tagline; 50 to 300) ``` `InactiveDim` works everywhere (a grid shows it at once) and never takes a diff --git a/docs/effects.md b/docs/effects.md index fec2d13b..eefa52d3 100644 --- a/docs/effects.md +++ b/docs/effects.md @@ -6,7 +6,10 @@ selection) or reduce its contrast. The effects are sugar; the indicators are how a person knows where they are. Every G stage is reviewed against this. Lift, for one, falls only on pane bodies and rails, never on a tag, a grip or a header, and its strength is capped so text and the selection keep min(their -contrast, 4.5) (tests in src/gui/gui.zig). A dim touches only the unfocused +contrast, 4.5) (tests in src/gui/gui.zig). The rule binds effects, not a +theme's structure: the rules between panes (2px, `rule_px`) are the theme's +borders, and the one over a tag band stays in the band's slack, never over +its text. A dim touches only the unfocused panes, with the same floor, and the focused pane's text is never at less contrast than theirs (tests in src/draw.zig). diff --git a/docs/themes.md b/docs/themes.md index 8cc4ff9b..6b218b13 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -114,6 +114,12 @@ so existing exported themes remain valid. | `diagnostic_warning` | Warning text | Same foreground fallback | | `diagnostic_info` | Informational diagnostic text | Same foreground fallback | | `diagnostic_hint` | Hint text | Same foreground fallback | +| `tag_sel_bg` | A tag's selection ground | `sel_bg` | +| `sweep_bg`, `sweep_fg` | Three triples each: the select, exec and look sweeps' ground and ink | `sel_bg` tinted toward `kw`, `str` and `num`, in `sel_fg` | +| `tag_rule` | The one-pixel rule between a pane's tag and its body, quieter than the `rule_px` rules between panes and columns | Halfway between `tag_bg` and the page the shell draws | +| `rule_px` | Pixel shells: the width of the rules between columns, between panes stacked in a column, and under the workspace and column tags, in `border` (the rule between a tag and its body is always one pixel) | 2 | +| `box_border`, `box_dirty` | The grip is acme's button: `box` filled when focused, else a two-pixel ring of `box_border` round the tag's ground; `box_dirty` fills it inside either ring while its file is unsaved (a grid, which has no ring, shows a bold `*` on `box_dirty` in the grip's second cell) | `box_dim`; `diagnostic_warning`, then `num` | +| `rail_px` | Pixel shells: the scroll column's width, the grip's button over the scrollbar (thumb a pixel narrower), in pixels at a 17px tagline, scaled with it | 12, acme's Scrollwid | Native palettes give filenames a distinct hue at approximately the same brightness as the surrounding tag text. This also applies to output names diff --git a/src/body_layer.zig b/src/body_layer.zig index a0195fb8..875f2e36 100644 --- a/src/body_layer.zig +++ b/src/body_layer.zig @@ -156,6 +156,7 @@ pub fn paintPointerSelection( /// The selection colour of each pointer button's sweep: the select button's /// and the look and exec buttons', each tinted toward its own accent. pub fn buttonTints(th: *const colors.Theme) [3][3]u8 { + if (th.sweep_bg) |own| return own; const accent2 = if (std.mem.eql(u8, &th.num, &th.str)) th.comment else th.num; return .{ colors.mix(th.sel_bg, colors.mix(th.sel_bg, th.kw)), @@ -164,6 +165,11 @@ pub fn buttonTints(th: *const colors.Theme) [3][3]u8 { }; } +/// The ink over each button's sweep. +pub fn buttonInks(th: *const colors.Theme) [3][3]u8 { + return th.sweep_fg orelse @splat(th.sel_fg); +} + /// A pane's body, painted once. A file body with compact tree-sitter /// context rows is painted into its body layer -- more rows than the grid /// has, the context rows at the tagline's height -- and the grid's rows are @@ -312,7 +318,7 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane // A select sweep over the tag is the tag's selection, painted there // in the tag's own selection colours. if (b == pardes.sel_slot and @min(sl.r0, sl.r1) < pane.tag_rows) continue; - paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sl, pane.tag_rows, sel_btn[b], th.sel_fg); + paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sl, pane.tag_rows, sel_btn[b], buttonInks(th)[b]); } // modal line selection (helix `x`): whole body rows, normal mode only if ((pane.body.mode == .normal or modal_hover) and pane.body.msel.active) { diff --git a/src/builtins.zig b/src/builtins.zig index e49deb71..c25311e3 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -172,7 +172,7 @@ pub const registry = struct { if (@intFromEnum(b) == i) return switch (setting.action) { // a switch flips bare, and DumpDir bare is the default .toggle, .transition, .scene, .dump_dir, .lift => false, - .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms, .shader, .shader_animation, .motion, .inactive_dim => true, + .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms, .shader, .shader_animation, .motion, .inactive_dim, .grip_width => true, }; unreachable; } diff --git a/src/colors.zig b/src/colors.zig index aeff7b00..25aa838f 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -63,6 +63,29 @@ pub const Theme = struct { // The current line number is a quiet foreground cue, not a tag-colored // background block. Older themes fall back to their normal gutter ink. lineno_active: ?[3]u8 = null, + /// A tag's selection, where it differs from the body's (acme's #9eeeee). + tag_sel_bg: ?[3]u8 = null, + /// Each pointer button's sweep, select, exec and look (Mouse.Button + /// order), and the ink over it. Unset, tints of `sel_bg` in `sel_fg`. + sweep_bg: ?[3][3]u8 = null, + sweep_fg: ?[3][3]u8 = null, + /// A pixel shell's rules, in pixels (2 unset); the rule between a tag + /// and its body is always one. + rule_px: ?u8 = null, + /// A grip is acme's button: `box` filled when focused, else a ring of + /// `box_border` (unset: `box_dim`) round the tag's ground; `box_dirty` + /// (unset: `diagnostic_warning`, then `num`) fills it, in either ring, + /// while its file is unsaved. + box_border: ?[3]u8 = null, + box_dirty: ?[3]u8 = null, + /// The rule between a pane's tag and its body, one pixel, quieter than + /// the rules between panes (acme's #8888cc under its 2px black ones). + /// Unset, halfway between the tag's ground and the page. + tag_rule: ?[3]u8 = null, + /// A pixel shell's scroll column, the grip's button over the scrollbar, + /// in pixels at a 17px tagline, the thumb one narrower (acme's 12px + /// Scrollwid, the default). + rail_px: ?u8 = null, }; const curated = struct { @@ -108,6 +131,8 @@ fn fold(comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme { } pub const themes = fold(curated) ++ fold(@import("generated_themes")); +/// The first `native_count` of `themes` are pardes's own; the rest imported. +pub const native_count = @typeInfo(curated).@"struct".decls.len; /// Original and classic-inspired Pardes palettes precede retained imports. pub const native_theme_count = 15; diff --git a/src/config.zig b/src/config.zig index ed7bee92..24e058c7 100644 --- a/src/config.zig +++ b/src/config.zig @@ -118,6 +118,7 @@ pub const leader_path = paths: { if (builtins.capabilities.lift) table.set(.Lift, null); table.set(.Motion, null); table.set(.InactiveDim, null); + if (builtins.capabilities.lift) table.set(.GripWidth, null); if (builtins.capabilities.panel_transitions) { table.set(.PanelSlide, "as"); table.set(.PanelZoom, "az"); @@ -629,6 +630,10 @@ pub const Runtime = struct { /// percent (0 off): the focused pane wins by comparison, its tag tint /// untouched. Kept above min(each cell's contrast, 4.5). inactive_dim: u8 = 0, + /// The grip's button and the scrollbar under it, one width, as a percent + /// of the theme's `rail_px` (acme's 12px Scrollwid at a 17px tagline): + /// a pixel shell's (docs/themes.md). + grip_width: u16 = 150, /// How the fx track's animations move (animation.Motion): off for /// reduced motion. motion: animation.Motion.Flavour = .smooth, @@ -862,6 +867,7 @@ pub const Runtime = struct { motion, /// A percentage, 0 through 100. inactive_dim, + grip_width, transition: animation.Transition, scene: Scene, /// A Shadertoy file into the post chain or out of it; `off` empties @@ -899,6 +905,7 @@ pub const Runtime = struct { .{ .word = "Lift", .action = .lift, .availability = .lift }, .{ .word = "Motion", .action = .motion }, .{ .word = "InactiveDim", .action = .inactive_dim }, + .{ .word = "GripWidth", .action = .grip_width, .availability = .lift }, .{ .word = "MessageLinger", .action = .{ .message_ms = .linger } }, .{ .word = "MessageFall", .action = .{ .message_ms = .fall } }, .{ .word = "MessageDissolve", .action = .{ .message_ms = .dissolve } }, @@ -960,8 +967,8 @@ pub const Runtime = struct { @compileError("scene effects must use the scene-shader capability"), // A toggle is plain state any shell can hold, except one for // what only some shells draw: that one exists where they do. - .lift => if (setting.availability != .lift) - @compileError("Lift must use the lift capability"), + .lift, .grip_width => if (setting.availability != .lift) + @compileError("Lift and GripWidth must use the lift capability (a pixel shell's)"), .toggle => |field| if (field == .ligatures) { if (setting.availability != .ligatures) @compileError("the ligatures toggle must use the ligatures capability"); @@ -1102,6 +1109,12 @@ pub const Runtime = struct { if (percent > 100) return false; state.inactive_dim = percent; }, + .grip_width => { + const text = std.mem.trim(u8, argument orelse return false, " \t\r\n%"); + const percent = std.fmt.parseInt(u16, text, 10) catch return false; + if (percent < 50 or percent > 300) return false; + state.grip_width = percent; + }, .theme, .font => return false, } return true; @@ -1277,6 +1290,7 @@ pub const Runtime = struct { try out.print("{s} unsupported\n", .{setting.word}), .motion => try out.print("{s} {s}\n", .{ setting.word, @tagName(state.motion) }), .inactive_dim => try out.print("{s} {d}\n", .{ setting.word, state.inactive_dim }), + .grip_width => try out.print("{s} {d}\n", .{ setting.word, state.grip_width }), }; if (context.startup_config_path) |path| @@ -1547,6 +1561,7 @@ pub const Runtime = struct { .lift => "rim", .motion => "playful", .inactive_dim => "35", + .grip_width => "200", else => null, }; try std.testing.expect(apply(&state, setting, argument)); @@ -1600,6 +1615,7 @@ pub const Runtime = struct { .lift => "Lift rim\n", .motion => "Motion playful\n", .inactive_dim => "InactiveDim 35\n", + .grip_width => "GripWidth 200\n", .theme, .font => unreachable, }; try std.testing.expect(std.mem.indexOf(u8, report, expected) != null); diff --git a/src/detached/wire.zig b/src/detached/wire.zig index 2a2ccc6b..c0e17054 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -11,6 +11,19 @@ const limits = @import("../memory.zig").limits; /// line), and a frame carries the core's placed regions after its layers. pub const version: u16 = 8; +/// The Chrome palette is carried field by field as its declaration lists +/// them, so any change to it changes the wire: bump `version`, then this. +const chrome_layout: u64 = 0x7e3d0952317bfee; +comptime { + @setEvalBranchQuota(10_000); + var hash = std.hash.Fnv1a_64.init(); + for (@typeInfo(pardes.Chrome).@"struct".fields) |field| { + hash.update(field.name); + hash.update(@typeName(field.type)); + } + if (hash.final() != chrome_layout) @compileError(std.fmt.comptimePrint("pardes.Chrome changed (layout 0x{x}): bump wire.version, then chrome_layout", .{hash.final()})); +} + pub const Error = error{ Truncated, Overlong, BadTag, BadValue, Trailing, NoSpace }; pub const max_cols: u16 = 512; @@ -248,7 +261,10 @@ pub const Frame = struct { region.owner = try r.getByte(); region.serial = try r.getU32(); region.rect = .{ .x = try r.getU16(), .y = try r.getU16(), .w = try r.getU16(), .h = try r.getU16() }; - region.active = try r.getBool(); + const flags = try r.getByte(); + if (flags > 3) return error.BadValue; + region.active = flags & 1 != 0; + region.dirty = flags & 2 != 0; region.thumb_y = try r.getU16(); region.thumb_h = try r.getU16(); region.notice = try r.getByte(); @@ -807,7 +823,7 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point try w.putU16(region.rect.y); try w.putU16(region.rect.w); try w.putU16(region.rect.h); - try w.putBool(region.active); + try w.putByte(@as(u8, @intFromBool(region.active)) | @as(u8, @intFromBool(region.dirty)) << 1); try w.putU16(region.thumb_y); try w.putU16(region.thumb_h); try w.putByte(region.notice); @@ -1898,10 +1914,10 @@ test "detached wire carries compact body rows and exact pointer metrics" { var tag_cells: [3]pardes.Cell = @splat(.{}); tag_cells[2] = logical[10]; const tag: pardes.Layer = .{ .kind = .column, .id = 1, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 1 }, .cols = 3, .rows = 1, .bg = .{ 11, 22, 33 }, .cells = &tag_cells, .cursor = .{ .x = 2, .y = 0, .bar = true } }; - const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .thumb_y = 1, .thumb_h = 2, .clip = 1, .lift = 0.5 }}; + const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .dirty = true, .thumb_y = 1, .thumb_h = 2, .clip = 1, .lift = 0.5 }}; const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}, &placed)); defer gpa.free(out); - const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 } }; + const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 }, .rule_px = 3, .tag_rule = .{ 13, 14, 15 }, .rail_px = 12, .grip_border = .{ 16, 17, 18 }, .grip_focus_ring = .{ 19, 20, 21 }, .grip_dirty = .{ 22, 23, 24 }, .lift_style = 2 }; const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag}, &placed, palette); const packet = (try framed(encoded)).?; const frame = (try decodeServer(packet.tag, packet.payload)).frame; diff --git a/src/draw.zig b/src/draw.zig index 2e608132..1f30a40b 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -58,7 +58,7 @@ pub fn place(p: *Pardes, s: *Surface) void { const active = id == p.active; const tag_y = p.tagTop(pane, r); const body_y = p.bodyTop(pane, r); - s.addRegion(.{ .kind = .grip, .owner = owner, .serial = pane.serial, .active = active, .rect = .{ .x = r.x, .y = tag_y, .w = config.GUTTER, .h = pane.tag_rows } }); + s.addRegion(.{ .kind = .grip, .owner = owner, .serial = pane.serial, .active = active, .dirty = tagline.dirty(pane), .rect = .{ .x = r.x, .y = tag_y, .w = config.GUTTER, .h = pane.tag_rows } }); s.addRegion(.{ .kind = .tag, .owner = owner, .serial = pane.serial, .active = active, .rect = .{ .x = r.x + config.GUTTER, .y = tag_y, .w = r.w - config.GUTTER, .h = pane.tag_rows } }); // A folded pane is its tag rows and nothing else. if (pane.collapsed) continue; @@ -160,6 +160,29 @@ pub fn place(p: *Pardes, s: *Surface) void { } } +/// A pane's grip in the grid: `box` focused, else halfway between its ring +/// and `box_dim`, so it shows on its tag without the ring a pixel shell +/// draws (gui.zig draws its button from the region and Chrome instead). +fn gripBg(p: *const Pardes, active: bool) [3]u8 { + const chrome = p.chromeTheme(); + if (active) return chrome.box; + return colors.mix(boxBorder(p), chrome.box_dim); +} + +/// An unfocused grip's ring, and a dirty one's fill (colors.Theme). +pub fn boxBorder(p: *const Pardes) [3]u8 { + return p.theme().box_border orelse p.chromeTheme().box_dim; +} +pub fn boxDirty(p: *const Pardes) [3]u8 { + const th = p.theme(); + return th.box_dirty orelse th.diagnostic_warning orelse th.num; +} + +fn inkOn(bg: [3]u8) [3]u8 { + const lum = (@as(u16, bg[0]) * 3 + @as(u16, bg[1]) * 6 + @as(u16, bg[2])) / 10; + return if (lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; +} + /// The Lift style as drawn: `auto` is a shadow on a light page, and on a /// dark one, where a shadow has nothing to darken, no lift at all (the other /// panes recede instead, `inactiveDim`). @@ -366,6 +389,12 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { .sel_fg = p.theme().sel_fg, .sel_bg = p.theme().sel_bg, .lift_style = @intFromEnum(liftStyle(p)), + .rule_px = p.theme().rule_px orelse 2, + .grip_border = boxBorder(p), + .grip_focus_ring = chrome.box, + .grip_dirty = boxDirty(p), + .tag_rule = p.theme().tag_rule, + .rail_px = @intCast(@min(255, @as(u32, p.theme().rail_px orelse 12) * p.settings.grip_width / 100)), }; place(p, s); // Separators have their own role; a scrollbar need not frame the page. @@ -666,21 +695,28 @@ fn renderPane(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane, r: if (th.bg) |bg| s.fill(tx, r.y, tw, r.h, .{ .bg = .{ .rgb = bg } }); } - const box_bg = if (active) chrome.box else chrome.box_dim; - const box_lum = (@as(u16, box_bg[0]) * 3 + @as(u16, box_bg[1]) * 6 + @as(u16, box_bg[2])) / 10; - const box_ink: [3]u8 = if (box_lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; + // The grip is acme's button: the focused pane's filled, the others' + // a ring round the tag's ground, a dirty one's the dirty colour inside + // its ring. A pixel shell draws the ring (gui.zig gripDecor); a grid, + // which cannot, marks dirty in the grip's second cell with a bold `*` + // on the dirty colour, a cue that survives any palette. + const band = if (active and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; + const dirty = tagline.dirty(pane); + const box_bg = gripBg(p, active); const box_style: CellStyle = .{ - .fg = .{ .rgb = box_ink }, + .fg = .{ .rgb = inkOn(box_bg) }, .bg = .{ .rgb = box_bg }, // Grips keep physical grid dimensions, independently of tag text. .font_role = .tagline, }; - // The grip is the first row's alone, as acme's button is; the rows - // under it are the tag band's, and still grab the pane (the region - // spans them all, as acme's button column does). + // On the first row alone, as acme's button is; the rows under it are + // the tag band's, and still grab the pane (the region spans them all, + // as acme's button column does). s.fill(r.x, tag_y, config.GUTTER, @min(1, pane.tag_rows), box_style); + if (dirty and pane.tag_rows > 0 and config.GUTTER > 1) + s.set(r.x + config.GUTTER - 1, tag_y, "*", .{ .fg = .{ .rgb = inkOn(boxDirty(p)) }, .bg = .{ .rgb = boxDirty(p) }, .bold = true, .font_role = .tagline }); s.fill(r.x, tag_y + 1, config.GUTTER, pane.tag_rows -| 1, .{ - .bg = .{ .rgb = if (active and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg }, + .bg = .{ .rgb = band }, .font_role = .tagline, }); s.set(r.x, tag_y, switch (pane.body.mode) { @@ -880,7 +916,7 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, var cell = panes.File.rawDisplayCol(shown, bounds.lo) -| scroll; const end = panes.File.rawDisplayCol(shown, bounds.hi) -| scroll; while (cell < end and cell < columns) : (cell += 1) { - surface.at(@intCast(cell), 0).style.bg = .{ .rgb = p.theme().sel_bg }; + surface.at(@intCast(cell), 0).style.bg = .{ .rgb = p.theme().tag_sel_bg orelse p.theme().sel_bg }; surface.at(@intCast(cell), 0).style.fg = .{ .rgb = p.theme().sel_fg }; } }; @@ -890,7 +926,7 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, var col = panes.File.rawDisplayCol(shown, span.lo) -| scroll; const end = panes.File.rawDisplayCol(shown, span.hi) -| scroll; while (col < end and col < columns) : (col += 1) { - surface.at(@intCast(col), 0).style.bg = .{ .rgb = p.theme().sel_bg }; + surface.at(@intCast(col), 0).style.bg = .{ .rgb = p.theme().tag_sel_bg orelse p.theme().sel_bg }; surface.at(@intCast(col), 0).style.fg = .{ .rgb = p.theme().sel_fg }; } } @@ -1131,7 +1167,7 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa while (col + pane.tag_scroll < end and col < tw) : (col += 1) { const cell = s.at(tx + @as(u16, @intCast(col)), y); cell.default = false; - cell.style.bg = .{ .rgb = th.sel_bg }; + cell.style.bg = .{ .rgb = th.tag_sel_bg orelse th.sel_bg }; cell.style.fg = .{ .rgb = th.sel_fg }; } } @@ -1151,7 +1187,7 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa for (pane.sel, 0..) |selection, button| { if (selection.state == .none or (selection.state == .done and button != 0)) continue; if (button != pardes.sel_slot or @min(selection.r0, selection.r1) >= rows) continue; - body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, th.sel_bg, th.sel_fg); + body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, th.tag_sel_bg orelse th.sel_bg, th.sel_fg); } if (p.look_hover_preview) |preview| { if (preview.pane == id and preview.serial == pane.serial) if (preview.sel) |sel| if (@min(sel.r0, sel.r1) < rows) { @@ -1160,11 +1196,71 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa }; } const sel_btn = body_layer.buttonTints(th); + const sel_ink = body_layer.buttonInks(th); for (pane.sel, 0..) |selection, button| { if (selection.state == .none or (selection.state == .done and button != 0)) continue; if (button == pardes.sel_slot or @min(selection.r0, selection.r1) >= rows) continue; - body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, sel_btn[button], th.sel_fg); + body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, sel_btn[button], sel_ink[button]); + } +} + +test "an unfocused grip shows on its tag in the grid, in every native theme" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + p.animate_theme_changes = false; + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + for (pardes.themes[0..colors.native_count], 0..) |theme, index| { + colors.setThemeIndex(p, index); + p.active = 1; + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + const r = p.rects[0]; + const grip = s.at(r.x, p.tagTop(p.panes[0].?, r)).style.bg.rgb; + const ratio = contrast(grip, p.chromeTheme().tag_bg); + // ponytail: dark and helix have always had a faint grip (1.2:1); flagged, not changed here. + const floor: f32 = if (std.mem.eql(u8, theme.name, "dark") or std.mem.eql(u8, theme.name, "helix")) 1.2 else 1.5; + if (ratio < floor) std.debug.print("{s}: grip {d:.2}\n", .{ theme.name, ratio }); + try std.testing.expect(ratio >= floor); + } +} + +test "a grip's four states, focused or not and clean or dirty, are each its own in every native theme" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); + defer p.deinit(); + const pane = try p.setTestFile("text\n"); + p.newScratchBelow(0); + p.animate_theme_changes = false; + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const State = struct { first: Cell, second: Cell, dirty: bool }; + for (0..colors.native_count) |index| { + colors.setThemeIndex(p, index); + var states: [4]State = undefined; + for (&states, 0..) |*state, i| { + p.active = if (i & 1 == 0) 0 else 1; + const saved = pane.file.?.saved_revision; + pane.file.?.revision = if (i & 2 != 0) saved + 1 else saved; + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + const r = p.rects[0]; + const y = p.tagTop(pane, r); + state.* = .{ .first = s.at(r.x, y).*, .second = s.at(r.x + 1, y).*, .dirty = s.region(.grip, 0).?.dirty }; + try std.testing.expectEqual(i & 2 != 0, state.dirty); + if (i & 2 == 0) continue; + // Dirty is more than a shade: the grid prints a bold `*` (one + // grid for every shell; a pixel shell draws its button over it). + try std.testing.expectEqualStrings("*", state.second.grapheme()); + try std.testing.expect(state.second.style.bold); + } + for (states, 0..) |a, i| for (states[i + 1 ..]) |b| { + const same = std.meta.eql(a.first.style.bg, b.first.style.bg) and a.second.visuallyEqual(&b.second); + try std.testing.expect(!same); + }; } + pane.file.?.revision = pane.file.?.saved_revision; } test "a tag of three rows has its grip on the first, and band under it that still grabs the pane" { diff --git a/src/gui/gui.zig b/src/gui/gui.zig index d8bae6dc..1a1b1f99 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -2111,7 +2111,13 @@ const Cover = packed struct(u16) { focus: bool = false, /// At a column's left edge, where a tag's anchor is inset. anchor: bool = false, - _: u7 = 0, + /// In a dirty pane's grip. + dirty: bool = false, + /// In a pane's grip (not a column's). + pane_grip: bool = false, + /// In a grip's first row, where its button is. + grip_top: bool = false, + _: u4 = 0, }; /// Marks the grid cells a body or tag layer covers, which the layers paint @@ -2143,10 +2149,13 @@ fn coverFrame(cover: []Cover, cols: u16, rows: u16, body_layers: []const pardes. if (region.kind == .column_grip) bar = region.rect; } for (regions) |region| switch (region.kind) { - .grip => { + .grip, .column_grip => { mark.rect(cover, cols, rows, region.rect, "grip", true); for (0..@min(region.rect.w, 16)) |offset| mark.rect(cover, cols, rows, .{ .x = region.rect.x +| @as(u16, @intCast(offset)), .y = region.rect.y, .w = 1, .h = region.rect.h }, "grip_offset", @as(u4, @intCast(offset))); - if (region.active) mark.rect(cover, cols, rows, region.rect, "focus", true); + if (region.kind == .grip and region.active) mark.rect(cover, cols, rows, region.rect, "focus", true); + if (region.dirty) mark.rect(cover, cols, rows, region.rect, "dirty", true); + if (region.kind == .grip) mark.rect(cover, cols, rows, region.rect, "pane_grip", true); + mark.rect(cover, cols, rows, .{ .x = region.rect.x, .y = region.rect.y, .w = region.rect.w, .h = @min(1, region.rect.h) }, "grip_top", true); }, .tag => if (region.active) mark.rect(cover, cols, rows, region.rect, "focus", true), .column => { @@ -6022,6 +6031,67 @@ fn anchorInset(g: *const Gui, row: u16, win_h: f32) u32 { return taglineBandOffset(row, win_h, g.cell_h, g.tagline_height); } +/// acme's Scrollwid column at a pane's or column's left edge, in window +/// pixels: where its button sits over its scrollbar, one width for both +/// (`rail_px` at acme's 17px font, scaled with the tagline, and always +/// short of the cell so the text keeps a gap), right of the column's rule. +fn scrollColumn(g: *const Gui, chrome: *const pardes.Chrome, col: u16, layout: CellLayout) [2]f32 { + const rule: f32 = if (col == 0) 0 else rulePx(chrome); + const x0 = @round(@as(f32, @floatFromInt(col)) * layout.w + rule); + const scaled: f32 = @round(@as(f32, @floatFromInt(@max(1, chrome.rail_px))) * @as(f32, @floatFromInt(g.tagline_height)) / 17); + // The rail and the grip are GUTTER cells wide; the text starts after. + const w = std.math.clamp(scaled, 4, @max(4, @floor(layout.w * @as(f32, @floatFromInt(config.GUTTER))) - rule - 3)); + return .{ x0, x0 + w }; +} + +/// acme's button for the grip whose first cell is (col, row), in window +/// pixels: the scroll column wide, its tag row tall, from the rule above to +/// the rule below, so button and scrollbar read as one strip. +fn gripButton(g: *const Gui, chrome: *const pardes.Chrome, col: u16, row: u16, layout: CellLayout, win_h: f32) [4]f32 { + _ = win_h; + const x = scrollColumn(g, chrome, col, layout); + const y0 = @round(@as(f32, @floatFromInt(row)) * layout.h); + return .{ x[0], y0, x[1], @round(@as(f32, @floatFromInt(row + 1)) * layout.h) }; +} + +/// A pane's button fill, from its region's state, not the grid's cells +/// (which are drawn for cell shells): dirty, focused, or the tag's ground. +fn gripFill(chrome: *const pardes.Chrome, focus: bool, dirty: bool) [3]u8 { + if (dirty) return chrome.grip_dirty; + return if (focus) chrome.grip_focus_ring else chrome.tag_bg; +} + +fn inkOn(bg: [3]u8) [3]u8 { + const lum = (@as(u16, bg[0]) * 3 + @as(u16, bg[1]) * 6 + @as(u16, bg[2])) / 10; + return if (lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; +} + +/// Cuts an instance's quad to a pixel rectangle, its atlas window with it so +/// the glyph neither moves nor stretches; nothing left, an empty quad. +fn clipInstance(instance: *CellInstance, r: [4]f32, win_w: f32, win_h: f32) void { + const x0 = (instance.x0 + 1) / 2 * win_w; + const x1 = (instance.x1 + 1) / 2 * win_w; + const y0 = (1 - instance.y0) / 2 * win_h; + const y1 = (1 - instance.y1) / 2 * win_h; + const left = @max(0, r[0] - x0); + const right = @max(0, x1 - r[2]); + const top = @max(0, r[1] - y0); + const bottom = @max(0, y1 - r[3]); + if (x1 - right <= x0 + left or y1 - bottom <= y0 + top) { + instance.x1 = instance.x0; + instance.y1 = instance.y0; + return; + } + instance.x0 += left / win_w * 2; + instance.u0 += left / atlas_w; + instance.x1 -= right / win_w * 2; + instance.u1 -= right / atlas_w; + instance.y0 -= top / win_h * 2; + instance.v0 += top / atlas_h; + instance.y1 += bottom / win_h * 2; + instance.v1 -= bottom / atlas_h; +} + fn emitSurfaceCell( g: *Gui, chrome: *const pardes.Chrome, @@ -6054,33 +6124,82 @@ fn emitSurfaceCell( emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .body, (&band_base)[0..1], 0, null, old_layer, false, page); next.* += 1; // Tag glyphs keep their font size inside a full physical-width grip. - emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .tagline, line, at, cursor, old_layer, is_cursor, page); + // A pane's grip cells are the cell shells' form (a dirty `*`): the + // button draws its own glyph over a blank of them. + var blank = line[at]; + blank.text[0] = ' '; + blank.len = 1; + if (here.pane_grip) + emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .tagline, (&blank)[0..1], 0, null, old_layer, false, page) + else + emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .tagline, line, at, cursor, old_layer, is_cursor, page); // A grip's glyph is the cell at its left edge, and a blank one is // no glyph: its marks are decor. const glyph = &line[at - @min(here.grip_offset, at)]; - const grip = here.grip and !glyph.default and glyph.printableAscii() != ' ' and !glyph.style.invisible; + const grip = here.grip and here.grip_top and !glyph.default and glyph.printableAscii() != ' ' and !glyph.style.invisible; const offset: u16 = if (grip) @min(here.grip_offset, at) else 0; // An anchor starts at its column's rule: its left edge is inset by // its top margin so the band frames it on the left as above and // below, and its mark moves right by half that to stay centered. const inset: u32 = if (line_cover[at - offset].anchor) anchorInset(g, row, win_h) else 0; + // A grip is acme's button: its fill is this cell's ground, cut to + // the button (and a dirty one's inset a pixel inside its ring, so + // the ring reads round it), its glyph centered in it. + const button = if (here.grip and here.grip_top) gripButton(g, chrome, col - @min(here.grip_offset, @as(u16, @intCast(at))), row, body_layout, win_h) else null; if (grip) { // A grip is one glyph centered across the gutter, not text: it is // looked up by its codepoint's glyph, never shaped. const drawn = glyphForCodepoint(g, cellCodepoint(glyph)); const cached = cachedGlyph(g, .{ .face = drawn.face, .glyph = drawn.glyph, .role = .tagline, .decoration = GlyphDecoration.fromCell(glyph), .centered = true }); const slot = cached orelse g.space_slot; - const shift: f32 = if (cached != null) @as(f32, @floatFromInt(@as(u32, offset) * g.cell_w)) - @as(f32, @floatFromInt(inset / 2)) else 0; + const b = button.?; + const center = (b[0] + b[2]) / 2 - @as(f32, @floatFromInt(col - offset)) * body_layout.w; + const shift: f32 = if (cached != null) @as(f32, @floatFromInt(@as(u32, offset) * g.cell_w)) + @as(f32, @floatFromInt(config.GUTTER * g.cell_w)) / 2 - center else 0; const resolved = resolveCell(g, glyph[0..1], 0, null, .tagline, false, page); - instances[next.*].fr = @as(f32, @floatFromInt(resolved.fg[0])) / 255; - instances[next.*].fg = @as(f32, @floatFromInt(resolved.fg[1])) / 255; - instances[next.*].fb = @as(f32, @floatFromInt(resolved.fg[2])) / 255; + const ink = if (here.pane_grip) inkOn(gripFill(chrome, here.focus, here.dirty)) else resolved.fg; + instances[next.*].fr = @as(f32, @floatFromInt(ink[0])) / 255; + instances[next.*].fg = @as(f32, @floatFromInt(ink[1])) / 255; + instances[next.*].fb = @as(f32, @floatFromInt(ink[2])) / 255; instances[next.*].u0 = (@as(f32, @floatFromInt(slot.u)) + shift) / atlas_w; instances[next.*].v0 = @as(f32, @floatFromInt(slot.v)) / atlas_h; instances[next.*].v1 = @as(f32, @floatFromInt(slot.v + g.tagline_height)) / atlas_h; } instances[next.*].u1 = instances[next.*].u0 + @as(f32, @floatFromInt(g.cell_w)) / atlas_w; - if (inset > 0 and (!grip or offset == 0)) { + if (button) |b| { + // acme's button fills its tag row from the rule above to the rule + // below: the underlay is its fill, cut to its columns; a pane's + // from its region's state, not the grid's cell form (whose dirty + // `*` is the cell shells'), a column's its cell's own ground. + const base = &instances[next.* - 1]; + const own: [3]f32 = .{ instances[next.*].br, instances[next.*].bg, instances[next.*].bb }; + if (here.pane_grip) { + const fill = gripFill(chrome, here.focus, here.dirty); + base.br = @as(f32, @floatFromInt(fill[0])) / 255; + base.bg = @as(f32, @floatFromInt(fill[1])) / 255; + base.bb = @as(f32, @floatFromInt(fill[2])) / 255; + } else { + base.br = own[0]; + base.bg = own[1]; + base.bb = own[2]; + } + base.effect &= ~clear_bg_bit; + clipInstance(base, .{ b[0], -1e9, b[2], 1e9 }, win_w, win_h); + // Over it, only its own glyph; the band round it and the ring + // are decor (gripDecor). + if (here.pane_grip and !grip) { + instances[next.*].u0 = @as(f32, @floatFromInt(g.space_slot.u)) / atlas_w; + instances[next.*].u1 = instances[next.*].u0 + @as(f32, @floatFromInt(g.cell_w)) / atlas_w; + instances[next.*].v0 = @as(f32, @floatFromInt(g.space_slot.v)) / atlas_h; + instances[next.*].v1 = @as(f32, @floatFromInt(g.space_slot.v + g.tagline_height)) / atlas_h; + } + // Its ground is the fill again (a see-through ground would be + // written, not blended, in an opaque window). + instances[next.*].br = base.br; + instances[next.*].bg = base.bg; + instances[next.*].bb = base.bb; + instances[next.*].effect &= ~clear_bg_bit; + clipInstance(&instances[next.*], b, win_w, win_h); + } else if (inset > 0) { instances[next.*].x0 += @as(f32, @floatFromInt(inset)) / win_w * 2; instances[next.*].u0 += @as(f32, @floatFromInt(inset)) / atlas_w; } @@ -6091,6 +6210,102 @@ fn emitSurfaceCell( next.* += 1; } +test "every pixel of a grip column is band, button or ring, and the button sits over the scrollbar" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 60, .rows = 20 }); + defer core.deinit(); + const pane = try core.setTestFile("text\n" ** 60); + pane.tag.own = try gpa.dupe(u8, "second\nthird"); + core.newScratchBelow(0); + for ([_]struct { w: u32, h: u32, tag: u32 }{ .{ .w = 10, .h = 20, .tag = 12 }, .{ .w = 17, .h = 35, .tag = 30 }, .{ .w = 9, .h = 19, .tag = 15 }, .{ .w = 12, .h = 24, .tag = 24 } }) |m| for ([_]bool{ false, true }) |bottom| for ([_]usize{ 0, 1 }) |focus| for ([_]bool{ false, true }) |dirty| { + core.settings.tag_bottom = bottom; + core.active = focus; + pane.file.?.revision = pane.file.?.saved_revision + @intFromBool(dirty); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + var g: Gui = undefined; + g.words = .{}; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = m.w; + g.cell_h = m.h; + g.tagline_width = m.w; + g.tagline_height = m.tag; + g.space_slot = .{ .u = 0, .v = 0 }; + const layout = fixedCellLayout(&g); + const win_w: f32 = @floatFromInt(60 * m.w); + const win_h: f32 = @floatFromInt(20 * m.h); + const cover = try gpa.alloc(Cover, @as(usize, s.cols) * s.rows); + defer gpa.free(cover); + coverFrame(cover, s.cols, s.rows, s.bodyLayers(), s.tagLayers(), s.regionList()); + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, layout, win_w, win_h, true); + const grip = s.region(.grip, 0).?; + // The grip column's pixels, band first, then its cells, then decor. + const x_px: u32 = grip.rect.x * m.w; + const w_px: u32 = config.GUTTER * m.w; + const y_px: u32 = grip.rect.y * m.h; + const h_px: u32 = grip.rect.h * m.h; + const pixels = try gpa.alloc(?[3]f32, w_px * h_px); + defer gpa.free(pixels); + @memset(pixels, null); + const paint = struct { + fn quad(into: []?[3]f32, x0n: f32, y0n: f32, x1n: f32, y1n: f32, rgb: [3]f32, ww: f32, wh: f32, ox: u32, oy: u32, pw: u32, ph: u32) void { + const px0: i64 = @intFromFloat(@round((x0n + 1) / 2 * ww)); + const px1: i64 = @intFromFloat(@round((x1n + 1) / 2 * ww)); + const py0: i64 = @intFromFloat(@round((1 - y0n) / 2 * wh)); + const py1: i64 = @intFromFloat(@round((1 - y1n) / 2 * wh)); + var y = @max(py0, oy); + while (y < @min(py1, oy + ph)) : (y += 1) { + var x = @max(px0, ox); + while (x < @min(px1, ox + pw)) : (x += 1) into[@intCast((y - oy) * pw + (x - ox))] = rgb; + } + } + }; + var instances: [4]CellInstance = undefined; + for (0..grip.rect.h) |line| for (0..config.GUTTER) |dx| { + const col: u16 = grip.rect.x + @as(u16, @intCast(dx)); + const row: u16 = grip.rect.y + @as(u16, @intCast(line)); + const at = @as(usize, row) * s.cols; + var next: u32 = 0; + emitSurfaceCell(&g, &s.chrome, &instances, &next, col, row, layout, win_w, win_h, null, s.cells[at..][0..s.cols], cover[at..][0..s.cols], col, null, false, false, Ground.opaqueRgb(bg_default)); + for (instances[0..next]) |i| paint.quad(pixels, i.x0, i.y0, i.x1, i.y1, .{ i.br, i.bg, i.bb }, win_w, win_h, x_px, y_px, w_px, h_px); + }; + for (g.decor.items) |i| if (i.fr >= 1 and i.clip_x1 < i.clip_x0) paint.quad(pixels, i.x0, i.y0, i.x1, i.y1, .{ i.br, i.bg, i.bb }, win_w, win_h, x_px, y_px, w_px, h_px); + const rgb = struct { + fn of(v: [3]u8) [3]f32 { + return .{ @as(f32, @floatFromInt(v[0])) / 255, @as(f32, @floatFromInt(v[1])) / 255, @as(f32, @floatFromInt(v[2])) / 255 }; + } + }.of; + const chrome = &s.chrome; + const band = if (focus == 0) chrome.tag_focus_bg else chrome.tag_bg; + // Band, fills, rings, the rules between stacked panes and under the + // tag, and the mode marks' ink (the grip's first cell's). + const ink = inkOn(gripFill(chrome, grip.active, grip.dirty)); + const allowed = [_][3]f32{ rgb(band), rgb(core.chromeTheme().box), rgb(pardes.draw.boxDirty(core)), rgb(chrome.grip_border), rgb(chrome.grip_focus_ring), rgb(chrome.border), rgb(ink), rgb(chrome.tag_rule orelse mixRgb(chrome.tag_bg, chrome.page orelse bg_default)) }; + for (pixels, 0..) |pixel, n| { + const got = pixel orelse return error.TestUnpaintedGripPixel; + const known = for (allowed) |a| { + if (std.meta.eql(a, got)) break true; + } else false; + // Under the first row there is no button: band and rules only. + if (n / w_px >= m.h and n / w_px < h_px - 1 and !std.meta.eql(got, rgb(band)) and !std.meta.eql(got, rgb(chrome.border))) + return error.TestButtonUnderFirstRow; + if (!known) std.debug.print("sliver {any} at {d},{d} of {d}x{d} (metrics {d}x{d}/{d}, bottom {}, focus {d}, dirty {}) page {any}\n", .{ got, n % w_px, n / w_px, w_px, h_px, m.w, m.h, m.tag, bottom, focus, dirty, s.chrome.page }); + try std.testing.expect(known); + } + // The rule under the tag runs unbroken across the grip column. + if (!bottom) for (pixels[(h_px - 1) * w_px ..][0..w_px]) |pixel| + try std.testing.expectEqual(rgb(chrome.tag_rule orelse mixRgb(chrome.tag_bg, chrome.page orelse bg_default)), pixel.?); + // The button's edges are the scrollbar's. + const button = gripButton(&g, chrome, grip.rect.x, grip.rect.y, layout, win_h); + const column = scrollColumn(&g, chrome, s.region(.rail, 0).?.rect.x, layout); + try std.testing.expectEqual(column[0], button[0]); + try std.testing.expectEqual(column[1], button[2]); + }; + pane.file.?.revision = pane.file.?.saved_revision; +} + test "small tagline fonts leave no background gutters between adjacent tags" { const chrome: pardes.Chrome = .{}; var g: Gui = undefined; @@ -6778,9 +6993,9 @@ test "pane indicator halves share dissolve changes and safe exhausted atlas" { try std.testing.expect(gripCellChanged(&surface, &cover, &previous_cover, 1, 0)); try std.testing.expect(!gripCellChanged(&surface, &cover, &previous_cover, 2, 0)); try std.testing.expect(!cover[2].grip); - // A column's grip has no glyph of a pane's. + // A column's grip is a button too, cut the same way (it has no glyph). coverFrame(&cover, 6, 2, &.{}, &.{}, &.{.{ .kind = .column_grip, .rect = .{ .x = 0, .y = 0, .w = 2, .h = 1 } }}); - try std.testing.expect(!cover[0].grip); + try std.testing.expect(cover[0].grip); coverFrame(&cover, 6, 2, &.{}, &.{}, &grip); const chrome: pardes.Chrome = .{}; @@ -7714,6 +7929,12 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g for (groups.items[0..groups.len], 0..) |*group, index| { group.decor_start = @intCast(g.decor.items.len); defer group.decor_count = @as(u32, @intCast(g.decor.items.len)) - group.decor_start; + // The grips' buttons before any rule: the rules over their rows cut + // them, and the band round them never covers a rule. + if (index <= groups.tracks) { + const closing = if (group.track) |active| active.phase == .closing else false; + try gripDecor(g, gpa, if (closing) surface.previous_regions else regions, if (closing) surface.previous_cells else surface.cells, groups, index, chrome, layout, surface.cols, surface.rows, win_w, win_h, group.track); + } if (index == 0) { const top = for (regions) |region| { if (region.kind == .workspace_tag) break region.rect.h; @@ -7723,7 +7944,8 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g .grip, .tag, .column_grip, .column_tag => if (region.rect.y == top) break true, else => {}, } else false; - const height = topbarPaneBorderPixels(g.cell_h, g.tagline_height); + // As wide as the other rules, where the tagline leaves the room. + const height: u32 = @max(1, @min(chrome.rule_px, (g.cell_h -| g.tagline_height) * 2)); if (top != 0 and top < surface.rows and tag_under and height != 0) { const y0: f32 = @floatFromInt(top * g.cell_h - height / 2); try addDecor(g, gpa, 0, y0, win_w, y0 + @as(f32, @floatFromInt(height)), chrome.topbar_rule, 1, null, win_w, win_h); @@ -7732,7 +7954,7 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g for (regions) |bar| { if (bar.kind != .column_grip) continue; const y = @as(f32, @floatFromInt(bar.rect.y + bar.rect.h)) * layout.h; - try addDecor(g, gpa, 0, y - 1, win_w, y, chrome.border, 1, null, win_w, win_h); + try addDecor(g, gpa, 0, y - rulePx(chrome), win_w, y, chrome.border, 1, null, win_w, win_h); break; } // A tag on the last row runs on through the pixels under the grid. @@ -7756,7 +7978,6 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g nbodies += 1; }; const bodies = body_buf[0..nbodies]; - const grid = if (closing) surface.previous_cells else surface.cells; try paneDecor(g, gpa, placed, groups, index, chrome, layout, surface.cols, surface.rows, win_w, win_h, track); // A single spine runs down each column, through every tag and body. if (index == 0) for (regions) |column| { @@ -7765,9 +7986,10 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g const y0 = @as(f32, @floatFromInt(column.rect.y)) * layout.h; const grid_y1 = @as(f32, @floatFromInt(surface.rows)) * layout.h; const y1 = if (win_h - grid_y1 < layout.h) @max(grid_y1, win_h) else grid_y1; - if (y1 > y0) try addDecor(g, gpa, x, y0, x + 1, y1, chrome.border, 1, null, win_w, win_h); + // None at the window's edge: nothing to rule apart there. + if (column.rect.x == 0) continue; + if (y1 > y0) try addDecor(g, gpa, x, y0, x + rulePx(chrome), y1, chrome.border, 1, null, win_w, win_h); }; - try gripDecor(g, gpa, placed, grid, groups, index, layout, surface.cols, surface.rows, win_w, win_h, track); // Cast last in the group, over its cells, images and chrome: a // lifted pane's shadow darkens its neighbours' rails and rules // as it darkens their text, and never the pane itself. @@ -7837,47 +8059,210 @@ fn paneDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, gr const rule_x1 = if (win_w - x1 < layout.w) @max(x1, win_w) else x1; const tag_bottom = tag.rect.y > rail.rect.y; const rule_y = @as(f32, @floatFromInt(if (tag_bottom) tag.rect.y else tag.rect.y + tag.rect.h)) * layout.h; - try addDecor(g, gpa, x0, if (tag_bottom) rule_y else rule_y - 1, rule_x1, if (tag_bottom) rule_y + 1 else rule_y, chrome.border, 1, track, win_w, win_h); + try addDecor(g, gpa, x0, if (tag_bottom) rule_y else rule_y - 1, rule_x1, if (tag_bottom) rule_y + 1 else rule_y, chrome.tag_rule orelse mixRgb(chrome.tag_bg, page), 1, track, win_w, win_h); + // Stacked panes are ruled apart as columns are, where one pane's + // body meets the next one's tag (acme's Border). + { + const pane_top = @min(tag.rect.y, rail.rect.y); + const stacked = for (regions) |other| { + if ((other.kind == .body or other.kind == .tag) and other.owner != rail.owner and other.rect.y + other.rect.h == pane_top and + other.rect.x < tag.rect.x + tag.rect.w and tag.rect.x < other.rect.x + other.rect.w) break true; + } else false; + // Inside the tag band's slack, never over its glyphs: the top + // slack of a tag on top, the bottom slack of the one above when + // tags are at the bottom; a pixel at least. + const y = @as(f32, @floatFromInt(pane_top)) * layout.h; + const spare = g.cell_h -| g.tagline_height; + if (stacked and !tag_bottom) { + const room = taglineBandOffset(pane_top, win_h, g.cell_h, g.tagline_height); + try addDecor(g, gpa, x0, y, rule_x1, y + @as(f32, @floatFromInt(@max(1, @min(chrome.rule_px, room)))), chrome.border, 1, track, win_w, win_h); + } else if (stacked and pane_top > 0) { + const room = spare - taglineBandOffset(pane_top - 1, win_h, g.cell_h, g.tagline_height); + try addDecor(g, gpa, x0, y - @as(f32, @floatFromInt(@max(1, @min(chrome.rule_px, room)))), rule_x1, y, chrome.border, 1, track, win_w, win_h); + } + } const y0 = @as(f32, @floatFromInt(rail.rect.y)) * layout.h; const body_end = rail.rect.y + rail.rect.h; const body_y1 = @as(f32, @floatFromInt(body_end)) * layout.h; const y1 = if (body_end == rows and win_h - body_y1 < layout.h) @max(body_y1, win_h) else body_y1; + // A theme's own track fills its width, the thumb one pixel short + // of it so the track edges it (acme's scrollbar); else a thin rail. + // acme's scrollbar: the track fills the scroll column, under the + // grip's button; the thumb is a pixel short of its right edge, so + // the track edges it. + const column = scrollColumn(g, chrome, rail.rect.x, layout); try addDecor(g, gpa, x0, y0, x0 + layout.w, y1, page, 1, track, win_w, win_h); - try addDecor(g, gpa, x0, y0, x0 + @min(layout.w, 2), y1, chrome.scroll_track, 1, track, win_w, win_h); + try addDecor(g, gpa, column[0], y0, column[1], y1, chrome.scroll_track, 1, track, win_w, win_h); if (rail.thumb_h != 0) { const thumb_y0 = @as(f32, @floatFromInt(rail.rect.y + rail.thumb_y)) * layout.h; const thumb_y1 = if (rail.thumb_y + rail.thumb_h == rail.rect.h) y1 else @as(f32, @floatFromInt(rail.rect.y + rail.thumb_y + rail.thumb_h)) * layout.h; - try addDecor(g, gpa, x0, thumb_y0, x0 + @min(layout.w, @max(2, @floor(layout.w * 0.5))), thumb_y1, chrome.scroll_thumb, 1, track, win_w, win_h); + try addDecor(g, gpa, column[0], thumb_y0, column[1] - 1, thumb_y1, chrome.scroll_thumb, 1, track, win_w, win_h); } } } +/// Halfway between two colours. +fn mixRgb(a: [3]u8, b: [3]u8) [3]u8 { + var out: [3]u8 = undefined; + for (&out, a, b) |*o, x, y| o.* = @intCast((@as(u16, x) + y) / 2); + return out; +} + +/// A rule's width: the theme's own, or one pixel. +fn rulePx(chrome: *const pardes.Chrome) f32 { + return @floatFromInt(@max(1, chrome.rule_px)); +} + /// The two short bars on each row of a grip with no mode glyph there, /// centered in its anchor. Only the panes of group `index`. -fn gripDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, cells: []const pardes.Cell, groups: *const Groups, index: usize, layout: CellLayout, cols: u16, rows: u16, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { +fn gripDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, cells: []const pardes.Cell, groups: *const Groups, index: usize, chrome: *const pardes.Chrome, layout: CellLayout, cols: u16, rows: u16, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { if (g.tagline_height < 6 or g.tagline_width < 3) return; for (regions) |grip| { - if (grip.kind != .grip or groupOf(groups, grip.serial) != index) continue; + if ((grip.kind != .grip and grip.kind != .column_grip) or groupOf(groups, grip.serial) != index) continue; + if (grip.rect.h == 0 or grip.rect.y >= rows) continue; + const button = gripButton(g, chrome, grip.rect.x, grip.rect.y, layout, win_h); + // Round the button, its first row is the tag's band: nothing of the + // cells under it shows there. + const band = if (grip.active) chrome.tag_focus_bg else chrome.tag_bg; + const left = @as(f32, @floatFromInt(grip.rect.x)) * layout.w; + const right = @as(f32, @floatFromInt(grip.rect.x + grip.rect.w)) * layout.w; + try addDecor(g, gpa, left, button[1], button[0], button[3], band, 1, track, win_w, win_h); + try addDecor(g, gpa, button[2], button[1], right, button[3], band, 1, track, win_w, win_h); + if (grip.kind != .grip) continue; + // acme's button ring, two pixels (ButtonBorder) inside the button: + // the focused one's in its own fill. An unfocused dirty one keeps a + // pixel of band inside its ring, so the four states differ in shape + // whatever a theme's colours (a focused dirty one is acme's + // modbutton, filled up to its ring). + { + // The rules drawn over the row (paneDecor, after this) cut into + // it: the ring runs inside them, from rule to rule. + const tag = for (regions) |part| { + if (part.kind == .tag and part.owner == grip.owner) break part; + } else grip; + const tag_bottom = for (regions) |part| { + if (part.kind == .rail and part.owner == grip.owner) break part.rect.y < tag.rect.y; + } else false; + const stacked = !tag_bottom and for (regions) |other| { + if ((other.kind == .body or other.kind == .tag) and other.owner != grip.owner and other.rect.y + other.rect.h == grip.rect.y and + other.rect.x < tag.rect.x + tag.rect.w and tag.rect.x < other.rect.x + other.rect.w) break true; + } else false; + const top_rule: f32 = if (tag_bottom) 1 else if (stacked) @floatFromInt(@max(1, @min(chrome.rule_px, taglineBandOffset(grip.rect.y, win_h, g.cell_h, g.tagline_height)))) else 0; + const bottom_rule: f32 = if (!tag_bottom and grip.rect.h == 1) 1 else 0; + const x0 = button[0]; + const x1 = button[2]; + const y0 = button[1] + top_rule; + const y1 = button[3] - bottom_rule; + const ring = if (grip.active) chrome.grip_focus_ring else chrome.grip_border; + try addDecor(g, gpa, x0, y0, x1, y0 + 2, ring, 1, track, win_w, win_h); + try addDecor(g, gpa, x0, y1 - 2, x1, y1, ring, 1, track, win_w, win_h); + try addDecor(g, gpa, x0, y0 + 2, x0 + 2, y1 - 2, ring, 1, track, win_w, win_h); + try addDecor(g, gpa, x1 - 2, y0 + 2, x1, y1 - 2, ring, 1, track, win_w, win_h); + if (grip.dirty and !grip.active) { + try addDecor(g, gpa, x0 + 2, y0 + 2, x1 - 2, y0 + 3, band, 1, track, win_w, win_h); + try addDecor(g, gpa, x0 + 2, y1 - 3, x1 - 2, y1 - 2, band, 1, track, win_w, win_h); + try addDecor(g, gpa, x0 + 2, y0 + 3, x0 + 3, y1 - 3, band, 1, track, win_w, win_h); + try addDecor(g, gpa, x1 - 3, y0 + 3, x1 - 2, y1 - 3, band, 1, track, win_w, win_h); + } + } const x = grip.rect.x; for (0..grip.rect.h) |line| { const y = grip.rect.y + @as(u16, @intCast(line)); if (x >= cols or y >= rows) continue; const cell = &cells[@as(usize, y) * cols + x]; if (cell.default or cell.printableAscii() != ' ') continue; - const rgb = switch (cell.style.fg) { - .rgb => |rgb| rgb, - else => continue, - }; - const center_x = (@as(f32, @floatFromInt(x)) + @as(f32, @floatFromInt(config.GUTTER)) / 2) * layout.w + @as(f32, @floatFromInt(anchorInset(g, y, win_h) / 2)); + // The marks are in the button's ink, from the region's state. + const rgb = inkOn(gripFill(chrome, grip.active, grip.dirty)); + // The marks are the first row's alone, centered in the button. + if (line != 0) continue; + const center_x = @floor((button[0] + button[2]) / 2); const center_y = @as(f32, @floatFromInt(y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(y, win_h, g.cell_h, g.tagline_height))) + @as(f32, @floatFromInt(g.tagline_height)) / 2; - const half_w = @max(1, @min(3, layout.w - 2)); + const half_w = @max(1, @min(3, @floor((button[2] - button[0] - 6) / 2))); try addDecor(g, gpa, @floor(center_x - half_w), @floor(center_y - 2), @floor(center_x + half_w), @floor(center_y - 1), rgb, 1, track, win_w, win_h); try addDecor(g, gpa, @floor(center_x - half_w), @floor(center_y + 1), @floor(center_x + half_w), @floor(center_y + 2), rgb, 1, track, win_w, win_h); } } } +test "a theme's own rules and scrollbar are drawn at its widths: acme's 2px black rules and 12px track" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false)); + try std.testing.expect(core.executeBuiltinLine(0, "Theme acme")); + for (0..40) |_| core.update(.tick); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + try std.testing.expectEqual(@as(u8, 2), s.chrome.rule_px); + // acme's 12px, at GripWidth's 150% default. + try std.testing.expectEqual(@as(u8, 18), s.chrome.rail_px); + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 16; + g.cell_h = 20; + g.tagline_width = 10; + g.tagline_height = 12; + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, fixedCellLayout(&g), 1280, 480, true); + const px = struct { + fn w(item: CellInstance) f32 { + return (item.x1 - item.x0) / 2 * 1280; + } + }; + var spines: usize = 0; + var tracks: usize = 0; + for (g.decor.items) |item| { + const black = item.br == 0 and item.bg == 0 and item.bb == 0; + // No rule at the window's left edge; one 2px spine where columns meet. + if (black and @abs(px.w(item) - 2) < 0.01 and item.y1 < -0.9) { + try std.testing.expect(item.x0 > -1); + spines += 1; + } + const column = scrollColumn(&g, &s.chrome, 40, fixedCellLayout(&g)); + if (@abs(item.br - @as(f32, 0x99) / 255) < 1e-3 and @abs(item.bb - @as(f32, 0x4c) / 255) < 1e-3 and @abs(px.w(item) - (column[1] - column[0])) < 0.01) tracks += 1; + } + try std.testing.expectEqual(@as(usize, 1), spines); + try std.testing.expect(tracks >= 2); +} + +test "the rule between stacked panes stays in the tag band's slack, at any tagline size" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + core.newScratchBelow(0); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + const lower = for (s.regionList()) |region| { + if (region.kind == .tag and region.rect.y > 2) break region; + } else return error.TestExpectedStackedTag; + for ([_]u32{ 12, 20 }) |tagline_h| { + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = tagline_h; + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, fixedCellLayout(&g), 800, 480, true); + const top = @as(f32, @floatFromInt(lower.rect.y)) * 20; + const slack: f32 = @floatFromInt(@max(1, pardes.taglineBandOffset(lower.rect.y, 480, 20, tagline_h))); + var found = false; + for (g.decor.items) |item| { + const y0 = (1 - item.y0) / 2 * 480; + const y1 = (1 - item.y1) / 2 * 480; + if (@abs(y0 - top) > 0.01 or @abs(item.br - @as(f32, @floatFromInt(s.chrome.border[0])) / 255) > 1e-3) continue; + found = true; + try std.testing.expect(y1 - y0 <= slack + 0.01); + } + try std.testing.expect(found); + } +} + test "decor is read from the regions, each pane's in its own group" { const gpa = std.testing.allocator; const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); @@ -7905,15 +8290,23 @@ test "decor is read from the regions, each pane's in its own group" { // workspace tag; a rail two pixels wide at each pane's. for (core.col_x[0..core.ncol]) |x| { const left = ndc.x(@as(f32, @floatFromInt(x)) * 10); + // Two pixels (rule_px), and none at the window's own edge. const spine = for (g.decor.items) |item| { - if (item.x0 == left and @abs(item.x1 - ndc.x(@as(f32, @floatFromInt(x)) * 10 + 1)) < 0.0001) break item; - } else return error.TestExpectedSpine; - try std.testing.expectApproxEqAbs(@as(f32, 1 - 20.0 / 480.0 * 2), spine.y0, 0.0001); - try std.testing.expectEqual(@as(f32, -1), spine.y1); + if (item.x0 == left and @abs(item.x1 - ndc.x(@as(f32, @floatFromInt(x)) * 10 + 2)) < 0.0001 and + @abs(item.y0 - (1 - 20.0 / 480.0 * 2)) < 0.0001 and std.mem.eql(f32, &.{ item.br, item.bg, item.bb }, &.{ @as(f32, @floatFromInt(s.chrome.border[0])) / 255, @as(f32, @floatFromInt(s.chrome.border[1])) / 255, @as(f32, @floatFromInt(s.chrome.border[2])) / 255 })) break item; + } else null; + try std.testing.expectEqual(x == 0, spine == null); + if (spine) |rule| { + try std.testing.expectApproxEqAbs(@as(f32, 1 - 20.0 / 480.0 * 2), rule.y0, 0.0001); + try std.testing.expectEqual(@as(f32, -1), rule.y1); + } + // The scrollbar fills the scroll column right of the rule. + const column = scrollColumn(&g, &s.chrome, x, layout); const rail = for (g.decor.items) |item| { - if (item.x0 == left and @abs(item.x1 - ndc.x(@as(f32, @floatFromInt(x)) * 10 + 2)) < 0.0001) break item; + if (@abs(item.x0 - ndc.x(column[0])) < 0.0001 and @abs(item.x1 - ndc.x(column[1])) < 0.0001 and + @abs(item.br - @as(f32, @floatFromInt(s.chrome.scroll_track[0])) / 255) < 0.0001) break item; } else return error.TestExpectedRail; - try std.testing.expectApproxEqAbs(@as(f32, s.chrome.scroll_track[0]) / 255, rail.br, 0.0001); + _ = rail; } try std.testing.expectEqual(@as(u32, 0), groups.items[groups.notices].decor_count); const still = groups.items[0].decor_count; diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 1c21d314..b28e02fa 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -308,6 +308,7 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void { .lift => try w.print("{s} {s}\n", .{ word, @tagName(s.lift) }), .motion => try w.print("{s} {s}\n", .{ word, @tagName(s.motion) }), .inactive_dim => try w.print("{s} {d}\n", .{ word, s.inactive_dim }), + .grip_width => try w.print("{s} {d}\n", .{ word, s.grip_width }), } } try w.writeAll("LocationsConfig "); diff --git a/src/surface.zig b/src/surface.zig index f16993a8..bc0a019d 100644 --- a/src/surface.zig +++ b/src/surface.zig @@ -356,6 +356,8 @@ pub const Region = struct { rect: layout.Rect, /// The pane or column has the keyboard's focus. active: bool = false, + /// A grip's pane has changes it has not saved. + dirty: bool = false, /// A rail's thumb: rows from the rail's top, and how many. Zero rows is /// a rail with no thumb (an image). A native PDF's thumb is measured in /// document pixels and filled in by Pdf.draw while it paints, so read @@ -403,6 +405,19 @@ pub const Chrome = struct { sel_bg: [3]u8 = .{ 0, 0, 0 }, /// How a lifted part stands off the page (config.Runtime.LiftStyle). lift_style: u8 = 0, + /// Rule width in pixels (colors.Theme.rule_px). + rule_px: u8 = 2, + /// The ring round a grip, acme's button: unfocused, and focused. + grip_border: [3]u8 = .{ 0, 0, 0 }, + grip_focus_ring: [3]u8 = .{ 0, 0, 0 }, + /// A dirty pane's button fill. + grip_dirty: [3]u8 = .{ 0, 0, 0 }, + /// The rule between a pane's tag and its body; null is halfway between + /// the tag's ground and the page the shell actually draws. + tag_rule: ?[3]u8 = null, + /// The scroll column's width, button and scrollbar alike, in pixels at + /// a 17px tagline (acme's 12 at its font); a pixel shell scales it. + rail_px: u8 = 12, }; /// The canonical interface: what a frame of pardes IS. The tty shell writes diff --git a/src/tagline.zig b/src/tagline.zig index 59c51b4d..d0907cbd 100644 --- a/src/tagline.zig +++ b/src/tagline.zig @@ -24,6 +24,12 @@ pub const locations_pane_tail = file_pane_tail ++ " LocationsConfig"; pub const terminal_pane_tail = " " ++ config.terminal_pane_builtins_str; pub const command_pane_tail = " " ++ config.command_pane_builtins_str; const dirty_marker = " *"; + +/// A file with changes it has not saved. +pub fn dirty(pane: *const panes.Pane) bool { + const f = pane.file orelse return false; + return panes.Output.fileTraits(f.output).saves and f.revision != f.saved_revision; +} pub const legacy_pane_tail = " Del"; pub const legacy_file_pane_tail = " Save Del"; // The defaults from the release before Newtty joined every tagline. Recognized @@ -136,7 +142,7 @@ pub fn pathPrefix(arena: std.mem.Allocator, pane: *Pane) ![]const u8 { } if (pane.file) |f| { const name = if (pane.prompt == .name) pane.input.own orelse f.path else f.path; - if (panes.Output.fileTraits(f.output).saves and f.revision != f.saved_revision) + if (dirty(pane)) return std.fmt.allocPrint(arena, "{s}{s}", .{ name, dirty_marker }); return name; } diff --git a/src/themes/acme.zig b/src/themes/acme.zig index d91e626b..17ef4224 100644 --- a/src/themes/acme.zig +++ b/src/themes/acme.zig @@ -6,20 +6,28 @@ //! in) is a mid grey, and 15 (bright white) is BLACK, because a program asking //! for the brightest color on a light page means "make this stand out". //! -//! The selection is acme's own: plan9's DDarkyellow #eeee9e, the colour acme -//! sweeps text with, and black ink over it — acme changes the paper under a -//! selection and never the letters. Out of the same draw.h palette the -//! scrollbar's #99994c (DYellowgreen) is already taken from. +//! Every colour below that acme has is acme's own, from plan9port's acme.c +//! iconinit and draw.h: the tag #eaffff and body #ffffea (each 25% of +//! DPalebluegreen or DPaleyellow over white), their selections #9eeeee and +//! #eeee9e with black ink (acme changes the paper under a selection, never +//! the letters), the exec and look sweeps #aa0000 and #006600 with white ink, +//! the scrollbar's #99994c track under a #ffffea thumb, 12px wide, the +//! #8888cc (DPurpleblue) line between a tag and its body and the column +//! button, and 2px black rules between columns, windows and tags. +//! +//! acme has no focus colour; pardes needs one, so the focused pane's button +//! is filled #8888cc (the column button's look) and every other pane's is +//! acme's own: #eaffff in a 2px #8888cc ring, #000099 inside it when dirty. pub const theme = .{ .name = "acme", .bg = .{ 0xff, 0xff, 0xea }, .fg = .{ 0x00, 0x00, 0x00 }, - .sel_bg = .{ 0xfa, 0xfa, 0x92 }, + .sel_bg = .{ 0xee, 0xee, 0x9e }, .sel_fg = .{ 0x00, 0x00, 0x00 }, .tag_bg = .{ 0xea, 0xff, 0xff }, .tag_fg = .{ 0x00, 0x00, 0x00 }, - .box = .{ 0x66, 0x3f, 0xe0 }, - .box_dim = .{ 0x35, 0x3a, 0x58 }, + .box = .{ 0x88, 0x88, 0xcc }, + .box_dim = .{ 0xea, 0xff, 0xff }, .kw = .{ 0x97, 0x0d, 0xae }, .str = .{ 0x00, 0x77, 0x33 }, .num = .{ 0xa0, 0x52, 0x00 }, @@ -27,6 +35,17 @@ pub const theme = .{ .lineno = .{ 0x99, 0x99, 0x80 }, .scroll_track = .{ 0x99, 0x99, 0x4c }, .scroll_thumb = .{ 0xff, 0xff, 0xea }, + .border = .{ 0x00, 0x00, 0x00 }, + .tag_rule = .{ 0x88, 0x88, 0xcc }, + .rule_px = 2, + .box_border = .{ 0x88, 0x88, 0xcc }, + .box_dirty = .{ 0x00, 0x00, 0x99 }, + .rail_px = 12, + .tag_sel_bg = .{ 0x9e, 0xee, 0xee }, + .sweep_bg = .{ .{ 0xee, 0xee, 0x9e }, .{ 0xaa, 0x00, 0x00 }, .{ 0x00, 0x66, 0x00 } }, + .sweep_fg = .{ .{ 0x00, 0x00, 0x00 }, .{ 0xff, 0xff, 0xff }, .{ 0xff, 0xff, 0xff } }, + .column_box = .{ 0x88, 0x88, 0xcc }, + .column_box_dim = .{ 0x88, 0x88, 0xcc }, .palette = .{ .{ 0x00, 0x00, 0x00 }, .{ 0xaa, 0x22, 0x22 }, .{ 0x22, 0x80, 0x22 }, .{ 0x88, 0x66, 0x00 }, .{ 0x22, 0x44, 0xbb }, .{ 0x99, 0x33, 0x99 }, .{ 0x11, 0x88, 0x88 }, .{ 0x55, 0x55, 0x55 }, diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt index 87134609..4c19dc4f 100644 --- a/test/gui-goldens.txt +++ b/test/gui-goldens.txt @@ -1,18 +1,18 @@ -01-boot d4e3eb9dba35133df10d46b1d27ab7a90fa9bdefb64bcc304add221326ffdfc9 -02-second-document 235cdf75f3ce1d5800dbbdd7dcfde066af50cde01231a35d00c66c4310213a05 -03-notice 4d59205fe6b89a4589e94c68521257f21b40917c0198ea20248925687663d9f5 -04-context-rows-mid-thumb 7129ee318975f858c430b1e610b26ca756d5bb37553338c0dc6caab2290de2a4 -05-body-selection 39103f3747aae07a12b5151e7e377430b88b96e504670ac2f2d0272197eb827b -06-tag-focus-selection 6a7f50e292f4ede92df24a817218818b93a01fd15fcf39467cf06c3772077834 -07-prompt-notice d5cdacd74ae6d29834f0b09a607bfce5a2ca8a549c259be50b76a57a2d2d3271 -08-wide-glyph-edges 123a182b82a2a8c455291a5c89ccfa28e1f8ce9566172ca404c0b9c71dd33b48 -09-collapsed-focus-tint ae66076fdfb0cab9a906b052df257deee5ba91a6274712d52a76d8a7adc5b415 -10-tagbottom dd8ba0d3ecabf3420d000b0c9591a57278179cbd2a9060f849d283f3881d1847 -11-column-tags 6b1731d1611404597c96ad77f34c2d5cd5a81c1085ec68c6779388e07ce28614 -12-multiline-tag 0ea8f8a83fecef0e9a72d11f56bb7174739109d15074c56165274046bab48e17 -13-image 33986b288f6e2f4b3dc82113c7890980fc10362be471b3d6bf04eb1df7785e6b -14-theme-ink 3c435c76370d09c9856a7b481a52206f4ab0314729da79eef8b442d25fa210fa -15-theme-acme-light 3cf7ed81fc85ec91b5d448cab3d943f4724d754215944b4525e44885f73bccea -16-debug d2fcf18c15c1cbf771c0141b7e7f680185a099bec9102af147bf7951446afb16 -17-terminal 9b46bf9969382991243aafba88977db3bd276bb2d72dd06552b1683d3ee7d90e -18-mid-transition 11473788385e248cd7e1cc952629b3ad9a55c2068b51bf78d1494f631509e343 +01-boot 39c940960ccd264a0ed2e9dd99a2c352a47ca64c67d42e3313ae344713c712ae +02-second-document 9a810fe372fa37bc3d338d7f0bd04b5224957cb57ef63952c8fc02d1e72e705a +03-notice b52ebd3860694d35a43e56d149d180249b2609d4a3fc4374347d8cd3b5ebc05c +04-context-rows-mid-thumb e7b9f5d6da06143fff79b9f2686c29050556173f97c759a4119a201542f993ef +05-body-selection 06b7cbcf368bb4371873b26f70286fb2b5ada4293b758fa73458157a248c7aa6 +06-tag-focus-selection a781c95cebb8c47a5c5168e1b7f9747f44a0aad043cc4eec1338bb1f08d0a340 +07-prompt-notice f2a39f0d1136d818e57d693873256f2134d89f10805d4078eaaf16a59bece1ce +08-wide-glyph-edges ad7439148a7705aac6f5f3de3f6a47f9863dccffeba561c70a77e9b9838c5847 +09-collapsed-focus-tint 807bd7621d54948b84cf2965ef463d3928d050259e8ee2d27472b1d3d378f91e +10-tagbottom 03086852528d9c8a76f5926a7e7ee371200da617db279f42cbbb6825b5f41fce +11-column-tags 28ac090425c28515893f184b8c1fd0ba1dc3f05c0385372bb23a0b9e2abd080e +12-multiline-tag 72be302d73e36e6d51d940300d557e34b081b94ce7ec4e50e3bbd56968ed0d58 +13-image 162e6404d5578d1a8c34a8abd152b91d54588e8ddba540e88d48396560c06f71 +14-theme-ink 42b756fd9f1fba42004ac40983bc6c45445fd7d039a970c11378cac652e00d89 +15-theme-acme-light 5cecfb32915c217c5d39492b8708532bd826a8a55ad6b8bd49d61ed5cee29ab9 +16-debug 51628eb1d8dce799256e336d081d8510fe70b7a3f28f9efefcbca7126583b348 +17-terminal 65794362c9e76fa2930e85e888f17216d24cace7475e29da3610c0dab411887b +18-mid-transition 72ce5905098af73e571a1efe009a700940458924502bcf5e96bfafe7c4b8f29c diff --git a/test/snapshots/chordcut.golden b/test/snapshots/chordcut.golden index d5266717..bff929b3 100644 --- a/test/snapshots/chordcut.golden +++ b/test/snapshots/chordcut.golden @@ -20,7 +20,7 @@ | ls | f.txt g.txt == snap cut grid=120x31 cursor=7,8 -|6: /tmp/pardes-snap/chordcut/cwd/f.txt * Save Tty Collapse D +|6: * /tmp/pardes-snap/chordcut/cwd/f.txt * Save Tty Collapse D |8: 2 2 == snap copied grid=120x31 cursor=10,9 == snap pointpaste grid=120x31 cursor=16,10 @@ -30,7 +30,7 @@ == snap cancelexec grid=120x31 cursor=25,8 |8: 2 2echo B''OO g.txt == snap execafter grid=120x31 cursor=25,8 -|6: /tmp/pardes-snap/chordcut/cwd/f.txt * Save Tty Collapse D /tmp/pardes-snap/chordcut/cwd (echo B''OO) exit 0 Kill Sa +|6: * /tmp/pardes-snap/chordcut/cwd/f.txt * Save Tty Collapse D /tmp/pardes-snap/chordcut/cwd (echo B''OO) exit 0 Kill Sa |7: 1 line 1 Exec % echo B''OO |8: 2 2echo B''OO g.txt BOO |9: 3 line 3 exit 0 @@ -48,13 +48,13 @@ |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-59 d,#0d1410, 60-60 d,#608269, 61-119 d,#0d1410, -|6: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-37 #d3b0d9,#1d2824, 38-59 #b2c3b8,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-119 #b2c3b8,#1d2824, +|6: 0-0 #ffffff,#496656, 1-1 #000000,#f9cb70,b 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-37 #d3b0d9,#1d2824, 38-59 #b2c3b8,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-119 #b2c3b8,#1d2824, |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-24 #e5eee5,#0d1410, 25-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |11: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|12: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-37 #dfbce5,#26362d, 38-59 #becfc4,#26362d, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|12: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-37 #dfbce5,#26362d, 38-59 #becfc4,#26362d, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #f5f7ee,#3c614a, 11-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, @@ -77,7 +77,7 @@ |13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, == snap ttyprobe grid=120x31 cursor=42,22 -|12: /tmp/pardes-snap/chordcut/cwd/g.txt * Save Tty Collapse D +|12: * /tmp/pardes-snap/chordcut/cwd/g.txt * Save Tty Collapse D |17: $ /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter C |18: $ ls |20: $ stty -icanon -echo; printf '\x1b[?2004h\x1b[?1000h\x1b[? diff --git a/test/snapshots/delask.golden b/test/snapshots/delask.golden index 6b5331ec..35562f00 100644 --- a/test/snapshots/delask.golden +++ b/test/snapshots/delask.golden @@ -18,7 +18,7 @@ | | | -| /tmp/pardes-snap/delask/cwd/+New * Save Tty Collapse Del +| * /tmp/pardes-snap/delask/cwd/+New * Save Tty Collapse Del | 1 middle-pane | | @@ -27,7 +27,7 @@ | | | -| /tmp/pardes-snap/delask/cwd/+New * Save Tty Collapse Del +| * /tmp/pardes-snap/delask/cwd/+New * Save Tty Collapse Del | 1 bottom-pane Up == snap asked grid=100x36 cursor=18,20 |20: 1 middle-pane Del: give space to k above j below @@ -40,7 +40,7 @@ |29: == snap typed grid=100x36 cursor=27,20 |20: 1 bottom-pane DelBelow -|28: /tmp/pardes-snap/delask/cwd/+New * Save Tty Collapse Del +|28: * /tmp/pardes-snap/delask/cwd/+New * Save Tty Collapse Del |29: 1 third-pane == snap below-took-click grid=100x36 cursor=17,20 |20: 1 third-pane diff --git a/test/snapshots/fileedit.golden b/test/snapshots/fileedit.golden index 4d367d72..8a9555e0 100644 --- a/test/snapshots/fileedit.golden +++ b/test/snapshots/fileedit.golden @@ -5,7 +5,7 @@ | ls ls | f.txt f.txt | -| /tmp/pardes-snap/fileedit/cwd/f.txt * Save Tty Collapse Del +| * /tmp/pardes-snap/fileedit/cwd/f.txt * Save Tty Collapse Del | 1 liEDIT''MEne 1 | 2 line 2 | 3 line 3 diff --git a/test/snapshots/filewatch.golden b/test/snapshots/filewatch.golden index de44f0ca..ba9bce7c 100644 --- a/test/snapshots/filewatch.golden +++ b/test/snapshots/filewatch.golden @@ -1,7 +1,7 @@ == snap edited grid=210x31 cursor=15,3 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/filewatch/cwd/w.txt * Save Tty Collapse Del /tmp/pardes-snap/filewatch/cwd Tty+bash Save Mode Filter Collapse Del +| * /tmp/pardes-snap/filewatch/cwd/w.txt * Save Tty Collapse Del /tmp/pardes-snap/filewatch/cwd Tty+bash Save Mode Filter Collapse Del | 1 liMYEDITne 1 ls | 2 line 2 sub w.txt | 3 /tmp/pardes-snap/filewatch/cwd/sub/s.txt @@ -52,7 +52,7 @@ |25: |26: == snap undone grid=210x31 cursor=15,3 -|2: /tmp/pardes-snap/filewatch/cwd/w.txt * Save Tty Collapse Del /tmp/pardes-snap/filewatch/cwd Tty+bash Save Mode Filter Collapse Del +|2: * /tmp/pardes-snap/filewatch/cwd/w.txt * Save Tty Collapse Del /tmp/pardes-snap/filewatch/cwd Tty+bash Save Mode Filter Collapse Del |3: 1 liMYEDITne 1 ls |4: 2 line 2 sub w.txt |5: 3 /tmp/pardes-snap/filewatch/cwd/sub/s.txt diff --git a/test/snapshots/lsp-client-edit.golden b/test/snapshots/lsp-client-edit.golden index c75bae4c..7fe973f9 100644 --- a/test/snapshots/lsp-client-edit.golden +++ b/test/snapshots/lsp-client-edit.golden @@ -1,7 +1,7 @@ == snap formatted grid=100x25 cursor=7,4 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/lsp-client-edit/cwd/main.rs * Save Tty Collapse Del TreeContext +| * /tmp/pardes-snap/lsp-client-edit/cwd/main.rs * Save Tty Collapse Del TreeContext | 1 // formatted by mock formatted 1 range(s) | 2 mod lib; | 3 diff --git a/test/snapshots/lsp-rename.golden b/test/snapshots/lsp-rename.golden index f6dc8358..9151d1f3 100644 --- a/test/snapshots/lsp-rename.golden +++ b/test/snapshots/lsp-rename.golden @@ -1,7 +1,7 @@ == snap renamed grid=110x25 cursor=17,10 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/lsp-rename/cwd/rename.zig * Save Tty Collapse Del TreeContext +| * /tmp/pardes-snap/lsp-rename/cwd/rename.zig * Save Tty Collapse Del TreeContext | 1 const std = @import("std"); renamed 2 range(s) | 2 | 3 fn renamed_helper(x: u32) u32 { diff --git a/test/snapshots/lspcomplete.golden b/test/snapshots/lspcomplete.golden index 8efa7edf..0a58a231 100644 --- a/test/snapshots/lspcomplete.golden +++ b/test/snapshots/lspcomplete.golden @@ -16,7 +16,7 @@ | 12 // note. see below | 13 == snap indent grid=120x41 cursor=11,12 -|2: ^ /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save Tty Collapse Del TreeContext +|2: ^* /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save Tty Collapse Del TreeContext |12: 10 } == snap commentindent grid=120x41 cursor=19,14 |14: 12 // note. see below @@ -28,17 +28,17 @@ |40: 2 a.zig:3:5-12 cinnabar cinnabar, == snap refill grid=120x41 cursor=16,10 == snap step grid=120x41 cursor=19,4 -|2: /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save Tty Collapse Del TreeContext +|2: * /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save Tty Collapse Del TreeContext == snap txtindent grid=120x41 cursor=15,3 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcomplete/cwd/notes.txt * Save Tty Collapse Del +|^* /tmp/pardes-snap/lspcomplete/cwd/notes.txt * Save Tty Collapse Del | 1 abc. def ghi | 2 == snap nosemi grid=100x19 cursor=29,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcomplete/cwd/c.zig * Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lspcomplete/cwd/c.zig * Save Tty Collapse Del TreeContext | 1 const Shade = enum { | 2 verdigris, | 3 cinnabar, @@ -58,7 +58,7 @@ == snap nobrace grid=100x19 cursor=16,10 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcomplete/cwd/d.zig * Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lspcomplete/cwd/d.zig * Save Tty Collapse Del TreeContext | 1 const Shade = enum { | 2 verdigris, | 3 cinnabar, diff --git a/test/snapshots/lspcompletemoved.golden b/test/snapshots/lspcompletemoved.golden index f12b3f0c..797bcd11 100644 --- a/test/snapshots/lspcompletemoved.golden +++ b/test/snapshots/lspcompletemoved.golden @@ -1,7 +1,7 @@ == snap typedthrough grid=120x21 cursor=16,14 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcompletemoved/cwd/a.zig * Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lspcompletemoved/cwd/a.zig * Save Tty Collapse Del TreeContext | 1 const Shade = enum { | 2 verdigris, | 3 cinnabar, diff --git a/test/snapshots/lsprelpath.golden b/test/snapshots/lsprelpath.golden index 96dc4c99..e2201d5c 100644 --- a/test/snapshots/lsprelpath.golden +++ b/test/snapshots/lsprelpath.golden @@ -1,7 +1,7 @@ == snap absrow grid=100x35 cursor=16,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig * Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig * Save Tty Collapse Del TreeContext | 1 const shade = @import("../shade.zig"); | 2 | 3 pub fn pick(s: shade.Shade) u8 { @@ -35,7 +35,7 @@ | 1 /tmp/pardes-snap/lsprelpath/cwd/shade.zig:2:5-13 verdigris verdigris, | 2 /tmp/pardes-snap/lsprelpath/cwd/shade.zig:3:5-12 cinnabar cinnabar, == snap abslook grid=100x35 cursor=19,19 -|2: /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig * Save Tty Collapse Del TreeContext +|2: * /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig * Save Tty Collapse Del TreeContext |17: /tmp/pardes-snap/lsprelpath/cwd/shade.zig Save Tty Collapse Del TreeContext |18: 1 pub const Shade = enum { |19: 2 verdigris, @@ -45,7 +45,7 @@ == style abslook grid=100x35 |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, -|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, +|2: 0-0 #ffffff,#496656, 1-1 #000000,#f9cb70,b 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-27 #c9a7ed,#0d1410, 28-28 #e5eee5,#0d1410, 29-42 #b8d88a,#0d1410, 43-44 #e5eee5,#0d1410, 45-99 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-12 #c9a7ed,#0d1410, 13-38 #e5eee5,#0d1410, 39-99 d,#0d1410, @@ -81,7 +81,7 @@ == snap subrow grid=100x35 cursor=16,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save Tty Collapse Del TreeContext | 1 const tint = @import("inner/tint.zig"); | 2 | 3 pub fn hue(t: tint.Tint) u8 { @@ -115,7 +115,7 @@ | 1 inner/tint.zig:2:5-9 ochre ochre, | 2 inner/tint.zig:3:5-10 indigo indigo, == snap sublook grid=100x35 cursor=15,19 -|2: /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save Tty Collapse Del TreeContext +|2: * /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save Tty Collapse Del TreeContext |17: /tmp/pardes-snap/lsprelpath/cwd/sub/inner/tint.zig Save Tty Collapse Del TreeContext |18: 1 pub const Tint = enum { |19: 2 ochre, @@ -125,7 +125,7 @@ == style sublook grid=100x35 |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, -|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, +|2: 0-0 #ffffff,#496656, 1-1 #000000,#f9cb70,b 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-26 #c9a7ed,#0d1410, 27-27 #e5eee5,#0d1410, 28-43 #b8d88a,#0d1410, 44-45 #e5eee5,#0d1410, 46-99 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-12 #c9a7ed,#0d1410, 13-35 #e5eee5,#0d1410, 36-99 d,#0d1410, diff --git a/test/snapshots/msg.golden b/test/snapshots/msg.golden index d9a4a079..293e137c 100644 --- a/test/snapshots/msg.golden +++ b/test/snapshots/msg.golden @@ -45,7 +45,7 @@ |3: 1 AGAIN |4: 2 == snap msgbuiltin grid=70x17 cursor=29,5 -|2: /tmp/pardes-snap/msg/cwd/a.txt * Save Tty Collapse Del +|2: * /tmp/pardes-snap/msg/cwd/a.txt * Save Tty Collapse Del |3: 1 AGAIN hand-written report |5: 3 Msg hand-written report == snap msgtail grid=70x17 cursor=32,7 diff --git a/test/snapshots/multicursor.golden b/test/snapshots/multicursor.golden index 96762bd5..010199b1 100644 --- a/test/snapshots/multicursor.golden +++ b/test/snapshots/multicursor.golden @@ -52,13 +52,13 @@ |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-74 #e5eee5,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-74 #e5eee5,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, == snap edited-at-three grid=150x31 cursor=9,9 -|6: /tmp/pardes-snap/multicursor/cwd/f.txt * Save Tty Collapse Del +|6: * /tmp/pardes-snap/multicursor/cwd/f.txt * Save Tty Collapse Del |7: 1 >>line 1 |8: 2 >>line 2 |9: 3 >>line 3 == snap three-selections grid=150x31 cursor=8,9 == style three-selections grid=150x31 -|6: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-35 #becfc4,#26362d, 36-40 #dfbce5,#26362d, 41-64 #becfc4,#26362d, 65-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|6: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-35 #becfc4,#26362d, 36-40 #dfbce5,#26362d, 41-64 #becfc4,#26362d, 65-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #f5f7ee,#243a2d, 8-8 #3c614a,#f5f7ee, 9-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #f5f7ee,#243a2d, 8-8 #3c614a,#f5f7ee, 9-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-8 #f5f7ee,#3c614a, 9-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, diff --git a/test/snapshots/ninep.golden b/test/snapshots/ninep.golden index 65fb38ec..5391a552 100644 --- a/test/snapshots/ninep.golden +++ b/test/snapshots/ninep.golden @@ -73,7 +73,7 @@ |7: 5 return 7; @7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-19 #e5eee5,#0d1410,,body 20-89 d,#0d1410,,body == snap edited-with-colors-off grid=90x15 cursor=7,4 -|2: /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext +|2: * /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext |3: 1 // café λ 界 |4: 2 const greeting = "café λ 界 é"; |5: 3 @@ -83,8 +83,8 @@ |9: 7 } |10: 8 == 9p edited-with-colors-off grid=90x15 cursor=7,4,block -|2: /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext -@2: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-73 #becfc4,#26362d,,tagline 74-89 d,#26362d,,tagline +|2: * /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext +@2: 0-0 #000000,#add38a,,tagline 1-1 #000000,#f9cb70,b,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-73 #becfc4,#26362d,,tagline 74-89 d,#26362d,,tagline |3: 1 // café λ 界 +3: 13="é" 15="λ" 17="界" 18="" @3: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-18 #e5eee5,#0d1410,,body 19-89 d,#0d1410,,body @@ -104,7 +104,7 @@ |10: 8 @10: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body == style colors-on grid=90x15 -|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-73 #becfc4,#26362d, 74-89 d,#26362d, +|2: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-73 #becfc4,#26362d, 74-89 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #b9f480,#0d1410, 18-18 d,d, 19-81 d,#0d1410, 82-82 d,#1d2824, 83-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-23 #e5eee5,#0d1410, 24-32 #b8d88a,#0d1410, 33-33 d,d, 34-36 #b8d88a,#0d1410, 37-37 #e5eee5,#0d1410, 38-89 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, @@ -128,7 +128,7 @@ == style resized grid=105x20 |0: 0-70 #b2c3b8,#1d2824, 71-104 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-104 d,#26362d, -|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-73 #becfc4,#26362d, 74-104 d,#26362d, +|2: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-73 #becfc4,#26362d, 74-104 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #b9f480,#0d1410, 18-18 d,d, 19-96 d,#0d1410, 97-97 d,#1d2824, 98-103 #b2c3b8,#1d2824, 104-104 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-23 #e5eee5,#0d1410, 24-32 #b8d88a,#0d1410, 33-33 d,d, 34-36 #b8d88a,#0d1410, 37-37 #e5eee5,#0d1410, 38-104 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-104 d,#0d1410, @@ -151,8 +151,8 @@ @0: 0-70 #b2c3b8,#1d2824,,tagline 71-104 d,#1d2824,,tagline |1: New Tty Find Grep Joincol Delcol @1: 0-1 #ffffff,#887455,,tagline 2-2 d,#26362d,,tagline 3-34 #becfc4,#26362d,,tagline 35-104 d,#26362d,,tagline -|2: /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext -@2: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-73 #becfc4,#26362d,,tagline 74-104 d,#26362d,,tagline +|2: * /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext +@2: 0-0 #000000,#add38a,,tagline 1-1 #000000,#f9cb70,b,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-73 #becfc4,#26362d,,tagline 74-104 d,#26362d,,tagline |3: 1 // café λ 界 Colors +3: 13="é" 15="λ" 17="界" 18="" @3: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-18 #b9f480,#0d1410,,body 19-96 d,#0d1410,,body 97-97 d,#1d2824,,tagline 98-103 #b2c3b8,#1d2824,,tagline 104-104 d,#1d2824,,tagline @@ -192,7 +192,7 @@ == snap resized grid=105x20 cursor=7,4 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext +| * /tmp/pardes-snap/ninep/cwd/demo.zig * Save Tty Collapse Del TreeContext | 1 // café λ 界 Colors | 2 const greeting = "café λ 界 é"; | 3 diff --git a/test/snapshots/pipe.golden b/test/snapshots/pipe.golden index 14353ebf..208986a8 100644 --- a/test/snapshots/pipe.golden +++ b/test/snapshots/pipe.golden @@ -5,7 +5,7 @@ | 1 alpha beta |tr a-z A-Z | 2 gamma delta == snap filtered grid=180x13 cursor=17,3 -|2: /tmp/pardes-snap/pipe/cwd/pipe.txt * Save Tty Collapse Del +|2: * /tmp/pardes-snap/pipe/cwd/pipe.txt * Save Tty Collapse Del |3: 1 ALPHA BETA == snap undone grid=180x13 cursor=17,3 |3: 1 alpha beta diff --git a/test/snapshots/savepane.golden b/test/snapshots/savepane.golden index 5fcdf656..f1a47696 100644 --- a/test/snapshots/savepane.golden +++ b/test/snapshots/savepane.golden @@ -4,7 +4,7 @@ | /tmp/pardes-snap/savepane/cwd Tty+bash Save Mode Filter Collapse Del | ls | -| /tmp/pardes-snap/savepane/cwd/+New * Save Tty Collapse Del +| * /tmp/pardes-snap/savepane/cwd/+New * Save Tty Collapse Del | 1 scratch bytes == snap prompt grid=100x31 cursor=99,7 |6: 1 scratch bytes Save diff --git a/test/snapshots/selregex.golden b/test/snapshots/selregex.golden index 8b45ae43..fa29b661 100644 --- a/test/snapshots/selregex.golden +++ b/test/snapshots/selregex.golden @@ -51,7 +51,7 @@ |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f5f7ee,#243a2d, 11-11 #3c614a,#f5f7ee, 12-18 #e5eee5,#0d1410, 19-19 #f8c082,#0d1410, 20-20 #e5eee5,#0d1410, 21-159 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f5f7ee,#243a2d, 11-11 #3c614a,#f5f7ee, 12-20 #e5eee5,#0d1410, 21-21 #f8c082,#0d1410, 22-22 #e5eee5,#0d1410, 23-159 d,#0d1410, == snap commented grid=160x25 cursor=14,3 -|2: /tmp/pardes-snap/selregex/cwd/m.zig * Save Tty Collapse Del TreeContext +|2: * /tmp/pardes-snap/selregex/cwd/m.zig * Save Tty Collapse Del TreeContext |3: 1 // const one = 1; |4: 2 // const two = 2; |5: 3 // const three = 3; diff --git a/test/snapshots/tagbottom.golden b/test/snapshots/tagbottom.golden index 8d770358..637d3143 100644 --- a/test/snapshots/tagbottom.golden +++ b/test/snapshots/tagbottom.golden @@ -164,7 +164,7 @@ |8: 7 mmm |9: 8 nnn |10: 9 ooo -|11: /tmp/pardes-snap/tagbottom/cwd/top.txt * Save Tty Collapse Del bottom.txt +|11: * /tmp/pardes-snap/tagbottom/cwd/top.txt * Save Tty Collapse Del bottom.txt |12: 1 ZZONE == file cut-buffer aaa diff --git a/test/snapshots/tagcmd.golden b/test/snapshots/tagcmd.golden index 7853de32..83106d0d 100644 --- a/test/snapshots/tagcmd.golden +++ b/test/snapshots/tagcmd.golden @@ -1,7 +1,7 @@ == snap edited grid=100x31 cursor=13,3 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/tagcmd/cwd/c.txt * Save Tty Collapse Del +| * /tmp/pardes-snap/tagcmd/cwd/c.txt * Save Tty Collapse Del | 1 ZZEDITline 1 | 2 line 2 | 3 line 3 diff --git a/test/snapshots/tagpath.golden b/test/snapshots/tagpath.golden index ba22a859..2363bcd6 100644 --- a/test/snapshots/tagpath.golden +++ b/test/snapshots/tagpath.golden @@ -43,15 +43,15 @@ |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, == snap yanked-path grid=100x31 cursor=41,3 -|2: /tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +|2: * /tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del |3: 1 l/tmp/pardes-snap/tagpath/cwd/c.txtine 1 == snap drag-cross grid=100x31 cursor=41,2 == style drag-cross grid=100x31 -|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-10 #becfc4,#26362d, 11-41 #f5f7ee,#3c614a, 42-60 #becfc4,#26362d, 61-99 d,#26362d, +|2: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-10 #becfc4,#26362d, 11-41 #f5f7ee,#3c614a, 42-60 #becfc4,#26362d, 61-99 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-41 #f5f7ee,#3c614a, 42-46 #e5eee5,#0d1410, 47-99 d,#0d1410, == snap name-draft grid=100x31 cursor=11,2 -|2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +|2: * /renamed/tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del == snap name-cancelled grid=100x31 cursor=41,3 -|2: /tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +|2: * /tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del == snap name-committed grid=100x31 cursor=41,3 -|2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +|2: * /renamed/tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden index 4986eaae..5e7a3dc4 100644 --- a/test/snapshots/theme.golden +++ b/test/snapshots/theme.golden @@ -612,9 +612,9 @@ |30: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, == snap acme grid=100x31 cursor=4,5 == style acme grid=100x31 -|0: 0-50 #000000,#eaffff, 51-59 #000000,#fafa92, 60-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-49 d,#eaffff, 50-51 #000000,#c5a87f, 52-52 d,#eaffff, 53-84 #000000,#eaffff, 85-99 d,#eaffff, -|2: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #ffffff,#353a58, 52-52 d,#eaffff, 53-99 #000000,#eaffff, +|0: 0-50 #000000,#eaffff, 51-59 #000000,#9eeeee, 60-70 #000000,#eaffff, 71-99 d,#eaffff, +|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-49 d,#eaffff, 50-51 #000000,#8888cc, 52-52 d,#eaffff, 53-84 #000000,#eaffff, 85-99 d,#eaffff, +|2: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #000000,#b9c3e5, 52-52 d,#eaffff, 53-99 #000000,#eaffff, |3: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |4: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |5: 0-99 d,#ffffea, @@ -629,7 +629,7 @@ |14: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |15: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |16: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, -|17: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|17: 0-1 #000000,#b9c3e5, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |18: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |19: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |20: 0-51 d,#ffffea, 52-99 #000000,#ffffea, diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden index 97d3ed41..65a4f563 100644 --- a/test/snapshots/themesel.golden +++ b/test/snapshots/themesel.golden @@ -630,8 +630,8 @@ |30: 24 Theme adwaita_dark == style step-acme grid=100x31 |0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, -|2: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, +|2: 0-1 #000000,#b9c3e5, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, @@ -646,7 +646,7 @@ |14: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |15: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |16: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|17: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, +|17: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, |18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, |19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, |20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, @@ -709,8 +709,8 @@ == snap step-back grid=100x31 cursor=7,26 == style step-back grid=100x31 |0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, -|2: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, +|2: 0-1 #000000,#b9c3e5, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, @@ -725,7 +725,7 @@ |14: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |15: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |16: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|17: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, +|17: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, |18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, |19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, |20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, @@ -769,7 +769,7 @@ |29: 27 line 27 |30: 28 line 28 == style picked grid=100x31 -|2: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|2: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-12 #000000,#ffffea, 13-99 d,#ffffea, |7: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |8: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, diff --git a/test/snapshots/yankpaste.golden b/test/snapshots/yankpaste.golden index 2efcd66d..12e7a74e 100644 --- a/test/snapshots/yankpaste.golden +++ b/test/snapshots/yankpaste.golden @@ -1,7 +1,7 @@ == snap linewise-below grid=100x31 cursor=13,6 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/yankpaste/cwd/f.txt * Save Tty Collapse Del +| * /tmp/pardes-snap/yankpaste/cwd/f.txt * Save Tty Collapse Del | 1 line 1 | 2 line 2 | 3 line 3 |
