diff --git a/build.zig b/build.zig index eca093d..0ac4e8b 100644 --- a/build.zig +++ b/build.zig @@ -210,6 +210,7 @@ pub fn build(b: *std.Build) void { .{ .name = "diff", .module = diff_mod }, .{ .name = "help.md", .module = help_mod }, .{ .name = "CaseData", .module = zg_dep.module("CaseData") }, + .{ .name = "code_point", .module = zg_dep.module("code_point") }, .{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") }, .{ .name = "zeit", .module = zeit_mod }, }, diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 9f3b2fc..fc185b1 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -10,6 +10,7 @@ const text_manip = @import("text_manip"); const syntax = @import("syntax"); const project_manager = @import("project_manager"); const CaseData = @import("CaseData"); +const code_point = @import("code_point"); const root_mod = @import("root"); const Plane = @import("renderer").Plane; @@ -3924,6 +3925,49 @@ pub const Editor = struct { } pub const to_lower_meta = .{ .description = "Convert selection or word to lower case" }; + fn switch_case_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root { + var root = root_; + const saved = cursel.*; + const sel = if (cursel.selection) |*sel| sel else ret: { + var sel = cursel.enable_selection(); + move_cursor_right(root, &sel.begin, self.metrics) catch return error.Stop; + break :ret sel; + }; + var sfa = std.heap.stackFallback(4096, self.allocator); + const cut_text = copy_selection(root, sel.*, sfa.get(), self.metrics) catch return error.Stop; + defer allocator.free(cut_text); + const cd = CaseData.init(allocator) catch return error.Stop; + defer cd.deinit(); + const flipped = blk: { + var bytes = std.ArrayList(u8).initCapacity(allocator, cut_text.len) catch return error.Stop; + defer bytes.deinit(); + + var iter = code_point.Iterator{ .bytes = cut_text }; + var buf: [4]u8 = undefined; + + while (iter.next()) |cp| { + const code = if (cd.isLower(cp.code)) cd.toUpper(cp.code) else cd.toLower(cp.code); + const len = std.unicode.utf8Encode(code, &buf) catch return error.Stop; + bytes.appendSliceAssumeCapacity(buf[0..len]); + } + + break :blk bytes.toOwnedSlice() catch return error.Stop; + }; + defer allocator.free(flipped); + root = try self.delete_selection(root, cursel, allocator); + root = self.insert(root, cursel, flipped, allocator) catch return error.Stop; + cursel.* = saved; + return root; + } + + pub fn switch_case(self: *Self, _: Context) Result { + const b = try self.buf_for_update(); + const root = try self.with_cursels_mut(b.root, switch_case_cursel, b.allocator); + try self.update_buf(root); + self.clamp(); + } + pub const switch_case_meta = .{ .description = "Switch the casing of selection or cell" }; + pub fn toggle_eol_mode(self: *Self, _: Context) Result { if (self.buffer) |b| { b.file_eol_mode = switch (b.file_eol_mode) { diff --git a/src/tui/mode/input/vim/normal.zig b/src/tui/mode/input/vim/normal.zig index 6785d47..8d072a7 100644 --- a/src/tui/mode/input/vim/normal.zig +++ b/src/tui/mode/input/vim/normal.zig @@ -212,6 +212,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) !void { 'o' => self.seq(.{ "smart_insert_line_before", "enter_mode" }, command.fmt(.{"vim/insert"})), 'k' => self.cmd("hover", .{}), + '`' => self.seq(.{ "switch_case", "move_right_vim" }, .{}), else => {}, }, 0 => switch (keypress) { diff --git a/src/tui/mode/input/vim/visual.zig b/src/tui/mode/input/vim/visual.zig index 7f9a17b..4f51543 100644 --- a/src/tui/mode/input/vim/visual.zig +++ b/src/tui/mode/input/vim/visual.zig @@ -208,6 +208,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) !void { 'o' => self.seq(.{ "smart_insert_line_before", "enter_mode" }, command.fmt(.{"vim/insert"})), + '`' => self.cmd("switch_case", .{}), else => {}, }, 0 => switch (keypress) {