Compare commits

...

6 commits

7 changed files with 106 additions and 27 deletions

View file

@ -413,11 +413,11 @@ pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMem
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime }; (try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime };
} }
pub fn walk_tree_done(self: *Self) OutOfMemoryError!void { pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
self.state.walk_tree = .done; self.state.walk_tree = .done;
if (self.walker) |pid| pid.deinit(); if (self.walker) |pid| pid.deinit();
self.walker = null; self.walker = null;
return self.loaded(); return self.loaded(parent);
} }
fn merge_pending_files(self: *Self) OutOfMemoryError!void { fn merge_pending_files(self: *Self) OutOfMemoryError!void {
@ -433,7 +433,7 @@ fn merge_pending_files(self: *Self) OutOfMemoryError!void {
} }
} }
fn loaded(self: *Self) OutOfMemoryError!void { fn loaded(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
inline for (@typeInfo(@TypeOf(self.state)).@"struct".fields) |f| inline for (@typeInfo(@TypeOf(self.state)).@"struct".fields) |f|
if (@field(self.state, f.name) == .running) return; if (@field(self.state, f.name) == .running) return;
@ -449,6 +449,8 @@ fn loaded(self: *Self) OutOfMemoryError!void {
self.files.items.len, self.files.items.len,
std.time.milliTimestamp() - self.open_time, std.time.milliTimestamp() - self.open_time,
}); });
parent.send(.{ "PRJ", "open_done", self.name, self.longest_file_path, self.files.items.len }) catch {};
} }
pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void { pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void {
@ -1913,13 +1915,13 @@ fn start_walker(self: *Self) void {
}; };
} }
pub fn process_git(self: *Self, m: tp.message) (OutOfMemoryError || error{Exit})!void { pub fn process_git(self: *Self, parent: tp.pid_ref, m: tp.message) (OutOfMemoryError || error{Exit})!void {
var value: []const u8 = undefined; var value: []const u8 = undefined;
var path: []const u8 = undefined; var path: []const u8 = undefined;
if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) { if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) {
self.state.workspace_path = .done; self.state.workspace_path = .done;
self.start_walker(); self.start_walker();
try self.loaded(); try self.loaded(parent);
} else if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.extract(&value) })) { } else if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.extract(&value) })) {
if (self.workspace) |p| self.allocator.free(p); if (self.workspace) |p| self.allocator.free(p);
self.workspace = try self.allocator.dupe(u8, value); self.workspace = try self.allocator.dupe(u8, value);
@ -1930,19 +1932,19 @@ pub fn process_git(self: *Self, m: tp.message) (OutOfMemoryError || error{Exit})
}; };
} else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.null_ })) { } else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.null_ })) {
self.state.current_branch = .done; self.state.current_branch = .done;
try self.loaded(); try self.loaded(parent);
} else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.extract(&value) })) { } else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.extract(&value) })) {
if (self.branch) |p| self.allocator.free(p); if (self.branch) |p| self.allocator.free(p);
self.branch = try self.allocator.dupe(u8, value); self.branch = try self.allocator.dupe(u8, value);
self.state.current_branch = .done; self.state.current_branch = .done;
try self.loaded(); try self.loaded(parent);
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) { } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) {
self.longest_file_path = @max(self.longest_file_path, path.len); self.longest_file_path = @max(self.longest_file_path, path.len);
const stat = std.fs.cwd().statFile(path) catch return; const stat = std.fs.cwd().statFile(path) catch return;
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime }; (try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime };
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) { } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) {
self.state.workspace_files = .done; self.state.workspace_files = .done;
try self.loaded(); try self.loaded(parent);
} else { } else {
self.logger_git.err("git", tp.unexpected(m)); self.logger_git.err("git", tp.unexpected(m));
} }

View file

@ -341,10 +341,10 @@ const Process = struct {
project.walk_tree_entry(path, mtime) catch |e| self.logger.err("walk_tree_entry", e); project.walk_tree_entry(path, mtime) catch |e| self.logger.err("walk_tree_entry", e);
} else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) { } else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) {
if (self.projects.get(project_directory)) |project| if (self.projects.get(project_directory)) |project|
project.walk_tree_done() catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; project.walk_tree_done(self.parent.ref()) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
} else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) { } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) {
const project: *Project = @ptrFromInt(context); const project: *Project = @ptrFromInt(context);
project.process_git(m) catch {}; project.process_git(self.parent.ref(), m) catch {};
} else if (try cbor.match(m.buf, .{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { } else if (try cbor.match(m.buf, .{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
} else if (try cbor.match(m.buf, .{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(&params_cb) })) { } else if (try cbor.match(m.buf, .{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(&params_cb) })) {

View file

@ -40,6 +40,7 @@ pub const Handlers = struct {
err: ?*const OutputHandler = null, err: ?*const OutputHandler = null,
exit: *const ExitHandler = log_exit_handler, exit: *const ExitHandler = log_exit_handler,
log_execute: bool = true, log_execute: bool = true,
line_buffered: bool = true,
}; };
pub fn execute(allocator: std.mem.Allocator, argv: tp.message, handlers: Handlers) Error!void { pub fn execute(allocator: std.mem.Allocator, argv: tp.message, handlers: Handlers) Error!void {
@ -145,6 +146,8 @@ const Process = struct {
logger: log.Logger, logger: log.Logger,
stdin_behavior: std.process.Child.StdIo, stdin_behavior: std.process.Child.StdIo,
handlers: Handlers, handlers: Handlers,
stdout_line_buffer: std.ArrayListUnmanaged(u8) = .empty,
stderr_line_buffer: std.ArrayListUnmanaged(u8) = .empty,
const Receiver = tp.Receiver(*Process); const Receiver = tp.Receiver(*Process);
@ -181,6 +184,8 @@ const Process = struct {
self.logger.deinit(); self.logger.deinit();
self.allocator.free(self.arg0); self.allocator.free(self.arg0);
self.allocator.free(self.argv.buf); self.allocator.free(self.argv.buf);
self.stdout_line_buffer.deinit(self.allocator);
self.stderr_line_buffer.deinit(self.allocator);
self.allocator.destroy(self); self.allocator.destroy(self);
} }
@ -215,9 +220,9 @@ const Process = struct {
} else if (try m.match(.{"close"})) { } else if (try m.match(.{"close"})) {
self.close(); self.close();
} else if (try m.match(.{ module_name, "stdout", tp.extract(&bytes) })) { } else if (try m.match(.{ module_name, "stdout", tp.extract(&bytes) })) {
self.handlers.out(self.handlers.context, self.parent.ref(), self.arg0, bytes); self.handle_stdout(bytes) catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ module_name, "stderr", tp.extract(&bytes) })) { } else if (try m.match(.{ module_name, "stderr", tp.extract(&bytes) })) {
(self.handlers.err orelse self.handlers.out)(self.handlers.context, self.parent.ref(), self.arg0, bytes); self.handle_stderr(bytes) catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ module_name, "term", tp.more })) { } else if (try m.match(.{ module_name, "term", tp.more })) {
defer self.sp = null; defer self.sp = null;
self.handle_terminated(m) catch |e| return tp.exit_error(e, @errorReturnTrace()); self.handle_terminated(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
@ -231,9 +236,57 @@ const Process = struct {
} }
} }
fn handle_stdout(self: *Process, bytes: []const u8) error{OutOfMemory}!void {
return if (!self.handlers.line_buffered)
self.handlers.out(self.handlers.context, self.parent.ref(), self.arg0, bytes)
else
self.handle_buffered_output(self.handlers.out, &self.stdout_line_buffer, bytes);
}
fn handle_stderr(self: *Process, bytes: []const u8) error{OutOfMemory}!void {
const handler = self.handlers.err orelse self.handlers.out;
return if (!self.handlers.line_buffered)
handler(self.handlers.context, self.parent.ref(), self.arg0, bytes)
else
self.handle_buffered_output(handler, &self.stderr_line_buffer, bytes);
}
fn handle_buffered_output(self: *Process, handler: *const OutputHandler, buffer: *std.ArrayListUnmanaged(u8), bytes: []const u8) error{OutOfMemory}!void {
var it = std.mem.splitScalar(u8, bytes, '\n');
var have_nl = false;
var prev = it.first();
while (it.next()) |next| {
have_nl = true;
try buffer.appendSlice(self.allocator, prev);
try buffer.append(self.allocator, '\n');
prev = next;
}
if (have_nl) {
handler(self.handlers.context, self.parent.ref(), self.arg0, buffer.items);
buffer.clearRetainingCapacity();
}
try buffer.appendSlice(self.allocator, prev);
}
fn flush_stdout(self: *Process) void {
self.flush_buffer(self.handlers.out, &self.stdout_line_buffer);
}
fn flush_stderr(self: *Process) void {
self.flush_buffer(self.handlers.err orelse self.handlers.out, &self.stderr_line_buffer);
}
fn flush_buffer(self: *Process, handler: *const OutputHandler, buffer: *std.ArrayListUnmanaged(u8)) void {
if (!self.handlers.line_buffered) return;
if (buffer.items.len > 0) handler(self.handlers.context, self.parent.ref(), self.arg0, buffer.items);
buffer.clearRetainingCapacity();
}
fn handle_terminated(self: *Process, m: tp.message) !void { fn handle_terminated(self: *Process, m: tp.message) !void {
var err_msg: []const u8 = undefined; var err_msg: []const u8 = undefined;
var exit_code: i64 = undefined; var exit_code: i64 = undefined;
self.flush_stdout();
self.flush_stderr();
if (try m.match(.{ tp.any, tp.any, "exited", 0 })) { if (try m.match(.{ tp.any, tp.any, "exited", 0 })) {
self.handlers.exit(self.handlers.context, self.parent.ref(), self.arg0, "exited", 0); self.handlers.exit(self.handlers.context, self.parent.ref(), self.arg0, "exited", 0);
} else if (try m.match(.{ tp.any, tp.any, "error.FileNotFound", 1 })) { } else if (try m.match(.{ tp.any, tp.any, "error.FileNotFound", 1 })) {

View file

@ -9,6 +9,7 @@ const keybind = @import("keybind");
const project_manager = @import("project_manager"); const project_manager = @import("project_manager");
const command = @import("command"); const command = @import("command");
const EventHandler = @import("EventHandler"); const EventHandler = @import("EventHandler");
const Buffer = @import("Buffer");
const tui = @import("../../tui.zig"); const tui = @import("../../tui.zig");
const MessageFilter = @import("../../MessageFilter.zig"); const MessageFilter = @import("../../MessageFilter.zig");
@ -23,6 +24,7 @@ pub fn Create(options: type) type {
match: std.ArrayList(u8), match: std.ArrayList(u8),
entries: std.ArrayList(Entry), entries: std.ArrayList(Entry),
complete_trigger_count: usize = 0, complete_trigger_count: usize = 0,
total_matches: usize = 0,
matched_entry: usize = 0, matched_entry: usize = 0,
commands: Commands = undefined, commands: Commands = undefined,
@ -171,14 +173,23 @@ pub fn Create(options: type) type {
fn do_complete(self: *Self) !void { fn do_complete(self: *Self) !void {
self.complete_trigger_count = @min(self.complete_trigger_count, self.entries.items.len); self.complete_trigger_count = @min(self.complete_trigger_count, self.entries.items.len);
self.file_path.clearRetainingCapacity(); self.file_path.clearRetainingCapacity();
const match_number = self.complete_trigger_count;
if (self.match.items.len > 0) { if (self.match.items.len > 0) {
try self.match_path(); try self.match_path();
if (self.total_matches == 1)
self.complete_trigger_count = 0;
} else if (self.entries.items.len > 0) { } else if (self.entries.items.len > 0) {
try self.construct_path(self.query.items, self.entries.items[self.complete_trigger_count - 1], self.complete_trigger_count - 1); try self.construct_path(self.query.items, self.entries.items[self.complete_trigger_count - 1], self.complete_trigger_count - 1);
} else { } else {
try self.construct_path(self.query.items, .{ .name = "", .type = .file }, 0); try self.construct_path(self.query.items, .{ .name = "", .type = .file }, 0);
} }
message("{d}/{d}", .{ self.matched_entry + 1, self.entries.items.len }); if (self.match.items.len > 0)
if (self.total_matches > 1)
message("{d}/{d} ({d}/{d} matches)", .{ self.matched_entry + 1, self.entries.items.len, match_number, self.total_matches })
else
message("{d}/{d} ({d} match)", .{ self.matched_entry + 1, self.entries.items.len, self.total_matches })
else
message("{d}/{d}", .{ self.matched_entry + 1, self.entries.items.len });
} }
fn construct_path(self: *Self, path_: []const u8, entry: Entry, entry_no: usize) error{OutOfMemory}!void { fn construct_path(self: *Self, path_: []const u8, entry: Entry, entry_no: usize) error{OutOfMemory}!void {
@ -193,22 +204,23 @@ pub fn Create(options: type) type {
} }
fn match_path(self: *Self) !void { fn match_path(self: *Self) !void {
var found_match: ?usize = null;
var matched: usize = 0; var matched: usize = 0;
var last: ?Entry = null; var last: ?Entry = null;
var last_no: usize = 0; var last_no: usize = 0;
for (self.entries.items, 0..) |entry, i| { for (self.entries.items, 0..) |entry, i| {
if (entry.name.len >= self.match.items.len and if (try prefix_compare_icase(self.allocator, self.match.items, entry.name)) {
std.mem.eql(u8, self.match.items, entry.name[0..self.match.items.len]))
{
matched += 1; matched += 1;
if (matched == self.complete_trigger_count) { if (matched == self.complete_trigger_count) {
try self.construct_path(self.query.items, entry, i); try self.construct_path(self.query.items, entry, i);
return; found_match = i;
} }
last = entry; last = entry;
last_no = i; last_no = i;
} }
} }
self.total_matches = matched;
if (found_match) |_| return;
if (last) |entry| { if (last) |entry| {
try self.construct_path(self.query.items, entry, last_no); try self.construct_path(self.query.items, entry, last_no);
self.complete_trigger_count = matched; self.complete_trigger_count = matched;
@ -218,6 +230,15 @@ pub fn Create(options: type) type {
} }
} }
fn prefix_compare_icase(allocator: std.mem.Allocator, prefix: []const u8, str: []const u8) error{OutOfMemory}!bool {
const icase_prefix = Buffer.unicode.get_letter_casing().toLowerStr(allocator, prefix) catch try allocator.dupe(u8, prefix);
defer allocator.free(icase_prefix);
const icase_str = Buffer.unicode.get_letter_casing().toLowerStr(allocator, str) catch try allocator.dupe(u8, str);
defer allocator.free(icase_str);
if (icase_str.len < icase_prefix.len) return false;
return std.mem.eql(u8, icase_prefix, icase_str[0..icase_prefix.len]);
}
fn delete_to_previous_path_segment(self: *Self) void { fn delete_to_previous_path_segment(self: *Self) void {
self.complete_trigger_count = 0; self.complete_trigger_count = 0;
if (self.file_path.items.len == 0) return; if (self.file_path.items.len == 0) return;

View file

@ -37,12 +37,9 @@ pub fn select(self: *Type) void {
var buf = std.ArrayList(u8).init(self.allocator); var buf = std.ArrayList(u8).init(self.allocator);
defer buf.deinit(); defer buf.deinit();
const file_path = project_manager.expand_home(&buf, self.file_path.items); const file_path = project_manager.expand_home(&buf, self.file_path.items);
if (root.is_directory(file_path)) {
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch return;
tp.self_pid().send(.{ "cmd", "change_project", .{file_path} }) catch {};
return;
}
if (file_path.len > 0)
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch {};
command.executeName("exit_mini_mode", .{}) catch {}; command.executeName("exit_mini_mode", .{}) catch {};
if (root.is_directory(file_path))
tp.self_pid().send(.{ "cmd", "change_project", .{file_path} }) catch {}
else if (file_path.len > 0)
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch {};
} }

View file

@ -36,6 +36,8 @@ longest: usize = 0,
commands: Commands = undefined, commands: Commands = undefined,
buffer_manager: ?*BufferManager, buffer_manager: ?*BufferManager,
const inputbox_label = "Search files by name";
pub fn create(allocator: std.mem.Allocator) !tui.Mode { pub fn create(allocator: std.mem.Allocator) !tui.Mode {
const mv = tui.mainview() orelse return error.NotFound; const mv = tui.mainview() orelse return error.NotFound;
const self = try allocator.create(Self); const self = try allocator.create(Self);
@ -51,7 +53,7 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
.logger = log.logger(@typeName(Self)), .logger = log.logger(@typeName(Self)),
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{ .inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
.ctx = self, .ctx = self,
.label = "Search files by name", .label = inputbox_label,
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable, }))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.buffer_manager = tui.get_buffer_manager(), .buffer_manager = tui.get_buffer_manager(),
}; };
@ -82,7 +84,7 @@ pub fn deinit(self: *Self) void {
} }
inline fn menu_width(self: *Self) usize { inline fn menu_width(self: *Self) usize {
return @min(self.longest, max_menu_width()) + 2; return @max(@min(self.longest, max_menu_width()) + 2, inputbox_label.len + 2);
} }
inline fn menu_pos_x(self: *Self) usize { inline fn menu_pos_x(self: *Self) usize {
@ -198,6 +200,10 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
self.need_reset = true; self.need_reset = true;
if (!std.mem.eql(u8, self.inputbox.text.items, query)) if (!std.mem.eql(u8, self.inputbox.text.items, query))
try self.start_query(); try self.start_query();
} else if (try cbor.match(m.buf, .{ "PRJ", "open_done", tp.string, tp.extract(&self.longest), tp.any })) {
self.query_pending = false;
self.need_reset = true;
try self.start_query();
} else { } else {
self.logger.err("receive", tp.unexpected(m)); self.logger.err("receive", tp.unexpected(m));
} }

View file

@ -153,7 +153,7 @@ pub fn Create(options: type) type {
fn do_resize(self: *Self) void { fn do_resize(self: *Self) void {
const screen = tui.screen(); const screen = tui.screen();
const w = @min(self.longest, max_menu_width) + 2 + 1 + self.longest_hint; const w = @max(@min(self.longest, max_menu_width) + 2 + 1 + self.longest_hint, options.label.len + 2);
const x = if (screen.w > w) (screen.w - w) / 2 else 0; const x = if (screen.w > w) (screen.w - w) / 2 else 0;
self.view_rows = get_view_rows(screen); self.view_rows = get_view_rows(screen);
const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count); const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count);