From 743e54723568485b24cd5279f764c28ef80d1fb7 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Mon, 23 Sep 2024 21:49:24 -0500 Subject: [PATCH 1/5] cli: "fancy" theme preview This adds a "fancy" theme preview to the `+list-themes` CLI action. By default, if the command is connected to a TTY, it will display the fancy preview. If it is not connected to a TTY, or the user specifies `--plain` on the command line, a simple list of themes will be printed to stdout. While in the preview `F1` or `?` will show a help screen. --- build.zig | 1 - build.zig.zon | 4 +- nix/zigCacheHash.nix | 2 +- src/cli/list_themes.zig | 1301 +++++++++++++++++++++++++++++++++++++-- src/cli/lorem_ipsum.txt | 45 ++ 5 files changed, 1295 insertions(+), 58 deletions(-) create mode 100644 src/cli/lorem_ipsum.txt diff --git a/build.zig b/build.zig index ba3e4f553..b834f57a3 100644 --- a/build.zig +++ b/build.zig @@ -1038,7 +1038,6 @@ fn addDeps( .optimize = optimize, .libxev = false, .images = false, - .text_input = false, }); const wuffs_dep = b.dependency("wuffs", .{ .target = target, diff --git a/build.zig.zon b/build.zig.zon index fbbd52131..f2add6c07 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -54,8 +54,8 @@ .hash = "122056fbb29863ec1678b7954fb76b1533ad8c581a34577c1b2efe419e29e05596df", }, .vaxis = .{ - .url = "git+https://github.com/rockorager/libvaxis?ref=main#a8baf9ce371b89a84383130c82549bb91401d15a", - .hash = "12207f53d7dddd3e5ca6577fcdd137dcf1fa32c9f22cbb0911ad0701cde4095a1c4c", + .url = "git+https://github.com/rockorager/libvaxis?ref=main#2cc1eb77f842dd8587dfc9cf399d42e4c1369175", + .hash = "12203c2d83911e6aacfbfdd48d31d6fc36e89947dfc7aec104debe3ac85e9f3a44f2", }, }, } diff --git a/nix/zigCacheHash.nix b/nix/zigCacheHash.nix index 62c7f4767..ad2e6a827 100644 --- a/nix/zigCacheHash.nix +++ b/nix/zigCacheHash.nix @@ -1,3 +1,3 @@ # This file is auto-generated! check build-support/check-zig-cache-hash.sh for # more details. -"sha256-MocGI5dxh+WO79p01HbdFuc+wR+sXSxBnoFAmrX4p0s=" +"sha256-MAzGg4tWlyv2X/GjAwm7s2whojawIKNMx1xWR+cZffQ=" diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index 4a90df1c5..dd812d5ca 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -4,15 +4,18 @@ const args = @import("args.zig"); const Action = @import("action.zig").Action; const Config = @import("../config/Config.zig"); const themepkg = @import("../config/theme.zig"); +const tui = @import("tui.zig"); const internal_os = @import("../os/main.zig"); const global_state = &@import("../global.zig").state; +const vaxis = @import("vaxis"); + pub const Options = struct { /// If true, print the full path to the theme. path: bool = false, - /// If true, show a small preview of the theme. - preview: bool = false, + /// If true, force a plain list of themes. + plain: bool = false, pub fn deinit(self: Options) void { _ = self; @@ -25,8 +28,31 @@ pub const Options = struct { } }; -/// The `list-themes` command is used to list all the available themes for -/// Ghostty. +const ThemeListElement = struct { + location: themepkg.Location, + path: []const u8, + theme: []const u8, + + fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { + // TODO: use Unicode-aware comparison + return std.ascii.orderIgnoreCase(lhs.theme, rhs.theme) == .lt; + } + + pub fn toUri(self: *const ThemeListElement, alloc: std.mem.Allocator) ![]const u8 { + const uri = std.Uri{ + .scheme = "file", + .host = .{ .raw = "" }, + .path = .{ .raw = self.path }, + }; + var buf = std.ArrayList(u8).init(alloc); + errdefer buf.deinit(); + try uri.writeToStream(.{ .scheme = true, .authority = true, .path = true }, buf.writer()); + return buf.toOwnedSlice(); + } +}; + +/// The `list-themes` command is used to preview or list all the available +/// themes for Ghostty. /// /// Two different directories will be searched for themes. /// @@ -48,7 +74,7 @@ pub const Options = struct { /// Flags: /// /// * `--path`: Show the full path to the theme. -/// * `--preview`: Show a short preview of the theme colors. +/// * `--plain`: Show a short preview of the theme colors. pub fn run(gpa_alloc: std.mem.Allocator) !u8 { var opts: Options = .{}; defer opts.deinit(); @@ -69,16 +95,6 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { try stderr.print("Could not find the Ghostty resources directory. Please ensure " ++ "that Ghostty is installed correctly.\n", .{}); - const ThemeListElement = struct { - location: themepkg.Location, - path: []const u8, - theme: []const u8, - fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { - // TODO: use Unicode-aware comparison - return std.ascii.orderIgnoreCase(lhs.theme, rhs.theme) == .lt; - } - }; - var count: usize = 0; var themes = std.ArrayList(ThemeListElement).init(alloc); @@ -108,53 +124,1230 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { } } + if (count == 0) { + try stderr.print("No themes found, check to make sure that the themes were installed correctly.", .{}); + return 1; + } + std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan); + if (tui.can_pretty_print and !opts.plain and std.posix.isatty(std.io.getStdOut().handle)) { + try preview(gpa_alloc, themes.items); + return 0; + } + for (themes.items) |theme| { if (opts.path) try stdout.print("{s} ({s}) {s}\n", .{ theme.theme, @tagName(theme.location), theme.path }) else try stdout.print("{s} ({s})\n", .{ theme.theme, @tagName(theme.location) }); - - if (opts.preview) { - var config = try Config.default(gpa_alloc); - defer config.deinit(); - if (config.loadFile(config._arena.?.allocator(), theme.path)) |_| { - if (!config._errors.empty()) { - try stderr.print(" Problems were encountered trying to load the theme:\n", .{}); - for (config._errors.list.items) |err| { - try stderr.print(" {s}\n", .{err.message}); - } - } - try stdout.print("\n ", .{}); - for (0..8) |i| { - try stdout.print(" {d:2} \x1b[38;2;{d};{d};{d}m██\x1b[0m", .{ - i, - config.palette.value[i].r, - config.palette.value[i].g, - config.palette.value[i].b, - }); - } - try stdout.print("\n ", .{}); - for (8..16) |i| { - try stdout.print(" {d:2} \x1b[38;2;{d};{d};{d}m██\x1b[0m", .{ - i, - config.palette.value[i].r, - config.palette.value[i].g, - config.palette.value[i].b, - }); - } - try stdout.print("\n\n", .{}); - } else |err| { - try stderr.print("unable to load {s}: {}", .{ theme.path, err }); - } - } - } - - if (count == 0) { - try stderr.print("No themes found, check to make sure that the themes were installed correctly.", .{}); - return 1; } return 0; } + +const Event = union(enum) { + key_press: vaxis.Key, + mouse: vaxis.Mouse, + color_scheme: vaxis.Color.Scheme, + winsize: vaxis.Winsize, +}; + +const Preview = struct { + allocator: std.mem.Allocator, + should_quit: bool, + tty: vaxis.Tty, + vx: vaxis.Vaxis, + mouse: ?vaxis.Mouse, + themes: []ThemeListElement, + current: usize, + hex: bool, + help_visible: bool, + color_scheme: vaxis.Color.Scheme, + + pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement) !Preview { + return .{ + .allocator = allocator, + .should_quit = false, + .tty = try vaxis.Tty.init(), + .vx = try vaxis.init(allocator, .{}), + .mouse = null, + .themes = themes, + .current = 0, + .hex = false, + .help_visible = false, + .color_scheme = .light, + }; + } + + pub fn deinit(self: *Preview) void { + self.vx.deinit(self.allocator, self.tty.anyWriter()); + self.tty.deinit(); + } + + pub fn run(self: *Preview) !void { + var loop: vaxis.Loop(Event) = .{ + .tty = &self.tty, + .vaxis = &self.vx, + }; + try loop.init(); + try loop.start(); + + try self.vx.enterAltScreen(self.tty.anyWriter()); + try self.vx.setTitle(self.tty.anyWriter(), "👻 Ghostty Theme Preview 👻"); + try self.vx.queryTerminal(self.tty.anyWriter(), 1 * std.time.ns_per_s); + try self.vx.setMouseMode(self.tty.anyWriter(), true); + if (self.vx.caps.color_scheme_updates) + try self.vx.subscribeToColorSchemeUpdates(self.tty.anyWriter()); + + while (!self.should_quit) { + var arena = std.heap.ArenaAllocator.init(self.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + loop.pollEvent(); + while (loop.tryEvent()) |event| { + try self.update(event, alloc); + } + try self.draw(alloc); + + var buffered = self.tty.bufferedWriter(); + try self.vx.render(buffered.writer().any()); + try buffered.flush(); + } + } + + fn up(self: *Preview, count: usize) void { + self.current = std.math.sub(usize, self.current, count) catch self.themes.len + self.current - count; + } + + fn down(self: *Preview, count: usize) void { + self.current = (self.current + count) % self.themes.len; + } + + pub fn update(self: *Preview, event: Event, alloc: std.mem.Allocator) !void { + switch (event) { + .key_press => |key| { + if (key.matches('c', .{ .ctrl = true })) + self.should_quit = true; + if (key.matches('q', .{})) + self.should_quit = true; + if (key.matches(vaxis.Key.escape, .{})) + self.should_quit = true; + if (key.matches('?', .{})) + self.help_visible = !self.help_visible; + if (key.matches('h', .{ .ctrl = true })) + self.help_visible = !self.help_visible; + if (key.matches(vaxis.Key.f1, .{})) + self.help_visible = !self.help_visible; + if (key.matches('0', .{})) + self.current = 0; + if (key.matches(vaxis.Key.home, .{})) + self.current = 0; + if (key.matches(vaxis.Key.kp_home, .{})) + self.current = 0; + if (key.matches(vaxis.Key.end, .{})) + self.current = self.themes.len - 1; + if (key.matches(vaxis.Key.kp_end, .{})) + self.current = self.themes.len - 1; + if (key.matches('j', .{})) + self.down(1); + if (key.matches('+', .{})) + self.down(1); + if (key.matches(vaxis.Key.down, .{})) + self.down(1); + if (key.matches(vaxis.Key.kp_down, .{})) + self.down(1); + if (key.matches(vaxis.Key.kp_add, .{})) + self.down(1); + if (key.matches(vaxis.Key.page_down, .{})) + self.down(20); + if (key.matches(vaxis.Key.kp_page_down, .{})) + self.down(20); + if (key.matches('k', .{})) + self.up(1); + if (key.matches('-', .{})) + self.up(1); + if (key.matches(vaxis.Key.up, .{})) + self.up(1); + if (key.matches(vaxis.Key.kp_up, .{})) + self.up(1); + if (key.matches(vaxis.Key.kp_subtract, .{})) + self.up(1); + if (key.matches(vaxis.Key.page_up, .{})) + self.up(20); + if (key.matches(vaxis.Key.kp_page_up, .{})) + self.up(20); + if (key.matches('h', .{})) + self.hex = true; + if (key.matches('x', .{})) + self.hex = true; + if (key.matches('d', .{})) + self.hex = false; + if (key.matches('c', .{})) + try self.vx.copyToSystemClipboard( + self.tty.anyWriter(), + self.themes[self.current].theme, + alloc, + ); + if (key.matches('c', .{ .shift = true })) + try self.vx.copyToSystemClipboard( + self.tty.anyWriter(), + self.themes[self.current].path, + alloc, + ); + }, + .color_scheme => |color_scheme| self.color_scheme = color_scheme, + .mouse => |mouse| self.mouse = mouse, + .winsize => |ws| try self.vx.resize(self.allocator, self.tty.anyWriter(), ws), + } + } + + pub fn draw(self: *Preview, alloc: std.mem.Allocator) !void { + const win = self.vx.window(); + win.clear(); + + self.vx.setMouseShape(.default); + + const ui_fg: vaxis.Color = switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } }, + .dark => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } }, + }; + const ui_bg: vaxis.Color = switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } }, + .dark => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } }, + }; + const ui_standard: vaxis.Style = .{ + .fg = ui_fg, + .bg = ui_bg, + }; + + const ui_hover_bg: vaxis.Color = switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xbb, 0xbb, 0xbb } }, + .dark => .{ .rgb = [_]u8{ 0x22, 0x22, 0x22 } }, + }; + + const ui_selected_fg: vaxis.Color = switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } }, + .dark => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } }, + }; + const ui_selected_bg: vaxis.Color = switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xaa, 0xaa, 0xaa } }, + .dark => .{ .rgb = [_]u8{ 0x33, 0x33, 0x33 } }, + }; + const ui_selected: vaxis.Style = .{ + .fg = ui_selected_fg, + .bg = ui_selected_bg, + }; + + const theme_list = win.child(.{ + .x_off = 0, + .y_off = 0, + .width = .{ .limit = 32 }, + .height = .{ .limit = win.height }, + }); + + const split = theme_list.height / 2; + + var highlight: ?usize = null; + + if (self.mouse) |mouse| { + self.mouse = null; + if (mouse.button == .wheel_up) { + self.up(1); + } + if (mouse.button == .wheel_down) { + self.down(1); + } + if (theme_list.hasMouse(mouse)) |_| { + if (mouse.button == .left and mouse.type == .release) { + if (mouse.row < split) self.up(split - mouse.row); + if (mouse.row > split) self.down(mouse.row - split); + } + highlight = mouse.row; + } + } + + theme_list.fill(.{ .style = ui_standard }); + + for (0..split) |i| { + const j = std.math.sub(usize, self.current, i + 1) catch self.themes.len + self.current - i - 1; + const theme = self.themes[j]; + const row = split - i - 1; + + _ = try theme_list.printSegment( + .{ + .text = theme.theme, + .style = .{ + .fg = ui_fg, + .bg = bg: { + if (highlight) |h| if (h == row) break :bg ui_hover_bg; + break :bg ui_bg; + }, + }, + .link = .{ + .uri = try theme.toUri(alloc), + }, + }, + .{ + .row_offset = row, + .col_offset = 2, + }, + ); + } + { + const theme = self.themes[self.current]; + _ = try theme_list.printSegment( + .{ + .text = "❯ ", + .style = ui_selected, + }, + .{ + .row_offset = split, + .col_offset = 0, + }, + ); + _ = try theme_list.printSegment( + .{ + .text = theme.theme, + .style = ui_selected, + .link = .{ + .uri = try theme.toUri(alloc), + }, + }, + .{ + .row_offset = split, + .col_offset = 2, + }, + ); + if (theme.theme.len < theme_list.width - 4) { + for (2 + theme.theme.len..theme_list.width - 2) |i| + _ = try theme_list.printSegment( + .{ + .text = " ", + .style = ui_selected, + }, + .{ + .row_offset = split, + .col_offset = i, + }, + ); + } + _ = try theme_list.printSegment( + .{ + .text = " ❮", + .style = ui_selected, + }, + .{ + .row_offset = split, + .col_offset = theme_list.width - 2, + }, + ); + } + for (split + 1..theme_list.height) |i| { + const j = (self.current + i - split) % self.themes.len; + const row = i; + const theme = self.themes[j]; + _ = try theme_list.printSegment(.{ + .text = theme.theme, + .style = .{ + .fg = ui_fg, + .bg = bg: { + if (highlight) |h| if (h == row) break :bg ui_hover_bg; + break :bg ui_bg; + }, + }, + .link = .{ + .uri = try theme.toUri(alloc), + }, + }, .{ + .row_offset = i, + .col_offset = 2, + }); + } + + try self.drawPreview(alloc, win, theme_list.x_off + theme_list.width, ui_fg, ui_bg); + + if (self.help_visible) { + const width = 60; + const height = 20; + const child = win.child( + .{ + .x_off = win.width / 2 -| width / 2, + .y_off = win.height / 2 -| height / 2, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = height, + }, + .border = .{ + .where = .all, + .style = ui_standard, + }, + }, + ); + + child.fill(.{ .style = ui_standard }); + + const key_help = [_]struct { keys: []const u8, help: []const u8 }{ + .{ .keys = "^C, q, ESC", .help = "Quit." }, + .{ .keys = "F1, ?, ^H", .help = "Toggle help window." }, + .{ .keys = "k, ↑", .help = "Move up 1 theme." }, + .{ .keys = "ScrollUp", .help = "Move up 1 theme." }, + .{ .keys = "PgUp", .help = "Move up 20 themes." }, + .{ .keys = "j, ↓", .help = "Move down 1 theme." }, + .{ .keys = "ScrollDown", .help = "Move down 1 theme." }, + .{ .keys = "PgDown", .help = "Move down 20 themes." }, + .{ .keys = "h, x", .help = "Show palette numbers in hexadecimal." }, + .{ .keys = "d", .help = "Show palette numbers in decimal." }, + .{ .keys = "c", .help = "Copy theme name to the clipboard." }, + .{ .keys = "C", .help = "Copy theme path to the clipboard." }, + .{ .keys = "0, Home", .help = "Go to the start of the list." }, + .{ .keys = "End", .help = "Go to the end of the list." }, + }; + + for (key_help, 0..) |help, i| { + _ = try child.printSegment( + .{ + .text = help.keys, + .style = ui_standard, + }, + .{ + .row_offset = i + 1, + .col_offset = 2, + }, + ); + _ = try child.printSegment( + .{ + .text = "—", + .style = ui_standard, + }, + .{ + .row_offset = i + 1, + .col_offset = 15, + }, + ); + _ = try child.printSegment( + .{ + .text = help.help, + .style = ui_standard, + }, + .{ + .row_offset = i + 1, + .col_offset = 17, + }, + ); + } + } + } + + pub fn drawPreview(self: *Preview, alloc: std.mem.Allocator, win: vaxis.Window, x_off: usize, ui_fg: vaxis.Color, ui_bg: vaxis.Color) !void { + const width = win.width - x_off; + + const ui_err_fg: vaxis.Color = switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } }, + .dark => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } }, + }; + + const theme = self.themes[self.current]; + + var config = try Config.default(alloc); + defer config.deinit(); + + config.loadFile(config._arena.?.allocator(), theme.path) catch |err| { + const child = win.child( + .{ + .x_off = x_off, + .y_off = 0, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = win.height, + }, + }, + ); + child.fill(.{ .style = .{ .fg = ui_fg, .bg = ui_bg } }); + const middle = child.height / 2; + { + const text = try std.fmt.allocPrint(alloc, "Unable to open {s} from:", .{theme.theme}); + _ = try child.printSegment( + .{ + .text = text, + .style = .{ + .fg = ui_err_fg, + .bg = ui_bg, + }, + }, + .{ + .row_offset = middle -| 1, + .col_offset = child.width / 2 -| text.len / 2, + }, + ); + } + { + _ = try child.printSegment( + .{ + .text = theme.path, + .style = .{ + .fg = ui_err_fg, + .bg = ui_bg, + }, + .link = .{ + .uri = try theme.toUri(alloc), + }, + }, + .{ + .row_offset = middle, + .col_offset = child.width / 2 -| theme.path.len / 2, + }, + ); + } + { + const text = try std.fmt.allocPrint(alloc, "{}", .{err}); + _ = try child.printSegment( + .{ + .text = text, + .style = .{ + .fg = ui_err_fg, + .bg = ui_bg, + }, + }, + .{ + .row_offset = middle + 1, + .col_offset = child.width / 2 -| text.len / 2, + }, + ); + } + return; + }; + + var next_start: usize = 0; + const fg: vaxis.Color = .{ + .rgb = [_]u8{ + config.foreground.r, + config.foreground.g, + config.foreground.b, + }, + }; + const bg: vaxis.Color = .{ + .rgb = [_]u8{ + config.background.r, + config.background.g, + config.background.b, + }, + }; + const standard: vaxis.Style = .{ + .fg = fg, + .bg = bg, + }; + const standard_bold: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .bold = true, + }; + const standard_italic: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .italic = true, + }; + const standard_bold_italic: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .bold = true, + .italic = true, + }; + const standard_underline: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .ul_style = .single, + }; + const standard_double_underline: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .ul_style = .double, + }; + const standard_dashed_underline: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .ul_style = .dashed, + }; + const standard_curly_underline: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .ul_style = .curly, + }; + const standard_dotted_underline: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .ul_style = .dotted, + }; + + { + const child = win.child( + .{ + .x_off = x_off, + .y_off = next_start, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = 4, + }, + }, + ); + child.fill(.{ .style = standard }); + _ = try child.printSegment( + .{ + .text = theme.theme, + .style = standard_bold_italic, + .link = .{ + .uri = try theme.toUri(alloc), + }, + }, + .{ + .row_offset = 1, + .col_offset = child.width / 2 -| theme.theme.len / 2, + }, + ); + _ = try child.printSegment( + .{ + .text = theme.path, + .style = standard, + .link = .{ + .uri = try theme.toUri(alloc), + }, + }, + .{ + .row_offset = 2, + .col_offset = child.width / 2 -| theme.path.len / 2, + .wrap = .none, + }, + ); + next_start += child.height; + } + + if (!config._errors.empty()) { + const child = win.child( + .{ + .x_off = x_off, + .y_off = next_start, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = if (config._errors.empty()) 0 else 2 + config._errors.list.items.len, + }, + }, + ); + { + const text = "Problems were encountered trying to load the theme:"; + _ = try child.printSegment( + .{ + .text = text, + .style = .{ + .fg = ui_err_fg, + .bg = ui_bg, + }, + }, + .{ + .row_offset = 0, + .col_offset = child.width / 2 -| (text.len / 2), + }, + ); + } + for (config._errors.list.items, 0..) |err, i| { + _ = try child.printSegment( + .{ + .text = err.message, + .style = .{ + .fg = ui_err_fg, + .bg = ui_bg, + }, + }, + .{ + .row_offset = 2 + i, + .col_offset = 2, + }, + ); + } + next_start += child.height; + } + { + const child = win.child(.{ + .x_off = x_off, + .y_off = next_start, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = 6, + }, + }); + + child.fill(.{ .style = standard }); + + for (0..16) |i| { + const r = i / 8; + const c = i % 8; + const text = if (self.hex) + try std.fmt.allocPrint(alloc, " {x:0>2}", .{i}) + else + try std.fmt.allocPrint(alloc, "{d:3}", .{i}); + _ = try child.printSegment( + .{ + .text = text, + .style = standard, + }, + .{ + .row_offset = 3 * r, + .col_offset = c * 8, + }, + ); + _ = try child.printSegment( + .{ + .text = "████", + .style = .{ + .fg = color(config, i), + .bg = bg, + }, + }, + .{ + .row_offset = 3 * r, + .col_offset = 4 + c * 8, + }, + ); + _ = try child.printSegment( + .{ + .text = "████", + .style = .{ + .fg = color(config, i), + .bg = bg, + }, + }, + .{ + .row_offset = 3 * r + 1, + .col_offset = 4 + c * 8, + }, + ); + } + next_start += child.height; + } + { + const child = win.child( + .{ + .x_off = x_off, + .y_off = next_start, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = 24, + }, + }, + ); + const bold: vaxis.Style = .{ + .fg = fg, + .bg = bg, + .bold = true, + }; + const color1: vaxis.Style = .{ + .fg = color(config, 1), + .bg = bg, + }; + const color2: vaxis.Style = .{ + .fg = color(config, 2), + .bg = bg, + }; + const color3: vaxis.Style = .{ + .fg = color(config, 3), + .bg = bg, + }; + const color4: vaxis.Style = .{ + .fg = color(config, 4), + .bg = bg, + }; + const color5: vaxis.Style = .{ + .fg = color(config, 5), + .bg = bg, + }; + const color6: vaxis.Style = .{ + .fg = color(config, 6), + .bg = bg, + }; + const color6ul: vaxis.Style = .{ + .fg = color(config, 6), + .bg = bg, + .ul_style = .single, + }; + const color10: vaxis.Style = .{ + .fg = color(config, 10), + .bg = bg, + }; + const color12: vaxis.Style = .{ + .fg = color(config, 12), + .bg = bg, + }; + const color238: vaxis.Style = .{ + .fg = color(config, 238), + .bg = bg, + }; + child.fill(.{ .style = standard }); + _ = try child.print( + &.{ + .{ .text = "→", .style = color2 }, + .{ .text = " ", .style = standard }, + .{ .text = "bat", .style = color4 }, + .{ .text = " ", .style = standard }, + .{ .text = "ziggzagg.zig", .style = color6ul }, + }, + .{ + .row_offset = 0, + .col_offset = 2, + }, + ); + { + _ = try child.print( + &.{ + .{ + .text = "───────┬", + .style = color238, + }, + }, + .{ + .row_offset = 1, + .col_offset = 2, + }, + ); + for (10..child.width) |col| { + _ = try child.print( + &.{ + .{ + .text = "─", + .style = color238, + }, + }, + .{ + .row_offset = 1, + .col_offset = col, + }, + ); + } + } + _ = try child.print( + &.{ + .{ + .text = " │ ", + .style = color238, + }, + + .{ + .text = "File: ", + .style = standard, + }, + + .{ + .text = "ziggzag.zig", + .style = bold, + }, + }, + .{ + .row_offset = 2, + .col_offset = 2, + }, + ); + { + _ = try child.print( + &.{ + .{ + .text = "───────┼", + .style = color238, + }, + }, + .{ + .row_offset = 3, + .col_offset = 2, + }, + ); + for (10..child.width) |col| { + _ = try child.print( + &.{ + .{ + .text = "─", + .style = color238, + }, + }, + .{ + .row_offset = 3, + .col_offset = col, + }, + ); + } + } + _ = try child.print( + &.{ + .{ .text = " 1 │ ", .style = color238 }, + .{ .text = "const", .style = color5 }, + .{ .text = " std ", .style = standard }, + .{ .text = "= @import", .style = color5 }, + .{ .text = "(", .style = standard }, + .{ .text = "\"std\"", .style = color10 }, + .{ .text = ");", .style = standard }, + }, + .{ + .row_offset = 4, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 2 │", .style = color238 }, + }, + .{ + .row_offset = 5, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 3 │ ", .style = color238 }, + .{ .text = "pub ", .style = color5 }, + .{ .text = "fn ", .style = color12 }, + .{ .text = "main", .style = color2 }, + .{ .text = "() ", .style = standard }, + .{ .text = "!", .style = color5 }, + .{ .text = "void", .style = color12 }, + .{ .text = " {", .style = standard }, + }, + .{ + .row_offset = 6, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 4 │ ", .style = color238 }, + .{ .text = "const ", .style = color5 }, + .{ .text = "stdout ", .style = standard }, + .{ .text = "=", .style = color5 }, + .{ .text = " std.io.getStdOut().writer();", .style = standard }, + }, + .{ + .row_offset = 7, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 5 │ ", .style = color238 }, + .{ .text = "var ", .style = color5 }, + .{ .text = "i:", .style = standard }, + .{ .text = " usize", .style = color12 }, + .{ .text = " =", .style = color5 }, + .{ .text = " 1", .style = color4 }, + .{ .text = ";", .style = standard }, + }, + .{ + .row_offset = 8, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 6 │ ", .style = color238 }, + .{ .text = "while ", .style = color5 }, + .{ .text = "(i ", .style = standard }, + .{ .text = "<= ", .style = color5 }, + .{ .text = "16", .style = color4 }, + .{ .text = ") : (i ", .style = standard }, + .{ .text = "+= ", .style = color5 }, + .{ .text = "1", .style = color4 }, + .{ .text = ") {", .style = standard }, + }, + .{ + .row_offset = 9, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 7 │ ", .style = color238 }, + .{ .text = "if ", .style = color5 }, + .{ .text = "(i ", .style = standard }, + .{ .text = "% ", .style = color5 }, + .{ .text = "15 ", .style = color4 }, + .{ .text = "== ", .style = color5 }, + .{ .text = "0", .style = color4 }, + .{ .text = ") {", .style = standard }, + }, + .{ + .row_offset = 10, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 8 │ ", .style = color238 }, + .{ .text = "try ", .style = color5 }, + .{ .text = "stdout.writeAll(", .style = standard }, + .{ .text = "\"ZiggZagg", .style = color10 }, + .{ .text = "\\n", .style = color12 }, + .{ .text = "\"", .style = color10 }, + .{ .text = ");", .style = standard }, + }, + .{ + .row_offset = 11, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 9 │ ", .style = color238 }, + .{ .text = "} ", .style = standard }, + .{ .text = "else if ", .style = color5 }, + .{ .text = "(i ", .style = standard }, + .{ .text = "% ", .style = color5 }, + .{ .text = "3 ", .style = color4 }, + .{ .text = "== ", .style = color5 }, + .{ .text = "0", .style = color4 }, + .{ .text = ") {", .style = standard }, + }, + .{ + .row_offset = 12, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 10 │ ", .style = color238 }, + .{ .text = "try ", .style = color5 }, + .{ .text = "stdout.writeAll(", .style = standard }, + .{ .text = "\"Zigg", .style = color10 }, + .{ .text = "\\n", .style = color12 }, + .{ .text = "\"", .style = color10 }, + .{ .text = ");", .style = standard }, + }, + .{ + .row_offset = 13, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 11 │ ", .style = color238 }, + .{ .text = "} ", .style = standard }, + .{ .text = "else if ", .style = color5 }, + .{ .text = "(i ", .style = standard }, + .{ .text = "% ", .style = color5 }, + .{ .text = "5 ", .style = color4 }, + .{ .text = "== ", .style = color5 }, + .{ .text = "0", .style = color4 }, + .{ .text = ") {", .style = standard }, + }, + .{ + .row_offset = 14, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 12 │ ", .style = color238 }, + .{ .text = "try ", .style = color5 }, + .{ .text = "stdout.writeAll(", .style = standard }, + .{ .text = "\"Zagg", .style = color10 }, + .{ .text = "\\n", .style = color12 }, + .{ .text = "\"", .style = color10 }, + .{ .text = ");", .style = standard }, + }, + .{ + .row_offset = 15, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 13 │ ", .style = color238 }, + .{ .text = "} ", .style = standard }, + .{ .text = "else ", .style = color5 }, + .{ .text = "{", .style = standard }, + }, + .{ + .row_offset = 16, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 14 │ ", .style = color238 }, + .{ .text = "try ", .style = color5 }, + .{ .text = "stdout.print(", .style = standard }, + .{ .text = "\"{d}", .style = color10 }, + .{ .text = "\\n", .style = color12 }, + .{ .text = "\"", .style = color10 }, + .{ .text = ", .{i});", .style = standard }, + }, + .{ + .row_offset = 17, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 15 │ ", .style = color238 }, + .{ .text = "}", .style = standard }, + }, + .{ + .row_offset = 18, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 16 │ ", .style = color238 }, + .{ .text = "}", .style = standard }, + }, + .{ + .row_offset = 19, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = " 17 │ ", .style = color238 }, + .{ .text = "}", .style = standard }, + }, + .{ + .row_offset = 20, + .col_offset = 2, + }, + ); + { + _ = try child.print( + &.{ + .{ + .text = "───────┴", + .style = color238, + }, + }, + .{ + .row_offset = 21, + .col_offset = 2, + }, + ); + for (10..child.width) |col| { + _ = try child.print( + &.{ + .{ + .text = "─", + .style = color238, + }, + }, + .{ + .row_offset = 21, + .col_offset = col, + }, + ); + } + } + _ = try child.print( + &.{ + .{ .text = "ghostty ", .style = color6 }, + .{ .text = "on ", .style = standard }, + .{ .text = " main ", .style = color4 }, + .{ .text = "[+] ", .style = color1 }, + .{ .text = "via ", .style = standard }, + .{ .text = " v0.13.0 ", .style = color3 }, + .{ .text = "via ", .style = standard }, + .{ .text = " impure (ghostty-env)", .style = color4 }, + }, + .{ + .row_offset = 22, + .col_offset = 2, + }, + ); + _ = try child.print( + &.{ + .{ .text = "✦ ", .style = color4 }, + .{ .text = "at ", .style = standard }, + .{ .text = "10:36:15 ", .style = color3 }, + .{ .text = "→", .style = color2 }, + }, + .{ + .row_offset = 23, + .col_offset = 2, + }, + ); + next_start += child.height; + } + if (next_start < win.height) { + const child = win.child( + .{ + .x_off = x_off, + .y_off = next_start, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = win.height - next_start, + }, + }, + ); + child.fill(.{ .style = standard }); + var it = std.mem.splitAny(u8, lorem_ipsum, " \n"); + var row: usize = 1; + var col: usize = 2; + while (row < child.height) { + const word = it.next() orelse line: { + it.reset(); + break :line it.next() orelse unreachable; + }; + if (col + word.len > child.width) { + row += 1; + col = 2; + } + const style: vaxis.Style = style: { + if (std.mem.eql(u8, "ipsum", word)) break :style .{ .fg = color(config, 2), .bg = bg }; + if (std.mem.eql(u8, "consectetur", word)) break :style standard_bold; + if (std.mem.eql(u8, "reprehenderit", word)) break :style standard_italic; + if (std.mem.eql(u8, "Praesent", word)) break :style standard_bold_italic; + if (std.mem.eql(u8, "auctor", word)) break :style standard_underline; + if (std.mem.eql(u8, "dui", word)) break :style standard_double_underline; + if (std.mem.eql(u8, "erat", word)) break :style standard_dashed_underline; + if (std.mem.eql(u8, "enim", word)) break :style standard_dotted_underline; + if (std.mem.eql(u8, "odio", word)) break :style standard_curly_underline; + break :style standard; + }; + _ = try child.printSegment( + .{ + .text = word, + .style = style, + }, + .{ + .row_offset = row, + .col_offset = col, + }, + ); + col += word.len + 1; + } + } + } +}; + +fn color(config: Config, palette: usize) vaxis.Color { + return .{ + .rgb = [_]u8{ + config.palette.value[palette].r, + config.palette.value[palette].g, + config.palette.value[palette].b, + }, + }; +} + +const lorem_ipsum = @embedFile("lorem_ipsum.txt"); + +fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement) !void { + var app = try Preview.init(allocator, themes); + defer app.deinit(); + try app.run(); +} diff --git a/src/cli/lorem_ipsum.txt b/src/cli/lorem_ipsum.txt new file mode 100644 index 000000000..13b9a52c1 --- /dev/null +++ b/src/cli/lorem_ipsum.txt @@ -0,0 +1,45 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras hendrerit aliquet +turpis non dictum. Mauris pulvinar nisl sit amet dui cursus tempus. Pellentesque +ut dui justo. Etiam quis magna sagittis nisi pretium consequat vitae ut nisl. +Sed at metus id odio pulvinar sodales. Vestibulum sollicitudin, sem id tristique +vestibulum, neque ante dictum tortor, in convallis mi enim ac lorem. Suspendisse +orci ex, ullamcorper sed leo vitae, mattis egestas nisl. Morbi id est vel +ipsum mollis convallis vel at mauris. Duis vehicula facilisis placerat. Aliquam +venenatis auctor ipsum vel elementum. Proin ac tincidunt lacus. Sed facilisis +tellus ullamcorper bibendum lobortis. Pellentesque porta, lacus quis efficitur +pulvinar, sem mi varius ante, sed finibus diam ante et risus. + +Morbi ut sollicitudin justo. Nulla mattis mi ac mauris tincidunt tempor. Morbi +vel gravida erat. Ut eu risus quis nisi facilisis aliquet varius id orci. +Pellentesque tortor diam, porttitor nec urna nec, convallis consectetur dui. +Vestibulum et hendrerit ipsum. Morbi pharetra dictum turpis in elementum. Ut +nec volutpat nunc, at venenatis leo. Morbi eget nulla luctus, tincidunt dui vel, +cursus urna. Maecenas ac pellentesque nisi. Quisque ut lorem porta, eleifend +metus id, pellentesque tellus. + +Vivamus gravida convallis felis, at hendrerit dolor. Vestibulum tincidunt id +augue quis hendrerit. Praesent venenatis elit quis posuere gravida. Praesent +at massa a purus maximus tempus. Proin dui leo, feugiat et erat ac, tincidunt +aliquam risus. Aenean rutrum hendrerit turpis, sit amet consectetur justo porta +non. Sed auctor justo elit, sed mollis odio ullamcorper nec. Pellentesque ac +hendrerit tortor. Praesent quis viverra dui, sit amet imperdiet magna. + +Mauris iaculis maximus felis, aliquet vehicula neque sagittis nec. Duis +convallis purus enim, vel scelerisque purus dignissim eu. Donec congue sapien +a neque rhoncus, sit amet accumsan libero tincidunt. Proin vitae placerat urna. +Donec dolor sapien, fringilla sed semper sit amet, sollicitudin sit amet orci. +Mauris maximus convallis vehicula. Aliquam urna ipsum, fermentum ac iaculis vel, +blandit eget lorem. Sed enim ante, sodales a diam in, convallis interdum quam. +Duis non urna risus. Proin ac neque at risus ullamcorper mattis eu vel nunc. +Proin et ipsum euismod, ullamcorper justo et, imperdiet est. Curabitur quis +arcu faucibus, bibendum nisl nec, hendrerit sapien. Curabitur vitae ante risus. +Praesent eget sagittis tortor. + +Mauris aliquam nec nibh eu congue. Nullam congue auctor vestibulum. Donec +posuere sapien nec massa efficitur tincidunt. Vestibulum ante ipsum primis in +faucibus orci luctus et ultrices posuere cubilia curae; Proin molestie, nisl +in tincidunt condimentum, ante metus fermentum felis, ac molestie lacus dui vel +dolor. Donec ornare laoreet posuere. Etiam id tincidunt ante. Maecenas semper +diam ac tortor facilisis egestas. Nam eu bibendum nisl. Integer tempor nisl nec +ex consectetur, quis lobortis enim finibus. Sed ac erat posuere, fermentum metus +sed, suscipit nisl. From a6a4f9ff8eb908597844980febb106c6e02421d1 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 24 Sep 2024 10:28:49 -0500 Subject: [PATCH 2/5] cli: list-themes should list symlinks Symbolic links should be listed in addition to normal files. Useful for system like home-manager that link config files to location in the Nix store. --- src/cli/list_themes.zig | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index dd812d5ca..d512dcc03 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -114,13 +114,17 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { var walker = dir.iterate(); while (try walker.next()) |entry| { - if (entry.kind != .file) continue; - count += 1; - try themes.append(.{ - .location = loc.location, - .path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }), - .theme = try alloc.dupe(u8, entry.name), - }); + switch (entry.kind) { + .file, .sym_link => { + count += 1; + try themes.append(.{ + .location = loc.location, + .path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }), + .theme = try alloc.dupe(u8, entry.name), + }); + }, + else => {}, + } } } From e313352c1f5b77bbd6f999ff64516e273c97a896 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 24 Sep 2024 10:42:57 -0500 Subject: [PATCH 3/5] cli: update +list-themes --help text --- src/cli/list_themes.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index d512dcc03..f3368e813 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -54,6 +54,15 @@ const ThemeListElement = struct { /// The `list-themes` command is used to preview or list all the available /// themes for Ghostty. /// +/// If this command is run from a TTY, a TUI preview of the themes will be +/// shown. While in the preview, `F1` will bring up a help screen and `ESC` will +/// exit the preview. Other keys that can be used to navigate the preview are +/// listed in the help screen. +/// +/// If this command is not run from a TTY, or the output is piped to another +/// command, a plain list of theme names will be printed to the screen. A plain +/// list can be forced using the `--plain` CLI flag. +/// /// Two different directories will be searched for themes. /// /// The first directory is the `themes` subdirectory of your Ghostty @@ -74,7 +83,7 @@ const ThemeListElement = struct { /// Flags: /// /// * `--path`: Show the full path to the theme. -/// * `--plain`: Show a short preview of the theme colors. +/// * `--plain`: Force a plain listing of themes. pub fn run(gpa_alloc: std.mem.Allocator) !u8 { var opts: Options = .{}; defer opts.deinit(); From a969364f93230ae8ae766cfeed84bdcc85a84a0a Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Wed, 25 Sep 2024 12:07:14 -0500 Subject: [PATCH 4/5] cli/list-themes: add ability to search theme names --- build.zig | 5 + build.zig.zon | 8 +- nix/zigCacheHash.nix | 2 +- src/cli/list_themes.zig | 709 ++++++++++++++++++++++++---------------- 4 files changed, 434 insertions(+), 290 deletions(-) diff --git a/build.zig b/build.zig index b834f57a3..63ac1a2cf 100644 --- a/build.zig +++ b/build.zig @@ -1043,6 +1043,10 @@ fn addDeps( .target = target, .optimize = optimize, }); + const zf_dep = b.dependency("zf", .{ + .target = target, + .optimize = optimize, + }); // Wasm we do manually since it is such a different build. if (step.rootModuleTarget().cpu.arch == .wasm32) { @@ -1129,6 +1133,7 @@ fn addDeps( step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); step.root_module.addImport("vaxis", vaxis_dep.module("vaxis")); step.root_module.addImport("wuffs", wuffs_dep.module("wuffs")); + step.root_module.addImport("zf", zf_dep.module("zf")); // Mac Stuff if (step.rootModuleTarget().isDarwin()) { diff --git a/build.zig.zon b/build.zig.zon index f2add6c07..973e6e6b5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -54,8 +54,12 @@ .hash = "122056fbb29863ec1678b7954fb76b1533ad8c581a34577c1b2efe419e29e05596df", }, .vaxis = .{ - .url = "git+https://github.com/rockorager/libvaxis?ref=main#2cc1eb77f842dd8587dfc9cf399d42e4c1369175", - .hash = "12203c2d83911e6aacfbfdd48d31d6fc36e89947dfc7aec104debe3ac85e9f3a44f2", + .url = "git+https://github.com/rockorager/libvaxis?ref=main#1961712c1f0cf46b235dd31418dc1b52442abbd5", + .hash = "12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133", + }, + .zf = .{ + .url = "git+https://github.com/natecraddock/zf.git?ref=main#bb27a917c3513785c6a91f0b1c10002a5029cacc", + .hash = "1220a74107c7f153a2f809e41c7fa7e8dbf75c91043e39fad998247804e5edac2cc8", }, }, } diff --git a/nix/zigCacheHash.nix b/nix/zigCacheHash.nix index ad2e6a827..92e656264 100644 --- a/nix/zigCacheHash.nix +++ b/nix/zigCacheHash.nix @@ -1,3 +1,3 @@ # This file is auto-generated! check build-support/check-zig-cache-hash.sh for # more details. -"sha256-MAzGg4tWlyv2X/GjAwm7s2whojawIKNMx1xWR+cZffQ=" +"sha256-qFt9sC3GekfU940Gd9oV9Gcbs5MdxVMojIMbkDo3m2A=" diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index f3368e813..ed375dff2 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -9,6 +9,7 @@ const internal_os = @import("../os/main.zig"); const global_state = &@import("../global.zig").state; const vaxis = @import("vaxis"); +const zf = @import("zf"); pub const Options = struct { /// If true, print the full path to the theme. @@ -32,6 +33,7 @@ const ThemeListElement = struct { location: themepkg.Location, path: []const u8, theme: []const u8, + rank: ?f64 = null, fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { // TODO: use Unicode-aware comparison @@ -173,29 +175,51 @@ const Preview = struct { vx: vaxis.Vaxis, mouse: ?vaxis.Mouse, themes: []ThemeListElement, + filtered: std.ArrayList(usize), current: usize, + window: usize, hex: bool, - help_visible: bool, + mode: enum { + normal, + help, + search, + }, color_scheme: vaxis.Color.Scheme, + text_input: vaxis.widgets.TextInput, - pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement) !Preview { - return .{ + pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement) !*Preview { + const self = try allocator.create(Preview); + + self.* = .{ .allocator = allocator, .should_quit = false, .tty = try vaxis.Tty.init(), .vx = try vaxis.init(allocator, .{}), .mouse = null, .themes = themes, + .filtered = try std.ArrayList(usize).initCapacity(allocator, themes.len), .current = 0, + .window = 0, .hex = false, - .help_visible = false, + .mode = .normal, .color_scheme = .light, + .text_input = vaxis.widgets.TextInput.init(allocator, &self.vx.unicode), }; + + for (0..themes.len) |i| { + try self.filtered.append(i); + } + + return self; } pub fn deinit(self: *Preview) void { - self.vx.deinit(self.allocator, self.tty.anyWriter()); + const allocator = self.allocator; + self.filtered.deinit(); + self.text_input.deinit(); + self.vx.deinit(allocator, self.tty.anyWriter()); self.tty.deinit(); + allocator.destroy(self); } pub fn run(self: *Preview) !void { @@ -230,12 +254,92 @@ const Preview = struct { } } + fn updateFiltered(self: *Preview) !void { + const relative = self.current -| self.window; + const selected = self.themes[self.filtered.items[self.current]].theme; + + const hash_algorithm = std.hash.Wyhash; + + const old_digest = d: { + var hash = hash_algorithm.init(0); + for (self.filtered.items) |item| + hash.update(std.mem.asBytes(&item)); + break :d hash.final(); + }; + + self.filtered.clearRetainingCapacity(); + + if (self.text_input.buf.realLength() > 0) { + const first_half = self.text_input.buf.firstHalf(); + const second_half = self.text_input.buf.secondHalf(); + + const buffer = try self.allocator.alloc(u8, first_half.len + second_half.len); + defer self.allocator.free(buffer); + + @memcpy(buffer[0..first_half.len], first_half); + @memcpy(buffer[first_half.len..], second_half); + + const string = try std.ascii.allocLowerString(self.allocator, buffer); + defer self.allocator.free(string); + + var tokens = std.ArrayList([]const u8).init(self.allocator); + defer tokens.deinit(); + + var it = std.mem.tokenizeScalar(u8, string, ' '); + while (it.next()) |token| try tokens.append(token); + + for (self.themes, 0..) |*theme, i| { + theme.rank = zf.rank(theme.theme, tokens.items, false, true); + if (theme.rank) |_| + try self.filtered.append(i); + } + } else { + for (self.themes, 0..) |*theme, i| { + try self.filtered.append(i); + theme.rank = null; + } + } + + const new_digest = d: { + var hash = hash_algorithm.init(0); + for (self.filtered.items) |item| + hash.update(std.mem.asBytes(&item)); + break :d hash.final(); + }; + + if (old_digest == new_digest) return; + + if (self.filtered.items.len == 0) { + self.current = 0; + self.window = 0; + return; + } + + self.current, self.window = current: { + for (self.filtered.items, 0..) |index, i| { + if (std.mem.eql(u8, self.themes[index].theme, selected)) + break :current .{ i, i -| relative }; + } + break :current .{ 0, 0 }; + }; + } + fn up(self: *Preview, count: usize) void { - self.current = std.math.sub(usize, self.current, count) catch self.themes.len + self.current - count; + if (self.filtered.items.len == 0) { + self.current = 0; + return; + } + self.current -|= count; } fn down(self: *Preview, count: usize) void { - self.current = (self.current + count) % self.themes.len; + if (self.filtered.items.len == 0) { + self.current = 0; + return; + } + self.current += count; + if (self.current >= self.filtered.items.len) + self.current = self.filtered.items.len - 1; } pub fn update(self: *Preview, event: Event, alloc: std.mem.Allocator) !void { @@ -243,72 +347,71 @@ const Preview = struct { .key_press => |key| { if (key.matches('c', .{ .ctrl = true })) self.should_quit = true; - if (key.matches('q', .{})) - self.should_quit = true; - if (key.matches(vaxis.Key.escape, .{})) - self.should_quit = true; - if (key.matches('?', .{})) - self.help_visible = !self.help_visible; - if (key.matches('h', .{ .ctrl = true })) - self.help_visible = !self.help_visible; - if (key.matches(vaxis.Key.f1, .{})) - self.help_visible = !self.help_visible; - if (key.matches('0', .{})) - self.current = 0; - if (key.matches(vaxis.Key.home, .{})) - self.current = 0; - if (key.matches(vaxis.Key.kp_home, .{})) - self.current = 0; - if (key.matches(vaxis.Key.end, .{})) - self.current = self.themes.len - 1; - if (key.matches(vaxis.Key.kp_end, .{})) - self.current = self.themes.len - 1; - if (key.matches('j', .{})) - self.down(1); - if (key.matches('+', .{})) - self.down(1); - if (key.matches(vaxis.Key.down, .{})) - self.down(1); - if (key.matches(vaxis.Key.kp_down, .{})) - self.down(1); - if (key.matches(vaxis.Key.kp_add, .{})) - self.down(1); - if (key.matches(vaxis.Key.page_down, .{})) - self.down(20); - if (key.matches(vaxis.Key.kp_page_down, .{})) - self.down(20); - if (key.matches('k', .{})) - self.up(1); - if (key.matches('-', .{})) - self.up(1); - if (key.matches(vaxis.Key.up, .{})) - self.up(1); - if (key.matches(vaxis.Key.kp_up, .{})) - self.up(1); - if (key.matches(vaxis.Key.kp_subtract, .{})) - self.up(1); - if (key.matches(vaxis.Key.page_up, .{})) - self.up(20); - if (key.matches(vaxis.Key.kp_page_up, .{})) - self.up(20); - if (key.matches('h', .{})) - self.hex = true; - if (key.matches('x', .{})) - self.hex = true; - if (key.matches('d', .{})) - self.hex = false; - if (key.matches('c', .{})) - try self.vx.copyToSystemClipboard( - self.tty.anyWriter(), - self.themes[self.current].theme, - alloc, - ); - if (key.matches('c', .{ .shift = true })) - try self.vx.copyToSystemClipboard( - self.tty.anyWriter(), - self.themes[self.current].path, - alloc, - ); + switch (self.mode) { + .normal => { + if (key.matchesAny(&.{ 'q', vaxis.Key.escape }, .{})) + self.should_quit = true; + if (key.matchesAny(&.{ '?', vaxis.Key.f1 }, .{})) + self.mode = .help; + if (key.matches('h', .{ .ctrl = true })) + self.mode = .help; + if (key.matches('/', .{})) + self.mode = .search; + if (key.matchesAny(&.{ 'x', '/' }, .{ .ctrl = true })) { + self.text_input.buf.clearRetainingCapacity(); + try self.updateFiltered(); + } + if (key.matchesAny(&.{ vaxis.Key.home, vaxis.Key.kp_home }, .{})) + self.current = 0; + if (key.matchesAny(&.{ vaxis.Key.end, vaxis.Key.kp_end }, .{})) + self.current = self.filtered.items.len - 1; + if (key.matchesAny(&.{ 'j', '+', vaxis.Key.down, vaxis.Key.kp_down, vaxis.Key.kp_add }, .{})) + self.down(1); + if (key.matchesAny(&.{ vaxis.Key.page_down, vaxis.Key.kp_down }, .{})) + self.down(20); + if (key.matchesAny(&.{ 'k', '-', vaxis.Key.up, vaxis.Key.kp_up, vaxis.Key.kp_subtract }, .{})) + self.up(1); + if (key.matchesAny(&.{ vaxis.Key.page_up, vaxis.Key.kp_page_up }, .{})) + self.up(20); + if (key.matchesAny(&.{ 'h', 'x' }, .{})) + self.hex = true; + if (key.matches('d', .{})) + self.hex = false; + if (key.matches('c', .{})) + try self.vx.copyToSystemClipboard( + self.tty.anyWriter(), + self.themes[self.filtered.items[self.current]].theme, + alloc, + ); + if (key.matches('c', .{ .shift = true })) + try self.vx.copyToSystemClipboard( + self.tty.anyWriter(), + self.themes[self.filtered.items[self.current]].path, + alloc, + ); + }, + .help => { + if (key.matches('q', .{})) + self.should_quit = true; + if (key.matchesAny(&.{ '?', vaxis.Key.escape, vaxis.Key.f1 }, .{})) + self.mode = .normal; + if (key.matches('h', .{ .ctrl = true })) + self.mode = .normal; + }, + .search => search: { + if (key.matchesAny(&.{ vaxis.Key.escape, vaxis.Key.enter }, .{})) { + self.mode = .normal; + break :search; + } + if (key.matchesAny(&.{ 'x', '/' }, .{ .ctrl = true })) { + self.text_input.clearRetainingCapacity(); + try self.updateFiltered(); + break :search; + } + try self.text_input.update(.{ .key_press = key }); + try self.updateFiltered(); + }, + } }, .color_scheme => |color_scheme| self.color_scheme = color_scheme, .mouse => |mouse| self.mouse = mouse, @@ -316,43 +419,82 @@ const Preview = struct { } } + pub fn ui_fg(self: *Preview) vaxis.Color { + return switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } }, + .dark => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } }, + }; + } + + pub fn ui_bg(self: *Preview) vaxis.Color { + return switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } }, + .dark => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } }, + }; + } + + pub fn ui_standard(self: *Preview) vaxis.Style { + return .{ + .fg = self.ui_fg(), + .bg = self.ui_bg(), + }; + } + + pub fn ui_hover_bg(self: *Preview) vaxis.Color { + return switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xbb, 0xbb, 0xbb } }, + .dark => .{ .rgb = [_]u8{ 0x22, 0x22, 0x22 } }, + }; + } + + pub fn ui_highlighted(self: *Preview) vaxis.Style { + return .{ + .fg = self.ui_fg(), + .bg = self.ui_hover_bg(), + }; + } + + pub fn ui_selected_fg(self: *Preview) vaxis.Color { + return switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } }, + .dark => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } }, + }; + } + + pub fn ui_selected_bg(self: *Preview) vaxis.Color { + return switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xaa, 0xaa, 0xaa } }, + .dark => .{ .rgb = [_]u8{ 0x33, 0x33, 0x33 } }, + }; + } + + pub fn ui_selected(self: *Preview) vaxis.Style { + return .{ + .fg = self.ui_selected_fg(), + .bg = self.ui_selected_bg(), + }; + } + + pub fn ui_err_fg(self: *Preview) vaxis.Color { + return switch (self.color_scheme) { + .light => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } }, + .dark => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } }, + }; + } + + pub fn ui_err(self: *Preview) vaxis.Style { + return .{ + .fg = self.ui_err_fg(), + .bg = self.ui_bg(), + }; + } + pub fn draw(self: *Preview, alloc: std.mem.Allocator) !void { const win = self.vx.window(); win.clear(); self.vx.setMouseShape(.default); - const ui_fg: vaxis.Color = switch (self.color_scheme) { - .light => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } }, - .dark => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } }, - }; - const ui_bg: vaxis.Color = switch (self.color_scheme) { - .light => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } }, - .dark => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } }, - }; - const ui_standard: vaxis.Style = .{ - .fg = ui_fg, - .bg = ui_bg, - }; - - const ui_hover_bg: vaxis.Color = switch (self.color_scheme) { - .light => .{ .rgb = [_]u8{ 0xbb, 0xbb, 0xbb } }, - .dark => .{ .rgb = [_]u8{ 0x22, 0x22, 0x22 } }, - }; - - const ui_selected_fg: vaxis.Color = switch (self.color_scheme) { - .light => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } }, - .dark => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } }, - }; - const ui_selected_bg: vaxis.Color = switch (self.color_scheme) { - .light => .{ .rgb = [_]u8{ 0xaa, 0xaa, 0xaa } }, - .dark => .{ .rgb = [_]u8{ 0x33, 0x33, 0x33 } }, - }; - const ui_selected: vaxis.Style = .{ - .fg = ui_selected_fg, - .bg = ui_selected_bg, - }; - const theme_list = win.child(.{ .x_off = 0, .y_off = 0, @@ -360,43 +502,73 @@ const Preview = struct { .height = .{ .limit = win.height }, }); - const split = theme_list.height / 2; + if (self.filtered.items.len == 0) { + self.current = 0; + self.window = 0; + } else { + const start = self.window; + const end = self.window + theme_list.height - 1; + if (self.current > end) + self.window = self.current - theme_list.height + 1; + if (self.current < start) + self.window = self.current; + if (self.window >= self.filtered.items.len) + self.window = self.filtered.items.len - 1; + } var highlight: ?usize = null; if (self.mouse) |mouse| { self.mouse = null; - if (mouse.button == .wheel_up) { - self.up(1); - } - if (mouse.button == .wheel_down) { - self.down(1); - } - if (theme_list.hasMouse(mouse)) |_| { - if (mouse.button == .left and mouse.type == .release) { - if (mouse.row < split) self.up(split - mouse.row); - if (mouse.row > split) self.down(mouse.row - split); + if (self.mode == .normal) { + if (mouse.button == .wheel_up) { + self.up(1); + } + if (mouse.button == .wheel_down) { + self.down(1); + } + if (theme_list.hasMouse(mouse)) |_| { + if (mouse.button == .left and mouse.type == .release) { + self.current = self.window + mouse.row; + } + highlight = mouse.row; } - highlight = mouse.row; } } - theme_list.fill(.{ .style = ui_standard }); + theme_list.fill(.{ .style = self.ui_standard() }); - for (0..split) |i| { - const j = std.math.sub(usize, self.current, i + 1) catch self.themes.len + self.current - i - 1; - const theme = self.themes[j]; - const row = split - i - 1; + for (0..theme_list.height) |row| { + const index = self.window + row; + if (index >= self.filtered.items.len) break; + const theme = self.themes[self.filtered.items[index]]; + + const style: enum { normal, highlighted, selected } = style: { + if (index == self.current) break :style .selected; + if (highlight) |h| if (h == row) break :style .highlighted; + break :style .normal; + }; + + if (style == .selected) { + _ = try theme_list.printSegment( + .{ + .text = "❯ ", + .style = self.ui_selected(), + }, + .{ + .row_offset = row, + .col_offset = 0, + }, + ); + } _ = try theme_list.printSegment( .{ .text = theme.theme, - .style = .{ - .fg = ui_fg, - .bg = bg: { - if (highlight) |h| if (h == row) break :bg ui_hover_bg; - break :bg ui_bg; - }, + .style = switch (style) { + .normal => self.ui_standard(), + .highlighted => self.ui_highlighted(), + .selected => self.ui_selected(), }, .link = .{ .uri = try theme.toUri(alloc), @@ -407,163 +579,140 @@ const Preview = struct { .col_offset = 2, }, ); - } - { - const theme = self.themes[self.current]; - _ = try theme_list.printSegment( - .{ - .text = "❯ ", - .style = ui_selected, - }, - .{ - .row_offset = split, - .col_offset = 0, - }, - ); - _ = try theme_list.printSegment( - .{ - .text = theme.theme, - .style = ui_selected, - .link = .{ - .uri = try theme.toUri(alloc), + if (style == .selected) { + if (theme.theme.len < theme_list.width - 4) { + for (2 + theme.theme.len..theme_list.width - 2) |i| + _ = try theme_list.printSegment( + .{ + .text = " ", + .style = self.ui_selected(), + }, + .{ + .row_offset = row, + .col_offset = i, + }, + ); + } + _ = try theme_list.printSegment( + .{ + .text = " ❮", + .style = self.ui_selected(), }, - }, - .{ - .row_offset = split, - .col_offset = 2, - }, - ); - if (theme.theme.len < theme_list.width - 4) { - for (2 + theme.theme.len..theme_list.width - 2) |i| - _ = try theme_list.printSegment( + .{ + .row_offset = row, + .col_offset = theme_list.width - 2, + }, + ); + } + } + + try self.drawPreview(alloc, win, theme_list.x_off + theme_list.width); + + switch (self.mode) { + .normal => { + win.hideCursor(); + }, + .help => { + win.hideCursor(); + const width = 60; + const height = 20; + const child = win.child( + .{ + .x_off = win.width / 2 -| width / 2, + .y_off = win.height / 2 -| height / 2, + .width = .{ + .limit = width, + }, + .height = .{ + .limit = height, + }, + .border = .{ + .where = .all, + .style = self.ui_standard(), + }, + }, + ); + + child.fill(.{ .style = self.ui_standard() }); + + const key_help = [_]struct { keys: []const u8, help: []const u8 }{ + .{ .keys = "^C, q, ESC", .help = "Quit." }, + .{ .keys = "F1, ?, ^H", .help = "Toggle help window." }, + .{ .keys = "k, ↑", .help = "Move up 1 theme." }, + .{ .keys = "ScrollUp", .help = "Move up 1 theme." }, + .{ .keys = "PgUp", .help = "Move up 20 themes." }, + .{ .keys = "j, ↓", .help = "Move down 1 theme." }, + .{ .keys = "ScrollDown", .help = "Move down 1 theme." }, + .{ .keys = "PgDown", .help = "Move down 20 themes." }, + .{ .keys = "h, x", .help = "Show palette numbers in hexadecimal." }, + .{ .keys = "d", .help = "Show palette numbers in decimal." }, + .{ .keys = "c", .help = "Copy theme name to the clipboard." }, + .{ .keys = "C", .help = "Copy theme path to the clipboard." }, + .{ .keys = "Home", .help = "Go to the start of the list." }, + .{ .keys = "End", .help = "Go to the end of the list." }, + .{ .keys = "/", .help = "Start search." }, + .{ .keys = "^X, ^/", .help = "Clear search." }, + .{ .keys = "⏎", .help = "Close search window." }, + }; + + for (key_help, 0..) |help, i| { + _ = try child.printSegment( .{ - .text = " ", - .style = ui_selected, + .text = help.keys, + .style = self.ui_standard(), }, .{ - .row_offset = split, - .col_offset = i, + .row_offset = i + 1, + .col_offset = 2, }, ); - } - _ = try theme_list.printSegment( - .{ - .text = " ❮", - .style = ui_selected, - }, - .{ - .row_offset = split, - .col_offset = theme_list.width - 2, - }, - ); - } - for (split + 1..theme_list.height) |i| { - const j = (self.current + i - split) % self.themes.len; - const row = i; - const theme = self.themes[j]; - _ = try theme_list.printSegment(.{ - .text = theme.theme, - .style = .{ - .fg = ui_fg, - .bg = bg: { - if (highlight) |h| if (h == row) break :bg ui_hover_bg; - break :bg ui_bg; - }, - }, - .link = .{ - .uri = try theme.toUri(alloc), - }, - }, .{ - .row_offset = i, - .col_offset = 2, - }); - } - - try self.drawPreview(alloc, win, theme_list.x_off + theme_list.width, ui_fg, ui_bg); - - if (self.help_visible) { - const width = 60; - const height = 20; - const child = win.child( - .{ - .x_off = win.width / 2 -| width / 2, - .y_off = win.height / 2 -| height / 2, + _ = try child.printSegment( + .{ + .text = "—", + .style = self.ui_standard(), + }, + .{ + .row_offset = i + 1, + .col_offset = 15, + }, + ); + _ = try child.printSegment( + .{ + .text = help.help, + .style = self.ui_standard(), + }, + .{ + .row_offset = i + 1, + .col_offset = 17, + }, + ); + } + }, + .search => { + const child = win.child(.{ + .x_off = 20, + .y_off = win.height - 5, .width = .{ - .limit = width, + .limit = win.width - 40, }, .height = .{ - .limit = height, + .limit = 3, }, .border = .{ .where = .all, - .style = ui_standard, + .style = self.ui_standard(), }, - }, - ); - - child.fill(.{ .style = ui_standard }); - - const key_help = [_]struct { keys: []const u8, help: []const u8 }{ - .{ .keys = "^C, q, ESC", .help = "Quit." }, - .{ .keys = "F1, ?, ^H", .help = "Toggle help window." }, - .{ .keys = "k, ↑", .help = "Move up 1 theme." }, - .{ .keys = "ScrollUp", .help = "Move up 1 theme." }, - .{ .keys = "PgUp", .help = "Move up 20 themes." }, - .{ .keys = "j, ↓", .help = "Move down 1 theme." }, - .{ .keys = "ScrollDown", .help = "Move down 1 theme." }, - .{ .keys = "PgDown", .help = "Move down 20 themes." }, - .{ .keys = "h, x", .help = "Show palette numbers in hexadecimal." }, - .{ .keys = "d", .help = "Show palette numbers in decimal." }, - .{ .keys = "c", .help = "Copy theme name to the clipboard." }, - .{ .keys = "C", .help = "Copy theme path to the clipboard." }, - .{ .keys = "0, Home", .help = "Go to the start of the list." }, - .{ .keys = "End", .help = "Go to the end of the list." }, - }; - - for (key_help, 0..) |help, i| { - _ = try child.printSegment( - .{ - .text = help.keys, - .style = ui_standard, - }, - .{ - .row_offset = i + 1, - .col_offset = 2, - }, - ); - _ = try child.printSegment( - .{ - .text = "—", - .style = ui_standard, - }, - .{ - .row_offset = i + 1, - .col_offset = 15, - }, - ); - _ = try child.printSegment( - .{ - .text = help.help, - .style = ui_standard, - }, - .{ - .row_offset = i + 1, - .col_offset = 17, - }, - ); - } + }); + child.fill(.{ .style = self.ui_standard() }); + self.text_input.drawWithStyle(child, self.ui_standard()); + }, } } - pub fn drawPreview(self: *Preview, alloc: std.mem.Allocator, win: vaxis.Window, x_off: usize, ui_fg: vaxis.Color, ui_bg: vaxis.Color) !void { + pub fn drawPreview(self: *Preview, alloc: std.mem.Allocator, win: vaxis.Window, x_off: usize) !void { const width = win.width - x_off; - const ui_err_fg: vaxis.Color = switch (self.color_scheme) { - .light => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } }, - .dark => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } }, - }; - - const theme = self.themes[self.current]; + const theme = self.themes[self.filtered.items[self.current]]; var config = try Config.default(alloc); defer config.deinit(); @@ -581,17 +730,14 @@ const Preview = struct { }, }, ); - child.fill(.{ .style = .{ .fg = ui_fg, .bg = ui_bg } }); + child.fill(.{ .style = self.ui_standard() }); const middle = child.height / 2; { const text = try std.fmt.allocPrint(alloc, "Unable to open {s} from:", .{theme.theme}); _ = try child.printSegment( .{ .text = text, - .style = .{ - .fg = ui_err_fg, - .bg = ui_bg, - }, + .style = self.ui_err(), }, .{ .row_offset = middle -| 1, @@ -603,10 +749,7 @@ const Preview = struct { _ = try child.printSegment( .{ .text = theme.path, - .style = .{ - .fg = ui_err_fg, - .bg = ui_bg, - }, + .style = self.ui_err(), .link = .{ .uri = try theme.toUri(alloc), }, @@ -622,10 +765,7 @@ const Preview = struct { _ = try child.printSegment( .{ .text = text, - .style = .{ - .fg = ui_err_fg, - .bg = ui_bg, - }, + .style = self.ui_err(), }, .{ .row_offset = middle + 1, @@ -637,6 +777,7 @@ const Preview = struct { }; var next_start: usize = 0; + const fg: vaxis.Color = .{ .rgb = [_]u8{ config.foreground.r, @@ -759,10 +900,7 @@ const Preview = struct { _ = try child.printSegment( .{ .text = text, - .style = .{ - .fg = ui_err_fg, - .bg = ui_bg, - }, + .style = self.ui_err(), }, .{ .row_offset = 0, @@ -774,10 +912,7 @@ const Preview = struct { _ = try child.printSegment( .{ .text = err.message, - .style = .{ - .fg = ui_err_fg, - .bg = ui_bg, - }, + .style = self.ui_err(), }, .{ .row_offset = 2 + i, From 5a3b942589b4c655057dda7816e0eb9f519818b4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Sep 2024 10:58:28 -0700 Subject: [PATCH 5/5] typos: ignore lorem --- typos.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/typos.toml b/typos.toml index 9f2c96f11..a72944e5f 100644 --- a/typos.toml +++ b/typos.toml @@ -1,9 +1,9 @@ [files] extend-exclude = [ + # vendored code "vendor/*", "pkg/*", "src/stb/*", - "*.xib", # "grey" color names are valid "src/terminal/res/rgb.txt", # Do not self-check @@ -17,7 +17,9 @@ extend-exclude = [ "*.icns", # Other "*.pdf", - "*.data" + "*.data", + "*.xib", + "src/cli/lorem_ipsum.txt" ] [default]