Compare commits

...

24 commits

Author SHA1 Message Date
146a8e8afb
fix: reduce horizontal space waste in command palette 2025-07-31 14:30:16 +02:00
9793bcdd9a
feat: persist syntax_no_render in editor state 2025-07-31 10:11:53 +02:00
666d30df3b
fix: make unindent_cursor work correctly in indent_mode tabs 2025-07-30 20:04:26 +02:00
ed1fe30e74
feat: make indent_size always equal to tab_width in indent_mode tabs 2025-07-30 20:03:12 +02:00
4100585b03
feat: make smart_insert_line and friends follow indent_mode 2025-07-30 19:37:28 +02:00
3abfd6555e
feat: make indent_cursor follow indent_mode and insert tabs 2025-07-30 19:17:52 +02:00
a74c0ecf46
feat: add indent_mode detection (auto mode) 2025-07-30 19:16:27 +02:00
9774b513d4
fix: update buffer file type in set_type
This fixes the buffer file type getting lost when switching buffers
if the file type was set with set_type.
2025-07-30 18:50:42 +02:00
196f516724
feat: save and restore indent_mode to editor state 2025-07-30 18:28:04 +02:00
910c69183d
feat: add indent_mode config option 2025-07-30 18:25:48 +02:00
cf6c9455c7
feat: update git status on focus_in events 2025-07-29 17:24:43 +02:00
488efd4605
fix: hint generally usable keybinds on home screen (flow mode) 2025-07-29 10:52:19 +02:00
6f82b4aaf3
feat: refresh branch status on file state change events 2025-07-29 10:29:06 +02:00
bfa851e886
feat: add back single key keybinds on home screen (flow mode) 2025-07-29 10:28:40 +02:00
a734a008e1
fix: refresh git branch status on project switch 2025-07-29 10:12:15 +02:00
73d118dcee
refactor: reduce event matching overhead in filestate widget 2025-07-29 10:11:16 +02:00
0ce522828d
build: update thespian for new cbor 2025-07-23 12:25:43 +02:00
54eb30468a
build: update libvaxis 2025-07-23 12:00:22 +02:00
aeb734ba64
fix: respond correctly to unsupported requests from language-servers
closes #276
2025-07-20 18:43:02 +02:00
1e33d128e7
feat: add Project.unsupported_lsp_request() method 2025-07-20 18:42:24 +02:00
996ec70e42
feat: add LSP client support for error responses to requests 2025-07-20 18:41:12 +02:00
5d256413da
refactor: dynamically allocate LSP client handles 2025-07-19 00:05:41 +02:00
efdad96054
refactor: improve create pattern to avoid leaks 2025-07-19 00:03:30 +02:00
de68c1a5d4
fix: remove run_async from flow mode add_cursor_all_matches binding
The extra run_async prevents the keybind hint from matching the
add_cursor_all_matches command. Not sure why it was bound to run_async
anyway so I'll just remove it.
2025-07-18 10:46:02 +02:00
55 changed files with 395 additions and 117 deletions

View file

@ -15,8 +15,8 @@
.hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK",
},
.thespian = .{
.url = "https://github.com/neurocyte/thespian/archive/ccdcbbff09f945eec063ebf889581db3e1312107.tar.gz",
.hash = "thespian-0.0.1-owFOjlgaBgCqc3FCnB4Xyg8-9jyIDWgHSJMGx_nt5Kcc",
.url = "git+https://github.com/neurocyte/thespian#f2980d3a747abdf0d18a01596dd8b953dd3e6243",
.hash = "thespian-0.0.1-owFOjk0aBgC8w9ibeiVdhftyEIaVIHCnubsJWfkktE8v",
},
.themes = .{
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz",
@ -27,8 +27,8 @@
.hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX",
},
.vaxis = .{
.url = "https://github.com/neurocyte/libvaxis/archive/6137cb4c44a7350996f0946a069739e5075d1f23.tar.gz",
.hash = "vaxis-0.1.0-BWNV_HwOCQCw5wTV63hQGSc1QJzsNcytH6sGf1GBc0hP",
.url = "git+https://github.com/neurocyte/libvaxis?ref=main#846ddb8bf483e8a7eb25628d6c34ba7e781155b6",
.hash = "vaxis-0.5.1-BWNV_AsQCQDvfb-li1CZEOBG_YsteinP9qI-PpV47-jf",
},
.zeit = .{
.url = "https://github.com/rockorager/zeit/archive/8fd203f85f597f16e0a525c1f1ca1e0bffded809.tar.gz",

View file

@ -118,6 +118,7 @@ pub fn send(self: Self, from_: tp.pid_ref, m: tp.message) tp.result {
pub fn empty(allocator: Allocator) !Self {
const child: type = struct {};
const widget = try allocator.create(child);
errdefer allocator.destroy(widget);
widget.* = .{};
return .{
.ptr = widget,

View file

@ -17,22 +17,34 @@ const OutOfMemoryError = error{OutOfMemory};
const SendError = error{SendFailed};
const SpawnError = error{ThespianSpawnFailed};
pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!Self {
return .{ .allocator = allocator, .pid = try Process.create(allocator, project, cmd) };
pub fn open(
allocator: std.mem.Allocator,
project: []const u8,
cmd: tp.message,
) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!*const Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.pid = try Process.create(allocator, project, cmd),
};
return self;
}
pub fn deinit(self: Self) void {
pub fn deinit(self: *const Self) void {
self.pid.send(.{"close"}) catch {};
self.pid.deinit();
self.allocator.destroy(self);
}
pub fn term(self: Self) void {
pub fn term(self: *const Self) void {
self.pid.send(.{"term"}) catch {};
self.pid.deinit();
self.allocator.destroy(self);
}
pub fn send_request(
self: Self,
self: *const Self,
allocator: std.mem.Allocator,
method: []const u8,
m: anytype,
@ -44,17 +56,56 @@ pub fn send_request(
return RequestContext(@TypeOf(ctx)).send(allocator, self.pid.ref(), ctx, tp.message.fmt(.{ "REQ", method, cb.items }));
}
pub fn send_notification(self: Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void {
pub fn send_notification(self: *const Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void {
var cb = std.ArrayList(u8).init(self.allocator);
defer cb.deinit();
try cbor.writeValue(cb.writer(), m);
return self.send_notification_raw(method, cb.items);
}
pub fn send_notification_raw(self: Self, method: []const u8, cb: []const u8) SendError!void {
pub fn send_notification_raw(self: *const Self, method: []const u8, cb: []const u8) SendError!void {
self.pid.send(.{ "NTFY", method, cb }) catch return error.SendFailed;
}
pub const ErrorCode = enum(i32) {
// Defined by JSON-RPC
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// Defined by LSP
RequestFailed = -32803,
ServerCancelled = -32802,
ContentModified = -32801,
RequestCancelled = -32800,
};
pub fn send_response(allocator: std.mem.Allocator, to: tp.pid_ref, cbor_id: []const u8, result: anytype) (SendError || OutOfMemoryError)!void {
var cb = std.ArrayList(u8).init(allocator);
defer cb.deinit();
const writer = cb.writer();
try cbor.writeArrayHeader(writer, 3);
try cbor.writeValue(writer, "RSP");
try writer.writeAll(cbor_id);
try cbor.writeValue(cb.writer(), result);
to.send_raw(.{ .buf = cb.items }) catch return error.SendFailed;
}
pub fn send_error_response(allocator: std.mem.Allocator, to: tp.pid_ref, cbor_id: []const u8, code: ErrorCode, message: []const u8) (SendError || OutOfMemoryError)!void {
var cb = std.ArrayList(u8).init(allocator);
defer cb.deinit();
const writer = cb.writer();
try cbor.writeArrayHeader(writer, 4);
try cbor.writeValue(writer, "ERR");
try writer.writeAll(cbor_id);
try cbor.writeValue(cb.writer(), code);
try cbor.writeValue(cb.writer(), message);
to.send_raw(.{ .buf = cb.items }) catch return error.SendFailed;
}
pub fn close(self: *Self) void {
self.deinit();
}
@ -144,6 +195,7 @@ const Process = struct {
return error.InvalidLspCommand;
}
const self = try allocator.create(Process);
errdefer allocator.destroy(self);
var sp_tag_ = std.ArrayList(u8).init(allocator);
defer sp_tag_.deinit();
try sp_tag_.appendSlice(tag);
@ -239,6 +291,8 @@ const Process = struct {
var err: []const u8 = "";
var code: u32 = 0;
var cbor_id: []const u8 = "";
var error_code: ErrorCode = undefined;
var message: []const u8 = "";
if (try cbor.match(m.buf, .{ "REQ", "initialize", tp.extract(&bytes) })) {
try self.send_request(from, "initialize", bytes);
@ -249,6 +303,8 @@ const Process = struct {
}
} else if (try cbor.match(m.buf, .{ "RSP", tp.extract_cbor(&cbor_id), tp.extract_cbor(&bytes) })) {
try self.send_response(cbor_id, bytes);
} else if (try cbor.match(m.buf, .{ "ERR", tp.extract_cbor(&cbor_id), tp.extract(&error_code), tp.extract(&message) })) {
try self.send_error_response(cbor_id, error_code, message);
} else if (try cbor.match(m.buf, .{ "NTFY", "initialized", tp.extract(&bytes) })) {
self.state = .running;
try self.send_notification("initialized", bytes);
@ -469,6 +525,39 @@ const Process = struct {
self.write_log("### SEND response:\n{s}\n###\n", .{output.items});
}
fn send_error_response(self: *Process, cbor_id: []const u8, error_code: ErrorCode, message: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
try cbor.writeValue(msg_writer, "id");
try msg_writer.writeAll(cbor_id);
try cbor.writeValue(msg_writer, "error");
try cbor.writeMapHeader(msg_writer, 2);
try cbor.writeValue(msg_writer, "code");
try cbor.writeValue(msg_writer, @intFromEnum(error_code));
try cbor.writeValue(msg_writer, "message");
try cbor.writeValue(msg_writer, message);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
const writer = output.writer();
const terminator = "\r\n";
const content_length = json.len + terminator.len;
try writer.print("Content-Length: {d}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n", .{content_length});
_ = try writer.write(json);
_ = try writer.write(terminator);
sp.send(output.items) catch return error.SendFailed;
self.write_log("### SEND error response:\n{s}\n###\n", .{output.items});
}
fn send_notification(self: *Process, method: []const u8, params_cb: []const u8) Error!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;

View file

@ -19,8 +19,8 @@ files: std.ArrayListUnmanaged(File) = .empty,
pending: std.ArrayListUnmanaged(File) = .empty,
longest_file_path: usize = 0,
open_time: i64,
language_servers: std.StringHashMap(LSP),
file_language_server: std.StringHashMap(LSP),
language_servers: std.StringHashMap(*const LSP),
file_language_server: std.StringHashMap(*const LSP),
tasks: std.ArrayList(Task),
persistent: bool = false,
logger: log.Logger,
@ -74,8 +74,8 @@ pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Sel
.allocator = allocator,
.name = try allocator.dupe(u8, name),
.open_time = std.time.milliTimestamp(),
.language_servers = std.StringHashMap(LSP).init(allocator),
.file_language_server = std.StringHashMap(LSP).init(allocator),
.language_servers = std.StringHashMap(*const LSP).init(allocator),
.file_language_server = std.StringHashMap(*const LSP).init(allocator),
.tasks = std.ArrayList(Task).init(allocator),
.logger = log.logger("project"),
.logger_lsp = log.logger("lsp"),
@ -231,6 +231,8 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
InvalidMapType,
InvalidUnion,
}!void {
tp.trace(tp.channel.debug, .{"restore_state_v0"});
defer self.sort_files_by_mtime();
@ -261,11 +263,14 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{
}
}
fn get_language_server_instance(self: *Self, language_server: []const u8) StartLspError!LSP {
fn get_language_server_instance(self: *Self, language_server: []const u8) StartLspError!*const LSP {
if (self.language_servers.get(language_server)) |lsp| {
if (!lsp.pid.expired()) return lsp;
lsp.deinit();
_ = self.language_servers.remove(language_server);
if (lsp.pid.expired()) {
_ = self.language_servers.remove(language_server);
lsp.deinit();
} else {
return lsp;
}
}
const lsp = try LSP.open(self.allocator, self.name, .{ .buf = language_server });
errdefer lsp.deinit();
@ -279,7 +284,7 @@ fn get_language_server_instance(self: *Self, language_server: []const u8) StartL
return lsp;
}
fn get_or_start_language_server(self: *Self, file_path: []const u8, language_server: []const u8) StartLspError!LSP {
fn get_or_start_language_server(self: *Self, file_path: []const u8, language_server: []const u8) StartLspError!*const LSP {
const lsp = self.file_language_server.get(file_path) orelse blk: {
const new_lsp = try self.get_language_server_instance(language_server);
const key = try self.allocator.dupe(u8, file_path);
@ -289,7 +294,7 @@ fn get_or_start_language_server(self: *Self, file_path: []const u8, language_ser
return lsp;
}
fn get_language_server(self: *Self, file_path: []const u8) LspError!LSP {
fn get_language_server(self: *Self, file_path: []const u8) LspError!*const LSP {
const lsp = self.file_language_server.get(file_path) orelse return error.NoLsp;
if (lsp.pid.expired()) {
if (self.file_language_server.fetchRemove(file_path)) |kv|
@ -1491,26 +1496,19 @@ pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void {
pub fn register_capability(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void {
_ = params_cb;
return self.send_lsp_response(from, cbor_id, null);
return LSP.send_response(self.allocator, from, cbor_id, null) catch error.ClientFailed;
}
pub fn workDoneProgress_create(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void {
_ = params_cb;
return self.send_lsp_response(from, cbor_id, null);
return LSP.send_response(self.allocator, from, cbor_id, null) catch error.ClientFailed;
}
pub fn send_lsp_response(self: *Self, from: tp.pid_ref, cbor_id: []const u8, result: anytype) ClientError!void {
var cb = std.ArrayList(u8).init(self.allocator);
defer cb.deinit();
const writer = cb.writer();
try cbor.writeArrayHeader(writer, 3);
try cbor.writeValue(writer, "RSP");
try writer.writeAll(cbor_id);
try cbor.writeValue(cb.writer(), result);
from.send_raw(.{ .buf = cb.items }) catch return error.ClientFailed;
pub fn unsupported_lsp_request(self: *Self, from: tp.pid_ref, cbor_id: []const u8, method: []const u8) ClientError!void {
return LSP.send_error_response(self.allocator, from, cbor_id, LSP.ErrorCode.MethodNotFound, method) catch error.ClientFailed;
}
fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8, language_server: []const u8) !void {
fn send_lsp_init_request(self: *Self, lsp: *const LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8, language_server: []const u8) !void {
const handler: struct {
language_server: []const u8,
lsp: LSP,

View file

@ -159,6 +159,7 @@ pub const Leaf = struct {
if (piece.len == 0)
return if (!bol and !eol) &empty_leaf else if (bol and !eol) &empty_bol_leaf else if (!bol and eol) &empty_eol_leaf else &empty_line_leaf;
const node = try allocator.create(Node);
errdefer allocator.destroy(node);
node.* = .{ .leaf = .{ .buf = piece, .bol = bol, .eol = eol } };
return node;
}
@ -267,6 +268,7 @@ const Node = union(enum) {
fn new(allocator: Allocator, l: *const Node, r: *const Node) !*const Node {
const node = try allocator.create(Node);
errdefer allocator.destroy(node);
const l_weights_sum = l.weights_sum();
var weights_sum_ = Weights{};
weights_sum_.add(l_weights_sum);
@ -1065,6 +1067,7 @@ const Node = union(enum) {
pub fn create(allocator: Allocator) error{OutOfMemory}!*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
const arena_a = if (builtin.is_test) allocator else std.heap.page_allocator;
self.* = .{
.arena = std.heap.ArenaAllocator.init(arena_a),

View file

@ -26,6 +26,7 @@ limit_auto_save_file_types: ?[]const []const u8 = null, // null means *all*
indent_size: usize = 4,
tab_width: usize = 8,
indent_mode: IndentMode = .auto,
top_bar: []const u8 = "tabs",
bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer",
@ -48,3 +49,9 @@ pub const LineNumberMode = enum {
relative,
absolute,
};
pub const IndentMode = enum {
auto,
spaces,
tabs,
};

View file

@ -66,6 +66,7 @@ const Process = struct {
pub fn create() !tp.pid {
const self = try allocator.create(Process);
errdefer allocator.destroy(self);
self.* = .{
.receiver = Receiver.init(Process.receive, self),
};

View file

@ -89,8 +89,8 @@
["ctrl+shift+d", "dupe_down"],
["ctrl+shift+z", "redo"],
["ctrl+shift+w", "close_file_without_saving"],
["ctrl+shift+l", "run_async", "add_cursor_all_matches"],
["ctrl+shift+i", "run_async", "toggle_inspector_view"],
["ctrl+shift+l", "add_cursor_all_matches"],
["ctrl+shift+i", "toggle_inspector_view"],
["ctrl+shift+m", "show_diagnostics"],
["ctrl+shift+enter", "smart_insert_line_before"],
["ctrl+shift+end", "select_buffer_end"],
@ -249,6 +249,19 @@
"inherit": "project",
"on_match_failure": "ignore",
"press": [
["ctrl+e", "find_file"],
["f", "find_file"],
["e", "find_file"],
["ctrl+shift+n", "create_new_file"],
["n", "create_new_file"],
["ctrl+o", "open_file"],
["o", "open_file"],
["ctrl+r", "open_recent_project"],
["r", "open_recent_project"],
["ctrl+shift+p", "open_command_palette"],
["p", "open_command_palette"],
["t", "change_theme"],
["a", "add_task"],
["c", "open_config"],
["g", "open_gui_config"],
["k", "open_keybind_config"],
@ -256,6 +269,9 @@
["ctrl+f ctrl+f ctrl+f ctrl+f ctrl+f", "home_sheeran"],
["ctrl+shift+r", "restart"],
["f6", "open_config"],
["v", "open_version_info"],
["ctrl+q", "quit"],
["q", "quit"],
["up", "home_menu_up"],
["down", "home_menu_down"],
["enter", "home_menu_activate"]

View file

@ -65,7 +65,7 @@ const Handler = struct {
bindings: *const BindingSet,
fn create(mode_name: []const u8, allocator: std.mem.Allocator, opts: anytype) !Mode {
const self: *@This() = try allocator.create(@This());
const self = try allocator.create(@This());
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,

View file

@ -263,6 +263,7 @@ const Process = struct {
fn create() SpawnError!tp.pid {
const allocator = std.heap.c_allocator;
const self = try allocator.create(Process);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.parent = tp.self_pid().clone(),
@ -619,11 +620,11 @@ const Process = struct {
project.register_capability(from, cbor_id, params_cb)
else if (std.mem.eql(u8, method, "window/workDoneProgress/create"))
project.workDoneProgress_create(from, cbor_id, params_cb)
else blk: {
else {
const params = try cbor.toJsonAlloc(self.allocator, params_cb);
defer self.allocator.free(params);
self.logger.print_err("lsp", "unsupported LSP request: {s} -> {s}", .{ method, params });
break :blk error.Unsupported;
self.logger.print("unsupported LSP request: {s} -> {s}", .{ method, params });
project.unsupported_lsp_request(from, cbor_id, method) catch {};
};
}
@ -755,6 +756,7 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_
fn spawn_link(allocator: std.mem.Allocator, parent: tp.pid_ref, project: *Project, max: usize, path: []const u8) (SpawnError || std.fs.Dir.OpenError)!void {
const self = try allocator.create(path_files);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.project_name = try allocator.dupe(u8, project.name),

View file

@ -58,6 +58,8 @@ pub const Error = error{
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
InvalidMapType,
InvalidUnion,
} || std.Thread.SpawnError;
pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self {
@ -180,7 +182,7 @@ fn handleSegfaultPosixNoAbort(sig: i32, info: *const std.posix.siginfo_t, ctx_pt
pub fn run(self: *Self) Error!void {
self.vx.sgr = .legacy;
self.vx.conpty_hacks = true;
self.vx.enable_workarounds = true;
panic_cleanup = .{ .allocator = self.allocator, .tty = &self.tty, .vx = &self.vx };
if (!self.no_alternate) self.vx.enterAltScreen(self.tty.anyWriter()) catch return error.TtyWriteError;
@ -639,7 +641,7 @@ const Loop = struct {
switch (builtin.os.tag) {
.windows => {
var parser: vaxis.Parser = .{
.graphemes = &self.vaxis.unicode.graphemes,
.grapheme_data = &self.vaxis.unicode.width_data.graphemes,
};
const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator");
while (!self.should_quit) {
@ -648,7 +650,7 @@ const Loop = struct {
},
else => {
var parser: vaxis.Parser = .{
.graphemes = &self.vaxis.unicode.graphemes,
.grapheme_data = &self.vaxis.unicode.width_data.graphemes,
};
const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator");

View file

@ -36,6 +36,8 @@ pub const Error = error{
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
InvalidMapType,
InvalidUnion,
} || std.Thread.SpawnError;
pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" });

View file

@ -85,6 +85,7 @@ const Process = struct {
pub fn create(allocator: std.mem.Allocator, query: []const u8, tag: [:0]const u8, stdin_behavior: std.process.Child.StdIo) !tp.pid {
const self = try allocator.create(Process);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.query = try allocator.dupe(u8, query),

View file

@ -45,6 +45,7 @@ const Process = struct {
pub fn create(allocator: std.mem.Allocator) Error!tp.pid {
const self = try allocator.create(Process);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.parent = tp.self_pid().clone(),

View file

@ -27,6 +27,8 @@ pub const Error = error{
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
InvalidMapType,
InvalidUnion,
};
pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void;
@ -156,6 +158,7 @@ const Process = struct {
return error.InvalidShellArg0;
const self = try allocator.create(Process);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.argv = argv,

View file

@ -62,6 +62,7 @@ pub const Error = CacheError || QueryParseError || QuerySerializeError;
pub fn create(allocator: std.mem.Allocator, opts: struct { lock: bool = false }) !*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.mutex = if (opts.lock) .{} else null,

View file

@ -29,9 +29,13 @@ tree: ?*treez.Tree = null,
pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
const query = try query_cache.get(file_type, .highlights);
errdefer query_cache.release(query, .highlights);
const errors_query = try query_cache.get(file_type, .errors);
errdefer query_cache.release(errors_query, .highlights);
const injections = try query_cache.get(file_type, .injections);
errdefer if (injections) |injections_| query_cache.release(injections_, .injections);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
@ -40,7 +44,6 @@ pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *Q
.errors_query = errors_query,
.injections = injections,
};
errdefer self.destroy(query_cache);
try self.parser.setLanguage(self.lang);
return self;
}
@ -58,6 +61,7 @@ pub fn static_create_guess_file_type_static(allocator: std.mem.Allocator, conten
pub fn destroy(self: *Self, query_cache: *QueryCache) void {
if (self.tree) |tree| tree.destroy();
query_cache.release(self.query, .highlights);
query_cache.release(self.errors_query, .highlights);
if (self.injections) |injections| query_cache.release(injections, .injections);
self.parser.destroy();
self.allocator.destroy(self);

View file

@ -285,6 +285,7 @@ pub const DeserializeError = error{
pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } {
var arena = try allocator.create(std.heap.ArenaAllocator);
errdefer allocator.destroy(arena);
arena.* = std.heap.ArenaAllocator.init(allocator);
errdefer arena.deinit();
const query = try arena.allocator().create(TSQuery);

View file

@ -48,6 +48,7 @@ pub fn Options(context: type) type {
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) error{OutOfMemory}!*State(ctx_type) {
const Self = State(ctx_type);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent);
errdefer n.deinit();
self.* = .{
@ -57,6 +58,7 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
.opts = opts,
};
self.opts.label = try self.allocator.dupe(u8, opts.label);
errdefer allocator.free(self.opts.label);
try self.init();
return self;
}

View file

@ -58,9 +58,10 @@ pub fn Options(context: type) type {
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !Widget {
const Self = State(ctx_type);
const self = try allocator.create(Self);
var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent);
errdefer n.deinit();
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.parent = parent,
.plane = n,

View file

@ -56,6 +56,7 @@ pub fn Options(context: type) type {
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) {
const self = try allocator.create(State(ctx_type));
errdefer allocator.destroy(self);
const container = try WidgetList.createH(allocator, parent, @typeName(@This()), .dynamic);
self.* = .{
.allocator = allocator,

View file

@ -59,6 +59,7 @@ pub fn Options(context: type) type {
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Widget, opts: Options(ctx_type)) !*State(ctx_type) {
const self = try allocator.create(State(ctx_type));
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = parent.plane.*,

View file

@ -224,6 +224,7 @@ pub fn hover(self: *Self) bool {
pub fn empty(allocator: Allocator, parent: Plane, layout_: Layout) !Self {
const child: type = struct { plane: Plane, layout: Layout };
const widget = try allocator.create(child);
errdefer allocator.destroy(widget);
const n = try Plane.init(&(Box{}).opts("empty"), parent);
widget.* = .{ .plane = n, .layout = layout_ };
return .{

View file

@ -32,21 +32,24 @@ after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on
on_resize: *const fn (ctx: ?*anyopaque, self: *Self, pos_: Widget.Box) void = on_resize_default,
pub fn createH(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) error{OutOfMemory}!*Self {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, .horizontal, layout_, Box{});
self.plane.hide();
return self;
}
pub fn createV(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) !*Self {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, .vertical, layout_, Box{});
self.plane.hide();
return self;
}
pub fn createBox(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !*Self {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, dir, layout_, box);
self.plane.hide();
return self;

View file

@ -23,6 +23,7 @@ const editor_gutter = @import("editor_gutter.zig");
const Widget = @import("Widget.zig");
const WidgetList = @import("WidgetList.zig");
const tui = @import("tui.zig");
const IndentMode = @import("config").IndentMode;
pub const Cursor = Buffer.Cursor;
pub const View = Buffer.View;
@ -318,6 +319,7 @@ pub const Editor = struct {
render_whitespace: WhitespaceMode,
indent_size: usize,
tab_width: usize,
indent_mode: IndentMode,
last: struct {
root: ?Buffer.Root = null,
@ -370,12 +372,16 @@ pub const Editor = struct {
const Result = command.Result;
pub fn write_state(self: *const Self, writer: Buffer.MetaWriter) !void {
try cbor.writeArrayHeader(writer, 8);
try cbor.writeArrayHeader(writer, 12);
try cbor.writeValue(writer, self.file_path orelse "");
try cbor.writeValue(writer, self.clipboard orelse "");
try cbor.writeValue(writer, self.last_find_query orelse "");
try cbor.writeValue(writer, self.enable_format_on_save);
try cbor.writeValue(writer, self.enable_auto_save);
try cbor.writeValue(writer, self.indent_size);
try cbor.writeValue(writer, self.tab_width);
try cbor.writeValue(writer, self.indent_mode);
try cbor.writeValue(writer, self.syntax_no_render);
if (self.find_history) |history| {
try cbor.writeArrayHeader(writer, history.items.len);
for (history.items) |item|
@ -411,6 +417,10 @@ pub const Editor = struct {
tp.extract(&last_find_query),
tp.extract(&self.enable_format_on_save),
tp.extract(&self.enable_auto_save),
tp.extract(&self.indent_size),
tp.extract(&self.tab_width),
tp.extract(&self.indent_mode),
tp.extract(&self.syntax_no_render),
tp.extract_cbor(&find_history),
tp.extract_cbor(&view_cbor),
tp.extract_cbor(&cursels_cbor),
@ -452,13 +462,15 @@ pub const Editor = struct {
fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void {
const logger = log.logger("editor");
const frame_rate = tp.env.get().num("frame-rate");
const indent_size = tui.config().indent_size;
const tab_width = tui.config().tab_width;
const indent_mode = tui.config().indent_mode;
const indent_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size;
self.* = Self{
.allocator = allocator,
.plane = n,
.indent_size = indent_size,
.tab_width = tab_width,
.indent_mode = indent_mode,
.metrics = self.plane.metrics(tab_width),
.logger = logger,
.file_path = null,
@ -574,6 +586,7 @@ pub const Editor = struct {
if (self.buffer) |_| try self.close();
self.buffer = new_buf;
const file_type = file_type_ orelse new_buf.file_type_name;
const buffer_meta = if (self.buffer) |buffer| buffer.get_meta() else null;
if (new_buf.root.lines() > root_mod.max_syntax_lines) {
self.logger.print("large file threshold {d} lines < file size {d} lines", .{
@ -583,15 +596,19 @@ pub const Editor = struct {
self.logger.print("syntax highlighting disabled", .{});
self.syntax_no_render = true;
}
var content = std.ArrayListUnmanaged(u8).empty;
defer content.deinit(std.heap.c_allocator);
{
const frame_ = tracy.initZone(@src(), .{ .name = "store" });
defer frame_.deinit();
try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode);
}
if (self.indent_mode == .auto)
self.detect_indent_mode(content.items);
self.syntax = syntax: {
const lang_override = file_type orelse tp.env.get().str("language");
var content = std.ArrayListUnmanaged(u8).empty;
defer content.deinit(std.heap.c_allocator);
{
const frame_ = tracy.initZone(@src(), .{ .name = "store" });
defer frame_.deinit();
try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode);
}
self.file_type = blk: {
const frame_ = tracy.initZone(@src(), .{ .name = "guess" });
@ -613,7 +630,7 @@ pub const Editor = struct {
null;
};
if (self.file_type) |ft| {
if (buffer_meta == null) if (self.file_type) |ft| {
const frame_ = tracy.initZone(@src(), .{ .name = "did_open" });
defer frame_.deinit();
project_manager.did_open(
@ -624,7 +641,7 @@ pub const Editor = struct {
new_buf.is_ephemeral(),
) catch |e|
self.logger.print("project_manager.did_open failed: {any}", .{e});
}
};
break :syntax syn;
};
self.syntax_no_render = tp.env.get().is("no-syntax");
@ -639,11 +656,11 @@ pub const Editor = struct {
buffer.file_type_color = ftc;
}
if (self.buffer) |buffer| if (buffer.get_meta()) |meta| {
if (buffer_meta) |meta| {
const frame_ = tracy.initZone(@src(), .{ .name = "extract_state" });
defer frame_.deinit();
try self.extract_state(meta, .none);
};
}
try self.send_editor_open(file_path, new_buf.file_exists, ftn, fti, ftc);
}
@ -663,6 +680,21 @@ pub const Editor = struct {
self.enable_auto_save = true;
}
fn detect_indent_mode(self: *Self, content: []const u8) void {
var it = std.mem.splitScalar(u8, content, '\n');
while (it.next()) |line| {
if (line.len == 0) continue;
if (line[0] == '\t') {
self.indent_size = self.tab_width;
self.indent_mode = .tabs;
return;
}
}
self.indent_size = tui.config().indent_size;
self.indent_mode = .spaces;
return;
}
fn close(self: *Self) !void {
var meta = std.ArrayListUnmanaged(u8).empty;
defer meta.deinit(self.allocator);
@ -3651,9 +3683,16 @@ pub const Editor = struct {
const space = " ";
var cursel: CurSel = .{};
cursel.cursor = cursor;
const cols = self.indent_size - find_first_non_ws(root, cursel.cursor.row, self.metrics) % self.indent_size;
try move_cursor_begin(root, &cursel.cursor, self.metrics);
return self.insert(root, &cursel, space[0..cols], allocator) catch return error.Stop;
switch (self.indent_mode) {
.spaces, .auto => {
const cols = self.indent_size - find_first_non_ws(root, cursel.cursor.row, self.metrics) % self.indent_size;
return self.insert(root, &cursel, space[0..cols], allocator) catch return error.Stop;
},
.tabs => {
return self.insert(root, &cursel, "\t", allocator) catch return error.Stop;
},
}
}
fn indent_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
@ -3688,16 +3727,16 @@ pub const Editor = struct {
const off = first % self.indent_size;
const cols = if (off == 0) self.indent_size else off;
const sel = cursel.enable_selection(root, self.metrics) catch return error.Stop;
sel.begin.move_begin();
try sel.end.move_to(root, sel.end.row, cols, self.metrics);
try sel.begin.move_to(root, sel.begin.row, first, self.metrics);
try sel.end.move_to(root, sel.end.row, first - cols, self.metrics);
var saved = false;
if (cursor_protect) |cp| if (cp.row == cursor.row and cp.col < cols) {
cp.col = cols + 1;
if (cursor_protect) |cp| if (cp.row == cursor.row and cp.col < first and cp.col >= first - cols) {
cp.col = first + 1;
saved = true;
};
newroot = try self.delete_selection(root, &cursel, allocator);
if (cursor_protect) |cp| if (saved) {
try cp.move_to(root, cp.row, 0, self.metrics);
try cp.move_to(root, cp.row, first - cols, self.metrics);
cp.clamp_to_buffer(newroot, self.metrics);
};
return newroot;
@ -4368,16 +4407,39 @@ pub const Editor = struct {
}
pub const insert_line_meta: Meta = .{ .description = "Insert line" };
fn generate_leading_ws(self: *Self, writer: anytype, leading_ws: usize) !void {
return switch (self.indent_mode) {
.spaces, .auto => generate_leading_spaces(writer, leading_ws),
.tabs => generate_leading_tabs(writer, leading_ws, self.tab_width),
};
}
fn generate_leading_spaces(writer: anytype, leading_ws: usize) !void {
var width = leading_ws;
while (width > 0) : (width -= 1)
try writer.writeByte(' ');
}
fn generate_leading_tabs(writer: anytype, leading_ws: usize, tab_width: usize) !void {
var width = leading_ws;
while (width > 0) if (width >= tab_width) {
width -= tab_width;
try writer.writeByte('\t');
} else {
width -= 1;
try writer.writeByte(' ');
};
}
fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator) !Buffer.Root {
var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col);
const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col);
var sfa = std.heap.stackFallback(512, self.allocator);
const allocator = sfa.get();
var stream = std.ArrayListUnmanaged(u8).empty;
defer stream.deinit(allocator);
var writer = stream.writer(allocator);
_ = try writer.write("\n");
while (leading_ws > 0) : (leading_ws -= 1)
_ = try writer.write(" ");
try self.generate_leading_ws(&writer, leading_ws);
return self.insert(root, cursel, stream.items, b_allocator);
}
@ -4431,7 +4493,7 @@ pub const Editor = struct {
const b = try self.buf_for_update();
var root = b.root;
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col);
const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col);
try move_cursor_begin(root, &cursel.cursor, self.metrics);
root = try self.insert(root, cursel, "\n", b.allocator);
try move_cursor_left(root, &cursel.cursor, self.metrics);
@ -4440,8 +4502,7 @@ pub const Editor = struct {
var stream = std.ArrayListUnmanaged(u8).empty;
defer stream.deinit(allocator);
var writer = stream.writer(self.allocator);
while (leading_ws > 0) : (leading_ws -= 1)
_ = try writer.write(" ");
try self.generate_leading_ws(&writer, leading_ws);
if (stream.items.len > 0)
root = try self.insert(root, cursel, stream.items, b.allocator);
};
@ -4466,7 +4527,7 @@ pub const Editor = struct {
const b = try self.buf_for_update();
var root = b.root;
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
var leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col);
const leading_ws = @min(find_first_non_ws(root, cursel.cursor.row, self.metrics), cursel.cursor.col);
try move_cursor_end(root, &cursel.cursor, self.metrics);
var sfa = std.heap.stackFallback(512, self.allocator);
const allocator = sfa.get();
@ -4474,8 +4535,7 @@ pub const Editor = struct {
defer stream.deinit(allocator);
var writer = stream.writer(allocator);
_ = try writer.write("\n");
while (leading_ws > 0) : (leading_ws -= 1)
_ = try writer.write(" ");
try self.generate_leading_ws(&writer, leading_ws);
if (stream.items.len > 0)
root = try self.insert(root, cursel, stream.items, b.allocator);
};
@ -5906,9 +5966,14 @@ pub const Editor = struct {
self.syntax_no_render = tp.env.get().is("no-syntax");
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
const ftn = if (self.file_type) |ft| ft.name else "text";
const fti = if (self.file_type) |ft| ft.icon orelse "🖹" else "🖹";
const ftc = if (self.file_type) |ft| ft.color orelse 0x000000 else 0x000000;
const ftn = if (self.file_type) |ft| ft.name else file_type_config.default.name;
const fti = if (self.file_type) |ft| ft.icon orelse file_type_config.default.icon else file_type_config.default.icon;
const ftc = if (self.file_type) |ft| ft.color orelse file_type_config.default.color else file_type_config.default.color;
if (self.buffer) |buffer| {
buffer.file_type_name = ftn;
buffer.file_type_icon = fti;
buffer.file_type_color = ftc;
}
const file_exists = if (self.buffer) |b| b.file_exists else false;
try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc);
self.logger.print("file type {s}", .{file_type});
@ -5941,7 +6006,8 @@ pub const EditorWidget = struct {
fn create(allocator: Allocator, parent: Plane, buffer_manager: *Buffer.Manager) !Widget {
const container = try WidgetList.createH(allocator, parent, "editor.container", .dynamic);
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
try self.init(allocator, container.plane, buffer_manager);
try self.commands.init(&self.editor);
const editorWidget = Widget.to(self);

View file

@ -43,7 +43,8 @@ const Kind = enum { insert, modified, delete };
const Symbol = struct { kind: Kind, line: usize };
pub fn create(allocator: Allocator, parent: Widget, event_source: Widget, editor: *ed.Editor) !Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*),

View file

@ -47,7 +47,8 @@ const Entry = struct {
};
pub fn create(allocator: Allocator, parent: Plane) !Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = try Plane.init(&(Widget.Box{}).opts(name), parent),

View file

@ -81,7 +81,8 @@ const Self = @This();
pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
const logger = log.logger("home");
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*);
errdefer n.deinit();
@ -303,6 +304,8 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
self.position_menu(self.v_center(5, self.menu_len, 5), self.center(x, self.menu_w));
}
if (self.plane.dim_y() < 3 or self.plane.dim_x() < root.version.len + 4) return false;
self.plane.cursor_move_yx(
@intCast(self.plane.dim_y() - 2),
@intCast(@max(self.plane.dim_x(), root.version.len + 3) - root.version.len - 3),
@ -311,6 +314,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
_ = self.plane.print("{s}", .{root.version}) catch return false;
if (builtin.mode == .Debug) {
const debug_warning_text = "debug build";
if (self.plane.dim_y() < 4 or self.plane.dim_x() < debug_warning_text.len + 4) return false;
self.plane.cursor_move_yx(
@intCast(self.plane.dim_y() - 3),
@intCast(@max(self.plane.dim_x(), debug_warning_text.len + 3) - debug_warning_text.len - 3),

View file

@ -14,7 +14,8 @@ view_rows: usize = 0,
lines: std.ArrayList([]const u8),
pub fn create(allocator: Allocator, parent: Plane) !Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = try Plane.init(&(Widget.Box{}).opts(name), parent),

View file

@ -30,9 +30,10 @@ const Entry = struct {
const Buffer = ArrayList(Entry);
pub fn create(allocator: Allocator, parent: Plane) !Widget {
const self: *Self = try allocator.create(Self);
var n = try Plane.init(&(Widget.Box{}).opts_vscroll(@typeName(Self)), parent);
errdefer n.deinit();
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.parent = parent,

View file

@ -26,7 +26,8 @@ const Self = @This();
pub fn create(allocator: Allocator, parent: Plane) !Widget {
const editor = tui.get_active_editor() orelse return error.NotFound;
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts_vscroll(name), parent),
.editor = editor,

View file

@ -38,7 +38,8 @@ const Level = enum {
};
pub fn create(allocator: Allocator, parent: Plane) !Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(name), parent) };
return Widget.to(self);
}

View file

@ -66,6 +66,7 @@ pub const CreateError = error{ OutOfMemory, ThespianSpawnFailed };
pub fn create(allocator: std.mem.Allocator) CreateError!Widget {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = tui.plane(),

View file

@ -21,7 +21,8 @@ pub fn Create(options: type) type {
const Self = @This();
pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.input = std.ArrayList(u8).init(allocator),

View file

@ -35,7 +35,8 @@ pub fn Create(options: type) type {
};
pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.file_path = std.ArrayList(u8).init(allocator),

View file

@ -28,7 +28,8 @@ commands: Commands = undefined,
pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const editor = tui.get_active_editor() orelse return error.NotFound;
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.input_ = ArrayList(u8).init(allocator),

View file

@ -25,7 +25,8 @@ last_input: []u8 = "",
commands: Commands = undefined,
pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{ .allocator = allocator };
try self.commands.init(self);
if (tui.get_active_selection(self.allocator)) |text| {

View file

@ -24,8 +24,9 @@ start: usize,
commands: Commands = undefined,
pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self: *Self = try allocator.create(Self);
const editor = tui.get_active_editor() orelse return error.NotFound;
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.start = editor.get_primary().cursor.row + 1,

View file

@ -38,7 +38,8 @@ pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tu
const direction: Direction = if (std.mem.indexOf(u8, operation_command, "_left")) |_| .left else .right;
const operation: Operation = if (tui.get_active_editor()) |editor| if (editor.get_primary().selection) |_| .select else .move else .move;
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.direction = direction,

View file

@ -21,11 +21,13 @@ pub const Entry = struct {
pub fn load_entries(palette: *Type) !usize {
const hints = if (tui.input_mode()) |m| m.keybind_hints else @panic("no keybind hints");
var longest_hint: usize = 0;
var longest_description: usize = 0;
var longest_total: usize = 0;
for (command.commands.items) |cmd_| if (cmd_) |p| {
if (p.meta.description.len > 0) {
const hint = hints.get(p.name) orelse "";
longest_hint = @max(longest_hint, hint.len);
longest_description = @max(longest_description, p.meta.description.len);
longest_total = @max(longest_total, p.meta.description.len + hint.len + 1);
(try palette.entries.addOne()).* = .{
.label = if (p.meta.description.len > 0) p.meta.description else p.name,
.name = p.name,
@ -35,7 +37,7 @@ pub fn load_entries(palette: *Type) !usize {
};
}
};
return longest_hint;
return longest_total - longest_description;
}
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {

View file

@ -38,7 +38,8 @@ buffer_manager: ?*BufferManager,
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
const mv = tui.mainview() orelse return error.NotFound;
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ .ctx = self }),

View file

@ -47,7 +47,8 @@ pub fn Create(options: type) type {
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
const mv = tui.mainview() orelse return error.NotFound;
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{

View file

@ -27,7 +27,8 @@ style_factory: ?*const fn (self: *Self, theme: *const Widget.Theme) Widget.Theme
const Self = @This();
pub fn create(allocator: Allocator, parent: Plane, event_source: ?Widget, event_sink: EventHandler) !Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.event_sink = event_sink,

View file

@ -20,7 +20,8 @@ pub fn Create(comptime layout_: Widget.Layout) @import("widget.zig").CreateFunct
break :blk Widget.Layout{ .static = size };
} else break :blk layout_;
} else layout_;
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.layout_ = layout__,

View file

@ -44,6 +44,7 @@ pub fn create(
.on_click = on_click,
.on_layout = layout,
.on_render = render,
.on_receive = receive,
.on_event = event_handler,
});
}
@ -60,18 +61,46 @@ pub fn ctx_deinit(self: *Self) void {
if (self.behind) |p| self.allocator.free(p);
}
fn on_click(_: *Self, _: *Button.State(Self)) void {
git.status(0) catch {};
fn on_click(self: *Self, _: *Button.State(Self)) void {
self.refresh_git_status();
command.executeName("show_git_status", .{}) catch {};
}
fn refresh_git_status(_: *Self) void {
git.status(0) catch {};
}
pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "E", tp.more }))
return self.process_event(m);
if (try m.match(.{ "PRJ", "open" }))
self.refresh_git_status();
return false;
}
fn process_event(self: *Self, m: tp.message) error{Exit}!bool {
if (try m.match(.{ tp.any, "dirty", tp.more }) or
try m.match(.{ tp.any, "save", tp.more }) or
try m.match(.{ tp.any, "open", tp.more }) or
try m.match(.{ tp.any, "close" }))
self.refresh_git_status();
return false;
}
fn receive_git(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
return if (try match(m.buf, .{ "git", more }))
self.process_git(m)
else if (try match(m.buf, .{"focus_in"}))
self.process_focus_in()
else
false;
}
fn process_focus_in(self: *Self) MessageFilter.Error!bool {
self.refresh_git_status();
return false;
}
fn process_git(self: *Self, m: tp.message) MessageFilter.Error!bool {
var value: []const u8 = undefined;
if (try match(m.buf, .{ any, any, "workspace_path", null_ })) {

View file

@ -30,7 +30,8 @@ pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?Event
return error.WidgetInitFailed;
};
defer env.deinit();
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),

View file

@ -200,24 +200,34 @@ fn render_terminal_title(self: *Self) void {
}
pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "E", tp.more }))
return self.process_event(m);
if (try m.match(.{ "PRJ", "open" })) {
if (!self.file)
self.show_project();
}
return false;
}
fn process_event(self: *Self, m: tp.message) error{Exit}!bool {
var file_path: []const u8 = undefined;
var file_type: []const u8 = undefined;
var file_icon: []const u8 = undefined;
var file_dirty: bool = undefined;
var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf);
if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) }))
if (try m.match(.{ tp.any, "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) }))
return false;
if (try m.match(.{ "E", "dirty", tp.extract(&file_dirty) })) {
if (try m.match(.{ tp.any, "dirty", tp.extract(&file_dirty) })) {
self.file_dirty = file_dirty;
} else if (try m.match(.{ "E", "eol_mode", tp.extract(&eol_mode), tp.extract(&self.utf8_sanitized) })) {
} else if (try m.match(.{ tp.any, "eol_mode", tp.extract(&eol_mode), tp.extract(&self.utf8_sanitized) })) {
self.eol_mode = @enumFromInt(eol_mode);
} else if (try m.match(.{ "E", "save", tp.extract(&file_path) })) {
} else if (try m.match(.{ tp.any, "save", tp.extract(&file_path) })) {
@memcpy(self.name_buf[0..file_path.len], file_path);
self.name = self.name_buf[0..file_path.len];
self.file_exists = true;
self.file_dirty = false;
self.name = project_manager.abbreviate_home(&self.name_buf, self.name);
} else if (try m.match(.{ "E", "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) {
} else if (try m.match(.{ tp.any, "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) {
self.eol_mode = .lf;
@memcpy(self.name_buf[0..file_path.len], file_path);
self.name = self.name_buf[0..file_path.len];
@ -229,7 +239,7 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message
self.file_dirty = false;
self.name = project_manager.abbreviate_home(&self.name_buf, self.name);
self.file = true;
} else if (try m.match(.{ "E", "close" })) {
} else if (try m.match(.{ tp.any, "close" })) {
self.name = "";
self.lines = 0;
self.line = 0;
@ -238,9 +248,6 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message
self.file = false;
self.eol_mode = .lf;
self.show_project();
} else if (try m.match(.{ "PRJ", "open" })) {
if (!self.file)
self.show_project();
}
return false;
}

View file

@ -12,7 +12,8 @@ plane: Plane,
const Self = @This();
pub fn create(allocator: std.mem.Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),

View file

@ -31,7 +31,8 @@ pub const width = idle_msg.len + 20;
pub fn create(allocator: Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const frame_rate = tp.env.get().num("frame-rate");
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.wipe_after_frames = @divTrunc(frame_rate, 2),

View file

@ -27,7 +27,8 @@ const Level = enum {
};
pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.msg = std.ArrayList(u8).init(allocator),

View file

@ -20,7 +20,8 @@ const Self = @This();
pub const width = 8;
pub fn create(allocator: Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
};

View file

@ -19,7 +19,8 @@ on_event: ?EventHandler,
const Self = @This();
pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const self: *Self = try allocator.create(Self);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.on_event = event_handler,

View file

@ -54,6 +54,7 @@ pub const Style = @"style.config";
pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const self = try allocator.create(TabBar);
errdefer allocator.destroy(self);
self.* = try TabBar.init(allocator, parent, event_handler);
return Widget.to(self);
}
@ -447,6 +448,7 @@ const spacer = struct {
event_handler: ?EventHandler,
) @import("widget.zig").CreateError!Widget {
const self: *Self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.layout_ = .{ .static = self.plane.egc_chunk_width(content, 0, 1) },

View file

@ -120,6 +120,9 @@ fn init(allocator: Allocator) InitError!*Self {
const frame_clock = try tp.metronome.init(frame_time);
var self = try allocator.create(Self);
// don't destroy
// if tui fails it is catastrophic anyway and we don't want to cause nock-on errors
// errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.config_ = conf,

View file

@ -21,6 +21,7 @@ pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.f
fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid {
const self = try allocator.create(tree_walker);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.root_path = try allocator.dupe(u8, root_path),