diff --git a/build.zig.zon b/build.zig.zon index c4bcecb3..7c33ca3a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,8 +6,8 @@ .dependencies = .{ .syntax = .{ - .url = "git+https://github.com/neurocyte/flow-syntax?ref=master#7b1fd3a97f00aba3a95cc65b95f34162347ed1ea", - .hash = "flow_syntax-0.7.2-X8jOoQhWAQBPt1rBRmttAGI0Z2QC-hCSZuoBZoZgr6Vv", + .url = "git+https://github.com/neurocyte/flow-syntax?ref=master#56929f0c523b59153e17919be2cd09d8bef32cd0", + .hash = "flow_syntax-0.7.2-X8jOoeFTAQBeP2Tn08Tw1jsMdifLEDBgPLqPqNelAupy", }, .flags = .{ .url = "git+https://github.com/neurocyte/flags?ref=main#984b27948da3e4e40a253f76c85b51ec1a9ada11", @@ -22,8 +22,8 @@ .hash = "thespian-0.0.1-owFOjlgiBgC8w4XqkCOegxz5vMy6kNErcssWQWf2QHeE", }, .themes = .{ - .url = "https://github.com/neurocyte/flow-themes/releases/download/master-750400d02ea8cacaabc869cd4d34dcebf04a53c8/flow-themes.tar.gz", - .hash = "N-V-__8AAEWxJQAyUV_rvRIWHB8EhIBxpQXqCB68SpilIjEt", + .url = "https://github.com/neurocyte/flow-themes/releases/download/master-c6c7f18cfb2e3945cd0b71dab24271465074dbc3/flow-themes.tar.gz", + .hash = "N-V-__8AAOKzJACguNxU76WX9M7RIhOYGuLnlasJ1-GDdhqT", }, .fuzzig = .{ .url = "https://github.com/fjebaker/fuzzig/archive/4251fe4230d38e721514394a485db62ee1667ff3.tar.gz", diff --git a/src/file_type_lsp.zig b/src/file_type_lsp.zig index a359e8a7..9064fc3c 100644 --- a/src/file_type_lsp.zig +++ b/src/file_type_lsp.zig @@ -232,11 +232,6 @@ pub const typst = .{ pub const uxntal = .{}; -pub const v = .{ - .language_server = .{"v-analyzer"}, - .formatter = .{ "v", "fmt", "-" }, -}; - pub const vim = .{}; pub const xml = .{ diff --git a/src/gui/rasterizer/truetype.zig b/src/gui/rasterizer/truetype.zig index 565fdb11..6a2bfb7c 100644 --- a/src/gui/rasterizer/truetype.zig +++ b/src/gui/rasterizer/truetype.zig @@ -49,29 +49,14 @@ pub fn loadFont(self: *Self, name: []const u8, size_px: u16) !Font { const scale = tt.scaleForPixelHeight(@floatFromInt(size_px)); const vm = tt.verticalMetrics(); - // Use the full block glyph (U+2588) to derive exact cell dimensions. - // Its rasterized bbox defines exactly how many pixels tall a full-height - // character is, and where the baseline sits within the cell. Using font - // metrics (vm.ascent/vm.descent) produces a cell_h that may differ from - // the glyph because the font bbox can diverge from nominal metrics. - const full_block_glyph = tt.codepointGlyphIndex('█'); - const block_bbox = tt.glyphBitmapBox(full_block_glyph, scale, scale); - const has_block = full_block_glyph != .notdef and block_bbox.y1 > block_bbox.y0; - const ascent_px: i32 = if (has_block) - -@as(i32, block_bbox.y0) - else - @as(i32, @intFromFloat(@ceil(@as(f32, @floatFromInt(vm.ascent)) * scale))); - const cell_h: u16 = @as(u16, if (has_block) - @intCast(@max(block_bbox.y1 - block_bbox.y0, 1)) - else blk: { - const d: i32 = @intFromFloat(@floor(@as(f32, @floatFromInt(vm.descent)) * scale)); - break :blk @intCast(@max(ascent_px - d, 1)); - }) -| 1; + const ascent_px: i32 = @intFromFloat(@round(@as(f32, @floatFromInt(vm.ascent)) * scale)); + const descent_px: i32 = @intFromFloat(@round(@as(f32, @floatFromInt(vm.descent)) * scale)); + const cell_h: u16 = @intCast(@max(ascent_px - descent_px, 1)); const m_glyph = tt.codepointGlyphIndex('M'); const m_hmetrics = tt.glyphHMetrics(m_glyph); const cell_w_f: f32 = @as(f32, @floatFromInt(m_hmetrics.advance_width)) * scale; - const cell_w: u16 = @as(u16, @intFromFloat(@ceil(cell_w_f))) -| 1; + const cell_w: u16 = @intFromFloat(@ceil(cell_w_f)); try self.font_data.append(self.allocator, data); diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 512e0585..9ce0934d 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -66,10 +66,8 @@ ["ctrl+k e", "switch_buffers"], ["alt+shift+v", "clipboard_history"], ["ctrl+0", "reset_fontsize"], - ["ctrl+alt+0", "reset_fontsize"], ["ctrl+plus", "adjust_fontsize", 1.0], ["ctrl+alt+plus", "adjust_fontsize", 1.0], - ["ctrl+alt+=", "adjust_fontsize", 1.0], ["ctrl+minus", "adjust_fontsize", -1.0], ["ctrl+alt+minus", "adjust_fontsize", -1.0], ["ctrl+f5", "open_config"], diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 07d82099..a844be61 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -81,62 +81,14 @@ ["dgg", "cut_buffer_begin"], ["\"_dd", "delete_line"], - ["diw", "cut_inside_word"], - ["di(", "cut_inside_parentheses"], - ["di)", "cut_inside_parentheses"], - ["di[", "cut_inside_square_brackets"], - ["di]", "cut_inside_square_brackets"], - ["di{", "cut_inside_braces"], - ["di}", "cut_inside_braces"], - - ["daw", "cut_around_word"], - ["da(", "cut_around_parentheses"], - ["da)", "cut_around_parentheses"], - ["da[", "cut_around_square_brackets"], - ["da]", "cut_around_square_brackets"], - ["da{", "cut_around_braces"], - ["da}", "cut_around_braces"], - ["cc", ["enter_mode", "insert"], ["cut_internal_vim"]], ["C", ["enter_mode", "insert"], ["cut_to_end_vim"]], ["D", "cut_to_end_vim"], ["cw", ["enter_mode", "insert"], ["cut_word_right_vim"]], ["cb", ["enter_mode", "insert"], ["cut_word_left_vim"]], - ["ciw", ["enter_mode", "insert"], ["cut_inside_word"]], - ["ci(", ["enter_mode", "insert"], ["cut_inside_parentheses"]], - ["ci)", ["enter_mode", "insert"], ["cut_inside_parentheses"]], - ["ci[", ["enter_mode", "insert"], ["cut_inside_square_brackets"]], - ["ci]", ["enter_mode", "insert"], ["cut_inside_square_brackets"]], - ["ci{", ["enter_mode", "insert"], ["cut_inside_braces"]], - ["ci}", ["enter_mode", "insert"], ["cut_inside_braces"]], - - ["caw", ["enter_mode", "insert"], ["cut_around_word"]], - ["ca(", ["enter_mode", "insert"], ["cut_around_parentheses"]], - ["ca)", ["enter_mode", "insert"], ["cut_around_parentheses"]], - ["ca[", ["enter_mode", "insert"], ["cut_around_square_brackets"]], - ["ca]", ["enter_mode", "insert"], ["cut_around_square_brackets"]], - ["ca{", ["enter_mode", "insert"], ["cut_around_braces"]], - ["ca}", ["enter_mode", "insert"], ["cut_around_braces"]], - ["yy", ["copy_line_internal_vim"], ["cancel"]], - ["yiw", ["copy_inside_word"], ["cancel"]], - ["yi(", ["copy_inside_parentheses"], ["cancel"]], - ["yi)", ["copy_inside_parentheses"], ["cancel"]], - ["yi[", ["copy_inside_square_brackets"], ["cancel"]], - ["yi]", ["copy_inside_square_brackets"], ["cancel"]], - ["yi{", ["copy_inside_braces"], ["cancel"]], - ["yi}", ["copy_inside_braces"], ["cancel"]], - - ["yaw", ["copy_around_word"], ["cancel"]], - ["ya(", ["copy_around_parentheses"], ["cancel"]], - ["ya)", ["copy_around_parentheses"], ["cancel"]], - ["ya[", ["copy_around_square_brackets"], ["cancel"]], - ["ya]", ["copy_around_square_brackets"], ["cancel"]], - ["ya{", ["copy_around_braces"], ["cancel"]], - ["ya}", ["copy_around_braces"], ["cancel"]], - ["", "move_scroll_half_page_up_vim"], ["", "move_scroll_half_page_down_vim"], @@ -207,22 +159,6 @@ ["B", "select_word_left"], ["e", "select_word_right_end_vim"], - ["iw", "select_inside_word"], - ["i(", "select_inside_parentheses"], - ["i)", "select_inside_parentheses"], - ["i[", "select_inside_square_brackets"], - ["i]", "select_inside_square_brackets"], - ["i{", "select_inside_braces"], - ["i}", "select_inside_braces"], - - ["aw", "select_around_word"], - ["a(", "select_around_parentheses"], - ["a)", "select_around_parentheses"], - ["a[", "select_around_square_brackets"], - ["a]", "select_around_square_brackets"], - ["a{", "select_around_braces"], - ["a}", "select_around_braces"], - ["^", "smart_move_begin"], ["$", "select_end"], [":", "open_command_palette"], diff --git a/src/main.zig b/src/main.zig index 054340ca..b191faf5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -882,10 +882,7 @@ pub fn read_theme(allocator: std.mem.Allocator, theme_name: []const u8) ?[]const const file_name = get_theme_file_name(theme_name) catch return null; var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch return null; defer file.close(); - return file.readToEndAlloc(allocator, 512 * 1024) catch |e| { - std.log.err("Error reading theme file: {t}", .{e}); - return null; - }; + return file.readToEndAlloc(allocator, 64 * 1024) catch null; } pub fn write_theme(theme_name: []const u8, content: []const u8) !void { @@ -898,15 +895,15 @@ pub fn write_theme(theme_name: []const u8, content: []const u8) !void { pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 { var dir = try std.fs.openDirAbsolute(try get_theme_directory(), .{ .iterate = true }); defer dir.close(); - var result: std.ArrayList([]const u8) = .empty; + var result = std.ArrayList([]const u8).init(allocator); var iter = dir.iterateAssumeFirstIteration(); while (try iter.next()) |entry| { switch (entry.kind) { - .file, .sym_link => try result.append(allocator, try allocator.dupe(u8, std.fs.path.stem(entry.name))), + .file, .sym_link => try result.append(try allocator.dupe(u8, std.fs.path.stem(entry.name))), else => continue, } } - return result.toOwnedSlice(allocator); + return result.toOwnedSlice(); } pub fn get_config_dir() ConfigDirError![]const u8 { diff --git a/src/soft_root.zig b/src/soft_root.zig index d076e942..a792e730 100644 --- a/src/soft_root.zig +++ b/src/soft_root.zig @@ -29,7 +29,6 @@ pub const root = struct { pub const read_theme = if (@hasDecl(hard_root, "read_theme")) hard_root.read_theme else dummy.read_theme; pub const write_theme = if (@hasDecl(hard_root, "write_theme")) hard_root.write_theme else dummy.write_theme; - pub const list_themes = if (@hasDecl(hard_root, "list_themes")) hard_root.list_themes else dummy.list_themes; pub const get_theme_file_name = if (@hasDecl(hard_root, "get_theme_file_name")) hard_root.get_theme_file_name else dummy.get_theme_file_name; pub const exit = if (@hasDecl(hard_root, "exit")) hard_root.exit else dummy.exit; @@ -110,10 +109,6 @@ const dummy = struct { pub fn write_theme(_: []const u8, _: []const u8) !void { @panic("dummy write_theme call"); } - pub fn list_themes(_: std.mem.Allocator) ![]const []const u8 { - @panic("dummy list_themes call"); - } - pub fn get_theme_file_name(_: []const u8) ![]const u8 { @panic("dummy get_theme_file_name call"); } diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index 9a473ecf..6db55a94 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const tp = @import("thespian"); -const root = @import("soft_root").root; const Plane = @import("renderer").Plane; const EventHandler = @import("EventHandler"); @@ -10,6 +9,7 @@ const tui = @import("tui.zig"); pub const Box = @import("Box.zig"); pub const Pos = struct { y: i32 = 0, x: i32 = 0 }; pub const Theme = @import("theme"); +pub const themes = @import("themes").themes; pub const scopes = @import("themes").scopes; pub const Type = @import("config").WidgetType; pub const StyleTag = @import("config").WidgetStyle; @@ -42,114 +42,6 @@ pub const Layout = union(enum) { } }; -pub const ThemeInfo = struct { - name: []const u8, - storage: ?std.json.Parsed(Theme) = null, - - pub fn get(self: *@This(), allocator: std.mem.Allocator) ?Theme { - if (load_theme_file(allocator, self.name) catch null) |parsed_theme| { - self.storage = parsed_theme; - return self.storage.?.value; - } - - for (static_themes) |theme_| { - if (std.mem.eql(u8, theme_.name, self.name)) - return theme_; - } - return null; - } - - fn load_theme_file(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Theme) { - return load_theme_file_internal(allocator, theme_name) catch |e| { - std.log.err("Error loading theme '{s}' from file: {t}", .{ theme_name, e }); - return e; - }; - } - fn load_theme_file_internal(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Theme) { - const json_str = root.read_theme(allocator, theme_name) orelse return null; - defer allocator.free(json_str); - return try std.json.parseFromSlice(Theme, allocator, json_str, .{ .allocate = .alloc_always }); - } -}; - -var themes_: ?std.StringHashMap(*ThemeInfo) = null; -var theme_names_: ?[]const []const u8 = null; -const static_themes = @import("themes").themes; - -fn get_themes(allocator: std.mem.Allocator) *std.StringHashMap(*ThemeInfo) { - if (themes_) |*themes__| return themes__; - - const theme_files = root.list_themes(allocator) catch @panic("OOM get_themes"); - var themes: std.StringHashMap(*ThemeInfo) = .init(allocator); - defer allocator.free(theme_files); - for (theme_files) |file| { - const theme_info = allocator.create(ThemeInfo) catch @panic("OOM get_themes"); - theme_info.* = .{ - .name = file, - }; - themes.put(theme_info.name, theme_info) catch @panic("OOM get_themes"); - } - - for (static_themes) |theme_| if (!themes.contains(theme_.name)) { - const theme_info = allocator.create(ThemeInfo) catch @panic("OOM get_themes"); - theme_info.* = .{ - .name = theme_.name, - }; - themes.put(theme_info.name, theme_info) catch @panic("OOM get_themes"); - }; - themes_ = themes; - return &themes_.?; -} - -fn get_theme_names() []const []const u8 { - if (theme_names_) |names_| return names_; - const themes = themes_ orelse return &.{}; - var i = get_themes(themes.allocator).iterator(); - var names: std.ArrayList([]const u8) = .empty; - while (i.next()) |theme_| names.append(themes.allocator, theme_.value_ptr.*.name) catch @panic("OOM get_theme_names"); - std.mem.sort([]const u8, names.items, {}, struct { - fn cmp(_: void, lhs: []const u8, rhs: []const u8) bool { - return std.mem.order(u8, lhs, rhs) == .lt; - } - }.cmp); - theme_names_ = names.toOwnedSlice(themes.allocator) catch @panic("OOM get_theme_names"); - return theme_names_.?; -} - -pub fn get_theme_by_name(allocator: std.mem.Allocator, name_: []const u8) ?Theme { - const themes = get_themes(allocator); - const theme = themes.get(name_) orelse return null; - return theme.get(allocator); -} - -pub fn get_next_theme_by_name(name_: []const u8) []const u8 { - const theme_names = get_theme_names(); - var next = false; - for (theme_names) |theme_name| { - if (next) - return theme_name; - if (std.mem.eql(u8, theme_name, name_)) - next = true; - } - return theme_names[0]; -} - -pub fn get_prev_theme_by_name(name_: []const u8) []const u8 { - const theme_names = get_theme_names(); - const last = theme_names[theme_names.len - 1]; - var prev: ?[]const u8 = null; - for (theme_names) |theme_name| { - if (std.mem.eql(u8, theme_name, name_)) - return prev orelse last; - prev = theme_name; - } - return last; -} - -pub fn list_themes() []const []const u8 { - return get_theme_names(); -} - pub const VTable = struct { deinit: *const fn (ctx: *anyopaque, allocator: Allocator) void, send: *const fn (ctx: *anyopaque, from: tp.pid_ref, m: tp.message) error{Exit}!bool, diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 74a99168..bdac85ac 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -41,7 +41,7 @@ const double_click_time_ms = 350; const syntax_full_reparse_time_limit = 0; // ms (0 = always use incremental) const syntax_full_reparse_error_threshold = 3; // number of tree-sitter errors that trigger a full reparse -pub const bracket_search_radius = if (builtin.mode == std.builtin.OptimizeMode.Debug) 8_192 else 65_536; +const bracket_search_radius = if (builtin.mode == std.builtin.OptimizeMode.Debug) 8_192 else 65_536; pub const max_matches = if (builtin.mode == std.builtin.OptimizeMode.Debug) 10_000 else 100_000; pub const max_match_lines = 15; @@ -2628,15 +2628,7 @@ pub const Editor = struct { return cursor.test_at(root, is_whitespace, metrics); } - pub fn is_whitespace_or_eol_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - return cursor.test_at(root, is_whitespace_or_eol, metrics); - } - - pub fn is_non_whitespace_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - return !cursor.test_at(root, is_whitespace, metrics); - } - - pub fn is_non_whitespace_or_eol_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + fn is_non_whitespace_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { return !cursor.test_at(root, is_whitespace_or_eol, metrics); } @@ -3753,7 +3745,7 @@ pub const Editor = struct { } pub fn move_cursor_right_until_non_whitespace(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void { - move_cursor_right_until(root, cursor, is_non_whitespace_or_eol_at_cursor, metrics); + move_cursor_right_until(root, cursor, is_non_whitespace_at_cursor, metrics); } pub fn move_word_left(self: *Self, ctx: Context) Result { diff --git a/src/tui/mode/overlay/theme_palette.zig b/src/tui/mode/overlay/theme_palette.zig index 1f9396df..a58e9844 100644 --- a/src/tui/mode/overlay/theme_palette.zig +++ b/src/tui/mode/overlay/theme_palette.zig @@ -33,8 +33,7 @@ pub fn load_entries(palette: *Type) !usize { var longest_hint: usize = 0; var idx: usize = 0; try set_previous_theme(palette, tui.theme().name); - for (Widget.list_themes()) |theme_name_| { - const theme = Widget.get_theme_by_name(palette.allocator, theme_name_) orelse continue; + for (Widget.themes) |theme| { idx += 1; (try palette.entries.addOne(palette.allocator)).* = .{ .label = theme.description, diff --git a/src/tui/mode/vim.zig b/src/tui/mode/vim.zig index 043ed35a..38eacbee 100644 --- a/src/tui/mode/vim.zig +++ b/src/tui/mode/vim.zig @@ -2,14 +2,6 @@ const std = @import("std"); const command = @import("command"); const cmd = command.executeName; -const tui = @import("../tui.zig"); - -const Buffer = @import("Buffer"); -const Cursor = Buffer.Cursor; -const CurSel = @import("../editor.zig").CurSel; -const Editor = @import("../editor.zig").Editor; -const bracket_search_radius = @import("../editor.zig").bracket_search_radius; - var commands: Commands = undefined; pub fn init() !void { @@ -146,398 +138,6 @@ const cmds_ = struct { //TODO return undefined; } + pub const copy_line_meta: Meta = .{ .description = "Copies the current line" }; - - pub fn select_inside_word(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_word_textobject, ed.metrics); - } - pub const select_inside_word_meta: Meta = .{ .description = "Select inside word" }; - - pub fn select_around_word(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_word_textobject, ed.metrics); - } - pub const select_around_word_meta: Meta = .{ .description = "Select around word" }; - - pub fn select_inside_parentheses(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_parentheses_textobject, ed.metrics); - } - pub const select_inside_parentheses_meta: Meta = .{ .description = "Select inside ()" }; - - pub fn select_around_parentheses(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_parentheses_textobject, ed.metrics); - } - pub const select_around_parentheses_meta: Meta = .{ .description = "Select around ()" }; - - pub fn select_inside_square_brackets(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_square_brackets_textobject, ed.metrics); - } - pub const select_inside_square_brackets_meta: Meta = .{ .description = "Select inside []" }; - - pub fn select_around_square_brackets(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_square_brackets_textobject, ed.metrics); - } - pub const select_around_square_brackets_meta: Meta = .{ .description = "Select around []" }; - - pub fn select_inside_braces(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_braces_textobject, ed.metrics); - } - pub const select_inside_braces_meta: Meta = .{ .description = "Select inside {}" }; - - pub fn select_around_braces(_: *void, _: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_braces_textobject, ed.metrics); - } - pub const select_around_braces_meta: Meta = .{ .description = "Select around {}" }; - - pub fn cut_inside_word(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_word_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_inside_word_meta: Meta = .{ .description = "Cut inside word" }; - - pub fn cut_around_word(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_word_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_around_word_meta: Meta = .{ .description = "Cut around word" }; - - pub fn cut_inside_parentheses(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_parentheses_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_inside_parentheses_meta: Meta = .{ .description = "Cut inside ()" }; - - pub fn cut_around_parentheses(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_parentheses_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_around_parentheses_meta: Meta = .{ .description = "Cut around ()" }; - - pub fn cut_inside_square_brackets(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_square_brackets_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_inside_square_brackets_meta: Meta = .{ .description = "Cut inside []" }; - - pub fn cut_around_square_brackets(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_square_brackets_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_around_square_brackets_meta: Meta = .{ .description = "Cut around []" }; - - pub fn cut_inside_braces(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_braces_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_inside_braces_meta: Meta = .{ .description = "Cut inside {}" }; - - pub fn cut_around_braces(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_braces_textobject, ed.metrics); - try ed.cut_internal_vim(ctx); - } - pub const cut_around_braces_meta: Meta = .{ .description = "Cut around {}" }; - - pub fn copy_inside_word(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_word_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_inside_word_meta: Meta = .{ .description = "Copy inside word" }; - - pub fn copy_around_word(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_word_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_around_word_meta: Meta = .{ .description = "Copy around word" }; - - pub fn copy_inside_parentheses(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_parentheses_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_inside_parentheses_meta: Meta = .{ .description = "Copy inside ()" }; - - pub fn copy_around_parentheses(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_parentheses_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_around_parentheses_meta: Meta = .{ .description = "Copy around ()" }; - - pub fn copy_inside_square_brackets(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_square_brackets_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_inside_square_brackets_meta: Meta = .{ .description = "Copy inside []" }; - - pub fn copy_around_square_brackets(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_square_brackets_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_around_square_brackets_meta: Meta = .{ .description = "Copy around []" }; - - pub fn copy_inside_braces(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_inside_braces_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_inside_braces_meta: Meta = .{ .description = "Copy inside {}" }; - - pub fn copy_around_braces(_: *void, ctx: Ctx) Result { - const mv = tui.mainview() orelse return; - const ed = mv.get_active_editor() orelse return; - const root = ed.buf_root() catch return; - - try ed.with_cursels_const(root, select_around_braces_textobject, ed.metrics); - try ed.copy_internal_vim(ctx); - } - pub const copy_around_braces_meta: Meta = .{ .description = "Copy around {}" }; }; - -fn is_tab_or_space(c: []const u8) bool { - return (c[0] == ' ') or (c[0] == '\t'); -} - -fn is_tab_or_space_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - return cursor.test_at(root, is_tab_or_space, metrics); -} -fn is_not_tab_or_space_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { - return !cursor.test_at(root, is_tab_or_space, metrics); -} - -fn select_inside_word_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_word_textobject(root, cursel, metrics, .inside); -} - -fn select_around_word_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_word_textobject(root, cursel, metrics, .around); -} - -fn select_word_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics, scope: enum { inside, around }) !void { - var prev = cursel.cursor; - var next = cursel.cursor; - - if (cursel.cursor.test_at(root, Editor.is_non_word_char, metrics)) { - if (cursel.cursor.test_at(root, Editor.is_whitespace_or_eol, metrics)) { - Editor.move_cursor_left_until(root, &prev, Editor.is_non_whitespace_at_cursor, metrics); - Editor.move_cursor_right_until(root, &next, Editor.is_non_whitespace_at_cursor, metrics); - } else { - Editor.move_cursor_left_until(root, &prev, Editor.is_whitespace_or_eol_at_cursor, metrics); - Editor.move_cursor_right_until(root, &next, Editor.is_whitespace_or_eol_at_cursor, metrics); - } - prev.move_right(root, metrics) catch {}; - } else { - Editor.move_cursor_left_until(root, &prev, Editor.is_word_boundary_left_vim, metrics); - Editor.move_cursor_right_until(root, &next, Editor.is_word_boundary_right_vim, metrics); - next.move_right(root, metrics) catch {}; - } - - if (scope == .around) { - const inside_prev = prev; - const inside_next = next; - - if (next.test_at(root, is_tab_or_space, metrics)) { - Editor.move_cursor_right_until(root, &next, is_not_tab_or_space_at_cursor, metrics); - } else { - next = inside_next; - prev.move_left(root, metrics) catch {}; - if (prev.test_at(root, is_tab_or_space, metrics)) { - Editor.move_cursor_left_until(root, &prev, is_not_tab_or_space_at_cursor, metrics); - prev.move_right(root, metrics) catch {}; - } else { - prev = inside_prev; - } - } - } - - const sel = cursel.enable_selection(root, metrics); - sel.begin = prev; - sel.end = next; - cursel.*.cursor = next; -} - -fn select_inside_parentheses_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_bracket_textobject(root, cursel, metrics, "(", ")", .inside); -} - -fn select_around_parentheses_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_bracket_textobject(root, cursel, metrics, "(", ")", .around); -} - -fn select_inside_square_brackets_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_bracket_textobject(root, cursel, metrics, "[", "]", .inside); -} - -fn select_around_square_brackets_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_bracket_textobject(root, cursel, metrics, "[", "]", .around); -} - -fn select_inside_braces_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_bracket_textobject(root, cursel, metrics, "{", "}", .inside); -} - -fn select_around_braces_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { - return try select_bracket_textobject(root, cursel, metrics, "{", "}", .around); -} - -fn select_bracket_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics, opening_char: []const u8, closing_char: []const u8, scope: enum { inside, around }) !void { - const current = cursel.cursor; - var prev = cursel.cursor; - var next = cursel.cursor; - - const bracket_egc, _, _ = root.egc_at(current.row, current.col, metrics) catch { - return error.Stop; - }; - if (std.mem.eql(u8, bracket_egc, opening_char)) { - const closing_row, const closing_col = try Editor.match_bracket(root, current, metrics); - - prev = current; - next.row = closing_row; - next.col = closing_col; - } else if (std.mem.eql(u8, bracket_egc, closing_char)) { - const opening_row, const opening_col = try Editor.match_bracket(root, current, metrics); - - prev.row = opening_row; - prev.col = opening_col; - next = current; - } else { - const opening_pos, const closing_pos = find_bracket_pair(root, cursel, metrics, .left, opening_char) catch try find_bracket_pair(root, cursel, metrics, .right, opening_char); - - prev.row = opening_pos[0]; - prev.col = opening_pos[1]; - next.row = closing_pos[0]; - next.col = closing_pos[1]; - } - - prev.move_right(root, metrics) catch {}; - - if (scope == .around) { - prev.move_left(root, metrics) catch {}; - next.move_right(root, metrics) catch {}; - } - - const sel = cursel.enable_selection(root, metrics); - sel.begin = prev; - sel.end = next; - cursel.*.cursor = next; -} - -fn find_bracket_pair(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics, direction: enum { left, right }, char: []const u8) error{Stop}!struct { struct { usize, usize }, struct { usize, usize } } { - const start = cursel.cursor; - var moving_cursor = cursel.cursor; - - var i: usize = 0; - while (i < bracket_search_radius) : (i += 1) { - switch (direction) { - .left => try moving_cursor.move_left(root, metrics), - .right => try moving_cursor.move_right(root, metrics), - } - - const curr_egc, _, _ = root.egc_at(moving_cursor.row, moving_cursor.col, metrics) catch { - return error.Stop; - }; - if (std.mem.eql(u8, char, curr_egc)) { - const closing_row, const closing_col = try Editor.match_bracket(root, moving_cursor, metrics); - - switch (direction) { - .left => if (closing_row > start.row or (closing_row == start.row and closing_col > start.col)) { - return .{ .{ moving_cursor.row, moving_cursor.col }, .{ closing_row, closing_col } }; - } else { - continue; - }, - .right => { - return .{ .{ moving_cursor.row, moving_cursor.col }, .{ closing_row, closing_col } }; - }, - } - } - } - - return error.Stop; -} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 6b04a30c..3d9a1ad6 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -62,7 +62,9 @@ logger: log.Logger, drag_source: ?Widget = null, drag_button: input.MouseType = 0, dark_theme: Widget.Theme, +dark_parsed_theme: ?std.json.Parsed(Widget.Theme), light_theme: Widget.Theme, +light_parsed_theme: ?std.json.Parsed(Widget.Theme), idle_frame_count: usize = 0, unrendered_input_events_count: usize = 0, init_timer: ?tp.timeout, @@ -158,9 +160,9 @@ fn init(allocator: Allocator) InitError!*Self { if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash) renderer.jit_debugger_enabled = true; - const dark_theme = Widget.get_theme_by_name(allocator, conf.theme) orelse Widget.get_theme_by_name(allocator, "dark_modern") orelse return error.UnknownTheme; + const dark_theme, const dark_parsed_theme = get_theme_by_name(allocator, conf.theme) orelse get_theme_by_name(allocator, "dark_modern") orelse return error.UnknownTheme; conf.theme = dark_theme.name; - const light_theme = Widget.get_theme_by_name(allocator, conf.light_theme) orelse Widget.get_theme_by_name(allocator, "default-light") orelse return error.UnknownTheme; + const light_theme, const light_parsed_theme = get_theme_by_name(allocator, conf.light_theme) orelse get_theme_by_name(allocator, "default-light") orelse return error.UnknownTheme; conf.light_theme = light_theme.name; if (build_options.gui) conf.enable_terminal_cursor = false; @@ -201,6 +203,8 @@ fn init(allocator: Allocator) InitError!*Self { .query_cache_ = try syntax.QueryCache.create(allocator, .{}), .dark_theme = dark_theme, .light_theme = light_theme, + .dark_parsed_theme = dark_parsed_theme, + .light_parsed_theme = light_parsed_theme, }; instance_ = self; defer instance_ = null; @@ -983,13 +987,21 @@ fn refresh_input_mode(self: *Self) command.Result { } fn set_theme_by_name(self: *Self, name: []const u8, action: enum { none, store }) !void { - const theme_ = Widget.get_theme_by_name(self.allocator, name) orelse { + const theme_, const parsed_theme = get_theme_by_name(self.allocator, name) orelse { self.logger.print("theme not found: {s}", .{name}); return; }; switch (self.color_scheme) { - .dark => self.dark_theme = theme_, - .light => self.light_theme = theme_, + .dark => { + if (self.dark_parsed_theme) |p| p.deinit(); + self.dark_parsed_theme = parsed_theme; + self.dark_theme = theme_; + }, + .light => { + if (self.light_parsed_theme) |p| p.deinit(); + self.light_parsed_theme = parsed_theme; + self.light_theme = theme_; + }, } self.set_terminal_style(&theme_); self.logger.print("theme: {s}", .{theme_.description}); @@ -1129,13 +1141,13 @@ const cmds = struct { pub const set_theme_meta: Meta = .{ .arguments = &.{.string} }; pub fn theme_next(self: *Self, _: Ctx) Result { - const name = Widget.get_next_theme_by_name(self.current_theme().name); + const name = get_next_theme_by_name(self.current_theme().name); return self.set_theme_by_name(name, .store); } pub const theme_next_meta: Meta = .{ .description = "Next color theme" }; pub fn theme_prev(self: *Self, _: Ctx) Result { - const name = Widget.get_prev_theme_by_name(self.current_theme().name); + const name = get_prev_theme_by_name(self.current_theme().name); return self.set_theme_by_name(name, .store); } pub const theme_prev_meta: Meta = .{ .description = "Previous color theme" }; @@ -2008,6 +2020,40 @@ pub fn theme() *const Widget.Theme { return current().current_theme(); } +pub fn get_theme_by_name(allocator: std.mem.Allocator, name: []const u8) ?struct { Widget.Theme, ?std.json.Parsed(Widget.Theme) } { + if (load_theme_file(allocator, name) catch null) |parsed_theme| { + std.log.info("loaded theme from file: {s}", .{name}); + return .{ parsed_theme.value, parsed_theme }; + } + + for (Widget.themes) |theme_| { + if (std.mem.eql(u8, theme_.name, name)) + return .{ theme_, null }; + } + return null; +} + +fn get_next_theme_by_name(name: []const u8) []const u8 { + var next = false; + for (Widget.themes) |theme_| { + if (next) + return theme_.name; + if (std.mem.eql(u8, theme_.name, name)) + next = true; + } + return Widget.themes[0].name; +} + +fn get_prev_theme_by_name(name: []const u8) []const u8 { + var prev: ?Widget.Theme = null; + for (Widget.themes) |theme_| { + if (std.mem.eql(u8, theme_.name, name)) + return (prev orelse Widget.themes[Widget.themes.len - 1]).name; + prev = theme_; + } + return Widget.themes[Widget.themes.len - 1].name; +} + pub fn find_scope_style(theme_: *const Widget.Theme, scope: []const u8) ?Widget.Theme.Token { return if (find_scope_fallback(scope)) |tm_scope| scope_to_theme_token(theme_, tm_scope) orelse @@ -2396,6 +2442,19 @@ fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const return try root.get_theme_file_name(theme_name); } +fn load_theme_file(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Widget.Theme) { + return load_theme_file_internal(allocator, theme_name) catch |e| { + std.log.err("loaded theme from file failed: {}", .{e}); + return e; + }; +} +fn load_theme_file_internal(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Widget.Theme) { + _ = std.json.Scanner; + const json_str = root.read_theme(allocator, theme_name) orelse return null; + defer allocator.free(json_str); + return try std.json.parseFromSlice(Widget.Theme, allocator, json_str, .{ .allocate = .alloc_always }); +} + pub const WidgetType = @import("config").WidgetType; pub const ConfigWidgetStyle = @import("config").WidgetStyle; pub const WidgetStyle = @import("WidgetStyle.zig");