From 4cb6d0af05a95cf86019d064999045b7415c6f75 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 15 Jul 2025 17:45:42 +0200 Subject: [PATCH 01/19] build: update to zig-0.15.0-dev.1034+bd97b6618 --- build.zig.version | 2 +- build.zig.zon | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.zig.version b/build.zig.version index ca633d3..2e95286 100644 --- a/build.zig.version +++ b/build.zig.version @@ -1 +1 @@ -0.15.0-dev.936+fc2c1883b +0.15.0-dev.1034+bd97b6618 diff --git a/build.zig.zon b/build.zig.zon index a2e2509..d60a43c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,8 +15,8 @@ .hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK", }, .thespian = .{ - .url = "https://github.com/neurocyte/thespian/archive/9af61100d08d79ec52e98fa34ea36611dcc1c361.tar.gz", - .hash = "thespian-0.0.1-owFOjlgaBgAwqxQerLVXpy7blPPZjTqdfH9MLf9_v7dt", + .url = "git+https://github.com/neurocyte/thespian#4d317d7bf02eef26da56eddd2d49e46984b50572", + .hash = "thespian-0.0.1-owFOjuEaBgA9zNohtfOqnA8alObpvYjJNNDjEV8dKvLj", }, .themes = .{ .url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz", From 82c431c952e7f17eee3f1b2e493b09833ebdf3ea Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Thu, 17 Jul 2025 11:57:58 +0200 Subject: [PATCH 02/19] update to zig 0.15.0-dev.1048+f43f89a70 --- src/syntax/build.zig | 4 ++-- src/syntax/build.zig.zon | 2 +- src/syntax/src/file_type.zig | 2 +- src/syntax/src/syntax.zig | 8 ++++---- src/syntax/src/treez_dummy.zig | 4 ++-- src/syntax/src/ts_bin_query_gen.zig | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/syntax/build.zig b/src/syntax/build.zig index e287604..4251ae1 100644 --- a/src/syntax/build.zig +++ b/src/syntax/build.zig @@ -25,8 +25,8 @@ pub fn build(b: *std.Build) void { .name = "ts_bin_query_gen", .root_module = b.createModule(.{ .root_source_file = b.path("src/ts_bin_query_gen.zig"), - .target = target, - .optimize = optimize, + .target = b.graph.host, + .optimize = .Debug, }), }); ts_bin_query_gen.linkLibC(); diff --git a/src/syntax/build.zig.zon b/src/syntax/build.zig.zon index 218a167..5d8194d 100644 --- a/src/syntax/build.zig.zon +++ b/src/syntax/build.zig.zon @@ -2,7 +2,7 @@ .name = .flow_syntax, .version = "0.1.0", .fingerprint = 0x3ba2584ea1cec85f, - .minimum_zig_version = "0.14.1", + .minimum_zig_version = "0.15.0-dev.1048+f43f89a70", .dependencies = .{ .tree_sitter = .{ diff --git a/src/syntax/src/file_type.zig b/src/syntax/src/file_type.zig index 88a1494..9faa6fa 100644 --- a/src/syntax/src/file_type.zig +++ b/src/syntax/src/file_type.zig @@ -87,7 +87,7 @@ fn ft_func_name(comptime lang: []const u8) []const u8 { return &func_name; } -pub const LangFn = *const fn () callconv(.C) ?*const treez.Language; +pub const LangFn = *const fn () callconv(.c) ?*const treez.Language; pub const FirstLineMatch = struct { prefix: ?[]const u8 = null, diff --git a/src/syntax/src/syntax.zig b/src/syntax/src/syntax.zig index bd67ccf..cb54b08 100644 --- a/src/syntax/src/syntax.zig +++ b/src/syntax/src/syntax.zig @@ -45,12 +45,12 @@ pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *Q return self; } -pub fn static_create_file_type(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self { +pub fn create_file_type_static(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self { const file_type = FileType.get_by_name_static(lang_name) orelse return error.NotFound; return create(file_type, allocator, query_cache); } -pub fn static_create_guess_file_type_static(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self { +pub fn create_guess_file_type_static(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self { const file_type = FileType.guess_static(file_path, content) orelse return error.NotFound; return create(file_type, allocator, query_cache); } @@ -98,7 +98,7 @@ pub fn refresh_from_buffer(self: *Self, buffer: anytype, metrics: anytype) !void const input: Input = .{ .payload = &state, .read = struct { - fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.C) [*:0]const u8 { + fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.c) [*:0]const u8 { const ctx: *State = @ptrCast(@alignCast(payload orelse return "")); const result = ctx.buffer.get_from_pos(.{ .row = position.row, .col = position.column }, &ctx.result_buf, ctx.metrics); bytes_read.* = @intCast(result.len); @@ -124,7 +124,7 @@ pub fn refresh_from_string(self: *Self, content: [:0]const u8) !void { const input: Input = .{ .payload = &state, .read = struct { - fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.C) [*:0]const u8 { + fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.c) [*:0]const u8 { bytes_read.* = 0; const ctx: *State = @ptrCast(@alignCast(payload orelse return "")); const pos = (find_line_begin(ctx.content, position.row) orelse return "") + position.column; diff --git a/src/syntax/src/treez_dummy.zig b/src/syntax/src/treez_dummy.zig index b73b1f4..bee4c42 100644 --- a/src/syntax/src/treez_dummy.zig +++ b/src/syntax/src/treez_dummy.zig @@ -23,12 +23,12 @@ pub const InputEncoding = enum(c_uint) { }; pub const Input = extern struct { payload: ?*anyopaque, - read: ?*const fn (payload: ?*anyopaque, byte_index: u32, position: Point, bytes_read: *u32) callconv(.C) [*:0]const u8, + read: ?*const fn (payload: ?*anyopaque, byte_index: u32, position: Point, bytes_read: *u32) callconv(.c) [*:0]const u8, encoding: InputEncoding, }; pub const Language = struct { var dummy: @This() = .{}; - pub fn LangFn() callconv(.C) ?*const Language { + pub fn LangFn() callconv(.c) ?*const Language { return &dummy; } }; diff --git a/src/syntax/src/ts_bin_query_gen.zig b/src/syntax/src/ts_bin_query_gen.zig index e32c6ef..2584921 100644 --- a/src/syntax/src/ts_bin_query_gen.zig +++ b/src/syntax/src/ts_bin_query_gen.zig @@ -91,7 +91,7 @@ const FileType = struct { highlights: [:0]const u8, injections: ?[:0]const u8, }; -const LangFn = *const fn () callconv(.C) ?*const treez.Language; +const LangFn = *const fn () callconv(.c) ?*const treez.Language; fn load_file_types(comptime Namespace: type) []const FileType { comptime switch (@typeInfo(Namespace)) { From a1c9c53d5c2c2f1bf5e1fd4b7d847f99691f1094 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 21 Jul 2025 12:58:46 +0200 Subject: [PATCH 03/19] build: update fuzzig for zig-0.15.0-dev.1034+bd97b6618 --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index d60a43c..c02835b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -23,8 +23,8 @@ .hash = "N-V-__8AAJiAIgDMVIi8CRb_xko9_qVQ-UiQzd5FTBBr0aPa", }, .fuzzig = .{ - .url = "https://github.com/fjebaker/fuzzig/archive/44c04733c7c0fee3db83672aaaaf4ed03e943156.tar.gz", - .hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX", + .url = "https://github.com/fjebaker/fuzzig/archive/4251fe4230d38e721514394a485db62ee1667ff3.tar.gz", + .hash = "fuzzig-0.1.1-Ji0xivxIAQBD0g8O_NV_0foqoPf3elsg9Sc3pNfdVH4D", }, .vaxis = .{ .url = "https://github.com/neurocyte/libvaxis/archive/2a4137dadbe560b13b712fd3aa8a1c313fdd8c6e.tar.gz", From 54eb30468a995da502287d1d8e55fb4b7b0fb4ca Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 23 Jul 2025 12:00:22 +0200 Subject: [PATCH 04/19] build: update libvaxis --- build.zig.zon | 4 ++-- src/renderer/vaxis/renderer.zig | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 7d9b927..e9b4e2c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -27,8 +27,8 @@ .hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX", }, .vaxis = .{ - .url = "https://github.com/neurocyte/libvaxis/archive/6137cb4c44a7350996f0946a069739e5075d1f23.tar.gz", - .hash = "vaxis-0.1.0-BWNV_HwOCQCw5wTV63hQGSc1QJzsNcytH6sGf1GBc0hP", + .url = "git+https://github.com/neurocyte/libvaxis?ref=main#846ddb8bf483e8a7eb25628d6c34ba7e781155b6", + .hash = "vaxis-0.5.1-BWNV_AsQCQDvfb-li1CZEOBG_YsteinP9qI-PpV47-jf", }, .zeit = .{ .url = "https://github.com/rockorager/zeit/archive/8fd203f85f597f16e0a525c1f1ca1e0bffded809.tar.gz", diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 2ac6f8c..1c01745 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -180,7 +180,7 @@ fn handleSegfaultPosixNoAbort(sig: i32, info: *const std.posix.siginfo_t, ctx_pt pub fn run(self: *Self) Error!void { self.vx.sgr = .legacy; - self.vx.conpty_hacks = true; + self.vx.enable_workarounds = true; panic_cleanup = .{ .allocator = self.allocator, .tty = &self.tty, .vx = &self.vx }; if (!self.no_alternate) self.vx.enterAltScreen(self.tty.anyWriter()) catch return error.TtyWriteError; @@ -639,7 +639,7 @@ const Loop = struct { switch (builtin.os.tag) { .windows => { var parser: vaxis.Parser = .{ - .graphemes = &self.vaxis.unicode.graphemes, + .grapheme_data = &self.vaxis.unicode.width_data.graphemes, }; const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator"); while (!self.should_quit) { @@ -648,7 +648,7 @@ const Loop = struct { }, else => { var parser: vaxis.Parser = .{ - .graphemes = &self.vaxis.unicode.graphemes, + .grapheme_data = &self.vaxis.unicode.width_data.graphemes, }; const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator"); From 0ce522828d825e1dde5c3e5d7fbc471665201a1a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 23 Jul 2025 12:19:10 +0200 Subject: [PATCH 05/19] build: update thespian for new cbor --- build.zig.zon | 4 ++-- src/Project.zig | 2 ++ src/renderer/vaxis/renderer.zig | 2 ++ src/renderer/win32/renderer.zig | 2 ++ src/shell.zig | 2 ++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index e9b4e2c..0a34e23 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,8 +15,8 @@ .hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK", }, .thespian = .{ - .url = "https://github.com/neurocyte/thespian/archive/ccdcbbff09f945eec063ebf889581db3e1312107.tar.gz", - .hash = "thespian-0.0.1-owFOjlgaBgCqc3FCnB4Xyg8-9jyIDWgHSJMGx_nt5Kcc", + .url = "git+https://github.com/neurocyte/thespian#f2980d3a747abdf0d18a01596dd8b953dd3e6243", + .hash = "thespian-0.0.1-owFOjk0aBgC8w9ibeiVdhftyEIaVIHCnubsJWfkktE8v", }, .themes = .{ .url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz", diff --git a/src/Project.zig b/src/Project.zig index f9da66c..8e97119 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -231,6 +231,8 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, }!void { tp.trace(tp.channel.debug, .{"restore_state_v0"}); defer self.sort_files_by_mtime(); diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 1c01745..3141e34 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -58,6 +58,8 @@ pub const Error = error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, } || std.Thread.SpawnError; pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self { diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index a56adb7..a4d8ed8 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -36,6 +36,8 @@ pub const Error = error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, } || std.Thread.SpawnError; pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" }); diff --git a/src/shell.zig b/src/shell.zig index 5869a3d..3e316ab 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -27,6 +27,8 @@ pub const Error = error{ JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, + InvalidMapType, + InvalidUnion, }; pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void; From 73d118dcee5b4bdf34c7eb3be332cb96e168aa62 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 29 Jul 2025 10:02:34 +0200 Subject: [PATCH 06/19] refactor: reduce event matching overhead in filestate widget --- src/tui/status/filestate.zig | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index e9a92e0..9493d07 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -200,24 +200,34 @@ fn render_terminal_title(self: *Self) void { } pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { + if (try m.match(.{ "E", tp.more })) + return self.process_event(m); + if (try m.match(.{ "PRJ", "open" })) { + if (!self.file) + self.show_project(); + } + return false; +} + +fn process_event(self: *Self, m: tp.message) error{Exit}!bool { var file_path: []const u8 = undefined; var file_type: []const u8 = undefined; var file_icon: []const u8 = undefined; var file_dirty: bool = undefined; var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); - if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) + if (try m.match(.{ tp.any, "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) return false; - if (try m.match(.{ "E", "dirty", tp.extract(&file_dirty) })) { + if (try m.match(.{ tp.any, "dirty", tp.extract(&file_dirty) })) { self.file_dirty = file_dirty; - } else if (try m.match(.{ "E", "eol_mode", tp.extract(&eol_mode), tp.extract(&self.utf8_sanitized) })) { + } else if (try m.match(.{ tp.any, "eol_mode", tp.extract(&eol_mode), tp.extract(&self.utf8_sanitized) })) { self.eol_mode = @enumFromInt(eol_mode); - } else if (try m.match(.{ "E", "save", tp.extract(&file_path) })) { + } else if (try m.match(.{ tp.any, "save", tp.extract(&file_path) })) { @memcpy(self.name_buf[0..file_path.len], file_path); self.name = self.name_buf[0..file_path.len]; self.file_exists = true; self.file_dirty = false; self.name = project_manager.abbreviate_home(&self.name_buf, self.name); - } else if (try m.match(.{ "E", "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) { + } else if (try m.match(.{ tp.any, "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) { self.eol_mode = .lf; @memcpy(self.name_buf[0..file_path.len], file_path); self.name = self.name_buf[0..file_path.len]; @@ -229,7 +239,7 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message self.file_dirty = false; self.name = project_manager.abbreviate_home(&self.name_buf, self.name); self.file = true; - } else if (try m.match(.{ "E", "close" })) { + } else if (try m.match(.{ tp.any, "close" })) { self.name = ""; self.lines = 0; self.line = 0; @@ -238,9 +248,6 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message self.file = false; self.eol_mode = .lf; self.show_project(); - } else if (try m.match(.{ "PRJ", "open" })) { - if (!self.file) - self.show_project(); } return false; } From a734a008e1749458c6861a90363ca8da6aa869af Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 29 Jul 2025 10:12:15 +0200 Subject: [PATCH 07/19] fix: refresh git branch status on project switch --- src/tui/status/branch.zig | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index ce368ef..7f6dfc4 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -44,6 +44,7 @@ pub fn create( .on_click = on_click, .on_layout = layout, .on_render = render, + .on_receive = receive, .on_event = event_handler, }); } @@ -60,11 +61,21 @@ pub fn ctx_deinit(self: *Self) void { if (self.behind) |p| self.allocator.free(p); } -fn on_click(_: *Self, _: *Button.State(Self)) void { - git.status(0) catch {}; +fn on_click(self: *Self, _: *Button.State(Self)) void { + self.refresh_git_status(); command.executeName("show_git_status", .{}) catch {}; } +fn refresh_git_status(_: *Self) void { + git.status(0) catch {}; +} + +pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { + if (try m.match(.{ "PRJ", "open" })) + self.refresh_git_status(); + return false; +} + fn receive_git(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool { return if (try match(m.buf, .{ "git", more })) self.process_git(m) From bfa851e8860e1e4380c0f15a5761eabca57aa68e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 29 Jul 2025 10:28:40 +0200 Subject: [PATCH 08/19] feat: add back single key keybinds on home screen (flow mode) --- src/keybind/builtin/flow.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 03f7a9f..97c3cdc 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -249,6 +249,14 @@ "inherit": "project", "on_match_failure": "ignore", "press": [ + ["f", "find_file"], + ["e", "find_file"], + ["n", "create_new_file"], + ["o", "open_file"], + ["r", "open_recent_project"], + ["p", "open_command_palette"], + ["t", "change_theme"], + ["a", "add_task"], ["c", "open_config"], ["g", "open_gui_config"], ["k", "open_keybind_config"], @@ -256,6 +264,9 @@ ["ctrl+f ctrl+f ctrl+f ctrl+f ctrl+f", "home_sheeran"], ["ctrl+shift+r", "restart"], ["f6", "open_config"], + ["v", "open_version_info"], + ["ctrl+q", "quit"], + ["q", "quit"], ["up", "home_menu_up"], ["down", "home_menu_down"], ["enter", "home_menu_activate"] From 6f82b4aaf36176f5adeea6e2dfebabe896ccfdce Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 29 Jul 2025 10:29:06 +0200 Subject: [PATCH 09/19] feat: refresh branch status on file state change events --- src/tui/status/branch.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index 7f6dfc4..9e4ff44 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -71,11 +71,22 @@ fn refresh_git_status(_: *Self) void { } pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { + if (try m.match(.{ "E", tp.more })) + return self.process_event(m); if (try m.match(.{ "PRJ", "open" })) self.refresh_git_status(); return false; } +fn process_event(self: *Self, m: tp.message) error{Exit}!bool { + if (try m.match(.{ tp.any, "dirty", tp.more }) or + try m.match(.{ tp.any, "save", tp.more }) or + try m.match(.{ tp.any, "open", tp.more }) or + try m.match(.{ tp.any, "close" })) + self.refresh_git_status(); + return false; +} + fn receive_git(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool { return if (try match(m.buf, .{ "git", more })) self.process_git(m) From 488efd4605144e5fb9ef526df37ee3544160ca81 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 29 Jul 2025 10:52:19 +0200 Subject: [PATCH 10/19] fix: hint generally usable keybinds on home screen (flow mode) --- src/keybind/builtin/flow.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 97c3cdc..339972b 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -249,11 +249,16 @@ "inherit": "project", "on_match_failure": "ignore", "press": [ + ["ctrl+e", "find_file"], ["f", "find_file"], ["e", "find_file"], + ["ctrl+shift+n", "create_new_file"], ["n", "create_new_file"], + ["ctrl+o", "open_file"], ["o", "open_file"], + ["ctrl+r", "open_recent_project"], ["r", "open_recent_project"], + ["ctrl+shift+p", "open_command_palette"], ["p", "open_command_palette"], ["t", "change_theme"], ["a", "add_task"], From cf6c9455c7a27b4f2a67eae99651d8ff9a5302ef Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 29 Jul 2025 17:24:43 +0200 Subject: [PATCH 11/19] feat: update git status on focus_in events --- src/tui/status/branch.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index 9e4ff44..3ad6457 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -90,10 +90,17 @@ fn process_event(self: *Self, m: tp.message) error{Exit}!bool { fn receive_git(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool { return if (try match(m.buf, .{ "git", more })) self.process_git(m) + else if (try match(m.buf, .{"focus_in"})) + self.process_focus_in() else false; } +fn process_focus_in(self: *Self) MessageFilter.Error!bool { + self.refresh_git_status(); + return false; +} + fn process_git(self: *Self, m: tp.message) MessageFilter.Error!bool { var value: []const u8 = undefined; if (try match(m.buf, .{ any, any, "workspace_path", null_ })) { From 910c69183d76469655fde210b6dbac1f5ffbbb36 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 18:25:48 +0200 Subject: [PATCH 12/19] feat: add indent_mode config option --- src/config.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config.zig b/src/config.zig index 9efce2c..7436e84 100644 --- a/src/config.zig +++ b/src/config.zig @@ -26,6 +26,7 @@ limit_auto_save_file_types: ?[]const []const u8 = null, // null means *all* indent_size: usize = 4, tab_width: usize = 8, +indent_mode: IndentMode = .auto, top_bar: []const u8 = "tabs", bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer", @@ -48,3 +49,9 @@ pub const LineNumberMode = enum { relative, absolute, }; + +pub const IndentMode = enum { + auto, + spaces, + tabs, +}; From 196f5167242ba751a74ad0c6e1635504ab5a8ff5 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 18:28:04 +0200 Subject: [PATCH 13/19] feat: save and restore indent_mode to editor state --- src/tui/editor.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index dd4c0b4..9115bda 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -23,6 +23,7 @@ const editor_gutter = @import("editor_gutter.zig"); const Widget = @import("Widget.zig"); const WidgetList = @import("WidgetList.zig"); const tui = @import("tui.zig"); +const IndentMode = @import("config").IndentMode; pub const Cursor = Buffer.Cursor; pub const View = Buffer.View; @@ -318,6 +319,7 @@ pub const Editor = struct { render_whitespace: WhitespaceMode, indent_size: usize, tab_width: usize, + indent_mode: IndentMode, last: struct { root: ?Buffer.Root = null, @@ -370,12 +372,15 @@ pub const Editor = struct { const Result = command.Result; pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) !void { - try cbor.writeArrayHeader(writer, 8); + try cbor.writeArrayHeader(writer, 11); try cbor.writeValue(writer, self.file_path orelse ""); try cbor.writeValue(writer, self.clipboard orelse ""); try cbor.writeValue(writer, self.last_find_query orelse ""); try cbor.writeValue(writer, self.enable_format_on_save); try cbor.writeValue(writer, self.enable_auto_save); + try cbor.writeValue(writer, self.indent_size); + try cbor.writeValue(writer, self.tab_width); + try cbor.writeValue(writer, self.indent_mode); if (self.find_history) |history| { try cbor.writeArrayHeader(writer, history.items.len); for (history.items) |item| @@ -411,6 +416,9 @@ pub const Editor = struct { tp.extract(&last_find_query), tp.extract(&self.enable_format_on_save), tp.extract(&self.enable_auto_save), + tp.extract(&self.indent_size), + tp.extract(&self.tab_width), + tp.extract(&self.indent_mode), tp.extract_cbor(&find_history), tp.extract_cbor(&view_cbor), tp.extract_cbor(&cursels_cbor), @@ -454,11 +462,13 @@ pub const Editor = struct { const frame_rate = tp.env.get().num("frame-rate"); const indent_size = tui.config().indent_size; const tab_width = tui.config().tab_width; + const indent_mode = tui.config().indent_mode; self.* = Self{ .allocator = allocator, .plane = n, .indent_size = indent_size, .tab_width = tab_width, + .indent_mode = indent_mode, .metrics = self.plane.metrics(tab_width), .logger = logger, .file_path = null, From 9774b513d4d65b025da9d199a07a19f828ba7161 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 18:50:42 +0200 Subject: [PATCH 14/19] fix: update buffer file type in set_type This fixes the buffer file type getting lost when switching buffers if the file type was set with set_type. --- src/tui/editor.zig | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 9115bda..5ae958e 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -584,6 +584,7 @@ pub const Editor = struct { if (self.buffer) |_| try self.close(); self.buffer = new_buf; const file_type = file_type_ orelse new_buf.file_type_name; + const buffer_meta = if (self.buffer) |buffer| buffer.get_meta() else null; if (new_buf.root.lines() > root_mod.max_syntax_lines) { self.logger.print("large file threshold {d} lines < file size {d} lines", .{ @@ -623,7 +624,7 @@ pub const Editor = struct { null; }; - if (self.file_type) |ft| { + if (buffer_meta == null) if (self.file_type) |ft| { const frame_ = tracy.initZone(@src(), .{ .name = "did_open" }); defer frame_.deinit(); project_manager.did_open( @@ -634,7 +635,7 @@ pub const Editor = struct { new_buf.is_ephemeral(), ) catch |e| self.logger.print("project_manager.did_open failed: {any}", .{e}); - } + }; break :syntax syn; }; self.syntax_no_render = tp.env.get().is("no-syntax"); @@ -649,11 +650,11 @@ pub const Editor = struct { buffer.file_type_color = ftc; } - if (self.buffer) |buffer| if (buffer.get_meta()) |meta| { + if (buffer_meta) |meta| { const frame_ = tracy.initZone(@src(), .{ .name = "extract_state" }); defer frame_.deinit(); try self.extract_state(meta, .none); - }; + } try self.send_editor_open(file_path, new_buf.file_exists, ftn, fti, ftc); } @@ -5916,9 +5917,14 @@ pub const Editor = struct { self.syntax_no_render = tp.env.get().is("no-syntax"); self.syntax_report_timing = tp.env.get().is("syntax-report-timing"); - const ftn = if (self.file_type) |ft| ft.name else "text"; - const fti = if (self.file_type) |ft| ft.icon orelse "🖹" else "🖹"; - const ftc = if (self.file_type) |ft| ft.color orelse 0x000000 else 0x000000; + const ftn = if (self.file_type) |ft| ft.name else file_type_config.default.name; + const fti = if (self.file_type) |ft| ft.icon orelse file_type_config.default.icon else file_type_config.default.icon; + const ftc = if (self.file_type) |ft| ft.color orelse file_type_config.default.color else file_type_config.default.color; + if (self.buffer) |buffer| { + buffer.file_type_name = ftn; + buffer.file_type_icon = fti; + buffer.file_type_color = ftc; + } const file_exists = if (self.buffer) |b| b.file_exists else false; try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc); self.logger.print("file type {s}", .{file_type}); From a74c0ecf4619291e1d02ba5090e3ef0b7fbbf69c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 19:16:27 +0200 Subject: [PATCH 15/19] feat: add indent_mode detection (auto mode) --- src/tui/editor.zig | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5ae958e..f9e84d9 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -594,15 +594,19 @@ pub const Editor = struct { self.logger.print("syntax highlighting disabled", .{}); self.syntax_no_render = true; } + + var content = std.ArrayListUnmanaged(u8).empty; + defer content.deinit(std.heap.c_allocator); + { + const frame_ = tracy.initZone(@src(), .{ .name = "store" }); + defer frame_.deinit(); + try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode); + } + if (self.indent_mode == .auto) + self.detect_indent_mode(content.items); + self.syntax = syntax: { const lang_override = file_type orelse tp.env.get().str("language"); - var content = std.ArrayListUnmanaged(u8).empty; - defer content.deinit(std.heap.c_allocator); - { - const frame_ = tracy.initZone(@src(), .{ .name = "store" }); - defer frame_.deinit(); - try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode); - } self.file_type = blk: { const frame_ = tracy.initZone(@src(), .{ .name = "guess" }); @@ -674,6 +678,19 @@ pub const Editor = struct { self.enable_auto_save = true; } + fn detect_indent_mode(self: *Self, content: []const u8) void { + var it = std.mem.splitScalar(u8, content, '\n'); + while (it.next()) |line| { + if (line.len == 0) continue; + if (line[0] == '\t') { + self.indent_mode = .tabs; + return; + } + } + self.indent_mode = .spaces; + return; + } + fn close(self: *Self) !void { var meta = std.ArrayListUnmanaged(u8).empty; defer meta.deinit(self.allocator); From 3abfd6555e0fb2ca315b42f55ad77a7fabf4eaad Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 19:17:52 +0200 Subject: [PATCH 16/19] feat: make indent_cursor follow indent_mode and insert tabs --- src/tui/editor.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index f9e84d9..5424ab7 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -3679,9 +3679,16 @@ pub const Editor = struct { const space = " "; var cursel: CurSel = .{}; cursel.cursor = cursor; - const cols = self.indent_size - find_first_non_ws(root, cursel.cursor.row, self.metrics) % self.indent_size; try move_cursor_begin(root, &cursel.cursor, self.metrics); - return self.insert(root, &cursel, space[0..cols], allocator) catch return error.Stop; + switch (self.indent_mode) { + .spaces, .auto => { + const cols = self.indent_size - find_first_non_ws(root, cursel.cursor.row, self.metrics) % self.indent_size; + return self.insert(root, &cursel, space[0..cols], allocator) catch return error.Stop; + }, + .tabs => { + return self.insert(root, &cursel, "\t", allocator) catch return error.Stop; + }, + } } fn indent_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root { From 4100585b03627ab77e211a0bbf9f6782a2fc4814 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 19:37:28 +0200 Subject: [PATCH 17/19] feat: make smart_insert_line and friends follow indent_mode --- src/tui/editor.zig | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5424ab7..8146541 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -4403,16 +4403,39 @@ pub const Editor = struct { } pub const insert_line_meta: Meta = .{ .description = "Insert line" }; + fn generate_leading_ws(self: *Self, writer: anytype, leading_ws: usize) !void { + return switch (self.indent_mode) { + .spaces, .auto => generate_leading_spaces(writer, leading_ws), + .tabs => generate_leading_tabs(writer, leading_ws, self.tab_width), + }; + } + + fn generate_leading_spaces(writer: anytype, leading_ws: usize) !void { + var width = leading_ws; + while (width > 0) : (width -= 1) + try writer.writeByte(' '); + } + + fn generate_leading_tabs(writer: anytype, leading_ws: usize, tab_width: usize) !void { + var width = leading_ws; + while (width > 0) if (width >= tab_width) { + width -= tab_width; + try writer.writeByte('\t'); + } else { + width -= 1; + try writer.writeByte(' '); + }; + } + fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator) !Buffer.Root { - var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); var sfa = std.heap.stackFallback(512, self.allocator); const allocator = sfa.get(); var stream = std.ArrayListUnmanaged(u8).empty; defer stream.deinit(allocator); var writer = stream.writer(allocator); _ = try writer.write("\n"); - while (leading_ws > 0) : (leading_ws -= 1) - _ = try writer.write(" "); + try self.generate_leading_ws(&writer, leading_ws); return self.insert(root, cursel, stream.items, b_allocator); } @@ -4466,7 +4489,7 @@ pub const Editor = struct { const b = try self.buf_for_update(); var root = b.root; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); try move_cursor_begin(root, &cursel.cursor, self.metrics); root = try self.insert(root, cursel, "\n", b.allocator); try move_cursor_left(root, &cursel.cursor, self.metrics); @@ -4475,8 +4498,7 @@ pub const Editor = struct { var stream = std.ArrayListUnmanaged(u8).empty; defer stream.deinit(allocator); var writer = stream.writer(self.allocator); - while (leading_ws > 0) : (leading_ws -= 1) - _ = try writer.write(" "); + try self.generate_leading_ws(&writer, leading_ws); if (stream.items.len > 0) root = try self.insert(root, cursel, stream.items, b.allocator); }; @@ -4501,7 +4523,7 @@ pub const Editor = struct { const b = try self.buf_for_update(); var root = b.root; for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); + const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col); try move_cursor_end(root, &cursel.cursor, self.metrics); var sfa = std.heap.stackFallback(512, self.allocator); const allocator = sfa.get(); @@ -4509,8 +4531,7 @@ pub const Editor = struct { defer stream.deinit(allocator); var writer = stream.writer(allocator); _ = try writer.write("\n"); - while (leading_ws > 0) : (leading_ws -= 1) - _ = try writer.write(" "); + try self.generate_leading_ws(&writer, leading_ws); if (stream.items.len > 0) root = try self.insert(root, cursel, stream.items, b.allocator); }; From ed1fe30e7422ee99baa379cc3813fd798a0777c8 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 20:03:12 +0200 Subject: [PATCH 18/19] feat: make indent_size always equal to tab_width in indent_mode tabs --- src/tui/editor.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 8146541..0aaae44 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -460,9 +460,9 @@ pub const Editor = struct { fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void { const logger = log.logger("editor"); const frame_rate = tp.env.get().num("frame-rate"); - const indent_size = tui.config().indent_size; const tab_width = tui.config().tab_width; const indent_mode = tui.config().indent_mode; + const indent_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size; self.* = Self{ .allocator = allocator, .plane = n, @@ -683,10 +683,12 @@ pub const Editor = struct { while (it.next()) |line| { if (line.len == 0) continue; if (line[0] == '\t') { + self.indent_size = self.tab_width; self.indent_mode = .tabs; return; } } + self.indent_size = tui.config().indent_size; self.indent_mode = .spaces; return; } From 666d30df3bf61e0ef9e5ecf0d10681cfceacd1ff Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 30 Jul 2025 20:04:26 +0200 Subject: [PATCH 19/19] fix: make unindent_cursor work correctly in indent_mode tabs --- src/tui/editor.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 0aaae44..6b3938e 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -3725,16 +3725,16 @@ pub const Editor = struct { const off = first % self.indent_size; const cols = if (off == 0) self.indent_size else off; const sel = cursel.enable_selection(root, self.metrics) catch return error.Stop; - sel.begin.move_begin(); - try sel.end.move_to(root, sel.end.row, cols, self.metrics); + try sel.begin.move_to(root, sel.begin.row, first, self.metrics); + try sel.end.move_to(root, sel.end.row, first - cols, self.metrics); var saved = false; - if (cursor_protect) |cp| if (cp.row == cursor.row and cp.col < cols) { - cp.col = cols + 1; + if (cursor_protect) |cp| if (cp.row == cursor.row and cp.col < first and cp.col >= first - cols) { + cp.col = first + 1; saved = true; }; newroot = try self.delete_selection(root, &cursel, allocator); if (cursor_protect) |cp| if (saved) { - try cp.move_to(root, cp.row, 0, self.metrics); + try cp.move_to(root, cp.row, first - cols, self.metrics); cp.clamp_to_buffer(newroot, self.metrics); }; return newroot;