From 6c789b3209be52fd1664716f2085c7aee01708d7 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 25 Nov 2025 16:48:20 +0100 Subject: [PATCH 1/4] feat: change default theme to something more modern and pretty --- src/config.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.zig b/src/config.zig index 72f4988..d95970a 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,8 +1,8 @@ const builtin = @import("builtin"); frame_rate: usize = 60, -theme: []const u8 = "default", -light_theme: []const u8 = "default-light", +theme: []const u8 = "ayu-mirage-bordered", +light_theme: []const u8 = "ayu-light", input_mode: []const u8 = "flow", gutter_line_numbers_mode: ?LineNumberMode = null, gutter_line_numbers_style: DigitStyle = .ascii, From 57c5066451946cf188201b2496e7a3a7c50b3acd Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 25 Nov 2025 17:12:57 +0100 Subject: [PATCH 2/4] fix: add support for non-authorative file URIs from LSPs (part 2) --- src/Project.zig | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Project.zig b/src/Project.zig index 967ec9e..ed3b05a 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -956,16 +956,20 @@ fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row: }, handler) catch return error.LspFailed; } +fn file_uri_to_path(uri: []const u8, file_path_buf: []u8) error{InvalidTargetURI}![]const u8 { + return std.Uri.percentDecodeBackwards(file_path_buf, if (std.mem.eql(u8, uri[0..7], "file://")) + uri[7..] + else if (std.mem.eql(u8, uri[0..5], "file:")) + uri[5..] + else + return error.InvalidTargetURI); +} + fn navigate_to_location_link(from: tp.pid_ref, location_link: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { const location: LocationLink = try read_locationlink(location_link); if (location.targetUri == null or location.targetRange == null) return error.InvalidMessageField; var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; - var file_path = std.Uri.percentDecodeBackwards(&file_path_buf, if (std.mem.eql(u8, location.targetUri.?[0..7], "file://")) - location.targetUri.?[7..] - else if (std.mem.eql(u8, location.targetUri.?[0..5], "file:")) - location.targetUri.?[5..] - else - return error.InvalidTargetURI); + var file_path = try file_uri_to_path(location.targetUri.?, &file_path_buf); if (builtin.os.tag == .windows) { if (file_path[0] == '/') file_path = file_path[1..]; for (file_path, 0..) |c, i| if (c == '/') { @@ -1087,9 +1091,8 @@ fn send_reference(tag: []const u8, to: tp.pid_ref, location_: []const u8, name: const allocator = std.heap.c_allocator; const location: LocationLink = try read_locationlink(location_); if (location.targetUri == null or location.targetRange == null) return error.InvalidMessageField; - if (!std.mem.eql(u8, location.targetUri.?[0..7], "file://")) return error.InvalidTargetURI; var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; - var file_path = std.Uri.percentDecodeBackwards(&file_path_buf, location.targetUri.?[7..]); + var file_path = try file_uri_to_path(location.targetUri.?, &file_path_buf); if (builtin.os.tag == .windows) { if (file_path[0] == '/') file_path = file_path[1..]; for (file_path, 0..) |c, i| if (c == '/') { @@ -1332,9 +1335,8 @@ fn send_symbol_information(to: tp.pid_ref, file_path: []const u8, item: []const var fp = file_path; if (location) |location_| { if (location_.targetUri == null or location_.targetRange == null) return error.InvalidMessageField; - if (!std.mem.eql(u8, location_.targetUri.?[0..7], "file://")) return error.InvalidTargetURI; var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; - var file_path_ = std.Uri.percentDecodeBackwards(&file_path_buf, location_.targetUri.?[7..]); + var file_path_ = try file_uri_to_path(location_.targetUri.?, &file_path_buf); if (builtin.os.tag == .windows) { if (file_path_[0] == '/') file_path_ = file_path_[1..]; for (file_path_, 0..) |c, i| if (c == '/') { @@ -1520,9 +1522,8 @@ pub fn rename_symbol(self: *Self, from: tp.pid_ref, file_path: []const u8, row: try cbor.writeValue(w, "rename_symbol_item"); try cbor.writeArrayHeader(w, renames.items.len); for (renames.items) |rename| { - if (!std.mem.eql(u8, rename.uri[0..7], "file://")) return error.InvalidTargetURI; var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; - var file_path_ = std.Uri.percentDecodeBackwards(&file_path_buf, rename.uri[7..]); + var file_path_ = try file_uri_to_path(rename.uri, &file_path_buf); if (builtin.os.tag == .windows) { if (file_path_[0] == '/') file_path_ = file_path_[1..]; for (file_path_, 0..) |c, i| if (c == '/') { @@ -1789,9 +1790,8 @@ pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) ( } if (uri == null) return error.InvalidMessageField; - if (!std.mem.eql(u8, uri.?[0..7], "file://")) return error.InvalidTargetURI; var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; - const file_path = std.Uri.percentDecodeBackwards(&file_path_buf, uri.?[7..]); + const file_path = try file_uri_to_path(uri.?, &file_path_buf); try self.send_clear_diagnostics(to, file_path); From 2ff0521040038dcf006f7346e8b34186d1ef988e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sun, 23 Nov 2025 22:15:13 +0100 Subject: [PATCH 3/4] refactor: move all zg LetterCasing usage to Buffer.unicode --- src/buffer/unicode.zig | 22 +++++++++++++++++++++- src/tui/editor.zig | 10 +++------- src/tui/mode/mini/file_browser.zig | 4 ++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/buffer/unicode.zig b/src/buffer/unicode.zig index 0571e14..4f3562f 100644 --- a/src/buffer/unicode.zig +++ b/src/buffer/unicode.zig @@ -90,12 +90,32 @@ pub const LetterCasing = @import("LetterCasing"); var letter_casing: ?LetterCasing = null; var letter_casing_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator); -pub fn get_letter_casing() *LetterCasing { +fn get_letter_casing() *LetterCasing { if (letter_casing) |*cd| return cd; letter_casing = LetterCasing.init(letter_casing_arena.allocator()) catch @panic("LetterCasing.init"); return &letter_casing.?; } +pub fn to_upper(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { + return get_letter_casing().toUpperStr(allocator, text); +} + +pub fn to_lower(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { + return get_letter_casing().toLowerStr(allocator, text); +} + +pub fn case_fold(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { + return get_letter_casing().toLowerStr(allocator, text); +} + +pub fn switch_case(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { + const letter_casing_ = get_letter_casing(); + return if (letter_casing_.isLowerStr(text)) + letter_casing_.toUpperStr(allocator, text) + else + letter_casing_.toLowerStr(allocator, text); +} + const spinner = [_][]const u8{ "⠋", "⠙", diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 1777b9a..6ecdc91 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -6182,7 +6182,7 @@ pub const Editor = struct { const sfa_allocator = sfa.get(); const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; defer sfa_allocator.free(cut_text); - const ucased = Buffer.unicode.get_letter_casing().toUpperStr(sfa_allocator, cut_text) catch return error.Stop; + const ucased = Buffer.unicode.to_upper(sfa_allocator, cut_text) catch return error.Stop; defer sfa_allocator.free(ucased); root = try self.delete_selection(root, cursel, allocator); root = self.insert(root, cursel, ucased, allocator) catch return error.Stop; @@ -6211,7 +6211,7 @@ pub const Editor = struct { const sfa_allocator = sfa.get(); const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; defer sfa_allocator.free(cut_text); - const ucased = Buffer.unicode.get_letter_casing().toLowerStr(sfa_allocator, cut_text) catch return error.Stop; + const ucased = Buffer.unicode.to_lower(sfa_allocator, cut_text) catch return error.Stop; defer sfa_allocator.free(ucased); root = try self.delete_selection(root, cursel, buffer_allocator); root = self.insert(root, cursel, ucased, buffer_allocator) catch return error.Stop; @@ -6241,11 +6241,7 @@ pub const Editor = struct { root.write_range(sel.*, &range.writer, null, self.metrics) catch return error.Stop; const bytes = range.written(); - const letter_casing = Buffer.unicode.get_letter_casing(); - const flipped = if (letter_casing.isLowerStr(bytes)) - letter_casing.toUpperStr(self.allocator, bytes) catch return error.Stop - else - letter_casing.toLowerStr(self.allocator, bytes) catch return error.Stop; + const flipped = Buffer.unicode.switch_case(self.allocator, bytes) catch return error.Stop; defer self.allocator.free(flipped); root = try self.delete_selection(root, cursel, allocator); diff --git a/src/tui/mode/mini/file_browser.zig b/src/tui/mode/mini/file_browser.zig index d5a17f1..4b171d0 100644 --- a/src/tui/mode/mini/file_browser.zig +++ b/src/tui/mode/mini/file_browser.zig @@ -248,9 +248,9 @@ pub fn Create(options: type) type { } fn prefix_compare_icase(allocator: std.mem.Allocator, prefix: []const u8, str: []const u8) error{OutOfMemory}!bool { - const icase_prefix = Buffer.unicode.get_letter_casing().toLowerStr(allocator, prefix) catch try allocator.dupe(u8, prefix); + const icase_prefix = Buffer.unicode.case_fold(allocator, prefix) catch try allocator.dupe(u8, prefix); defer allocator.free(icase_prefix); - const icase_str = Buffer.unicode.get_letter_casing().toLowerStr(allocator, str) catch try allocator.dupe(u8, str); + const icase_str = Buffer.unicode.case_fold(allocator, str) catch try allocator.dupe(u8, str); defer allocator.free(icase_str); if (icase_str.len < icase_prefix.len) return false; return std.mem.eql(u8, icase_prefix, icase_str[0..icase_prefix.len]); From e35a0555f16ef77a8f34368ded6868ec46661358 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 25 Nov 2025 21:14:59 +0100 Subject: [PATCH 4/4] feat: port to_upper and to_lower to uucode --- build.zig | 7 +-- build.zig.zon | 8 +-- src/buffer/unicode.zig | 139 ++++++++++++++++++++++++++--------------- 3 files changed, 90 insertions(+), 64 deletions(-) diff --git a/build.zig b/build.zig index 1ea58e4..37af278 100644 --- a/build.zig +++ b/build.zig @@ -324,11 +324,6 @@ pub fn build_exe( .root_source_file = b.path("src/tracy_noop.zig"), }); - const zg_dep = b.dependency("zg", .{ - .target = target, - .optimize = optimize, - }); - const zeit_dep = b.dependency("zeit", .{ .target = target, .optimize = optimize, @@ -424,7 +419,7 @@ pub fn build_exe( .imports = &.{ .{ .name = "cbor", .module = cbor_mod }, .{ .name = "thespian", .module = thespian_mod }, - .{ .name = "LetterCasing", .module = zg_dep.module("LetterCasing") }, + .{ .name = "vaxis", .module = vaxis_mod }, .{ .name = "file_type_config", .module = file_type_config_mod }, }, }); diff --git a/build.zig.zon b/build.zig.zon index d5fbda4..1ad0c3c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -30,8 +30,8 @@ .hash = "fuzzig-0.1.1-Ji0xivxIAQBD0g8O_NV_0foqoPf3elsg9Sc3pNfdVH4D", }, .vaxis = .{ - .url = "git+https://github.com/neurocyte/libvaxis?ref=main#78aff0865f0f6206ece9263dcd9b051352da1e82", - .hash = "vaxis-0.5.1-BWNV_BkyCQAo2UBeY_gbzP09d4pHDaqtg44snF5k3mh2", + .url = "git+https://github.com/neurocyte/libvaxis?ref=main#164dfadbad9a5df529ade8e3f0e5c52939d27e81", + .hash = "vaxis-0.5.1-BWNV_M4yCQBLJYI9ooJL-8dstYnId58coiBeHhF4m9wz", }, .zeit = .{ .url = "git+https://github.com/rockorager/zeit?ref=zig-0.15#ed2ca60db118414bda2b12df2039e33bad3b0b88", @@ -42,10 +42,6 @@ .hash = "zigwin32-25.0.28-preview-AAAAAICM5AMResOGQnQ85mfe60TTOQeMtt7GRATUOKoP", .lazy = true, }, - .zg = .{ - .url = "git+https://codeberg.org/neurocyte/zg?ref=master#cdcab8b9ea3458efd710008055d993c5dbdb1af7", - .hash = "zg-0.15.2-oGqU3AtAtAI7gs7zPvzg2_TlVIqi9wCNEw7DLvD5OvDN", - }, }, .paths = .{ "include", diff --git a/src/buffer/unicode.zig b/src/buffer/unicode.zig index 4f3562f..c16c1b3 100644 --- a/src/buffer/unicode.zig +++ b/src/buffer/unicode.zig @@ -64,58 +64,6 @@ pub const open_close_pairs = [_]struct { []const u8, []const u8 }{ .{ "¡", "!" }, }; -fn raw_byte_to_utf8(cp: u8, buf: []u8) ![]const u8 { - var utf16le: [1]u16 = undefined; - const utf16le_as_bytes = std.mem.sliceAsBytes(utf16le[0..]); - std.mem.writeInt(u16, utf16le_as_bytes[0..2], cp, .little); - return buf[0..try std.unicode.utf16LeToUtf8(buf, &utf16le)]; -} - -const std = @import("std"); - -pub fn utf8_sanitize(allocator: std.mem.Allocator, input: []const u8) error{ - OutOfMemory, - DanglingSurrogateHalf, - ExpectedSecondSurrogateHalf, - UnexpectedSecondSurrogateHalf, -}![]u8 { - var output: std.ArrayListUnmanaged(u8) = .{}; - const writer = output.writer(allocator); - var buf: [4]u8 = undefined; - for (input) |byte| try writer.writeAll(try raw_byte_to_utf8(byte, &buf)); - return output.toOwnedSlice(allocator); -} - -pub const LetterCasing = @import("LetterCasing"); -var letter_casing: ?LetterCasing = null; -var letter_casing_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator); - -fn get_letter_casing() *LetterCasing { - if (letter_casing) |*cd| return cd; - letter_casing = LetterCasing.init(letter_casing_arena.allocator()) catch @panic("LetterCasing.init"); - return &letter_casing.?; -} - -pub fn to_upper(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { - return get_letter_casing().toUpperStr(allocator, text); -} - -pub fn to_lower(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { - return get_letter_casing().toLowerStr(allocator, text); -} - -pub fn case_fold(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { - return get_letter_casing().toLowerStr(allocator, text); -} - -pub fn switch_case(allocator: std.mem.Allocator, text: []const u8) error{ OutOfMemory, Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }![]u8 { - const letter_casing_ = get_letter_casing(); - return if (letter_casing_.isLowerStr(text)) - letter_casing_.toUpperStr(allocator, text) - else - letter_casing_.toLowerStr(allocator, text); -} - const spinner = [_][]const u8{ "⠋", "⠙", @@ -136,3 +84,90 @@ const spinner_short = [_][]const u8{ "⠦", "⠇", }; + +fn raw_byte_to_utf8(cp: u8, buf: []u8) ![]const u8 { + var utf16le: [1]u16 = undefined; + const utf16le_as_bytes = std.mem.sliceAsBytes(utf16le[0..]); + std.mem.writeInt(u16, utf16le_as_bytes[0..2], cp, .little); + return buf[0..try std.unicode.utf16LeToUtf8(buf, &utf16le)]; +} + +pub fn utf8_sanitize(allocator: std.mem.Allocator, input: []const u8) error{ + OutOfMemory, + DanglingSurrogateHalf, + ExpectedSecondSurrogateHalf, + UnexpectedSecondSurrogateHalf, +}![]u8 { + var output: std.ArrayListUnmanaged(u8) = .{}; + const writer = output.writer(allocator); + var buf: [4]u8 = undefined; + for (input) |byte| try writer.writeAll(try raw_byte_to_utf8(byte, &buf)); + return output.toOwnedSlice(allocator); +} + +pub const TransformError = error{ + InvalidUtf8, + OutOfMemory, + Utf8CannotEncodeSurrogateHalf, + CodepointTooLarge, + WriteFailed, +}; + +fn utf8_transform(comptime field: uucode.FieldEnum, allocator: std.mem.Allocator, text: []const u8) TransformError![]u8 { + var result: std.Io.Writer.Allocating = .init(allocator); + defer result.deinit(); + const view: std.unicode.Utf8View = try .init(text); + var it = view.iterator(); + while (it.nextCodepoint()) |cp| { + const cp_ = switch (field) { + .simple_uppercase_mapping, .simple_lowercase_mapping => uucode.get(field, cp) orelse cp, + .case_folding_simple => uucode.get(field, cp), + else => @compileError(@tagName(field) ++ " is not a unicode transformation"), + }; + var utf8_buf: [6]u8 = undefined; + const size = try std.unicode.utf8Encode(cp_, &utf8_buf); + try result.writer.writeAll(utf8_buf[0..size]); + } + return result.toOwnedSlice(); +} + +fn utf8_predicate(comptime field: uucode.FieldEnum, text: []const u8) TransformError!bool { + const view: std.unicode.Utf8View = try .init(text); + var it = view.iterator(); + while (it.nextCodepoint()) |cp| { + const result = switch (field) { + .is_lowercase => uucode.get(field, cp), + else => @compileError(@tagName(field) ++ " is not a unicode predicate"), + }; + if (!result) return false; + } + return true; +} + +pub fn to_upper(allocator: std.mem.Allocator, text: []const u8) error{ + InvalidUtf8, + OutOfMemory, + Utf8CannotEncodeSurrogateHalf, + CodepointTooLarge, + WriteFailed, +}![]u8 { + return utf8_transform(.simple_uppercase_mapping, allocator, text); +} + +pub fn to_lower(allocator: std.mem.Allocator, text: []const u8) TransformError![]u8 { + return utf8_transform(.simple_lowercase_mapping, allocator, text); +} + +pub fn case_fold(allocator: std.mem.Allocator, text: []const u8) TransformError![]u8 { + return utf8_transform(.case_folding_simple, allocator, text); +} + +pub fn switch_case(allocator: std.mem.Allocator, text: []const u8) TransformError![]u8 { + return if (try utf8_predicate(.is_lowercase, text)) + to_upper(allocator, text) + else + to_lower(allocator, text); +} + +const std = @import("std"); +const uucode = @import("vaxis").uucode;