Compare commits

...

10 commits

5 changed files with 157 additions and 54 deletions

View file

@ -669,6 +669,22 @@ const Node = union(enum) {
return self.delete_bytes(sel.begin.row, pos, size, allocator, metrics) catch return error.Stop;
}
pub fn delete_range_char(self: *const Node, sel: Selection, allocator: Allocator, size_: ?*usize, metrics: Metrics) error{Stop}!struct { Root, ?u8 } {
var size: usize = 0;
defer if (size_) |p| {
p.* = size;
};
_ = self.get_range(sel, null, &size, null, metrics) catch return error.Stop;
const char = if (size == 1) blk: {
var result_buf: [6]u8 = undefined;
const result = self.get_range(sel, &result_buf, null, null, metrics) catch break :blk null;
break :blk (result orelse break :blk null)[0];
} else null;
const pos = try self.get_line_width_to_pos(sel.begin.row, sel.begin.col, metrics);
const root = self.delete_bytes(sel.begin.row, pos, size, allocator, metrics) catch return error.Stop;
return .{ root, char };
}
pub fn delete_bytes(self: *const Node, line: usize, pos_: usize, bytes: usize, allocator: Allocator, metrics_: Metrics) !Root {
const Ctx = struct {
allocator: Allocator,

View file

@ -3,6 +3,7 @@ const tp = @import("thespian");
const log = @import("log");
const cbor = @import("cbor");
pub var log_execute: bool = false;
pub var context_check: ?*const fn () void = null;
pub const ID = usize;
@ -69,15 +70,10 @@ pub fn Closure(comptime T: type) type {
}
pub fn register(self: *Self) !void {
if (command_names.get(self.vtbl.name)) |id| {
self.vtbl.id = id;
reAddCommand(&self.vtbl) catch |e| return log.err("cmd", "reAddCommand", e);
// log.print("cmd", "reAddCommand({s}) => {d}", .{ self.vtbl.name, self.vtbl.id });
} else {
self.vtbl.id = try addCommand(&self.vtbl);
command_names.put(self.vtbl.name, self.vtbl.id) catch |e| return log.err("cmd", "addCommand", e);
// log.print("cmd", "addCommand({s}) => {d}", .{ self.vtbl.name, self.vtbl.id });
}
if (command_names.get(self.vtbl.name)) |id|
reAddCommand(id, &self.vtbl) catch |e| return log.err("cmd", "reAddCommand", e)
else
addCommand(&self.vtbl);
}
pub fn unregister(self: *Self) void {
@ -100,21 +96,31 @@ pub var commands: CommandTable = .empty;
var command_names: std.StringHashMap(ID) = std.StringHashMap(ID).init(command_table_allocator);
const command_table_allocator = std.heap.c_allocator;
fn addCommand(cmd: *Vtable) !ID {
try commands.append(command_table_allocator, cmd);
return commands.items.len - 1;
fn assignCommandId(name: []const u8) ID {
commands.append(command_table_allocator, null) catch |e| std.debug.panic("assignCommandId: {t}", .{e});
const id = commands.items.len - 1;
command_names.put(name, id) catch |e| std.debug.panic("assignCommandId: {t}", .{e});
return id;
}
fn reAddCommand(cmd: *Vtable) !void {
if (commands.items[cmd.id] != null) return error.DuplicateCommand;
commands.items[cmd.id] = cmd;
fn addCommand(cmd: *Vtable) void {
commands.append(command_table_allocator, cmd) catch |e| std.debug.panic("addCommand: {t}", .{e});
const id = commands.items.len - 1;
cmd.id = id;
command_names.put(cmd.name, id) catch |e| std.debug.panic("assignCommandId: {t}", .{e});
}
fn reAddCommand(id: ID, cmd: *Vtable) !void {
cmd.id = id;
if (commands.items[id] != null) return error.DuplicateCommand;
commands.items[id] = cmd;
}
pub fn removeCommand(id: ID) void {
commands.items[id] = null;
}
pub fn execute(id: ID, ctx: Context) tp.result {
pub fn execute(id: ID, name: []const u8, ctx: Context) tp.result {
if (tp.env.get().enabled(tp.channel.debug)) trace: {
var iter = ctx.args.buf;
var len = cbor.decodeArrayHeader(&iter) catch break :trace;
@ -140,20 +146,26 @@ pub fn execute(id: ID, ctx: Context) tp.result {
}
if (context_check) |check| check();
if (id >= commands.items.len)
return tp.exit_fmt("CommandNotFound: {d}", .{id});
return notFoundError(id, name);
const cmd = commands.items[id];
if (cmd) |p| {
// var buf: [tp.max_message_size]u8 = undefined;
// log.print("cmd", "execute({s}) {s}", .{ p.name, ctx.args.to_json(&buf) catch "" }) catch |e| return tp.exit_error(e, @errorReturnTrace());
if (log_execute) {
var buf: [tp.max_message_size]u8 = undefined;
log.print("cmd", "execute({d}) {s} {s}", .{ id, p.name, if (ctx.args.buf.len > 0) ctx.args.to_json(&buf) catch "(error)" else "" });
}
return p.run(p, ctx);
} else {
return tp.exit_fmt("CommandNotAvailable: {d}", .{id});
return notFoundError(id, name);
}
}
pub fn get_id(name: []const u8) ?ID {
var id: ?ID = null;
return get_id_cache(name, &id);
const id = get_name_id(name);
return if (commands.items[id]) |_| id else null;
}
pub fn get_name_id(name: []const u8) ID {
return command_names.get(name) orelse assignCommandId(name);
}
pub fn get_name(id: ID) ?[]const u8 {
@ -164,19 +176,17 @@ pub fn get_name(id: ID) ?[]const u8 {
tp.trace(tp.channel.debug, .{ "command", "get_name", "null", id });
}
if (id >= commands.items.len) return null;
return (commands.items[id] orelse return null).name;
if (commands.items[id]) |cmd| return cmd.name;
var iter = command_names.iterator();
while (iter.next()) |kv| if (kv.value_ptr.* == id)
return kv.key_ptr.*;
return null;
}
pub fn get_id_cache(name: []const u8, id: *?ID) ?ID {
for (commands.items) |cmd| {
if (cmd) |p|
if (std.mem.eql(u8, p.name, name)) {
id.* = p.id;
return p.id;
};
}
tp.trace(tp.channel.debug, .{ "command", "get_id_cache", "failed", name });
return null;
pub fn get_id_cache(name: []const u8, cached_id: *?ID) ID {
const id = get_name_id(name);
cached_id.* = id;
return id;
}
pub fn get_description(id: ID) ?[]const u8 {
@ -201,14 +211,12 @@ const suppressed_errors = std.StaticStringMap(void).initComptime(.{
});
pub fn executeName(name: []const u8, ctx: Context) tp.result {
const id = get_id(name);
if (id) |id_| return execute(id_, ctx);
return notFoundError(name);
return execute(get_name_id(name), name, ctx);
}
pub fn notFoundError(name: []const u8) !void {
fn notFoundError(id: ID, name: []const u8) !void {
if (!suppressed_errors.has(name))
return tp.exit_fmt("CommandNotFound: {s}", .{name});
return tp.exit_fmt("CommandNotFound: {s}({d})", .{ name, id });
}
fn CmdDef(comptime T: type) type {

View file

@ -349,19 +349,17 @@ const Command = struct {
fn execute(self: *@This()) !void {
const id = self.command_id orelse
command.get_id_cache(self.command, &self.command_id) orelse {
return command.notFoundError(self.command);
};
command.get_id_cache(self.command, &self.command_id);
var buf: [2048]u8 = undefined;
@memcpy(buf[0..self.args.len], self.args);
if (integer_argument) |int_arg| {
if (cbor.match(self.args, .{}) catch false and has_integer_argument(id)) {
integer_argument = null;
try command.execute(id, command.fmt(.{int_arg}));
try command.execute(id, self.command, command.fmt(.{int_arg}));
return;
}
}
try command.execute(id, .{ .args = .{ .buf = buf[0..self.args.len] } });
try command.execute(id, self.command, .{ .args = .{ .buf = buf[0..self.args.len] } });
}
fn execute_const(self: *const @This()) void {
@ -662,11 +660,9 @@ const BindingSet = struct {
if (enable_insert_events)
self.send_insert_event(globals.insert_command, globals.input_buffer.items);
const id = globals.insert_command_id orelse
command.get_id_cache(globals.insert_command, &globals.insert_command_id) orelse {
return tp.exit_error(error.InputTargetNotFound, null);
};
command.get_id_cache(globals.insert_command, &globals.insert_command_id);
if (!builtin.is_test) {
try command.execute(id, command.fmt(.{globals.input_buffer.items}));
try command.execute(id, globals.insert_command, command.fmt(.{globals.input_buffer.items}));
}
}
}

View file

@ -258,6 +258,26 @@ pub const CurSel = struct {
}
};
pub const TriggerEvent = enum {
insert,
delete,
};
pub const TriggerSymbol = struct {
char: u8,
command: command.ID,
pub fn cborEncode(self: @This(), writer: *std.Io.Writer) std.io.Writer.Error!void {
try cbor.writeArrayHeader(writer, 2);
try cbor.writeValue(writer, self.char);
try cbor.writeValue(writer, self.command);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return try cbor.matchValue(iter, .{ cbor.extract(&self.char), cbor.extract(&self.command) });
}
};
pub const Diagnostic = struct {
source: []const u8,
code: []const u8,
@ -379,6 +399,9 @@ pub const Editor = struct {
syntax_last_rendered_root: ?Buffer.Root = null,
syntax_incremental_reparse: bool = false,
insert_triggers: std.ArrayList(TriggerSymbol) = .empty,
delete_triggers: std.ArrayList(TriggerSymbol) = .empty,
style_cache: ?StyleCache = null,
style_cache_theme: []const u8 = "",
@ -465,7 +488,7 @@ pub const Editor = struct {
}
pub fn write_state(self: *const Self, writer: *std.Io.Writer) !void {
try cbor.writeArrayHeader(writer, 10);
try cbor.writeArrayHeader(writer, 12);
try cbor.writeValue(writer, self.file_path orelse "");
try cbor.writeValue(writer, self.last_find_query orelse "");
try cbor.writeValue(writer, self.enable_format_on_save);
@ -473,6 +496,8 @@ pub const Editor = struct {
try cbor.writeValue(writer, self.tab_width);
try cbor.writeValue(writer, self.indent_mode);
try cbor.writeValue(writer, self.syntax_no_render);
try cbor.writeValue(writer, self.insert_triggers);
try cbor.writeValue(writer, self.delete_triggers);
if (self.find_history) |history| {
try cbor.writeArrayHeader(writer, history.items.len);
for (history.items) |item|
@ -503,6 +528,8 @@ pub const Editor = struct {
tp.extract(&self.tab_width),
tp.extract(&self.indent_mode),
tp.extract(&self.syntax_no_render),
cbor.extractAlloc(&self.insert_triggers, self.allocator),
cbor.extractAlloc(&self.delete_triggers, self.allocator),
tp.extract_cbor(&find_history),
tp.extract_cbor(&view_cbor),
tp.extract_cbor(&cursels_cbor),
@ -566,6 +593,7 @@ pub const Editor = struct {
.enable_terminal_cursor = tui.config().enable_terminal_cursor,
.render_whitespace = tui.config().whitespace_mode,
};
self.add_default_symbol_triggers();
}
fn deinit(self: *Self) void {
@ -575,6 +603,8 @@ pub const Editor = struct {
for (self.diagnostics.items) |*d| d.deinit(self.allocator);
self.diagnostics.deinit(self.allocator);
self.completions.deinit(self.allocator);
self.insert_triggers.deinit(self.allocator);
self.delete_triggers.deinit(self.allocator);
if (self.syntax) |syn| syn.destroy(tui.query_cache());
self.cancel_all_tabstops();
self.cursels.deinit(self.allocator);
@ -2243,8 +2273,9 @@ pub const Editor = struct {
cursel.cursor = sel.begin;
cursel.disable_selection_normal();
var size: usize = 0;
const root_ = try root.delete_range(sel, allocator, &size, self.metrics);
const root_, const trigger_char = try root.delete_range_char(sel, allocator, &size, self.metrics);
self.nudge_delete(sel, cursel, size);
if (trigger_char) |char| self.run_triggers(char, .delete);
return root_;
}
@ -2823,6 +2854,7 @@ pub const Editor = struct {
cursor.row, cursor.col, root_ = try root_.insert_chars(cursor.row, cursor.col, s, allocator, self.metrics);
cursor.target = cursor.col;
self.nudge_insert(.{ .begin = begin, .end = cursor.* }, cursel, s.len);
if (s.len == 1) self.run_triggers(s[0], .insert);
return root_;
}
@ -5702,7 +5734,7 @@ pub const Editor = struct {
pub fn goto_next_diagnostic(self: *Self, _: Context) Result {
if (self.diagnostics.items.len == 0) {
if (command.get_id("goto_next_file")) |id|
return command.execute(id, .{});
return command.execute(id, "goto_next_file", .{});
return;
}
self.sort_diagnostics();
@ -5718,7 +5750,7 @@ pub const Editor = struct {
pub fn goto_prev_diagnostic(self: *Self, _: Context) Result {
if (self.diagnostics.items.len == 0) {
if (command.get_id("goto_prev_file")) |id|
return command.execute(id, .{});
return command.execute(id, "goto_prev_file", .{});
return;
}
self.sort_diagnostics();
@ -6184,6 +6216,52 @@ pub const Editor = struct {
self.need_render();
}
fn get_event_triggers(self: *Self, event: TriggerEvent) *std.ArrayList(TriggerSymbol) {
return switch (event) {
.insert => &self.insert_triggers,
.delete => &self.delete_triggers,
};
}
fn add_default_symbol_triggers(self: *Self) void {
const id = command.get_name_id("completion");
self.add_symbol_trigger('.', id, .insert) catch {};
}
pub fn add_symbol_trigger(self: *Self, char: u8, command_: command.ID, event: TriggerEvent) error{OutOfMemory}!void {
(try self.get_event_triggers(event).addOne(self.allocator)).* = .{ .char = char, .command = command_ };
}
pub fn remove_symbol_trigger(self: *Self, char: u8, command_: command.ID, event: TriggerEvent) bool {
const triggers = self.get_event_triggers(event);
for (triggers.items, 0..) |item, i| if (item.char == char and item.command == command_) {
_ = triggers.orderedRemove(i);
return true;
};
return false;
}
pub fn run_triggers(self: *Self, char: u8, event: TriggerEvent) void {
switch (char) {
'\n', '\t', ' ' => return,
else => {},
}
for (self.get_event_triggers(event).items) |item| if (item.char == char) {
if (command.log_execute)
self.logger.print("trigger: {t} '{c}' {?s}({d})", .{ event, char, command.get_name(item.command), item.command });
tp.self_pid().send(.{ "cmd", "run_trigger", .{ item.command, [_]u8{char} } }) catch {};
};
}
pub fn run_trigger(_: *Self, ctx: Context) Result {
var cmd: command.ID = undefined;
var trigger_char: []const u8 = undefined;
if (!try ctx.args.match(.{ tp.extract(&cmd), tp.extract(&trigger_char) }))
return error.InvalidRunTriggerArgument;
tp.self_pid().send(.{ "cmd", cmd, .{trigger_char} }) catch {};
}
pub const run_trigger_meta: Meta = .{ .arguments = &.{ .integer, .string } };
pub fn add_completion(self: *Self, row: usize, col: usize, is_incomplete: bool, msg: tp.message) Result {
if (!(row == self.completion_row and col == self.completion_col)) {
self.completions.clearRetainingCapacity();

View file

@ -416,7 +416,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
if (try m.match(.{ "cmd", tp.extract(&cmd) }))
return command.executeName(cmd, ctx) catch |e| self.logger.err(cmd, e);
if (try m.match(.{ "cmd", tp.extract(&cmd_id) }))
return command.execute(cmd_id, ctx) catch |e| self.logger.err("command", e);
return command.execute(cmd_id, command.get_name(cmd_id) orelse "(unknown)", ctx) catch |e| self.logger.err("command", e);
var arg: []const u8 = undefined;
@ -426,7 +426,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
}
if (try m.match(.{ "cmd", tp.extract(&cmd_id), tp.extract_cbor(&arg) })) {
ctx.args = .{ .buf = arg };
return command.execute(cmd_id, ctx) catch |e| self.logger.err("command", e);
return command.execute(cmd_id, command.get_name(cmd_id) orelse "(unknown)", ctx) catch |e| self.logger.err("command", e);
}
if (try m.match(.{"quit"})) {
project_manager.shutdown();
@ -736,7 +736,7 @@ fn dispatch_event(ctx: *anyopaque, cbor_msg: []const u8) void {
fn handle_system_clipboard(self: *Self, text: []const u8) !void {
if (command.get_id("mini_mode_paste")) |id|
return command.execute(id, command.fmt(.{text}));
return command.execute(id, "mini_mode_paste", command.fmt(.{text}));
{
const text_ = try clipboard_system_clipboard_text(self.allocator);
@ -1127,6 +1127,11 @@ const cmds = struct {
}
pub const toggle_keybind_hints_meta: Meta = .{ .description = "Toggle keybind hints" };
pub fn toggle_command_logging(_: *Self, _: Ctx) Result {
command.log_execute = !command.log_execute;
}
pub const toggle_command_logging_meta: Meta = .{ .description = "Toggle logging of executed commands" };
pub fn scroll_keybind_hints(_: *Self, _: Ctx) Result {
@import("keyhints.zig").scroll();
}