Add first-class job rail profiles
This commit is contained in:
@@ -376,6 +376,8 @@ Backed by current code/tests:
|
||||
- leader rail seed behavior and command-intent separation;
|
||||
- transient panel/rendering foundations;
|
||||
- protocol/session/job/LSP/diagnostic skeletons;
|
||||
- `Space t` lint/build/test/check job rail backed by source-patched command profiles,
|
||||
captured output panels, missing-tool/cancel/timeout rows, and jump/yank actions;
|
||||
- basic trace commands for open, insert, save, panels, diagnostics, repo files,
|
||||
and local context.
|
||||
|
||||
@@ -390,7 +392,6 @@ Design-only and needing future implementation slices:
|
||||
- parameter navigation in function calls;
|
||||
- compact-first hover/signature cards with expandable scroll/search panels;
|
||||
- full provider arbitration for multiple LSP/tool sources;
|
||||
- lint/format/build/test command execution through the job surface;
|
||||
- source-profile format-on-save policy and one-shot save-without-format;
|
||||
- contextual rails for all prefixes listed in this document;
|
||||
- Insert pending-space rail, including `Space` pause then `n` to Normal;
|
||||
|
||||
+136
@@ -13,8 +13,144 @@ pub const Error = error{
|
||||
InvalidCommand,
|
||||
InvalidJobRow,
|
||||
InvalidPath,
|
||||
MissingCurrentFile,
|
||||
};
|
||||
|
||||
pub const Profile = enum {
|
||||
lint_file,
|
||||
lint_project,
|
||||
build,
|
||||
tests,
|
||||
check,
|
||||
|
||||
pub fn name(self: Profile) []const u8 {
|
||||
return switch (self) {
|
||||
.lint_file => "lint_file",
|
||||
.lint_project => "lint_project",
|
||||
.build => "build",
|
||||
.tests => "test",
|
||||
.check => "check",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn provider(self: Profile) []const u8 {
|
||||
return switch (self) {
|
||||
.lint_file, .lint_project => "zig-fmt",
|
||||
.build, .tests, .check => "zig-build",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn scope(self: Profile) []const u8 {
|
||||
return switch (self) {
|
||||
.lint_file => "file",
|
||||
.lint_project, .build, .tests, .check => "project",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn argvForProfileAlloc(allocator: std.mem.Allocator, profile: Profile, current_file: ?[]const u8) ![][]const u8 {
|
||||
var argv = std.ArrayList([]const u8).empty;
|
||||
errdefer {
|
||||
for (argv.items) |arg| allocator.free(arg);
|
||||
argv.deinit(allocator);
|
||||
}
|
||||
switch (profile) {
|
||||
.lint_file => {
|
||||
const path = current_file orelse return Error.MissingCurrentFile;
|
||||
try validatePath(path);
|
||||
try argv.append(allocator, try allocator.dupe(u8, "zig"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "fmt"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "--check"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, path));
|
||||
},
|
||||
.lint_project => {
|
||||
try argv.append(allocator, try allocator.dupe(u8, "zig"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "fmt"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "--check"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "src"));
|
||||
},
|
||||
.build => {
|
||||
try argv.append(allocator, try allocator.dupe(u8, "zig"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "build"));
|
||||
},
|
||||
.tests => {
|
||||
try argv.append(allocator, try allocator.dupe(u8, "zig"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "build"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "test"));
|
||||
},
|
||||
.check => {
|
||||
try argv.append(allocator, try allocator.dupe(u8, "zig"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "build"));
|
||||
try argv.append(allocator, try allocator.dupe(u8, "v1-smoke"));
|
||||
},
|
||||
}
|
||||
return argv.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
pub fn freeArgv(allocator: std.mem.Allocator, argv: []const []const u8) void {
|
||||
for (argv) |arg| allocator.free(arg);
|
||||
allocator.free(argv);
|
||||
}
|
||||
|
||||
pub fn profileRowsAlloc(allocator: std.mem.Allocator, io: std.Io, cwd: []const u8, profile: Profile, current_file: ?[]const u8) ![][]const u8 {
|
||||
const argv = argvForProfileAlloc(allocator, profile, current_file) catch |err| {
|
||||
if (err == Error.MissingCurrentFile) return missingCurrentFileRowsAlloc(allocator, profile);
|
||||
return err;
|
||||
};
|
||||
defer freeArgv(allocator, argv);
|
||||
return runRowsWithPreviewAlloc(allocator, io, cwd, argv, "job", profilePreview(profile));
|
||||
}
|
||||
|
||||
pub fn missingToolRowsAlloc(allocator: std.mem.Allocator, profile: Profile, tool: []const u8) ![][]const u8 {
|
||||
var rows = std.ArrayList([]const u8).empty;
|
||||
errdefer freeOwnedRows(allocator, rows.items);
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:profile:{s}:scope:{s}:provider:{s}", .{ profile.name(), profile.scope(), profile.provider() }));
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:status:missing_tool:{s}:no_install_attempted", .{sanitizeToken(tool)}));
|
||||
return rows.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
pub fn cancelRowsAlloc(allocator: std.mem.Allocator, profile: Profile) ![][]const u8 {
|
||||
var rows = std.ArrayList([]const u8).empty;
|
||||
errdefer freeOwnedRows(allocator, rows.items);
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:profile:{s}:scope:{s}:provider:{s}", .{ profile.name(), profile.scope(), profile.provider() }));
|
||||
try rows.append(allocator, try allocator.dupe(u8, "job:status:cancelled:user"));
|
||||
return rows.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
pub fn timeoutRowsAlloc(allocator: std.mem.Allocator, profile: Profile) ![][]const u8 {
|
||||
var rows = std.ArrayList([]const u8).empty;
|
||||
errdefer freeOwnedRows(allocator, rows.items);
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:profile:{s}:scope:{s}:provider:{s}", .{ profile.name(), profile.scope(), profile.provider() }));
|
||||
try rows.append(allocator, try allocator.dupe(u8, "job:status:timeout:recoverable"));
|
||||
return rows.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
fn missingCurrentFileRowsAlloc(allocator: std.mem.Allocator, profile: Profile) ![][]const u8 {
|
||||
var rows = std.ArrayList([]const u8).empty;
|
||||
errdefer freeOwnedRows(allocator, rows.items);
|
||||
try rows.append(allocator, try std.fmt.allocPrint(allocator, "job:profile:{s}:scope:{s}:provider:{s}", .{ profile.name(), profile.scope(), profile.provider() }));
|
||||
try rows.append(allocator, try allocator.dupe(u8, "job:status:missing_current_file"));
|
||||
return rows.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
fn profilePreview(profile: Profile) []const u8 {
|
||||
return switch (profile) {
|
||||
.lint_file => "profile:lint_file:scope:file:provider:zig-fmt",
|
||||
.lint_project => "profile:lint_project:scope:project:provider:zig-fmt",
|
||||
.build => "profile:build:scope:project:provider:zig-build",
|
||||
.tests => "profile:test:scope:project:provider:zig-build",
|
||||
.check => "profile:check:scope:project:provider:zig-build",
|
||||
};
|
||||
}
|
||||
|
||||
fn sanitizeToken(text: []const u8) []const u8 {
|
||||
if (text.len == 0) return "unknown";
|
||||
for (text) |byte| {
|
||||
if (byte <= 0x20 or byte == ':' or byte == '|') return "invalid";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
pub const Location = struct {
|
||||
path: []const u8,
|
||||
line: usize,
|
||||
|
||||
@@ -15,6 +15,7 @@ pub const Feature = enum {
|
||||
panel_close,
|
||||
lsp,
|
||||
diagnostics,
|
||||
jobs,
|
||||
};
|
||||
|
||||
pub const Action = union(enum) {
|
||||
@@ -33,6 +34,14 @@ pub const Action = union(enum) {
|
||||
diagnostics_next,
|
||||
diagnostics_previous,
|
||||
diagnostics_filter,
|
||||
job_lint_file,
|
||||
job_lint_project,
|
||||
job_build,
|
||||
job_test,
|
||||
job_check,
|
||||
job_cancel,
|
||||
job_jump,
|
||||
job_yank,
|
||||
repeat_rail,
|
||||
restore_last,
|
||||
not_built: Feature,
|
||||
@@ -52,6 +61,7 @@ const Mode = enum {
|
||||
search_rail,
|
||||
language_rail,
|
||||
diagnostic_rail,
|
||||
tool_rail,
|
||||
open_prompt,
|
||||
};
|
||||
|
||||
@@ -78,6 +88,7 @@ pub const Leader = struct {
|
||||
.search_rail => return self.handleSearchRail(event),
|
||||
.language_rail => return self.handleLanguageRail(event),
|
||||
.diagnostic_rail => return self.handleDiagnosticRail(event),
|
||||
.tool_rail => return self.handleToolRail(event),
|
||||
.open_prompt => return self.handleOpenPrompt(event),
|
||||
}
|
||||
}
|
||||
@@ -91,6 +102,7 @@ pub const Leader = struct {
|
||||
.search_rail => "search: f current file p project s symbols",
|
||||
.language_rail => "language: h hover s signature o open hover",
|
||||
.diagnostic_rail => "diagnostics: d/open n next p previous f filter-source",
|
||||
.tool_rail => "tools: l lint-file L lint-project b build t test c check x cancel j jump y yank",
|
||||
.open_prompt => "open: type path, Enter opens, Esc cancels",
|
||||
};
|
||||
}
|
||||
@@ -143,6 +155,10 @@ pub const Leader = struct {
|
||||
self.mode = .diagnostic_rail;
|
||||
return .diagnostics_open;
|
||||
}
|
||||
if (std.mem.eql(u8, text, "t")) {
|
||||
self.mode = .tool_rail;
|
||||
return .none;
|
||||
}
|
||||
if (std.mem.eql(u8, text, "q")) {
|
||||
self.mode = .idle;
|
||||
return .quit;
|
||||
@@ -272,6 +288,42 @@ pub const Leader = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn handleToolRail(self: *Leader, event: input.Event) Action {
|
||||
self.message = null;
|
||||
switch (event) {
|
||||
.text => |text| {
|
||||
self.mode = .idle;
|
||||
if (std.mem.eql(u8, text, "l")) return .job_lint_file;
|
||||
if (std.mem.eql(u8, text, "L")) return .job_lint_project;
|
||||
if (std.mem.eql(u8, text, "b")) return .job_build;
|
||||
if (std.mem.eql(u8, text, "t")) return .job_test;
|
||||
if (std.mem.eql(u8, text, "c")) return .job_check;
|
||||
if (std.mem.eql(u8, text, "x")) return .job_cancel;
|
||||
if (std.mem.eql(u8, text, "j")) return .job_jump;
|
||||
if (std.mem.eql(u8, text, "y")) return .job_yank;
|
||||
self.message = "unknown tool key";
|
||||
return .none;
|
||||
},
|
||||
.key => |key| switch (key) {
|
||||
.escape, .backspace => {
|
||||
self.mode = .idle;
|
||||
self.message = "tools cancelled";
|
||||
return .none;
|
||||
},
|
||||
else => {
|
||||
self.mode = .idle;
|
||||
self.message = "unknown tool key";
|
||||
return .none;
|
||||
},
|
||||
},
|
||||
.unknown => {
|
||||
self.mode = .idle;
|
||||
self.message = "unknown tool key";
|
||||
return .none;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handleSearchRail(self: *Leader, event: input.Event) Action {
|
||||
self.message = null;
|
||||
switch (event) {
|
||||
|
||||
+226
-5
@@ -134,6 +134,8 @@ pub const Client = struct {
|
||||
repo: repo_mod.Index,
|
||||
io: ?std.Io,
|
||||
viewport: Viewport,
|
||||
current_path: ?[]u8 = null,
|
||||
job_cwd: ?[]u8 = null,
|
||||
saved_bytes: ?[]u8 = null,
|
||||
message: ?[]const u8 = null,
|
||||
owned_message: ?[]u8 = null,
|
||||
@@ -178,6 +180,8 @@ pub const Client = struct {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Client) void {
|
||||
if (self.current_path) |path| self.allocator.free(path);
|
||||
if (self.job_cwd) |cwd| self.allocator.free(cwd);
|
||||
if (self.saved_bytes) |bytes| self.allocator.free(bytes);
|
||||
if (self.owned_message) |bytes| self.allocator.free(bytes);
|
||||
if (self.yank_bytes) |bytes| self.allocator.free(bytes);
|
||||
@@ -212,6 +216,10 @@ pub const Client = struct {
|
||||
if (std.mem.startsWith(u8, line, "lsp_signature_fixture ")) return self.addSignatureFixture(line[22..]);
|
||||
if (std.mem.startsWith(u8, line, "diagnostic_fixture ")) return self.addDiagnosticFixture(line[19..]);
|
||||
if (std.mem.startsWith(u8, line, "diagnostic_filter ")) return self.setDiagnosticFilter(line[18..]);
|
||||
if (std.mem.startsWith(u8, line, "current_file ")) return self.setCurrentPath(line[13..]);
|
||||
if (std.mem.startsWith(u8, line, "job_profile ")) return self.openJobProfileByName(line[12..]);
|
||||
if (std.mem.startsWith(u8, line, "job_missing_tool ")) return self.openMissingToolJob(line[17..]);
|
||||
if (std.mem.startsWith(u8, line, "job_timeout ")) return self.openTimeoutJob(line[12..]);
|
||||
if (std.mem.eql(u8, line, "file_picker")) return self.openRepoList("files", false, false);
|
||||
if (std.mem.eql(u8, line, "file_picker all")) return self.openRepoList("files", false, true);
|
||||
if (std.mem.eql(u8, line, "file_tree")) return self.openRepoList("tree", true, false);
|
||||
@@ -227,6 +235,7 @@ pub const Client = struct {
|
||||
if (std.mem.eql(u8, line, "job_status")) return self.openStaticJobRow("job-status", "job_status:idle");
|
||||
if (std.mem.eql(u8, line, "job_cancel")) return self.openStaticJobRow("job-status", "job_cancel:no_running_job");
|
||||
if (std.mem.startsWith(u8, line, "job_open_selected ")) return self.openSelectedJobLocation(line[18..]);
|
||||
if (std.mem.eql(u8, line, "job_yank_selected")) return self.yankSelectedJobLine();
|
||||
if (std.mem.startsWith(u8, line, "terminal_run ")) return self.openTerminalRun(line[13..]);
|
||||
if (std.mem.eql(u8, line, "terminal_status")) return self.openStaticJobRow("terminal-status", "terminal_status:idle");
|
||||
if (std.mem.eql(u8, line, "terminal_cancel")) return self.openStaticJobRow("terminal-status", "terminal_cancel:no_running_terminal");
|
||||
@@ -570,6 +579,7 @@ pub const Client = struct {
|
||||
const selected = self.session.selectListPanel() catch return Error.ProtocolRejected;
|
||||
const content = self.repo.content(selected) catch return Error.ProtocolRejected;
|
||||
try self.session.openFixture(content);
|
||||
try self.setCurrentPath(selected);
|
||||
try self.session.closePanel();
|
||||
self.message = null;
|
||||
}
|
||||
@@ -590,10 +600,16 @@ pub const Client = struct {
|
||||
const match = self.repo.searchOffsetFromRow(selected) catch return Error.ProtocolRejected;
|
||||
const content = self.repo.content(match.path) catch return Error.ProtocolRejected;
|
||||
try self.session.openFixtureAt(content, match.offset);
|
||||
try self.setCurrentPath(match.path);
|
||||
try self.session.closePanel();
|
||||
self.message = null;
|
||||
}
|
||||
|
||||
fn setCurrentPath(self: *Client, path: []const u8) !void {
|
||||
if (self.current_path) |old| self.allocator.free(old);
|
||||
self.current_path = try self.allocator.dupe(u8, path);
|
||||
}
|
||||
|
||||
fn openGitStatus(self: *Client, cwd: []const u8) !void {
|
||||
const io = self.io orelse return Error.ProtocolRejected;
|
||||
const rows = repo_mod.gitStatusRowsAlloc(self.allocator, io, cwd) catch return Error.ProtocolRejected;
|
||||
@@ -635,8 +651,8 @@ pub const Client = struct {
|
||||
}
|
||||
const rows = job_mod.runRowsAlloc(self.allocator, io, split.cwd, argv.items) catch return Error.ProtocolRejected;
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
try self.session.openListPanel("job-output", rows);
|
||||
self.message = null;
|
||||
try self.setJobCwd(split.cwd);
|
||||
try self.openJobPanel("job-output", rows);
|
||||
}
|
||||
|
||||
fn openTerminalRun(self: *Client, cwd_and_command: []const u8) !void {
|
||||
@@ -644,10 +660,72 @@ pub const Client = struct {
|
||||
const split = splitCwdAndCommand(cwd_and_command) orelse return Error.ProtocolRejected;
|
||||
const rows = job_mod.shellRowsAlloc(self.allocator, io, split.cwd, split.command) catch return Error.ProtocolRejected;
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
try self.session.openListPanel("terminal-output", rows);
|
||||
try self.setJobCwd(split.cwd);
|
||||
try self.openJobPanel("terminal-output", rows);
|
||||
}
|
||||
|
||||
fn openJobProfile(self: *Client, profile: job_mod.Profile) !void {
|
||||
const io = self.io orelse return Error.ProtocolRejected;
|
||||
const rows = job_mod.profileRowsAlloc(self.allocator, io, ".", profile, self.current_path) catch return Error.ProtocolRejected;
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
try self.setJobCwd(".");
|
||||
try self.openJobPanel("job-output", rows);
|
||||
}
|
||||
|
||||
fn openJobProfileByName(self: *Client, name: []const u8) !void {
|
||||
const profile = parseJobProfile(name) orelse return Error.ProtocolRejected;
|
||||
try self.openJobProfile(profile);
|
||||
}
|
||||
|
||||
fn openMissingToolJob(self: *Client, payload: []const u8) !void {
|
||||
var parts = std.mem.splitScalar(u8, payload, ' ');
|
||||
const profile_name = parts.next() orelse return Error.ProtocolRejected;
|
||||
const tool = parts.next() orelse return Error.ProtocolRejected;
|
||||
const profile = parseJobProfile(profile_name) orelse return Error.ProtocolRejected;
|
||||
const rows = try job_mod.missingToolRowsAlloc(self.allocator, profile, tool);
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
try self.setJobCwd(".");
|
||||
try self.openJobPanel("job-output", rows);
|
||||
}
|
||||
|
||||
fn openTimeoutJob(self: *Client, name: []const u8) !void {
|
||||
const profile = parseJobProfile(name) orelse return Error.ProtocolRejected;
|
||||
const rows = try job_mod.timeoutRowsAlloc(self.allocator, profile);
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
try self.setJobCwd(".");
|
||||
try self.openJobPanel("job-output", rows);
|
||||
}
|
||||
|
||||
fn openCancelledJob(self: *Client) !void {
|
||||
const rows = try job_mod.cancelRowsAlloc(self.allocator, .build);
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
try self.setJobCwd(".");
|
||||
try self.openJobPanel("job-output", rows);
|
||||
}
|
||||
|
||||
fn setJobCwd(self: *Client, cwd: []const u8) !void {
|
||||
if (self.job_cwd) |old| self.allocator.free(old);
|
||||
self.job_cwd = try self.allocator.dupe(u8, cwd);
|
||||
}
|
||||
|
||||
fn openJobPanel(self: *Client, title: []const u8, rows: []const []const u8) !void {
|
||||
const snap = try self.session.snapshot();
|
||||
if (snap.active_panel_title) |active| {
|
||||
if (std.mem.eql(u8, active, "job-output") or std.mem.eql(u8, active, "terminal-output")) self.session.closePanel() catch {};
|
||||
}
|
||||
try self.session.openListPanel(title, rows);
|
||||
self.message = null;
|
||||
}
|
||||
|
||||
fn parseJobProfile(name: []const u8) ?job_mod.Profile {
|
||||
if (std.mem.eql(u8, name, "lint_file")) return .lint_file;
|
||||
if (std.mem.eql(u8, name, "lint_project")) return .lint_project;
|
||||
if (std.mem.eql(u8, name, "build")) return .build;
|
||||
if (std.mem.eql(u8, name, "test")) return .tests;
|
||||
if (std.mem.eql(u8, name, "check")) return .check;
|
||||
return null;
|
||||
}
|
||||
|
||||
fn closeTerminalPanel(self: *Client) !void {
|
||||
try self.session.closePanel();
|
||||
self.message = null;
|
||||
@@ -799,15 +877,35 @@ pub const Client = struct {
|
||||
}
|
||||
|
||||
fn openSelectedJobLocation(self: *Client, cwd: []const u8) !void {
|
||||
const io = self.io orelse return Error.ProtocolRejected;
|
||||
const selected = self.session.selectListPanel() catch return Error.ProtocolRejected;
|
||||
const location = job_mod.locationFromRow(selected) catch return Error.ProtocolRejected;
|
||||
const content = repo_mod.readRepoFileAlloc(self.allocator, io, cwd, location.path) catch return Error.ProtocolRejected;
|
||||
if (self.current_path) |path| {
|
||||
if (std.mem.eql(u8, path, location.path)) {
|
||||
const snap = try self.session.snapshot();
|
||||
const content = try self.allocator.dupe(u8, snap.bytes);
|
||||
defer self.allocator.free(content);
|
||||
const offset = job_mod.byteOffsetForLineColumn(content, location.line, location.column) catch return Error.ProtocolRejected;
|
||||
try self.session.openFixtureAt(content, offset);
|
||||
try self.session.closePanel();
|
||||
self.message = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const io = self.io orelse return Error.ProtocolRejected;
|
||||
const content = repo_mod.readRepoFileAlloc(self.allocator, io, cwd, location.path) catch return Error.ProtocolRejected;
|
||||
defer self.allocator.free(content);
|
||||
const offset = job_mod.byteOffsetForLineColumn(content, location.line, location.column) catch return Error.ProtocolRejected;
|
||||
try self.session.openFixtureAt(content, offset);
|
||||
try self.setCurrentPath(location.path);
|
||||
try self.session.closePanel();
|
||||
self.message = null;
|
||||
}
|
||||
|
||||
fn yankSelectedJobLine(self: *Client) !void {
|
||||
const selected = self.session.activeListItem() catch return Error.ProtocolRejected;
|
||||
if (self.yank_bytes) |old| self.allocator.free(old);
|
||||
self.yank_bytes = try self.allocator.dupe(u8, selected);
|
||||
self.message = "yanked job line";
|
||||
}
|
||||
|
||||
fn handleTraceKey(self: *Client, key_name: []const u8) !void {
|
||||
@@ -1123,6 +1221,7 @@ pub const Client = struct {
|
||||
return;
|
||||
};
|
||||
try self.session.openFixtureAt(bytes, 0);
|
||||
try self.setCurrentPath(path);
|
||||
try self.closeActivePanel();
|
||||
self.message = path;
|
||||
}
|
||||
@@ -1137,6 +1236,7 @@ pub const Client = struct {
|
||||
return;
|
||||
};
|
||||
try self.session.openFixtureAt(bytes, target.offset);
|
||||
try self.setCurrentPath(target.path);
|
||||
try self.closeActivePanel();
|
||||
self.message = row;
|
||||
}
|
||||
@@ -1851,6 +1951,14 @@ pub const Client = struct {
|
||||
.diagnostics_next => try self.gotoDiagnostic(.next),
|
||||
.diagnostics_previous => try self.gotoDiagnostic(.previous),
|
||||
.diagnostics_filter => try self.filterDiagnosticsFromPanel(),
|
||||
.job_lint_file => try self.openJobProfile(.lint_file),
|
||||
.job_lint_project => try self.openJobProfile(.lint_project),
|
||||
.job_build => try self.openJobProfile(.build),
|
||||
.job_test => try self.openJobProfile(.tests),
|
||||
.job_check => try self.openJobProfile(.check),
|
||||
.job_cancel => try self.openCancelledJob(),
|
||||
.job_jump => try self.openSelectedJobLocation(self.job_cwd orelse "."),
|
||||
.job_yank => try self.yankSelectedJobLine(),
|
||||
.repeat_rail => self.openPrefix(.repeat),
|
||||
.restore_last => self.restoreLastRail(),
|
||||
.not_built => {},
|
||||
@@ -4002,3 +4110,116 @@ test "regular: diagnostic row model sanitizes and parses" {
|
||||
defer std.testing.allocator.free(panel);
|
||||
try std.testing.expect(std.mem.indexOf(u8, panel, "diag:stale:zls:error:1-4") != null);
|
||||
}
|
||||
|
||||
test "regular: job profiles produce source-patched command rows" {
|
||||
const argv = try job_mod.argvForProfileAlloc(std.testing.allocator, .lint_file, "src/main.zig");
|
||||
defer job_mod.freeArgv(std.testing.allocator, argv);
|
||||
try std.testing.expectEqualStrings("zig", argv[0]);
|
||||
try std.testing.expectEqualStrings("fmt", argv[1]);
|
||||
try std.testing.expectEqualStrings("--check", argv[2]);
|
||||
try std.testing.expectEqualStrings("src/main.zig", argv[3]);
|
||||
|
||||
const build_argv = try job_mod.argvForProfileAlloc(std.testing.allocator, .build, null);
|
||||
defer job_mod.freeArgv(std.testing.allocator, build_argv);
|
||||
try std.testing.expectEqualStrings("zig", build_argv[0]);
|
||||
try std.testing.expectEqualStrings("build", build_argv[1]);
|
||||
const test_argv = try job_mod.argvForProfileAlloc(std.testing.allocator, .tests, null);
|
||||
defer job_mod.freeArgv(std.testing.allocator, test_argv);
|
||||
try std.testing.expectEqualStrings("test", test_argv[2]);
|
||||
const check_argv = try job_mod.argvForProfileAlloc(std.testing.allocator, .check, null);
|
||||
defer job_mod.freeArgv(std.testing.allocator, check_argv);
|
||||
try std.testing.expectEqualStrings("v1-smoke", check_argv[2]);
|
||||
|
||||
const missing = try job_mod.profileRowsAlloc(std.testing.allocator, std.testing.io, ".", .lint_file, null);
|
||||
defer freeOwnedRows(std.testing.allocator, missing);
|
||||
try std.testing.expectEqualStrings("job:profile:lint_file:scope:file:provider:zig-fmt", missing[0]);
|
||||
try std.testing.expectEqualStrings("job:status:missing_current_file", missing[1]);
|
||||
|
||||
const cancel = try job_mod.cancelRowsAlloc(std.testing.allocator, .build);
|
||||
defer freeOwnedRows(std.testing.allocator, cancel);
|
||||
try std.testing.expectEqualStrings("job:status:cancelled:user", cancel[1]);
|
||||
|
||||
const timeout = try job_mod.timeoutRowsAlloc(std.testing.allocator, .tests);
|
||||
defer freeOwnedRows(std.testing.allocator, timeout);
|
||||
try std.testing.expectEqualStrings("job:status:timeout:recoverable", timeout[1]);
|
||||
|
||||
const missing_tool = try job_mod.missingToolRowsAlloc(std.testing.allocator, .check, "missing-zig");
|
||||
defer freeOwnedRows(std.testing.allocator, missing_tool);
|
||||
try std.testing.expectEqualStrings("job:status:missing_tool:missing-zig:no_install_attempted", missing_tool[1]);
|
||||
}
|
||||
|
||||
test "regular: Space t rail runs distinct lint scopes and replaces job panel" {
|
||||
var client = try Client.initWithIo(std.testing.allocator, .{ .width = 88, .height = 7 }, std.testing.io);
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("t");
|
||||
try client.handleInput("l");
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "job:profile:lint_file:scope:file:provider:zig-fmt") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "job:status:missing_current_file") != null);
|
||||
}
|
||||
|
||||
try client.handleTraceLine("current_file src/main.zig");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("t");
|
||||
try client.handleInput("L");
|
||||
const snap = try client.session.snapshot();
|
||||
try std.testing.expectEqual(@as(usize, 1), snap.panel_depth);
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "job:spawned:profile:lint_project:scope:project:provider:zig-fmt") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lint_file") == null);
|
||||
}
|
||||
|
||||
test "regular: Space t cancel yanks and timeout missing-tool rows are visible" {
|
||||
var client = try Client.initWithIo(std.testing.allocator, .{ .width = 88, .height = 6 }, std.testing.io);
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("t");
|
||||
try client.handleInput("x");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("t");
|
||||
try client.handleInput("y");
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "yanked job line") != null);
|
||||
}
|
||||
|
||||
try client.handleTraceLine("job_timeout test");
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "job:status:timeout:recoverable") != null);
|
||||
}
|
||||
|
||||
try client.handleTraceLine("job_missing_tool build missing-zig");
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "job:status:missing_tool:missing-zig:no_install_attempted") != null);
|
||||
}
|
||||
|
||||
test "regular: Space t jump opens selected diagnostic row and does not mix output" {
|
||||
var fixture = try makeTuiJobFixture(std.testing.allocator);
|
||||
defer {
|
||||
std.testing.allocator.free(fixture.cwd);
|
||||
fixture.tmp.cleanup();
|
||||
}
|
||||
var client = try Client.initWithIo(std.testing.allocator, .{ .width = 88, .height = 7 }, std.testing.io);
|
||||
defer client.deinit();
|
||||
|
||||
const run = try std.fmt.allocPrint(std.testing.allocator, "job_run {s} sh fail.sh", .{fixture.cwd});
|
||||
defer std.testing.allocator.free(run);
|
||||
try client.handleTraceLine(run);
|
||||
try client.handleTraceLine("list_filter src/main.zig");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("t");
|
||||
try client.handleInput("j");
|
||||
const snap = try client.session.snapshot();
|
||||
try std.testing.expectEqualStrings("one\nabcdTARGET\n", snap.bytes);
|
||||
try std.testing.expectEqual(@as(usize, 8), snap.cursor_byte);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user