diff --git a/build.zig b/build.zig index 60af927..37af278 100644 --- a/build.zig +++ b/build.zig @@ -502,6 +502,7 @@ pub fn build_exe( .{ .name = "EventHandler", .module = EventHandler_mod }, .{ .name = "input", .module = input_mod }, .{ .name = "thespian", .module = thespian_mod }, + .{ .name = "log", .module = log_mod }, .{ .name = "Buffer", .module = Buffer_mod }, .{ .name = "config", .module = config_mod }, }, diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index e20a1a0..79e5094 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -6,6 +6,7 @@ const std = @import("std"); const tp = @import("thespian"); const cbor = @import("cbor"); const builtin = @import("builtin"); +const log = @import("log"); const root = @import("soft_root").root; const input = @import("input"); @@ -15,8 +16,6 @@ const KeyEvent = input.KeyEvent; const SelectionStyle = @import("Buffer").Selection.Style; pub const CursorShape = @import("config").CursorShape; -const log = std.log.scoped(.keybind); - const parse_flow = @import("parse_flow.zig"); const parse_vim = @import("parse_vim.zig"); @@ -246,9 +245,11 @@ pub fn set_namespace(namespace_name: []const u8) LoadError!void { fn get_mode_binding_set(mode_name: []const u8, insert_command: []const u8) LoadError!*const BindingSet { const namespace = current_namespace(); var binding_set = namespace.get_mode(mode_name) orelse { - log.err("ERROR: mode not found: {s}", .{mode_name}); + const logger = log.logger("keybind"); + logger.print_err("get_namespace_mode", "ERROR: mode not found: {s}", .{mode_name}); var iter = namespace.modes.iterator(); - while (iter.next()) |entry| log.info("available modes: {s}", .{entry.key_ptr.*}); + while (iter.next()) |entry| logger.print("available modes: {s}", .{entry.key_ptr.*}); + logger.deinit(); return error.NotFound; }; binding_set.set_insert_command(insert_command); @@ -366,8 +367,11 @@ const Command = struct { fn execute_const(self: *const @This()) void { var buf: [2048]u8 = undefined; @memcpy(buf[0..self.args.len], self.args); - command.executeName(self.command, .{ .args = .{ .buf = buf[0..self.args.len] } }) catch |e| - log.err("ERROR: {s} {s}", .{ self.command, @errorName(e) }); + command.executeName(self.command, .{ .args = .{ .buf = buf[0..self.args.len] } }) catch |e| { + const logger = log.logger("keybind"); + logger.print_err("init/deinit_command", "ERROR: {s} {s}", .{ self.command, @errorName(e) }); + logger.deinit(); + }; } fn has_integer_argument(id: command.ID) bool { @@ -386,7 +390,9 @@ const Command = struct { switch (state) { .command => { if (token != .string) { - log.err("ERROR: invalid command token {any}", .{token}); + const logger = log.logger("keybind"); + logger.print_err("keybind.load", "ERROR: invalid command token {any}", .{token}); + logger.deinit(); return error.InvalidFormat; } command_ = try allocator.dupe(u8, token.string); @@ -398,7 +404,9 @@ const Command = struct { else => { const json = try std.json.Stringify.valueAlloc(allocator, token, .{}); defer allocator.free(json); - log.err("ERROR: invalid command argument '{s}'", .{json}); + const logger = log.logger("keybind"); + logger.print_err("keybind.load", "ERROR: invalid command argument '{s}'", .{json}); + logger.deinit(); return error.InvalidFormat; }, } @@ -530,22 +538,30 @@ const BindingSet = struct { _ = event; bindings: for (bindings) |entry| { if (entry.len < 2) { - log.err("ERROR: invalid binding definition {any}", .{entry}); + const logger = log.logger("keybind"); + logger.print_err("keybind.load", "ERROR: invalid binding definition {any}", .{entry}); + logger.deinit(); continue :bindings; } const keys = entry[0]; if (keys != .string) { - log.err("ERROR: invalid binding key definition {any}", .{keys}); + const logger = log.logger("keybind"); + logger.print_err("keybind.load", "ERROR: invalid binding key definition {any}", .{keys}); + logger.deinit(); continue :bindings; } const key_events = switch (self.syntax) { .flow => parse_flow.parse_key_events(allocator, keys.string) catch |e| { - log.err("ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message }); + const logger = log.logger("keybind"); + logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message }); + logger.deinit(); break; }, .vim => parse_vim.parse_key_events(allocator, keys.string) catch |e| { - log.err("ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message }); + const logger = log.logger("keybind"); + logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message }); + logger.deinit(); break; }, }; @@ -561,7 +577,9 @@ const BindingSet = struct { if (cmd_entry != .array) { const json = try std.json.Stringify.valueAlloc(allocator, cmd_entry, .{}); defer allocator.free(json); - log.err("ERROR: invalid command definition {s}", .{json}); + const logger = log.logger("keybind"); + logger.print_err("keybind.load", "ERROR: invalid command definition {s}", .{json}); + logger.deinit(); continue :bindings; } try cmds.append(allocator, try Command.load(allocator, cmd_entry.array.items)); @@ -775,7 +793,10 @@ const BindingSet = struct { input.key.left_shift, input.key.right_shift => return, else => {}, }; - log.info("{f} is unbound, press C-? for key hints", .{current_key_event_sequence_fmt()}); + + const logger = log.logger("keybind"); + defer logger.deinit(); + logger.print("C-? for key hints", .{}); } /// Retrieve bindings that will match a key event sequence @@ -879,15 +900,8 @@ const KeyEventSequenceFmt = struct { key_events: []const KeyEvent, pub fn format(self: @This(), writer: anytype) !void { - var first = true; - for (self.key_events) |key_event| { - if (first) { - first = false; - } else { - try writer.print(" ", .{}); - } - try writer.print("{f}", .{input.key_event_short_fmt(key_event)}); - } + for (self.key_events) |key_event| + try writer.print(" {f}", .{input.key_event_short_fmt(key_event)}); } }; @@ -971,7 +985,7 @@ test "match" { } test "json" { - var bindings: BindingSet = .{ .name = "test", .config_section = "test_section", .selection_style = .normal }; + var bindings: BindingSet = .{ .name = "test", .selection_style = .normal }; _ = try bindings.process_key_event(input.KeyEvent.from_key('j')); _ = try bindings.process_key_event(input.KeyEvent.from_key('k')); _ = try bindings.process_key_event(input.KeyEvent.from_key('g')); diff --git a/src/log.zig b/src/log.zig index 0be31e3..7561a73 100644 --- a/src/log.zig +++ b/src/log.zig @@ -260,11 +260,7 @@ pub fn std_log_function( const log_pid = std_log_pid orelse return; const prefix = "[" ++ comptime level.asText() ++ "] "; var buf: [max_log_message]u8 = undefined; - const fmt = switch (level) { - .warn, .debug => prefix ++ format, - .err, .info => format, - }; - const output = std.fmt.bufPrint(&buf, fmt, args) catch "MESSAGE TOO LARGE"; + const output = std.fmt.bufPrint(&buf, prefix ++ format, args) catch "MESSAGE TOO LARGE"; if (level == .err) { log_pid.send(.{ "log", "error", @tagName(scope), "std.log", "->", output }) catch {}; } else { diff --git a/src/main.zig b/src/main.zig index 5997395..e33201c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -30,7 +30,8 @@ pub const application_subtext = "a programmer's text editor"; pub const application_description = application_title ++ ": " ++ application_subtext; pub const std_options: std.Options = .{ - .log_level = if (builtin.mode == .Debug) .debug else .info, + // .log_level = if (builtin.mode == .Debug) .debug else .warn, + .log_level = if (builtin.mode == .Debug) .info else .warn, .logFn = log.std_log_function, }; diff --git a/src/shell.zig b/src/shell.zig index d82ecd4..b349395 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -206,7 +206,7 @@ const Process = struct { var buf: [1024]u8 = undefined; const json = self.argv.to_json(&buf) catch |e| return tp.exit_error(e, @errorReturnTrace()); if (self.handlers.log_execute) - self.logger.print("execute {s}", .{json}); + self.logger.print("shell: execute {s}", .{json}); self.sp = tp.subprocess.init(self.allocator, self.argv, module_name, self.stdin_behavior) catch |e| return tp.exit_error(e, @errorReturnTrace()); tp.receive(&self.receiver); } diff --git a/src/tui/keyhints.zig b/src/tui/keyhints.zig index 4c17a5a..577831f 100644 --- a/src/tui/keyhints.zig +++ b/src/tui/keyhints.zig @@ -12,13 +12,12 @@ var show_page: usize = 0; pub fn render_current_input_mode(allocator: std.mem.Allocator, select_mode: keybind.SelectMode, theme: *const Widget.Theme) void { const mode = tui.input_mode() orelse return; - const key_events = mode.current_key_event_sequence_bindings(allocator, select_mode) catch return; - const bindings = if (key_events.len > 0) - key_events - else - mode.current_bindings(allocator, select_mode) catch return; + const bindings = blk: { + const b = mode.current_key_event_sequence_bindings(allocator, select_mode) catch return; + break :blk if (b.len > 0) b else mode.current_bindings(allocator, select_mode) catch return; + }; defer allocator.free(bindings); - return render(mode, bindings, theme, if (key_events.len > 0) .no_key_event_prefix else .full); + return render(mode, bindings, theme, .full); } pub fn render_current_key_event_sequence(allocator: std.mem.Allocator, select_mode: keybind.SelectMode, theme: *const Widget.Theme) void { @@ -54,7 +53,7 @@ fn render(mode: *keybind.Mode, bindings: []const keybind.Binding, theme: *const const max_len = max_prefix_len + max_description_len + 2 + 2; const widget_style = tui.get_widget_style(widget_type); const scr = tui.screen(); - const max_screen_height = scr.h -| widget_style.padding.top -| widget_style.padding.bottom -| 3; + const max_screen_height = scr.h -| widget_style.padding.top -| widget_style.padding.bottom -| 1; const max_items = @min(bindings.len, max_screen_height); const page_size = max_screen_height; var top = show_page * page_size; @@ -91,22 +90,12 @@ fn render(mode: *keybind.Mode, bindings: []const keybind.Binding, theme: *const } if (widget_style.padding.top > 0) { top_layer_.cursor_move_yx(@intCast(0), @intCast(3)) catch return; - if (key_events.len > 0) { - _ = top_layer_.print("{s} {s}/{s} prefix: {s} {s}", .{ - widget_style.border.nib, - keybind.get_namespace(), - mode.bindings.config_section, - key_events, - widget_style.border.nie, - }) catch {}; - } else { - _ = top_layer_.print("{s} {s}/{s} {s}", .{ - widget_style.border.nib, - keybind.get_namespace(), - mode.bindings.config_section, - widget_style.border.nie, - }) catch {}; - } + _ = top_layer_.print("{s} {s}/{s} {s}", .{ + widget_style.border.nib, + keybind.get_namespace(), + mode.bindings.config_section, + widget_style.border.nie, + }) catch {}; } // workaround vaxis.Layer issue @@ -144,17 +133,14 @@ fn render(mode: *keybind.Mode, bindings: []const keybind.Binding, theme: *const break :blk writer.buffered(); }; plane.cursor_move_yx(@intCast(y), 0) catch break; - switch (render_mode) { - .no_key_event_prefix => _ = plane.print("{s}", .{keybind_txt[key_events.len..]}) catch {}, - .full => _ = plane.print(" {s}", .{keybind_txt}) catch {}, - } + _ = plane.print("{s}", .{keybind_txt[key_events.len..]}) catch {}; } plane.set_style(style_label); for (bindings[top..], 0..) |binding, y| { if (y >= max_items) break; - const padding = max_prefix_len + 3; + const padding = max_prefix_len + 2; const description = blk: { const id = binding.commands[0].command_id orelse diff --git a/src/tui/mode/helix.zig b/src/tui/mode/helix.zig index 4ecaca1..cceab4e 100644 --- a/src/tui/mode/helix.zig +++ b/src/tui/mode/helix.zig @@ -13,6 +13,8 @@ const Buffer = @import("Buffer"); const Cursor = Buffer.Cursor; const Selection = Buffer.Selection; +const Direction = enum { backwards, forwards }; + var commands: Commands = undefined; pub fn init() !void { @@ -242,62 +244,62 @@ const cmds_ = struct { pub const extend_line_below_meta: Meta = .{ .arguments = &.{.integer}, .description = "Select current line, if already selected, extend to next line" }; pub fn move_next_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, Editor.move_cursor_word_right_vim); + try move_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } pub const move_next_word_start_meta: Meta = .{ .description = "Move next word start", .arguments = &.{.integer} }; pub fn extend_next_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, Editor.move_cursor_word_right_vim); + try extend_to_word(ctx, Editor.move_cursor_word_right_vim, .forwards); } pub const extend_next_word_start_meta: Meta = .{ .description = "Extend next word start", .arguments = &.{.integer} }; pub fn move_next_long_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_right); + try move_to_word(ctx, move_cursor_long_word_right, .forwards); } pub const move_next_long_word_start_meta: Meta = .{ .description = "Move next long word start", .arguments = &.{.integer} }; pub fn extend_next_long_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_right); + try extend_to_word(ctx, move_cursor_long_word_right, .forwards); } pub const extend_next_long_word_start_meta: Meta = .{ .description = "Extend next long word start", .arguments = &.{.integer} }; pub fn move_prev_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_word_left_helix); + try move_to_word(ctx, move_cursor_word_left_helix, .backwards); } pub const move_prev_word_start_meta: Meta = .{ .description = "Move previous word start", .arguments = &.{.integer} }; pub fn extend_prev_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_word_left_helix); + try extend_to_word(ctx, move_cursor_word_left_helix, .backwards); } pub const extend_prev_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; pub fn move_prev_long_word_start(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_left); + try move_to_word(ctx, move_cursor_long_word_left, .backwards); } pub const move_prev_long_word_start_meta: Meta = .{ .description = "Move previous long word start", .arguments = &.{.integer} }; pub fn extend_prev_long_word_start(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_left); + try extend_to_word(ctx, move_cursor_long_word_left, .backwards); } pub const extend_prev_long_word_start_meta: Meta = .{ .description = "Extend previous word start", .arguments = &.{.integer} }; pub fn move_next_word_end(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_word_right_end_helix); + try move_to_word(ctx, move_cursor_word_right_end_helix, .forwards); } pub const move_next_word_end_meta: Meta = .{ .description = "Move next word end", .arguments = &.{.integer} }; pub fn extend_next_word_end(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_word_right_end_helix); + try extend_to_word(ctx, move_cursor_word_right_end_helix, .forwards); } pub const extend_next_word_end_meta: Meta = .{ .description = "Extend next word end", .arguments = &.{.integer} }; pub fn move_next_long_word_end(_: *void, ctx: Ctx) Result { - try move_to_word(ctx, move_cursor_long_word_right_end); + try move_to_word(ctx, move_cursor_long_word_right_end, .forwards); } pub const move_next_long_word_end_meta: Meta = .{ .description = "Move next long word end", .arguments = &.{.integer} }; pub fn extend_next_long_word_end(_: *void, ctx: Ctx) Result { - try extend_to_word(ctx, move_cursor_long_word_right_end); + try extend_to_word(ctx, move_cursor_long_word_right_end, .forwards); } pub const extend_next_long_word_end_meta: Meta = .{ .description = "Extend next long word end", .arguments = &.{.integer} }; @@ -526,7 +528,7 @@ fn match_bracket(root: Buffer.Root, cursel: *CurSel, ctx: command.Context, metri } } -fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const) command.Result { +fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const, direction: Direction) command.Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; const root = try ed.buf_root(); @@ -537,18 +539,45 @@ fn move_to_word(ctx: command.Context, move: Editor.cursor_operator_const) comman if (repeat > 1) ed.with_cursors_const_repeat(root, move, command.fmt(.{repeat - 1})) catch {}; for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { - cursel.selection = null; + var sel = Selection.from_cursor(&cursel.cursor); + const cur = sel.begin.test_at(root, is_not_whitespace_or_eol, ed.metrics); + if (direction == .backwards) { + sel.begin.move_left(root, ed.metrics) catch continue; + const prev = sel.begin.test_at(root, Editor.is_not_word_char, ed.metrics); + sel.begin = sel.end; + if (!cur or cur != prev) + sel.begin.move_right(root, ed.metrics) catch continue; + } else { + sel.end.move_right(root, ed.metrics) catch continue; + const next = sel.end.test_at(root, Editor.is_not_word_char, ed.metrics); + if (!cur and cur != next) + sel.begin = sel.end; + } + cursel.cursor = sel.end; + cursel.selection = sel; }; ed.with_selections_const_repeat(root, move, command.fmt(.{1})) catch {}; ed.clamp(); } -fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const) command.Result { +fn extend_to_word(ctx: command.Context, move: Editor.cursor_operator_const, _: Direction) command.Result { const mv = tui.mainview() orelse return; const ed = mv.get_active_editor() orelse return; const root = try ed.buf_root(); - ed.with_selections_const_repeat(root, move, ctx) catch {}; + var repeat: usize = 1; + _ = ctx.args.match(.{tp.extract(&repeat)}) catch false; + for (ed.cursels.items) |*cursel_| if (cursel_.*) |*cursel| { + const sel = cursel.enable_selection(root, ed.metrics); + const pivot: usize = if (sel.is_reversed()) sel.begin.col -| 1 else sel.begin.col; + var i: usize = repeat; + while (i > 0) : (i -= 1) { + try move(root, &sel.end, ed.metrics); + } + sel.begin.col = if (sel.is_reversed()) pivot +| 1 else pivot; + cursel.cursor = sel.end; + }; + ed.clamp(); } @@ -830,7 +859,6 @@ 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 { try Editor.move_cursor_right(root, cursor, metrics); Editor.move_cursor_right_until(root, cursor, Editor.is_word_boundary_right_vim, metrics); - try cursor.move_right(root, metrics); } fn move_cursor_to_char_left_beyond_eol(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics, ctx: command.Context) error{Stop}!void {