diff --git a/src/buffer/unicode.zig b/src/buffer/unicode.zig index 2cd84fa..0571e14 100644 --- a/src/buffer/unicode.zig +++ b/src/buffer/unicode.zig @@ -41,6 +41,7 @@ pub fn control_code_to_unicode(code: u8) [:0]const u8 { pub const char_pairs = [_]struct { []const u8, []const u8 }{ .{ "\"", "\"" }, .{ "'", "'" }, + .{ "`", "`" }, .{ "(", ")" }, .{ "[", "]" }, .{ "{", "}" }, @@ -48,6 +49,8 @@ pub const char_pairs = [_]struct { []const u8, []const u8 }{ .{ "“", "”" }, .{ "‚", "‘" }, .{ "«", "»" }, + .{ "¿", "?" }, + .{ "¡", "!" }, }; pub const open_close_pairs = [_]struct { []const u8, []const u8 }{ @@ -57,6 +60,8 @@ pub const open_close_pairs = [_]struct { []const u8, []const u8 }{ .{ "‘", "’" }, .{ "“", "”" }, .{ "«", "»" }, + .{ "¿", "?" }, + .{ "¡", "!" }, }; fn raw_byte_to_utf8(cp: u8, buf: []u8) ![]const u8 { diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 1df59b2..0a0c8a3 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -255,6 +255,7 @@ ["\"", "smart_insert_pair", "\"", "\""], ["'", "smart_insert_pair", "'", "'"], + ["`", "smart_insert_pair", "`", "`"], ["(", "smart_insert_pair", "(", ")"], [")", "smart_insert_pair_close", "(", ")"], ["[", "smart_insert_pair", "[", "]"], @@ -269,6 +270,8 @@ ["‘", "smart_insert_pair_close", "‚", "‘"], ["«", "smart_insert_pair", "«", "»"], ["»", "smart_insert_pair_close", "«", "»"], + ["¿", "smart_insert_pair", "¿", "?"], + ["¡", "smart_insert_pair", "¡", "!"], ["alt+0", "add_integer_argument_digit", 0], ["alt+1", "add_integer_argument_digit", 1], diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 1e9e063..a76828c 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -170,11 +170,11 @@ ["x", "extend_line_below"], ["m m", "match_brackets"], - ["m s", "surround_add"], - ["m r", "surround_replace"], - ["m d", "surround_delete"], - ["m a", "select_textobject_around"], - ["m i", "select_textobject_inner"], + ["m a", "match", "select_textobject_around"], + ["m i", "match", "select_textobject_inner"], + ["m d", "match", "surround_delete"], + ["m r", "match", "surround_replace"], + ["m s", "match", "surround_add"], ["[ D", "goto_first_diag"], ["[ G", "goto_first_change"], @@ -463,12 +463,12 @@ ["x", "extend_line_below"], - ["m m", "match_brackets", "helix_sel_mode"], - ["m s", "surround_add"], - ["m r", "surround_replace"], - ["m d", "surround_delete"], - ["m a", "select_textobject_around"], - ["m i", "select_textobject_inner"], + ["m m", "match_brackets"], + ["m a", "match", "select_textobject_around"], + ["m i", "match", "select_textobject_inner"], + ["m d", "match", "surround_delete"], + ["m r", "match", "surround_replace"], + ["m s", "match", "surround_add"], ["[ D", "goto_first_diag"], ["[ G", "goto_first_change"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5dd92b8..5c99f1d 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -2252,6 +2252,7 @@ pub const Editor = struct { '!' => true, '?' => true, '&' => true, + '@' => true, '-' => true, '<' => true, '>' => true, @@ -2259,7 +2260,7 @@ pub const Editor = struct { }; } - fn is_word_char(c: []const u8) bool { + pub fn is_word_char(c: []const u8) bool { return !is_not_word_char(c); } @@ -2271,7 +2272,7 @@ pub const Editor = struct { return cursor.test_at(root, is_not_word_char, metrics); } - fn is_word_boundary_left(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + pub fn is_word_boundary_left(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { if (cursor.col == 0) return true; if (is_non_word_char_at_cursor(root, cursor, metrics)) @@ -2324,7 +2325,7 @@ pub const Editor = struct { return false; } - fn is_word_boundary_right(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + pub fn is_word_boundary_right(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { const line_width = root.line_width(cursor.row, metrics) catch return true; if (cursor.col >= line_width) return true; diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index bd3322a..ed631ad 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -209,11 +209,12 @@ const cmds_ = struct { } pub const split_selection_on_newline_meta: Meta = .{ .description = "Add cursor to each line in selection helix" }; - pub fn match_brackets(_: *void, ctx: Ctx) Result { + pub fn match_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_once_arg(root, &match_bracket, ctx); + const m = tui.input_mode().?.*.name; + try ed.with_cursels_const_once_arg(root, &match_bracket, command.fmt(.{m})); ed.clamp(); } pub const match_brackets_meta: Meta = .{ .description = "Goto matching bracket" }; @@ -411,6 +412,44 @@ const cmds_ = struct { } pub const extend_to_char_right_helix_meta: Meta = .{ .description = "Extend Selection to char right" }; + pub fn select_textobject_inner(_: *void, ctx: Ctx) Result { + var action: []const u8 = ""; + + if (!try ctx.args.match(.{tp.extract(&action)})) return error.Stop; + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + const root = ed.buf_root() catch return; + + if (std.mem.eql(u8, action, "w")) { + try ed.with_cursels_const(root, select_inner_word, ed.metrics); + } else if (std.mem.eql(u8, action, "W")) { + try ed.with_cursels_const(root, select_inner_long_word, ed.metrics); + } else { + return; + } + ed.clamp(); + } + pub const select_textobject_inner_meta: Meta = .{ .description = "select inside object helix" }; + + pub fn select_textobject_around(_: *void, ctx: Ctx) Result { + var action: []const u8 = ""; + + if (!try ctx.args.match(.{tp.extract(&action)})) return error.Stop; + const mv = tui.mainview() orelse return; + const ed = mv.get_active_editor() orelse return; + const root = ed.buf_root() catch return; + + if (std.mem.eql(u8, action, "w")) { + try ed.with_cursels_const(root, select_around_word, ed.metrics); + } else if (std.mem.eql(u8, action, "W")) { + try ed.with_cursels_const(root, select_inner_long_word, ed.metrics); + } else { + return; + } + ed.clamp(); + } + pub const select_textobject_around_meta: Meta = .{ .description = "select around object helix" }; + pub fn copy_helix(_: *void, _: Ctx) Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; @@ -455,7 +494,7 @@ const cmds_ = struct { fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { var symbol: []const u8 = undefined; const mode: enum { helix_sel_mode, helix_nor_mode } = if ((ctx.args.match(.{tp.extract(&symbol)}) catch false) and - std.mem.eql(u8, @tagName(.helix_sel_mode), symbol)) .helix_sel_mode else .helix_nor_mode; + std.mem.eql(u8, "SEL", symbol)) .helix_sel_mode else .helix_nor_mode; if (mode == .helix_sel_mode) { const begin: Cursor = if (cursel.selection) |sel| sel.begin else cursel.*.cursor; @@ -522,6 +561,93 @@ fn to_char_helix(ctx: command.Context, move: Editor.cursel_operator_mut_once_arg ed.clamp(); } +fn select_inner_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + if (!cursel.cursor.test_at(root, Editor.is_word_char, metrics)) return; + var prev = cursel.cursor; + var next = cursel.cursor; + Editor.move_cursor_left_until(root, &prev, Editor.is_word_boundary_left, metrics); + Editor.move_cursor_right_until(root, &next, Editor.is_word_boundary_right, metrics); + try next.move_right(root, metrics); + const sel = try cursel.enable_selection(root, metrics); + sel.begin = prev; + sel.end = next; + cursel.*.cursor = next; +} + +fn select_inner_long_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + if (cursel.cursor.test_at(root, Editor.is_whitespace, metrics)) return; + var prev = cursel.cursor; + var next = cursel.cursor; + Editor.move_cursor_left_until(root, &prev, is_long_word_boundary_left, metrics); + Editor.move_cursor_right_until(root, &next, is_long_word_boundary_right, metrics); + try next.move_right(root, metrics); + const sel = try cursel.enable_selection(root, metrics); + sel.begin = prev; + sel.end = next; + cursel.*.cursor = next; +} + +fn is_tab_or_space(c: []const u8) bool { + return (c[0] == ' ') or (c[0] == '\t'); +} + +fn is_tab_or_espace_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_espace_at_cursor(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics) bool { + return !cursor.test_at(root, is_tab_or_space, metrics); +} + +fn select_around_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + if (!cursel.cursor.test_at(root, Editor.is_word_char, metrics)) return; + var expander = cursel.*; + try select_inner_word(root, &expander, metrics); + const sel_e = try expander.enable_selection(root, metrics); + var prev = sel_e.begin; + var next = sel_e.end; + if (next.test_at(root, is_tab_or_space, metrics)) { + Editor.move_cursor_right_until(root, &next, is_not_tab_or_espace_at_cursor, metrics); + } else { + next = sel_e.end; + 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_espace_at_cursor, metrics); + prev.move_right(root, metrics) catch {}; + } else { + prev = sel_e.begin; + } + } + const sel = try cursel.enable_selection(root, metrics); + sel.begin = prev; + sel.end = next; + cursel.*.cursor = next; +} + +fn select_around_long_word(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + if (!cursel.cursor.test_at(root, Editor.is_word_char, metrics)) return; + var expander = cursel.*; + try select_inner_long_word(root, &expander, metrics); + const sel_e = try expander.enable_selection(root, metrics); + var prev = sel_e.begin; + var next = sel_e.end; + if (next.test_at(root, is_tab_or_space, metrics)) { + Editor.move_cursor_right_until(root, &next, is_not_tab_or_espace_at_cursor, metrics); + } else { + next = sel_e.end; + 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_espace_at_cursor, metrics); + prev.move_right(root, metrics) catch {}; + } else { + prev = sel_e.begin; + } + } + const sel = try cursel.enable_selection(root, metrics); + sel.begin = prev; + sel.end = next; + cursel.*.cursor = next; +} + fn select_cursel_to_char_left_helix(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metrics: Buffer.Metrics) error{Stop}!void { var moving_cursor: Cursor = cursel.*.cursor; var begin = cursel.*.cursor; diff --git a/src/tui/mode/mini/match.zig b/src/tui/mode/mini/match.zig new file mode 100644 index 0000000..10b582b --- /dev/null +++ b/src/tui/mode/mini/match.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const cbor = @import("cbor"); +const command = @import("command"); +const tp = @import("thespian"); +const log = @import("log"); + +const tui = @import("../../tui.zig"); + +pub const Type = @import("get_char.zig").Create(@This()); +pub const create = Type.create; + +pub fn name(self: *Type) []const u8 { + var suffix: []const u8 = ""; + if ((self.ctx.args.match(.{tp.extract(&suffix)}) catch false)) { + return suffix; + } + return "󰅪 match"; +} + +pub fn process_egc(self: *Type, egc: []const u8) command.Result { + var action: []const u8 = ""; + if ((self.ctx.args.match(.{tp.extract(&action)}) catch false)) { + try command.executeName(action, command.fmt(.{egc})); + } + try command.executeName("exit_mini_mode", .{}); +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 09a8462..581baa8 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -1279,6 +1279,11 @@ const cmds = struct { } pub const underline_meta: Meta = .{ .description = "Underline with character" }; + pub fn match(self: *Self, ctx: Ctx) Result { + return enter_mini_mode(self, @import("mode/mini/match.zig"), ctx); + } + pub const match_meta: Meta = .{ .description = "Match mode" }; + pub fn open_file(self: *Self, ctx: Ctx) Result { if (get_active_selection(self.allocator)) |text| { defer self.allocator.free(text);