Merge branch 'master' into wio-sokol-gui

This commit is contained in:
CJ van den Berg 2026-04-13 22:58:59 +02:00
commit 02ec222ebf
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
9 changed files with 547 additions and 88 deletions

View file

@ -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(.changes_when_lowercased, cp))
uucode.get(.simple_lowercase_mapping, cp) orelse cp
else
uucode.get(.simple_uppercase_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);
}

View file

@ -87,6 +87,8 @@ hint_window_style: WidgetStyle = .thick_boxed,
info_box_style: WidgetStyle = .bar_left_spacious,
info_box_width_limit: usize = 80,
palette_placement: PalettePlacement = .top_center,
centered_view: bool = false,
centered_view_width: usize = 145,
centered_view_min_screen_width: usize = 145,
@ -256,3 +258,10 @@ pub const TerminalOnExit = enum {
close,
hold,
};
pub const PalettePlacement = enum {
top_center,
top_left,
top_right,
center,
};

View file

@ -157,6 +157,7 @@
["alt+l", "to_lower"],
["alt+q", "reflow"],
["alt+c", "switch_case"],
["ctrl+k c", "toggle_case"],
["ctrl+_", "underline"],
["ctrl+=", "underline_with_char", "=", "solid"],
["ctrl+plus", "underline_with_char", "="],
@ -187,8 +188,8 @@
["alt+[", "select_prev_sibling", true],
["alt+]", "select_next_sibling", true],
["alt+a", "select_all_siblings"],
["alt+shift+home", "move_scroll_left"],
["alt+shift+end", "move_scroll_right"],
["alt+shift+home", "scroll_left"],
["alt+shift+end", "scroll_right"],
["alt+shift+up", "add_cursor_up"],
["alt+shift+down", "add_cursor_down"],
["alt+shift+f12", "goto_type_definition"],

View file

@ -88,6 +88,8 @@
["di]", "cut_inside_square_brackets"],
["di{", "cut_inside_braces"],
["di}", "cut_inside_braces"],
["di'", "cut_inside_single_quotes"],
["di\"", "cut_inside_double_quotes"],
["daw", "cut_around_word"],
["da(", "cut_around_parentheses"],
@ -96,6 +98,8 @@
["da]", "cut_around_square_brackets"],
["da{", "cut_around_braces"],
["da}", "cut_around_braces"],
["da'", "cut_around_single_quotes"],
["da\"", "cut_around_double_quotes"],
["cc", ["enter_mode", "insert"], ["cut_internal_vim"]],
["C", ["enter_mode", "insert"], ["cut_to_end_vim"]],
@ -110,6 +114,8 @@
["ci]", ["enter_mode", "insert"], ["cut_inside_square_brackets"]],
["ci{", ["enter_mode", "insert"], ["cut_inside_braces"]],
["ci}", ["enter_mode", "insert"], ["cut_inside_braces"]],
["ci'", ["enter_mode", "insert"], ["cut_inside_single_quotes"]],
["ci\"", ["enter_mode", "insert"], ["cut_inside_double_quotes"]],
["caw", ["enter_mode", "insert"], ["cut_around_word"]],
["ca(", ["enter_mode", "insert"], ["cut_around_parentheses"]],
@ -118,6 +124,8 @@
["ca]", ["enter_mode", "insert"], ["cut_around_square_brackets"]],
["ca{", ["enter_mode", "insert"], ["cut_around_braces"]],
["ca}", ["enter_mode", "insert"], ["cut_around_braces"]],
["ca'", ["enter_mode", "insert"], ["cut_around_single_quotes"]],
["ca\"", ["enter_mode", "insert"], ["cut_around_double_quotes"]],
["yy", ["copy_line_internal_vim"], ["cancel"]],
@ -128,6 +136,8 @@
["yi]", ["copy_inside_square_brackets"], ["cancel"]],
["yi{", ["copy_inside_braces"], ["cancel"]],
["yi}", ["copy_inside_braces"], ["cancel"]],
["yi'", ["copy_inside_single_quotes"], ["cancel"]],
["yi\"", ["copy_inside_double_quotes"], ["cancel"]],
["yaw", ["copy_around_word"], ["cancel"]],
["ya(", ["copy_around_parentheses"], ["cancel"]],
@ -136,6 +146,8 @@
["ya]", ["copy_around_square_brackets"], ["cancel"]],
["ya{", ["copy_around_braces"], ["cancel"]],
["ya}", ["copy_around_braces"], ["cancel"]],
["ya'", ["copy_around_single_quotes"], ["cancel"]],
["ya\"", ["copy_around_double_quotes"], ["cancel"]],
["<C-u>", "move_scroll_half_page_up_vim"],
["<C-d>", "move_scroll_half_page_down_vim"],
@ -214,6 +226,8 @@
["i]", "select_inside_square_brackets"],
["i{", "select_inside_braces"],
["i}", "select_inside_braces"],
["i'", "select_inside_single_quotes"],
["i\"", "select_inside_double_quotes"],
["aw", "select_around_word"],
["a(", "select_around_parentheses"],
@ -222,6 +236,8 @@
["a]", "select_around_square_brackets"],
["a{", "select_around_braces"],
["a}", "select_around_braces"],
["a'", "select_around_single_quotes"],
["a\"", "select_around_double_quotes"],
["^", "smart_move_begin"],
["$", "select_end"],

View file

@ -1673,23 +1673,15 @@ pub const Editor = struct {
const style_ = style_cache_lookup(ctx.theme, ctx.cache, scope, id);
const style = if (style_) |sty| sty.style else return;
if (sel.end.row < ctx.self.view.row) return;
if (sel.begin.row > ctx.self.view.row + ctx.self.view.rows) return;
if (sel.begin.row < ctx.self.view.row) sel.begin.row = ctx.self.view.row;
if (sel.end.row > ctx.self.view.row + ctx.self.view.rows) sel.end.row = ctx.self.view.row + ctx.self.view.rows;
if (sel.end.col < ctx.self.view.col) return;
if (sel.begin.col > ctx.self.view.col + ctx.self.view.cols) return;
if (sel.begin.col < ctx.self.view.col) sel.begin.col = ctx.self.view.col;
if (sel.end.col > ctx.self.view.col + ctx.self.view.cols) sel.end.col = ctx.self.view.col + ctx.self.view.cols;
ctx.clamp_to_view(&sel.begin);
ctx.clamp_to_view(&sel.end);
for (sel.begin.row..sel.end.row + 1) |row| {
const begin_col = if (row == sel.begin.row) sel.begin.col else 0;
const begin_col = if (row == sel.begin.row) sel.begin.col else ctx.self.view.col;
const end_col = if (row == sel.end.row) sel.end.col else ctx.self.view.col + ctx.self.view.cols;
const y = @max(ctx.self.view.row, row) - ctx.self.view.row;
const x = @max(ctx.self.view.col, begin_col) - ctx.self.view.col;
const end_x = @max(ctx.self.view.col, end_col) - ctx.self.view.col;
if (x >= end_x) return;
const y = row - ctx.self.view.row;
const x = begin_col - ctx.self.view.col;
const end_x = end_col - ctx.self.view.col;
for (x..end_x) |x_|
try ctx.render_cell(y, x_, style);
}
@ -1701,6 +1693,19 @@ pub const Editor = struct {
cell.set_style(style);
_ = ctx.self.plane.putc(&cell) catch {};
}
fn clamp_to_view(ctx: *const @This(), cursor: *Cursor) void {
const row_off: u32 = @intCast(ctx.self.view.row);
const col_off: u32 = @intCast(ctx.self.view.col);
if (cursor.row < row_off) {
cursor.row = row_off;
cursor.col = col_off;
}
if (cursor.row >= row_off + ctx.self.view.rows) {
cursor.row = row_off + ctx.self.view.rows;
cursor.col = col_off + ctx.self.view.cols;
}
cursor.col = std.math.clamp(cursor.col, col_off, col_off + ctx.self.view.cols);
}
};
var ctx: Ctx = .{
.self = self,
@ -3002,17 +3007,15 @@ pub const Editor = struct {
self.update_animation_step(dest);
}
fn scroll_up(self: *Self) void {
const scroll_step_vertical = tui.config().scroll_step_vertical;
fn scroll_up_internal(self: *Self, count: usize) void {
var dest_row = self.scroll_dest;
dest_row = if (dest_row > scroll_step_vertical) dest_row - scroll_step_vertical else 0;
dest_row -|= count;
self.update_scroll_dest_abs(dest_row);
}
fn scroll_down(self: *Self) void {
const scroll_step_vertical = tui.config().scroll_step_vertical;
fn scroll_down_internal(self: *Self, count: usize) void {
var dest_row = self.scroll_dest;
dest_row += scroll_step_vertical;
dest_row += count;
self.update_scroll_dest_abs(dest_row);
}
@ -3035,7 +3038,7 @@ pub const Editor = struct {
else if (tui.fast_scroll())
self.scroll_pageup()
else
self.scroll_up();
self.scroll_up_internal(tui.config().scroll_step_vertical);
}
pub fn mouse_scroll_down(self: *Self) void {
@ -3044,7 +3047,7 @@ pub const Editor = struct {
else if (tui.fast_scroll())
self.scroll_pagedown()
else
self.scroll_down();
self.scroll_down_internal(tui.config().scroll_step_vertical);
}
pub fn scroll_to(self: *Self, row: usize) void {
@ -3969,6 +3972,132 @@ pub const Editor = struct {
}
pub const goto_bracket_meta: Meta = .{ .description = "Goto matching bracket" };
const QuoteRole = enum { opening, closing };
fn row_start_cursor(root: Buffer.Root, cursor: Cursor, metrics: Buffer.Metrics) Cursor {
var c = cursor;
while (true) {
var prev = c;
prev.move_left(root, metrics) catch break;
if (prev.row != c.row) break;
c = prev;
}
return c;
}
fn quote_is_escaped(root: Buffer.Root, quote_cursor: Cursor, metrics: Buffer.Metrics) bool {
var cursor = quote_cursor;
var backslashes: usize = 0;
while (true) {
var prev = cursor;
prev.move_left(root, metrics) catch break;
if (prev.row != cursor.row) break;
const egc, _, _ = root.egc_at(prev.row, prev.col, metrics) catch break;
if (!std.mem.eql(u8, egc, "\\")) break;
backslashes += 1;
cursor = prev;
}
return (backslashes % 2) == 1;
}
fn find_unescaped_quote(
root: Buffer.Root,
start: Cursor,
metrics: Buffer.Metrics,
direction: enum { left, right },
quote: []const u8,
) error{Stop}!Cursor {
var cursor = start;
var i: usize = 0;
while (i < bracket_search_radius) : (i += 1) {
switch (direction) {
.left => cursor.move_left(root, metrics) catch return error.Stop,
.right => cursor.move_right(root, metrics) catch return error.Stop,
}
const egc, _, _ = root.egc_at(cursor.row, cursor.col, metrics) catch {
return error.Stop;
};
if (!std.mem.eql(u8, egc, quote)) continue;
if (quote_is_escaped(root, cursor, metrics)) continue;
return cursor;
}
return error.Stop;
}
fn quote_role_on_row(
root: Buffer.Root,
quote_cursor: Cursor,
metrics: Buffer.Metrics,
quote: []const u8,
) error{Stop}!QuoteRole {
var cursor = row_start_cursor(root, .{ .row = quote_cursor.row, .col = 0 }, metrics);
var opening = true;
while (cursor.row == quote_cursor.row and cursor.col <= quote_cursor.col) {
const egc, _, _ = root.egc_at(cursor.row, cursor.col, metrics) catch {
return error.Stop;
};
if (std.mem.eql(u8, egc, quote) and !quote_is_escaped(root, cursor, metrics)) {
if (cursor.col == quote_cursor.col) {
return if (opening) .opening else .closing;
}
opening = !opening;
}
cursor.move_right(root, metrics) catch break;
}
return error.Stop;
}
pub fn find_quote_pair(
root: Buffer.Root,
original_cursor: Cursor,
metrics: Buffer.Metrics,
quote: []const u8,
) error{Stop}!struct { struct { usize, usize }, struct { usize, usize } } {
// If the cursor is already on a quote, use it directly as the anchor.
// Otherwise find the nearest quote, preferring rightward.
const cursor_egc, _, _ = root.egc_at(original_cursor.row, original_cursor.col, metrics) catch return error.Stop;
const anchor = if (std.mem.eql(u8, cursor_egc, quote))
original_cursor
else
find_unescaped_quote(root, original_cursor, metrics, .right, quote) catch
find_unescaped_quote(root, original_cursor, metrics, .left, quote) catch
return error.Stop;
const role = try quote_role_on_row(root, anchor, metrics, quote);
const other = switch (role) {
.opening => try find_unescaped_quote(root, anchor, metrics, .right, quote),
.closing => try find_unescaped_quote(root, anchor, metrics, .left, quote),
};
return switch (role) {
.opening => .{
.{ anchor.row, anchor.col },
.{ other.row, other.col },
},
.closing => .{
.{ other.row, other.col },
.{ anchor.row, anchor.col },
},
};
}
pub fn move_or_select_to_char_right(self: *Self, ctx: Context) Result {
const selected = if (self.get_primary().selection) |_| true else false;
if (selected) try self.select_to_char_right(ctx) else try self.move_to_char_right(ctx);
@ -4411,6 +4540,20 @@ pub const Editor = struct {
}
pub const unindent_meta: Meta = .{ .description = "Unindent current line", .arguments = &.{.integer} };
pub fn scroll_up(self: *Self, ctx: Context) Result {
var count: usize = 1;
_ = ctx.args.match(.{tp.extract(&count)}) catch false;
self.scroll_up_internal(count);
}
pub const scroll_up_meta: Meta = .{ .description = "Scroll up", .arguments = &.{.integer} };
pub fn scroll_down(self: *Self, ctx: Context) Result {
var count: usize = 1;
_ = ctx.args.match(.{tp.extract(&count)}) catch false;
self.scroll_down_internal(count);
}
pub const scroll_down_meta: Meta = .{ .description = "Scroll down", .arguments = &.{.integer} };
pub fn move_scroll_up(self: *Self, ctx: Context) Result {
const root = try self.buf_root();
self.with_cursors_const_repeat(root, move_cursor_up, ctx) catch {};
@ -4427,15 +4570,19 @@ pub const Editor = struct {
}
pub const move_scroll_down_meta: Meta = .{ .description = "Move and scroll down", .arguments = &.{.integer} };
pub fn move_scroll_left(self: *Self, _: Context) Result {
self.view.move_left(tui.config().scroll_step_horizontal) catch {};
pub fn scroll_left(self: *Self, ctx: Context) Result {
var count: usize = 1;
_ = ctx.args.match(.{tp.extract(&count)}) catch false;
self.view.move_left(count) catch {};
}
pub const move_scroll_left_meta: Meta = .{ .description = "Scroll left" };
pub const scroll_left_meta: Meta = .{ .description = "Scroll left", .arguments = &.{.integer} };
pub fn move_scroll_right(self: *Self, _: Context) Result {
self.view.move_right(tui.config().scroll_step_horizontal) catch {};
pub fn scroll_right(self: *Self, ctx: Context) Result {
var count: usize = 1;
_ = ctx.args.match(.{tp.extract(&count)}) catch false;
self.view.move_right(count) catch {};
}
pub const move_scroll_right_meta: Meta = .{ .description = "Scroll right" };
pub const scroll_right_meta: Meta = .{ .description = "Scroll right", .arguments = &.{.integer} };
pub fn mouse_scroll_left(self: *Self) void {
const scroll_step_horizontal = tui.config().scroll_step_horizontal;
@ -7110,27 +7257,43 @@ pub const Editor = struct {
.arguments = &.{.integer},
};
fn to_upper_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
fn get_selection_or_select_word(self: *Self, root: Buffer.Root, cursel: *CurSel) error{Stop}!*Selection {
if (cursel.selection) |*sel| {
return sel;
} else {
var sel = cursel.enable_selection(root, self.metrics);
try move_cursor_word_begin(root, &sel.begin, self.metrics);
try move_cursor_word_end(root, &sel.end, self.metrics);
return sel;
}
}
fn transform_cursel(
comptime transform: fn (std.mem.Allocator, []const u8) Buffer.unicode.TransformError![]u8,
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(root, self.metrics);
move_cursor_word_begin(root, &sel.begin, self.metrics) catch return error.Stop;
move_cursor_word_end(root, &sel.end, self.metrics) catch return error.Stop;
break :ret sel;
};
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 ucased = Buffer.unicode.to_upper(sfa_allocator, cut_text) catch return error.Stop;
defer sfa_allocator.free(ucased);
const transformed = transform(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, ucased, allocator) catch return error.Stop;
root = self.insert(root, cursel, transformed, allocator) catch return error.Stop;
cursel.* = saved;
return root;
}
fn to_upper_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
return transform_cursel(Buffer.unicode.to_upper, self, root_, cursel, allocator);
}
pub fn to_upper(self: *Self, _: Context) Result {
const b = try self.buf_for_update();
const root = try self.with_cursels_mut_once(b.root, to_upper_cursel, b.allocator);
@ -7139,25 +7302,8 @@ pub const Editor = struct {
}
pub const to_upper_meta: Meta = .{ .description = "Convert selection or word to upper case" };
fn to_lower_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, buffer_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(root, self.metrics);
move_cursor_word_begin(root, &sel.begin, self.metrics) catch return error.Stop;
move_cursor_word_end(root, &sel.end, self.metrics) catch return error.Stop;
break :ret sel;
};
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 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;
cursel.* = saved;
return root;
fn to_lower_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
return transform_cursel(Buffer.unicode.to_lower, self, root_, cursel, allocator);
}
pub fn to_lower(self: *Self, _: Context) Result {
@ -7199,6 +7345,18 @@ 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 {
return transform_cursel(Buffer.unicode.toggle_case, self, root_, cursel, allocator);
}
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();

View file

@ -430,6 +430,17 @@ const cmds_ = struct {
}
pub const select_textobject_inner_meta: Meta = .{ .description = "select inside object helix" };
pub fn surround_add(_: *void, ctx: Ctx) Result {
const mv = tui.mainview() orelse return;
const ed = mv.get_active_editor() orelse return;
const b = try ed.buf_for_update();
const root = try ed.with_cursels_mut_once_arg(b.root, surround_cursel_add, ed.allocator, ctx);
try ed.update_buf(root);
ed.clamp();
ed.need_render();
}
pub const surround_add_meta: Meta = .{ .description = "surround add" };
pub fn select_textobject_around(_: *void, ctx: Ctx) Result {
var action: []const u8 = "";
@ -481,8 +492,8 @@ const cmds_ = struct {
pub fn replace_with_character_helix(_: *void, ctx: Ctx) Result {
const mv = tui.mainview() orelse return;
const ed = mv.get_active_editor() orelse return;
var root = ed.buf_root() catch return;
root = try ed.with_cursels_mut_once_arg(root, replace_cursel_with_character, ed.allocator, ctx);
const b = try ed.buf_for_update();
const root = try ed.with_cursels_mut_once_arg(b.root, replace_cursel_with_character, ed.allocator, ctx);
try ed.update_buf(root);
ed.clamp();
ed.need_render();
@ -865,6 +876,50 @@ fn replace_cursel_with_character(ed: *Editor, root: Buffer.Root, cursel: *CurSel
return insert_replace_selection(ed, root, cursel, replacement, allocator) catch return error.Stop;
}
fn find_open_close_pair(bracket: []const u8) struct { left: []const u8, right: []const u8 } {
for (Buffer.unicode.open_close_pairs) |pair| {
if (std.mem.eql(u8, bracket, pair[0]) or std.mem.eql(u8, bracket, pair[1])) {
return .{ .left = pair[0], .right = pair[1] };
}
}
return .{ .left = bracket, .right = bracket };
}
fn surround_cursel_add(ed: *Editor, root: Buffer.Root, cursel: *CurSel, allocator: Allocator, ctx: command.Context) error{Stop}!Buffer.Root {
var encloser: []const u8 = undefined;
if (!(ctx.args.match(.{tp.extract(&encloser)}) catch return error.Stop))
return error.Stop;
const enclose_pair = find_open_close_pair(encloser);
var root_: Buffer.Root = root;
cursel.check_selection(root, ed.metrics);
const sel = cursel.enable_selection(root_, ed.metrics);
var begin = sel.begin;
var end = sel.end;
if (sel.is_reversed()) {
end = sel.begin;
begin = sel.end;
}
if (begin.row == end.row and end.col == begin.col) {
end.move_right(root_, ed.metrics) catch {};
}
_, _, root_ = root_.insert_chars(end.row, end.col, enclose_pair.right, allocator, ed.metrics) catch return error.Stop;
_, _, root_ = root_.insert_chars(begin.row, begin.col, enclose_pair.left, allocator, ed.metrics) catch return error.Stop;
if (begin.row == end.row) {
try end.move_right(root_, ed.metrics); // for left-bracket column shift on same row
}
try end.move_right(root_, ed.metrics); // skip past right bracket
cursel.selection = Selection{ .begin = begin, .end = end };
ed.nudge_insert(.{ .begin = begin, .end = end }, cursel, encloser.len * 2);
if (end.right_of(begin)) {
try cursel.*.cursor.move_right(root_, ed.metrics);
}
return root_;
}
fn move_noop(_: Buffer.Root, _: *Cursor, _: Buffer.Metrics) error{Stop}!void {}
fn move_cursor_word_right_end_helix(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void {

View file

@ -187,10 +187,36 @@ fn prepare_resize_menu(self: *Self, _: *MenuType, _: Widget.Box) Widget.Box {
}
fn prepare_resize(self: *Self) Widget.Box {
const screen = tui.screen();
const padding = tui.get_widget_style(widget_type).padding;
const w = self.menu_width();
const x = self.menu_pos_x();
const h = self.menu.menu.widgets.items.len;
return .{ .y = 0, .x = x, .w = w, .h = h };
return switch (tui.config().palette_placement) {
.top_center => .{
.y = 0,
.x = self.menu_pos_x(),
.w = w,
.h = h,
},
.top_left => .{
.y = 0,
.x = 0,
.w = w,
.h = h,
},
.top_right => .{
.y = 0,
.x = if (screen.w > (w - padding.right)) (screen.w - w - padding.right) else 0,
.w = w,
.h = h,
},
.center => .{
.y = if (screen.h > h) (screen.h - h) / 2 else 0,
.x = self.menu_pos_x(),
.w = w,
.h = h,
},
};
}
fn do_resize(self: *Self) void {

View file

@ -26,7 +26,17 @@ pub const Placement = enum {
top_center,
top_left,
top_right,
center,
primary_cursor,
fn from_config(conf: @import("config").PalettePlacement) Placement {
return switch (conf) {
.top_center => .top_center,
.top_left => .top_left,
.top_right => .top_right,
.center => .center,
};
}
};
pub const ActivateMode = enum {
@ -110,7 +120,10 @@ pub fn Create(options: type) type {
.mode = try keybind.mode("overlay/palette", allocator, .{
.insert_command = "overlay_insert_bytes",
}),
.placement = if (@hasDecl(options, "placement")) options.placement else .top_center,
.placement = if (@hasDecl(options, "placement"))
options.placement
else
Placement.from_config(tui.config().palette_placement),
};
try self.commands.init(self);
errdefer self.commands.deinit();
@ -205,6 +218,7 @@ pub fn Create(options: type) type {
.top_center => self.prepare_resize_top_center(screen, w),
.top_left => self.prepare_resize_top_left(screen, w),
.top_right => self.prepare_resize_top_right(screen, w, padding),
.center => self.prepare_resize_center(screen, w),
.primary_cursor => self.prepare_resize_primary_cursor(screen, w, padding),
};
}
@ -249,6 +263,14 @@ pub fn Create(options: type) type {
return self.prepare_resize_at_y_x(screen, w, cursor.row + 1 + padding.top, cursor.col);
}
fn prepare_resize_center(self: *Self, screen: Widget.Box, w: usize) Widget.Box {
const x = if (screen.w > w) (screen.w - w) / 2 else 0;
const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count);
const y = if (screen.h > h) (screen.h - h) / 2 else 0;
self.view_rows = get_view_rows(screen) -| y;
return .{ .y = y, .x = x, .w = w, .h = h };
}
fn after_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
return self.after_resize();
}

View file

@ -220,6 +220,42 @@ const cmds_ = struct {
}
pub const select_around_braces_meta: Meta = .{ .description = "Select around {}" };
pub fn select_inside_single_quotes(_: *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(root, select_inside_single_quotes_textobject, ed.metrics);
}
pub const select_inside_single_quotes_meta: Meta = .{ .description = "Select inside ''" };
pub fn select_around_single_quotes(_: *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(root, select_around_single_quotes_textobject, ed.metrics);
}
pub const select_around_single_quotes_meta: Meta = .{ .description = "Select around ''" };
pub fn select_inside_double_quotes(_: *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(root, select_inside_double_quotes_textobject, ed.metrics);
}
pub const select_inside_double_quotes_meta: Meta = .{ .description = "Select inside \"\"" };
pub fn select_around_double_quotes(_: *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(root, select_around_double_quotes_textobject, ed.metrics);
}
pub const select_around_double_quotes_meta: Meta = .{ .description = "Select around \"\"" };
pub fn cut_inside_word(_: *void, ctx: Ctx) Result {
const mv = tui.mainview() orelse return;
const ed = mv.get_active_editor() orelse return;
@ -300,6 +336,46 @@ const cmds_ = struct {
}
pub const cut_around_braces_meta: Meta = .{ .description = "Cut around {}" };
pub fn cut_inside_single_quotes(_: *void, ctx: 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(root, select_inside_single_quotes_textobject, ed.metrics);
try ed.cut_internal_vim(ctx);
}
pub const cut_inside_single_quotes_meta: Meta = .{ .description = "Cut inside ''" };
pub fn cut_around_single_quotes(_: *void, ctx: 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(root, select_around_single_quotes_textobject, ed.metrics);
try ed.cut_internal_vim(ctx);
}
pub const cut_around_single_quotes_meta: Meta = .{ .description = "Cut around ''" };
pub fn cut_inside_double_quotes(_: *void, ctx: 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(root, select_inside_double_quotes_textobject, ed.metrics);
try ed.cut_internal_vim(ctx);
}
pub const cut_inside_double_quotes_meta: Meta = .{ .description = "Cut inside \"\"" };
pub fn cut_around_double_quotes(_: *void, ctx: 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(root, select_around_double_quotes_textobject, ed.metrics);
try ed.cut_internal_vim(ctx);
}
pub const cut_around_double_quotes_meta: Meta = .{ .description = "Cut around \"\"" };
pub fn copy_inside_word(_: *void, ctx: Ctx) Result {
const mv = tui.mainview() orelse return;
const ed = mv.get_active_editor() orelse return;
@ -379,6 +455,46 @@ const cmds_ = struct {
try ed.copy_internal_vim(ctx);
}
pub const copy_around_braces_meta: Meta = .{ .description = "Copy around {}" };
pub fn copy_inside_single_quotes(_: *void, ctx: 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(root, select_inside_single_quotes_textobject, ed.metrics);
try ed.copy_internal_vim(ctx);
}
pub const copy_inside_single_quotes_meta: Meta = .{ .description = "Copy inside ''" };
pub fn copy_around_single_quotes(_: *void, ctx: 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(root, select_around_single_quotes_textobject, ed.metrics);
try ed.copy_internal_vim(ctx);
}
pub const copy_around_single_quotes_meta: Meta = .{ .description = "Copy around ''" };
pub fn copy_inside_double_quotes(_: *void, ctx: 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(root, select_inside_double_quotes_textobject, ed.metrics);
try ed.copy_internal_vim(ctx);
}
pub const copy_inside_double_quotes_meta: Meta = .{ .description = "Copy inside \"\"" };
pub fn copy_around_double_quotes(_: *void, ctx: 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(root, select_around_double_quotes_textobject, ed.metrics);
try ed.copy_internal_vim(ctx);
}
pub const copy_around_double_quotes_meta: Meta = .{ .description = "Copy around \"\"" };
};
fn is_tab_or_space(c: []const u8) bool {
@ -444,56 +560,94 @@ fn select_word_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Me
}
fn select_inside_parentheses_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_bracket_textobject(root, cursel, metrics, "(", ")", .inside);
return try select_scope_textobject(root, cursel, metrics, "(", ")", .inside);
}
fn select_around_parentheses_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_bracket_textobject(root, cursel, metrics, "(", ")", .around);
return try select_scope_textobject(root, cursel, metrics, "(", ")", .around);
}
fn select_inside_square_brackets_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_bracket_textobject(root, cursel, metrics, "[", "]", .inside);
return try select_scope_textobject(root, cursel, metrics, "[", "]", .inside);
}
fn select_around_square_brackets_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_bracket_textobject(root, cursel, metrics, "[", "]", .around);
return try select_scope_textobject(root, cursel, metrics, "[", "]", .around);
}
fn select_inside_braces_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_bracket_textobject(root, cursel, metrics, "{", "}", .inside);
return try select_scope_textobject(root, cursel, metrics, "{", "}", .inside);
}
fn select_around_braces_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_bracket_textobject(root, cursel, metrics, "{", "}", .around);
return try select_scope_textobject(root, cursel, metrics, "{", "}", .around);
}
fn select_bracket_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics, opening_char: []const u8, closing_char: []const u8, scope: enum { inside, around }) !void {
fn select_inside_single_quotes_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_scope_textobject(root, cursel, metrics, "'", "'", .inside);
}
fn select_around_single_quotes_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_scope_textobject(root, cursel, metrics, "'", "'", .around);
}
fn select_inside_double_quotes_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_scope_textobject(root, cursel, metrics, "\"", "\"", .inside);
}
fn select_around_double_quotes_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
return try select_scope_textobject(root, cursel, metrics, "\"", "\"", .around);
}
fn select_scope_textobject(
root: Buffer.Root,
cursel: *CurSel,
metrics: Buffer.Metrics,
opening_char: []const u8,
closing_char: []const u8,
scope: enum { inside, around },
) !void {
const current = cursel.cursor;
var prev = cursel.cursor;
var next = cursel.cursor;
const bracket_egc, _, _ = root.egc_at(current.row, current.col, metrics) catch {
return error.Stop;
};
if (std.mem.eql(u8, bracket_egc, opening_char)) {
const closing_row, const closing_col = try Editor.match_bracket(root, current, metrics);
prev = current;
next.row = closing_row;
next.col = closing_col;
} else if (std.mem.eql(u8, bracket_egc, closing_char)) {
const opening_row, const opening_col = try Editor.match_bracket(root, current, metrics);
prev.row = opening_row;
prev.col = opening_col;
next = current;
} else {
const opening_pos, const closing_pos = find_bracket_pair(root, cursel, metrics, .left, opening_char) catch try find_bracket_pair(root, cursel, metrics, .right, opening_char);
if (std.mem.eql(u8, opening_char, closing_char)) {
const opening_pos, const closing_pos =
try Editor.find_quote_pair(root, current, metrics, opening_char);
prev.row = opening_pos[0];
prev.col = opening_pos[1];
next.row = closing_pos[0];
next.col = closing_pos[1];
} else {
const bracket_egc, _, _ = root.egc_at(current.row, current.col, metrics) catch {
return error.Stop;
};
if (std.mem.eql(u8, bracket_egc, opening_char)) {
const closing_row, const closing_col =
try Editor.match_bracket(root, current, metrics);
prev = current;
next.row = closing_row;
next.col = closing_col;
} else if (std.mem.eql(u8, bracket_egc, closing_char)) {
const opening_row, const opening_col =
try Editor.match_bracket(root, current, metrics);
prev.row = opening_row;
prev.col = opening_col;
next = current;
} else {
const pair = find_bracket_pair(root, cursel, metrics, .left, opening_char) catch blk: {
break :blk try find_bracket_pair(root, cursel, metrics, .right, opening_char);
};
prev.row = pair[0][0];
prev.col = pair[0][1];
next.row = pair[1][0];
next.col = pair[1][1];
}
}
prev.move_right(root, metrics) catch {};