Add file and project search panels
This commit is contained in:
+8
-6
@@ -21,7 +21,9 @@ pub const Action = union(enum) {
|
|||||||
quit,
|
quit,
|
||||||
open: []u8,
|
open: []u8,
|
||||||
symbol: symbol_mod.Symbol,
|
symbol: symbol_mod.Symbol,
|
||||||
|
file_picker,
|
||||||
search_file,
|
search_file,
|
||||||
|
search_project,
|
||||||
repeat_rail,
|
repeat_rail,
|
||||||
restore_last,
|
restore_last,
|
||||||
not_built: Feature,
|
not_built: Feature,
|
||||||
@@ -127,6 +129,10 @@ pub const Leader = struct {
|
|||||||
self.mode = .open_prompt;
|
self.mode = .open_prompt;
|
||||||
return .none;
|
return .none;
|
||||||
}
|
}
|
||||||
|
if (std.mem.eql(u8, text, "f")) {
|
||||||
|
self.mode = .idle;
|
||||||
|
return .file_picker;
|
||||||
|
}
|
||||||
if (std.mem.eql(u8, text, "p")) {
|
if (std.mem.eql(u8, text, "p")) {
|
||||||
self.mode = .symbol_rail;
|
self.mode = .symbol_rail;
|
||||||
return .none;
|
return .none;
|
||||||
@@ -174,6 +180,7 @@ pub const Leader = struct {
|
|||||||
.text => |text| {
|
.text => |text| {
|
||||||
self.mode = .idle;
|
self.mode = .idle;
|
||||||
if (std.mem.eql(u8, text, "f")) return .search_file;
|
if (std.mem.eql(u8, text, "f")) return .search_file;
|
||||||
|
if (std.mem.eql(u8, text, "p")) return .search_project;
|
||||||
self.message = "search target is not built in this profile yet";
|
self.message = "search target is not built in this profile yet";
|
||||||
return .{ .not_built = .search };
|
return .{ .not_built = .search };
|
||||||
},
|
},
|
||||||
@@ -325,12 +332,7 @@ test "regular: search rail opens current-file search and rejects other targets"
|
|||||||
|
|
||||||
try expectActionTag(.none, try leader.handleEvent(input.normalize(" ")));
|
try expectActionTag(.none, try leader.handleEvent(input.normalize(" ")));
|
||||||
try expectActionTag(.none, try leader.handleEvent(input.normalize("s")));
|
try expectActionTag(.none, try leader.handleEvent(input.normalize("s")));
|
||||||
const action = try leader.handleEvent(input.normalize("p"));
|
try expectActionTag(.search_project, try leader.handleEvent(input.normalize("p")));
|
||||||
switch (action) {
|
|
||||||
.not_built => |feature| try std.testing.expectEqual(Feature.search, feature),
|
|
||||||
else => return error.ExpectedNotBuiltAction,
|
|
||||||
}
|
|
||||||
try std.testing.expectEqualStrings("search target is not built in this profile yet", leader.status());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "adversarial: unknown leader key does not dispatch and recovers to idle" {
|
test "adversarial: unknown leader key does not dispatch and recovers to idle" {
|
||||||
|
|||||||
+16
-4
@@ -192,10 +192,22 @@ fn openDirectoryPreview(allocator: std.mem.Allocator, io: std.Io, client: *tui.C
|
|||||||
while (try iterator.next(io)) |entry| {
|
while (try iterator.next(io)) |entry| {
|
||||||
if (count >= 80) break;
|
if (count >= 80) break;
|
||||||
if (entry.kind != .file and entry.kind != .directory) continue;
|
if (entry.kind != .file and entry.kind != .directory) continue;
|
||||||
const suffix = if (entry.kind == .directory) "/" else "";
|
const relative_path = try std.fmt.allocPrint(allocator, "{s}/{s}{s}", .{ path, entry.name, if (entry.kind == .directory) "/" else "" });
|
||||||
const command = try std.fmt.allocPrint(allocator, "repo_file {s}{s}=", .{ entry.name, suffix });
|
defer allocator.free(relative_path);
|
||||||
defer allocator.free(command);
|
if (entry.kind == .file) {
|
||||||
client.handleTraceLine(command) catch {};
|
const bytes = std.Io.Dir.cwd().readFileAlloc(io, relative_path, allocator, .limited(diagnostics.max_file_bytes)) catch |err| switch (err) {
|
||||||
|
error.StreamTooLong, error.AccessDenied, error.FileNotFound, error.NotDir => null,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
if (bytes) |content| {
|
||||||
|
defer allocator.free(content);
|
||||||
|
client.addRepoFileContent(relative_path, content) catch {};
|
||||||
|
} else {
|
||||||
|
client.addRepoFileContent(relative_path, "") catch {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.addRepoFileContent(relative_path, "") catch {};
|
||||||
|
}
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
try client.handleTraceLine("file_picker");
|
try client.handleTraceLine("file_picker");
|
||||||
|
|||||||
@@ -155,6 +155,14 @@ pub const Stack = struct {
|
|||||||
return list.selected.?;
|
return list.selected.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn activeListItem(self: *const Stack) ![]const u8 {
|
||||||
|
const index = self.active_index orelse return Error.NoPanelOpen;
|
||||||
|
const panel = self.panels.items[index];
|
||||||
|
if (panel.kind != .list or panel.list == null) return Error.ActivePanelIsNotList;
|
||||||
|
const list = panel.list.?;
|
||||||
|
return self.visibleItemAt(list.cursor) orelse Error.EmptyList;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cancelList(self: *Stack) !void {
|
pub fn cancelList(self: *Stack) !void {
|
||||||
_ = try self.activeList();
|
_ = try self.activeList();
|
||||||
try self.closeActive();
|
try self.closeActive();
|
||||||
|
|||||||
@@ -335,6 +335,10 @@ pub const Session = struct {
|
|||||||
return self.panels.selectList();
|
return self.panels.selectList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn activeListItem(self: *const Session) ![]const u8 {
|
||||||
|
return self.panels.activeListItem();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cancelListPanel(self: *Session) !void {
|
pub fn cancelListPanel(self: *Session) !void {
|
||||||
try self.panels.cancelList();
|
try self.panels.cancelList();
|
||||||
}
|
}
|
||||||
|
|||||||
+204
-18
@@ -40,6 +40,12 @@ const PrefixRail = enum {
|
|||||||
replace,
|
replace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PanelContext = enum {
|
||||||
|
none,
|
||||||
|
file_picker,
|
||||||
|
project_search,
|
||||||
|
};
|
||||||
|
|
||||||
const EditSnapshot = struct {
|
const EditSnapshot = struct {
|
||||||
bytes: []u8,
|
bytes: []u8,
|
||||||
cursor_byte: usize,
|
cursor_byte: usize,
|
||||||
@@ -140,6 +146,9 @@ pub const Client = struct {
|
|||||||
search_query: []u8 = &.{},
|
search_query: []u8 = &.{},
|
||||||
search_matches: std.ArrayList(usize) = .empty,
|
search_matches: std.ArrayList(usize) = .empty,
|
||||||
search_index: usize = 0,
|
search_index: usize = 0,
|
||||||
|
project_search_prompt_active: bool = false,
|
||||||
|
project_search_prompt: std.ArrayList(u8) = .empty,
|
||||||
|
panel_context: PanelContext = .none,
|
||||||
undo_stack: std.ArrayList(EditSnapshot) = .empty,
|
undo_stack: std.ArrayList(EditSnapshot) = .empty,
|
||||||
redo_stack: std.ArrayList(EditSnapshot) = .empty,
|
redo_stack: std.ArrayList(EditSnapshot) = .empty,
|
||||||
yank_bytes: ?[]u8 = null,
|
yank_bytes: ?[]u8 = null,
|
||||||
@@ -164,6 +173,7 @@ pub const Client = struct {
|
|||||||
if (self.saved_bytes) |bytes| self.allocator.free(bytes);
|
if (self.saved_bytes) |bytes| self.allocator.free(bytes);
|
||||||
if (self.yank_bytes) |bytes| self.allocator.free(bytes);
|
if (self.yank_bytes) |bytes| self.allocator.free(bytes);
|
||||||
self.search_prompt.deinit(self.allocator);
|
self.search_prompt.deinit(self.allocator);
|
||||||
|
self.project_search_prompt.deinit(self.allocator);
|
||||||
self.allocator.free(self.search_query);
|
self.allocator.free(self.search_query);
|
||||||
self.search_matches.deinit(self.allocator);
|
self.search_matches.deinit(self.allocator);
|
||||||
self.freeSnapshotStack(&self.undo_stack);
|
self.freeSnapshotStack(&self.undo_stack);
|
||||||
@@ -374,6 +384,7 @@ pub const Client = struct {
|
|||||||
self.message = null;
|
self.message = null;
|
||||||
|
|
||||||
if (self.search_prompt_active) return self.applySearchPromptInput(event);
|
if (self.search_prompt_active) return self.applySearchPromptInput(event);
|
||||||
|
if (self.project_search_prompt_active) return self.applyProjectSearchPromptInput(event);
|
||||||
|
|
||||||
const leader_trigger = isLeaderTrigger(event) and self.effectiveMode() != .insert;
|
const leader_trigger = isLeaderTrigger(event) and self.effectiveMode() != .insert;
|
||||||
if (self.leader.capturesInput() or leader_trigger) {
|
if (self.leader.capturesInput() or leader_trigger) {
|
||||||
@@ -425,13 +436,14 @@ pub const Client = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn effectiveMode(self: *const Client) EditorMode {
|
fn effectiveMode(self: *const Client) EditorMode {
|
||||||
if (self.search_prompt_active or self.leader.isPromptActive()) return .prompt;
|
if (self.search_prompt_active or self.project_search_prompt_active or self.leader.isPromptActive()) return .prompt;
|
||||||
const snap = self.session.snapshot() catch return self.mode;
|
const snap = self.session.snapshot() catch return self.mode;
|
||||||
if (snap.active_panel_title != null) return .panel;
|
if (snap.active_panel_title != null) return .panel;
|
||||||
return self.mode;
|
return self.mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn searchStatus(self: *const Client) []const u8 {
|
fn searchStatus(self: *const Client) []const u8 {
|
||||||
|
if (self.project_search_prompt_active) return "project search: type query, Enter accept, Esc cancel";
|
||||||
if (self.search_prompt_active) return "search: type query, Enter accept, Esc cancel";
|
if (self.search_prompt_active) return "search: type query, Enter accept, Esc cancel";
|
||||||
if (self.search_query.len != 0) return "search active: n next N previous";
|
if (self.search_query.len != 0) return "search active: n next N previous";
|
||||||
return "";
|
return "";
|
||||||
@@ -451,21 +463,24 @@ pub const Client = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn addRepoFileContent(self: *Client, path: []const u8, file_bytes: []const u8) !void {
|
||||||
|
if (file_bytes.len > diagnostics_mod.max_file_bytes) {
|
||||||
|
self.message = "diagnostic:file_too_large";
|
||||||
|
return Error.ProtocolRejected;
|
||||||
|
}
|
||||||
|
self.repo.addFile(path, file_bytes) catch |err| {
|
||||||
|
self.message = "diagnostic:unsupported_file";
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
self.message = null;
|
||||||
|
}
|
||||||
|
|
||||||
fn addRepoFile(self: *Client, payload: []const u8) !void {
|
fn addRepoFile(self: *Client, payload: []const u8) !void {
|
||||||
const separator = std.mem.indexOfScalar(u8, payload, '=') orelse {
|
const separator = std.mem.indexOfScalar(u8, payload, '=') orelse {
|
||||||
self.message = "diagnostic:unsupported_file:invalid_repo_file_payload";
|
self.message = "diagnostic:unsupported_file:invalid_repo_file_payload";
|
||||||
return repo_mod.Error.InvalidPath;
|
return repo_mod.Error.InvalidPath;
|
||||||
};
|
};
|
||||||
const file_bytes = payload[separator + 1 ..];
|
try self.addRepoFileContent(payload[0..separator], payload[separator + 1 ..]);
|
||||||
if (file_bytes.len > diagnostics_mod.max_file_bytes) {
|
|
||||||
self.message = "diagnostic:file_too_large";
|
|
||||||
return Error.ProtocolRejected;
|
|
||||||
}
|
|
||||||
self.repo.addFile(payload[0..separator], file_bytes) catch |err| {
|
|
||||||
self.message = "diagnostic:unsupported_file";
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
self.message = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn openRepoList(self: *Client, title: []const u8, tree: bool, include_ignored: bool) !void {
|
fn openRepoList(self: *Client, title: []const u8, tree: bool, include_ignored: bool) !void {
|
||||||
@@ -904,16 +919,15 @@ pub const Client = struct {
|
|||||||
.text => |text| {
|
.text => |text| {
|
||||||
if (std.mem.eql(u8, text, "j")) return self.applyProtocol("command list_down");
|
if (std.mem.eql(u8, text, "j")) return self.applyProtocol("command list_down");
|
||||||
if (std.mem.eql(u8, text, "k")) return self.applyProtocol("command list_up");
|
if (std.mem.eql(u8, text, "k")) return self.applyProtocol("command list_up");
|
||||||
if (std.mem.eql(u8, text, "q")) {
|
if (std.mem.eql(u8, text, "o")) return self.openActivePanelItem();
|
||||||
self.message = "panel close is not built in this profile yet";
|
if (std.mem.eql(u8, text, "q")) return self.closeActivePanel();
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.unknownPrefixOrInput("panel");
|
self.unknownPrefixOrInput("panel");
|
||||||
},
|
},
|
||||||
.key => |key| switch (key) {
|
.key => |key| switch (key) {
|
||||||
.arrow_down => self.message = "panel down is not built in this profile yet",
|
.arrow_down => try self.applyProtocol("command list_down"),
|
||||||
.arrow_up => self.message = "panel up is not built in this profile yet",
|
.arrow_up => try self.applyProtocol("command list_up"),
|
||||||
.escape => self.message = "panel close is not built in this profile yet",
|
.enter => try self.openActivePanelItem(),
|
||||||
|
.escape => try self.closeActivePanel(),
|
||||||
else => {},
|
else => {},
|
||||||
},
|
},
|
||||||
.unknown => self.unknownPrefixOrInput("panel"),
|
.unknown => self.unknownPrefixOrInput("panel"),
|
||||||
@@ -995,6 +1009,111 @@ pub const Client = struct {
|
|||||||
self.message = null;
|
self.message = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn openFilePickerPanel(self: *Client) !void {
|
||||||
|
try self.openRepoList("files", false, false);
|
||||||
|
self.panel_context = .file_picker;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openListRows(self: *Client, title: []const u8, rows: []const []const u8, context: PanelContext) !void {
|
||||||
|
if (rows.len == 0) {
|
||||||
|
self.message = "empty list";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var joined = std.ArrayList(u8).empty;
|
||||||
|
defer joined.deinit(self.allocator);
|
||||||
|
try joined.appendSlice(self.allocator, "list_open ");
|
||||||
|
try joined.appendSlice(self.allocator, title);
|
||||||
|
try joined.append(self.allocator, ' ');
|
||||||
|
for (rows, 0..) |row, i| {
|
||||||
|
if (i != 0) try joined.append(self.allocator, '|');
|
||||||
|
try joined.appendSlice(self.allocator, row);
|
||||||
|
}
|
||||||
|
try self.applyProtocol(joined.items);
|
||||||
|
self.panel_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openActivePanelItem(self: *Client) !void {
|
||||||
|
const item = self.session.activeListItem() catch |err| {
|
||||||
|
self.message = @errorName(err);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const owned = try self.allocator.dupe(u8, item);
|
||||||
|
defer self.allocator.free(owned);
|
||||||
|
switch (self.panel_context) {
|
||||||
|
.file_picker => try self.openRepoPathFromPanel(owned),
|
||||||
|
.project_search => try self.openProjectSearchResult(owned),
|
||||||
|
.none => self.message = "panel item has no action",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closeActivePanel(self: *Client) !void {
|
||||||
|
try self.applyProtocol("command list_cancel");
|
||||||
|
self.panel_context = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openRepoPathFromPanel(self: *Client, row: []const u8) !void {
|
||||||
|
const path = if (std.mem.startsWith(u8, row, "file ")) row[5..] else row;
|
||||||
|
const bytes = self.repo.content(path) catch |err| {
|
||||||
|
self.message = @errorName(err);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
try self.session.openFixtureAt(bytes, 0);
|
||||||
|
try self.closeActivePanel();
|
||||||
|
self.message = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openProjectSearchResult(self: *Client, row: []const u8) !void {
|
||||||
|
const target = self.repo.searchOffsetFromRow(row) catch |err| {
|
||||||
|
self.message = @errorName(err);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const bytes = self.repo.content(target.path) catch |err| {
|
||||||
|
self.message = @errorName(err);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
try self.session.openFixtureAt(bytes, target.offset);
|
||||||
|
try self.closeActivePanel();
|
||||||
|
self.message = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openProjectSearchPrompt(self: *Client) void {
|
||||||
|
self.project_search_prompt_active = true;
|
||||||
|
self.project_search_prompt.clearRetainingCapacity();
|
||||||
|
self.message = null;
|
||||||
|
self.prefix = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn applyProjectSearchPromptInput(self: *Client, event: input.Event) !void {
|
||||||
|
switch (event) {
|
||||||
|
.text => |text| try self.project_search_prompt.appendSlice(self.allocator, text),
|
||||||
|
.key => |key| switch (key) {
|
||||||
|
.enter => try self.commitProjectSearchPrompt(),
|
||||||
|
.escape => {
|
||||||
|
self.project_search_prompt_active = false;
|
||||||
|
self.message = "project search cancelled";
|
||||||
|
},
|
||||||
|
.backspace => {
|
||||||
|
if (self.project_search_prompt.items.len > 0) _ = self.project_search_prompt.pop();
|
||||||
|
},
|
||||||
|
.space => try self.project_search_prompt.append(self.allocator, ' '),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
.unknown => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commitProjectSearchPrompt(self: *Client) !void {
|
||||||
|
self.project_search_prompt_active = false;
|
||||||
|
const rows = try self.repo.searchRowsAlloc(self.allocator, self.project_search_prompt.items, false);
|
||||||
|
defer freeOwnedRows(self.allocator, rows);
|
||||||
|
if (rows.len == 0) {
|
||||||
|
self.message = "no project matches";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try self.openSearchList(self.project_search_prompt.items, false);
|
||||||
|
self.panel_context = .project_search;
|
||||||
|
}
|
||||||
|
|
||||||
fn openSearchPrompt(self: *Client) void {
|
fn openSearchPrompt(self: *Client) void {
|
||||||
self.search_prompt_active = true;
|
self.search_prompt_active = true;
|
||||||
self.search_prompt.clearRetainingCapacity();
|
self.search_prompt.clearRetainingCapacity();
|
||||||
@@ -1442,7 +1561,9 @@ pub const Client = struct {
|
|||||||
try self.applyProtocol(line);
|
try self.applyProtocol(line);
|
||||||
},
|
},
|
||||||
.symbol => |symbol| try self.applyProtocol(symbol_mod.protocolCommand(symbol)),
|
.symbol => |symbol| try self.applyProtocol(symbol_mod.protocolCommand(symbol)),
|
||||||
|
.file_picker => try self.openFilePickerPanel(),
|
||||||
.search_file => self.openSearchPrompt(),
|
.search_file => self.openSearchPrompt(),
|
||||||
|
.search_project => self.openProjectSearchPrompt(),
|
||||||
.repeat_rail => self.openPrefix(.repeat),
|
.repeat_rail => self.openPrefix(.repeat),
|
||||||
.restore_last => self.restoreLastRail(),
|
.restore_last => self.restoreLastRail(),
|
||||||
.not_built => {},
|
.not_built => {},
|
||||||
@@ -3353,3 +3474,68 @@ test "adversarial: search no match cancel utf8 and long line remain safe" {
|
|||||||
snap = try client.session.snapshot();
|
snap = try client.session.snapshot();
|
||||||
try std.testing.expect(snap.cursor_byte > 20);
|
try std.testing.expect(snap.cursor_byte > 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "regular: file picker panel opens selected repo file" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 80, .height = 10 });
|
||||||
|
defer client.deinit();
|
||||||
|
try client.handleTraceLine("repo_file src/main.zig=const main = 1;");
|
||||||
|
try client.handleTraceLine("repo_file src/lib.zig=const lib = 2;");
|
||||||
|
|
||||||
|
try client.handleInput(" ");
|
||||||
|
try client.handleInput("f");
|
||||||
|
var snap = try client.session.snapshot();
|
||||||
|
try std.testing.expect(snap.active_panel_title != null);
|
||||||
|
try client.handleInput("o");
|
||||||
|
snap = try client.session.snapshot();
|
||||||
|
try std.testing.expectEqualStrings("const main = 1;", snap.bytes);
|
||||||
|
try std.testing.expect(snap.active_panel_title == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "regular: project search panel opens selected result at match" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 80, .height = 10 });
|
||||||
|
defer client.deinit();
|
||||||
|
try client.handleTraceLine("repo_file src/main.zig=alpha\nneedle here\n");
|
||||||
|
try client.handleTraceLine("repo_file src/lib.zig=other\n");
|
||||||
|
|
||||||
|
try client.handleInput(" ");
|
||||||
|
try client.handleInput("s");
|
||||||
|
try client.handleInput("p");
|
||||||
|
try std.testing.expectEqualStrings("prompt", client.modeName());
|
||||||
|
try client.handleInput("n");
|
||||||
|
try client.handleInput("e");
|
||||||
|
try client.handleInput("e");
|
||||||
|
try client.handleInput("d");
|
||||||
|
try client.handleInput("l");
|
||||||
|
try client.handleInput("e");
|
||||||
|
try client.handleInput("\n");
|
||||||
|
var snap = try client.session.snapshot();
|
||||||
|
try std.testing.expect(snap.active_panel_title != null);
|
||||||
|
try client.handleInput("o");
|
||||||
|
snap = try client.session.snapshot();
|
||||||
|
try std.testing.expectEqualStrings("alpha\nneedle here\n", snap.bytes);
|
||||||
|
try std.testing.expect(snap.cursor_byte >= 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: file and project panels handle ignored empty missing and close" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 80, .height = 10 });
|
||||||
|
defer client.deinit();
|
||||||
|
try client.handleTraceLine("repo_file src/main.zig=visible");
|
||||||
|
|
||||||
|
try client.handleInput(" ");
|
||||||
|
try client.handleInput("f");
|
||||||
|
const picker_frame = try client.render(std.testing.allocator);
|
||||||
|
defer std.testing.allocator.free(picker_frame);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, picker_frame, "src/main.zig") != null);
|
||||||
|
try client.handleInput("q");
|
||||||
|
const snap = try client.session.snapshot();
|
||||||
|
try std.testing.expect(snap.active_panel_title == null);
|
||||||
|
|
||||||
|
try client.handleInput(" ");
|
||||||
|
try client.handleInput("s");
|
||||||
|
try client.handleInput("p");
|
||||||
|
try client.handleInput("missing");
|
||||||
|
try client.handleInput("\n");
|
||||||
|
const no_match_frame = try client.render(std.testing.allocator);
|
||||||
|
defer std.testing.allocator.free(no_match_frame);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, no_match_frame, "no project matches") != null);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user