From b2cb003d8235bb4e00f58b9ec93eb8432c0f60f7 Mon Sep 17 00:00:00 2001 From: Danylo Kondratiev Date: Thu, 22 Jan 2026 23:33:09 +0200 Subject: [PATCH] feat: add toggle_case --- src/buffer/unicode.zig | 18 ++++++++++++++++++ src/tui/editor.zig | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/buffer/unicode.zig b/src/buffer/unicode.zig index eca8f5e8..fd13e91d 100644 --- a/src/buffer/unicode.zig +++ b/src/buffer/unicode.zig @@ -201,6 +201,24 @@ pub fn switch_case(allocator: std.mem.Allocator, text: []const u8) TransformErro to_upper(allocator, text); } +pub fn toggle_case(allocator: std.mem.Allocator, text: []const u8) TransformError![]u8 { + var result: std.Io.Writer.Allocating = .init(allocator); + defer result.deinit(); + const writer = &result.writer; + const view: Utf8View = .initUnchecked(text); + var it = view.iterator(); + while (it.nextCodepoint()) |cp| { + const cp_ = if (uucode.get(.is_lowercase, cp)) + uucode.get(.simple_uppercase_mapping, cp) orelse cp + else + uucode.get(.simple_lowercase_mapping, cp) orelse cp; + var utf8_buf: [6]u8 = undefined; + const size = try utf8Encode(cp_, &utf8_buf); + try writer.writeAll(utf8_buf[0..size]); + } + return result.toOwnedSlice(); +} + pub fn is_lowercase(text: []const u8) bool { return utf8_predicate_all(.is_lowercase, text); } diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 620f2390..a4456dad 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -7347,6 +7347,30 @@ pub const Editor = struct { } pub const switch_case_meta: Meta = .{ .description = "Switch the case of selection or character at cursor" }; + fn toggle_case_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root { + var root = root_; + const saved = cursel.*; + const sel = try self.get_selection_or_select_word(root, cursel); + var sfa = std.heap.stackFallback(4096, self.allocator); + 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 transformed = Buffer.unicode.toggle_case(sfa_allocator, cut_text) catch return error.Stop; + defer sfa_allocator.free(transformed); + root = try self.delete_selection(root, cursel, allocator); + root = self.insert(root, cursel, transformed, allocator) catch return error.Stop; + cursel.* = saved; + return root; + } + + pub fn toggle_case(self: *Self, _: Context) Result { + const b = try self.buf_for_update(); + const root = try self.with_cursels_mut_once(b.root, toggle_case_cursel, b.allocator); + try self.update_buf(root); + self.clamp(); + } + pub const toggle_case_meta: Meta = .{ .description = "Toggle the case of each character in selection or character at cursor" }; + pub fn forced_mark_clean(self: *Self, _: Context) Result { if (self.buffer) |b| { b.mark_clean();