refactor(terminal): merge focus_terminal and open_terminal commands
This commit is contained in:
parent
2f5d4ded3c
commit
3553fbf0d2
5 changed files with 131 additions and 72 deletions
|
|
@ -23,7 +23,7 @@
|
||||||
["ctrl+6", "focus_split", 5],
|
["ctrl+6", "focus_split", 5],
|
||||||
["ctrl+7", "focus_split", 6],
|
["ctrl+7", "focus_split", 6],
|
||||||
["ctrl+8", "focus_split", 7],
|
["ctrl+8", "focus_split", 7],
|
||||||
["ctrl+`", "focus_terminal"],
|
["ctrl+`", "open_terminal"],
|
||||||
["ctrl+j", "toggle_panel"],
|
["ctrl+j", "toggle_panel"],
|
||||||
["ctrl+shift+j", "toggle_maximize_panel"],
|
["ctrl+shift+j", "toggle_maximize_panel"],
|
||||||
["ctrl+q", "quit"],
|
["ctrl+q", "quit"],
|
||||||
|
|
@ -596,7 +596,7 @@
|
||||||
["ctrl+6", "focus_split", 5],
|
["ctrl+6", "focus_split", 5],
|
||||||
["ctrl+7", "focus_split", 6],
|
["ctrl+7", "focus_split", 6],
|
||||||
["ctrl+8", "focus_split", 7],
|
["ctrl+8", "focus_split", 7],
|
||||||
["ctrl+`", "focus_terminal"],
|
["ctrl+`", "unfocus_terminal"],
|
||||||
["ctrl+j", "toggle_panel"],
|
["ctrl+j", "toggle_panel"],
|
||||||
["ctrl+shift+page_down", "terminal_scroll_down"],
|
["ctrl+shift+page_down", "terminal_scroll_down"],
|
||||||
["ctrl+shift+page_up", "terminal_scroll_up"],
|
["ctrl+shift+page_up", "terminal_scroll_up"],
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const style = struct {
|
||||||
\\open_recent_project
|
\\open_recent_project
|
||||||
\\find_in_files
|
\\find_in_files
|
||||||
\\open_command_palette
|
\\open_command_palette
|
||||||
\\focus_terminal
|
\\open_terminal
|
||||||
\\run_task
|
\\run_task
|
||||||
\\add_task
|
\\add_task
|
||||||
\\open_config
|
\\open_config
|
||||||
|
|
@ -53,7 +53,7 @@ const style = struct {
|
||||||
\\open_recent_project
|
\\open_recent_project
|
||||||
\\find_in_files
|
\\find_in_files
|
||||||
\\open_command_palette
|
\\open_command_palette
|
||||||
\\focus_terminal
|
\\open_terminal
|
||||||
\\run_task
|
\\run_task
|
||||||
\\add_task
|
\\add_task
|
||||||
\\open_config
|
\\open_config
|
||||||
|
|
|
||||||
|
|
@ -929,7 +929,7 @@ const cmds = struct {
|
||||||
else if (self.is_panel_view_showing(terminal_view))
|
else if (self.is_panel_view_showing(terminal_view))
|
||||||
try self.toggle_panel_view(terminal_view, .toggle)
|
try self.toggle_panel_view(terminal_view, .toggle)
|
||||||
else
|
else
|
||||||
try focus_terminal(self, .{});
|
try open_terminal(self, .{});
|
||||||
}
|
}
|
||||||
pub const toggle_panel_meta: Meta = .{ .description = "Toggle panel" };
|
pub const toggle_panel_meta: Meta = .{ .description = "Toggle panel" };
|
||||||
|
|
||||||
|
|
@ -985,20 +985,40 @@ const cmds = struct {
|
||||||
pub const toggle_terminal_view_meta: Meta = .{ .description = "Toggle terminal" };
|
pub const toggle_terminal_view_meta: Meta = .{ .description = "Toggle terminal" };
|
||||||
|
|
||||||
pub fn open_terminal(self: *Self, ctx: Ctx) Result {
|
pub fn open_terminal(self: *Self, ctx: Ctx) Result {
|
||||||
try self.toggle_panel_view_with_args(terminal_view, .enable, ctx);
|
const have_args = ctx.args.buf.len > 0 and try ctx.args.match(.{ tp.string, tp.more });
|
||||||
}
|
|
||||||
pub const open_terminal_meta: Meta = .{ .description = "Open terminal", .arguments = &.{.string} };
|
|
||||||
|
|
||||||
pub fn focus_terminal(self: *Self, _: Ctx) Result {
|
if (have_args and terminal_view.is_vt_running()) {
|
||||||
if (self.get_panel_view(terminal_view)) |vt| {
|
var msg: std.Io.Writer.Allocating = .init(self.allocator);
|
||||||
|
defer msg.deinit();
|
||||||
|
try msg.writer.writeAll("terminal is already running '");
|
||||||
|
try terminal_view.get_running_cmd(&msg.writer);
|
||||||
|
try msg.writer.writeAll("'");
|
||||||
|
return tp.exit(msg.written());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terminal_view.is_vt_running()) if (self.get_panel_view(terminal_view)) |vt| {
|
||||||
|
std.log.debug("open_terminal: toggle_focus", .{});
|
||||||
vt.toggle_focus();
|
vt.toggle_focus();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var buf: [tp.max_message_size]u8 = undefined;
|
||||||
|
std.log.debug("open_terminal: {s}", .{if (ctx.args.buf.len > 0) ctx.args.to_json(&buf) catch "(error)" else "(none)"});
|
||||||
|
if (self.get_panel_view(terminal_view)) |vt| {
|
||||||
|
try vt.run_cmd(ctx);
|
||||||
} else {
|
} else {
|
||||||
try self.toggle_panel_view(terminal_view, .enable);
|
try self.toggle_panel_view_with_args(terminal_view, .enable, ctx);
|
||||||
if (self.get_panel_view(terminal_view)) |vt|
|
if (self.get_panel_view(terminal_view)) |vt|
|
||||||
vt.focus();
|
vt.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const focus_terminal_meta: Meta = .{ .description = "Open terminal" };
|
pub const open_terminal_meta: Meta = .{ .description = "Open terminal" };
|
||||||
|
|
||||||
|
pub fn unfocus_terminal(self: *Self, _: Ctx) Result {
|
||||||
|
if (self.get_panel_view(terminal_view)) |vt|
|
||||||
|
vt.toggle_focus();
|
||||||
|
}
|
||||||
|
pub const unfocus_terminal_meta: Meta = .{};
|
||||||
|
|
||||||
pub fn close_terminal(self: *Self, _: Ctx) Result {
|
pub fn close_terminal(self: *Self, _: Ctx) Result {
|
||||||
if (self.get_panel_view(terminal_view)) |_|
|
if (self.get_panel_view(terminal_view)) |_|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ focused: bool = false,
|
||||||
input_mode: Mode,
|
input_mode: Mode,
|
||||||
hover: bool = false,
|
hover: bool = false,
|
||||||
vt: *Vt,
|
vt: *Vt,
|
||||||
|
last_cmd: ?[]const u8,
|
||||||
commands: Commands = undefined,
|
commands: Commands = undefined,
|
||||||
|
|
||||||
pub fn create(allocator: Allocator, parent: Plane, ctx: command.Context) !Widget {
|
pub fn create(allocator: Allocator, parent: Plane, ctx: command.Context) !Widget {
|
||||||
|
|
@ -49,45 +50,6 @@ pub fn create(allocator: Allocator, parent: Plane, ctx: command.Context) !Widget
|
||||||
var plane = try Plane.init(&(Widget.Box{}).opts(name), parent);
|
var plane = try Plane.init(&(Widget.Box{}).opts(name), parent);
|
||||||
errdefer plane.deinit();
|
errdefer plane.deinit();
|
||||||
|
|
||||||
var env = try std.process.getEnvMap(allocator);
|
|
||||||
errdefer env.deinit();
|
|
||||||
|
|
||||||
var cmd_arg: []const u8 = "";
|
|
||||||
var on_exit: TerminalOnExit = tui.config().terminal_on_exit;
|
|
||||||
const argv_msg: ?tp.message = if (ctx.args.match(.{tp.extract(&cmd_arg)}) catch false and cmd_arg.len > 0)
|
|
||||||
try shell.parse_arg0_to_argv(allocator, &cmd_arg)
|
|
||||||
else if (ctx.args.match(.{ tp.extract(&cmd_arg), tp.extract(&on_exit) }) catch false and cmd_arg.len > 0)
|
|
||||||
try shell.parse_arg0_to_argv(allocator, &cmd_arg)
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
defer if (argv_msg) |msg| allocator.free(msg.buf);
|
|
||||||
|
|
||||||
var argv_list: std.ArrayListUnmanaged([]const u8) = .empty;
|
|
||||||
defer argv_list.deinit(allocator);
|
|
||||||
if (argv_msg) |msg| {
|
|
||||||
var iter = msg.buf;
|
|
||||||
var len = try cbor.decodeArrayHeader(&iter);
|
|
||||||
while (len > 0) : (len -= 1) {
|
|
||||||
var arg: []const u8 = undefined;
|
|
||||||
if (try cbor.matchValue(&iter, cbor.extract(&arg)))
|
|
||||||
try argv_list.append(allocator, arg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const default_shell = if (builtin.os.tag == .windows)
|
|
||||||
env.get("COMSPEC") orelse "cmd.exe"
|
|
||||||
else
|
|
||||||
env.get("SHELL") orelse "/bin/sh";
|
|
||||||
try argv_list.append(allocator, default_shell);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the current plane dimensions for the initial pty size. The plane
|
|
||||||
// starts at 0×0 before the first resize, so use a sensible fallback
|
|
||||||
// so the pty isn't created with a zero-cell screen.
|
|
||||||
const cols: u16 = @intCast(@max(80, plane.dim_x()));
|
|
||||||
const rows: u16 = @intCast(@max(24, plane.dim_y()));
|
|
||||||
|
|
||||||
if (global_vt == null) try Vt.init(allocator, argv_list.items, env, rows, cols, on_exit);
|
|
||||||
|
|
||||||
const self = try allocator.create(Self);
|
const self = try allocator.create(Self);
|
||||||
errdefer allocator.destroy(self);
|
errdefer allocator.destroy(self);
|
||||||
|
|
||||||
|
|
@ -95,8 +57,10 @@ pub fn create(allocator: Allocator, parent: Plane, ctx: command.Context) !Widget
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.plane = plane,
|
.plane = plane,
|
||||||
.input_mode = try keybind.mode("terminal", allocator, .{ .insert_command = "do_nothing" }),
|
.input_mode = try keybind.mode("terminal", allocator, .{ .insert_command = "do_nothing" }),
|
||||||
.vt = &global_vt.?,
|
.vt = undefined,
|
||||||
|
.last_cmd = null,
|
||||||
};
|
};
|
||||||
|
try self.run_cmd(ctx);
|
||||||
|
|
||||||
try self.commands.init(self);
|
try self.commands.init(self);
|
||||||
try tui.message_filters().add(MessageFilter.bind(self, receive_filter));
|
try tui.message_filters().add(MessageFilter.bind(self, receive_filter));
|
||||||
|
|
@ -107,6 +71,73 @@ pub fn create(allocator: Allocator, parent: Plane, ctx: command.Context) !Widget
|
||||||
return container.widget();
|
return container.widget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_cmd(self: *Self, ctx: command.Context) !void {
|
||||||
|
var env = try std.process.getEnvMap(self.allocator);
|
||||||
|
errdefer env.deinit();
|
||||||
|
|
||||||
|
var cmd_arg: []const u8 = "";
|
||||||
|
var on_exit: TerminalOnExit = tui.config().terminal_on_exit;
|
||||||
|
const argv_msg: ?tp.message = if (ctx.args.match(.{tp.extract(&cmd_arg)}) catch false and cmd_arg.len > 0)
|
||||||
|
try shell.parse_arg0_to_argv(self.allocator, &cmd_arg)
|
||||||
|
else if (ctx.args.match(.{ tp.extract(&cmd_arg), tp.extract(&on_exit) }) catch false and cmd_arg.len > 0)
|
||||||
|
try shell.parse_arg0_to_argv(self.allocator, &cmd_arg)
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
defer if (argv_msg) |msg| self.allocator.free(msg.buf);
|
||||||
|
|
||||||
|
var argv_list: std.ArrayListUnmanaged([]const u8) = .empty;
|
||||||
|
defer argv_list.deinit(self.allocator);
|
||||||
|
if (argv_msg) |msg| {
|
||||||
|
var iter = msg.buf;
|
||||||
|
var len = try cbor.decodeArrayHeader(&iter);
|
||||||
|
while (len > 0) : (len -= 1) {
|
||||||
|
var arg: []const u8 = undefined;
|
||||||
|
if (try cbor.matchValue(&iter, cbor.extract(&arg)))
|
||||||
|
try argv_list.append(self.allocator, arg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const default_shell = if (builtin.os.tag == .windows)
|
||||||
|
env.get("COMSPEC") orelse "cmd.exe"
|
||||||
|
else
|
||||||
|
env.get("SHELL") orelse "/bin/sh";
|
||||||
|
try argv_list.append(self.allocator, default_shell);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the current plane dimensions for the initial pty size. The plane
|
||||||
|
// starts at 0×0 before the first resize, so use a sensible fallback
|
||||||
|
// so the pty isn't created with a zero-cell screen.
|
||||||
|
const cols: u16 = @intCast(@max(80, self.plane.dim_x()));
|
||||||
|
const rows: u16 = @intCast(@max(24, self.plane.dim_y()));
|
||||||
|
|
||||||
|
if (global_vt) |*vt| {
|
||||||
|
if (!vt.process_exited) {
|
||||||
|
var msg: std.Io.Writer.Allocating = .init(self.allocator);
|
||||||
|
defer msg.deinit();
|
||||||
|
try msg.writer.writeAll("terminal is already running '");
|
||||||
|
try get_running_cmd(&msg.writer);
|
||||||
|
try msg.writer.writeAll("'");
|
||||||
|
return tp.exit(msg.written());
|
||||||
|
}
|
||||||
|
vt.deinit(self.allocator);
|
||||||
|
global_vt = null;
|
||||||
|
}
|
||||||
|
try Vt.init(self.allocator, argv_list.items, env, rows, cols, on_exit);
|
||||||
|
self.vt = &global_vt.?;
|
||||||
|
|
||||||
|
if (self.last_cmd) |cmd| {
|
||||||
|
self.allocator.free(cmd);
|
||||||
|
self.last_cmd = null;
|
||||||
|
}
|
||||||
|
self.last_cmd = try self.allocator.dupe(u8, ctx.args.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn re_run_cmd(self: *Self) !void {
|
||||||
|
return if (self.last_cmd) |cmd|
|
||||||
|
self.run_cmd(.{ .args = .{ .buf = cmd } })
|
||||||
|
else
|
||||||
|
tp.exit("no command to re-run");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||||
if (try m.match(.{ "terminal_view", "output" })) {
|
if (try m.match(.{ "terminal_view", "output" })) {
|
||||||
tui.need_render(@src());
|
tui.need_render(@src());
|
||||||
|
|
@ -234,8 +265,7 @@ pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||||
};
|
};
|
||||||
if (self.vt.process_exited) {
|
if (self.vt.process_exited) {
|
||||||
if (keypress == input.key.enter) {
|
if (keypress == input.key.enter) {
|
||||||
self.vt.process_exited = false;
|
self.re_run_cmd() catch |e|
|
||||||
self.restart() catch |e|
|
|
||||||
std.log.err("terminal_view: restart failed: {}", .{e});
|
std.log.err("terminal_view: restart failed: {}", .{e});
|
||||||
tui.need_render(@src());
|
tui.need_render(@src());
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -267,6 +297,10 @@ pub fn unfocus(self: *Self) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
pub fn deinit(self: *Self, allocator: Allocator) void {
|
||||||
|
if (self.last_cmd) |cmd| {
|
||||||
|
self.allocator.free(cmd);
|
||||||
|
self.last_cmd = null;
|
||||||
|
}
|
||||||
if (global_vt) |*vt| if (vt.process_exited) {
|
if (global_vt) |*vt| if (vt.process_exited) {
|
||||||
vt.deinit(allocator);
|
vt.deinit(allocator);
|
||||||
global_vt = null;
|
global_vt = null;
|
||||||
|
|
@ -375,31 +409,22 @@ fn show_exit_message(self: *Self, code: u8) void {
|
||||||
w.writeAll("[process exited") catch {};
|
w.writeAll("[process exited") catch {};
|
||||||
if (code != 0)
|
if (code != 0)
|
||||||
w.print(" with code {d}", .{code}) catch {};
|
w.print(" with code {d}", .{code}) catch {};
|
||||||
w.writeAll("]\x1b[0m\r\n") catch {};
|
w.writeAll("]") catch {};
|
||||||
// Re-run prompt
|
// Re-run prompt
|
||||||
const cmd_argv = self.vt.vt.cmd.argv;
|
const cmd_argv = self.vt.vt.cmd.argv;
|
||||||
if (cmd_argv.len > 0) {
|
if (cmd_argv.len > 0) {
|
||||||
w.writeAll("\x1b[0m\x1b[2mPress enter to re-run '") catch {};
|
w.writeAll(" Press enter to re-run '") catch {};
|
||||||
_ = argv.write(w, cmd_argv) catch {};
|
_ = argv.write(w, cmd_argv) catch {};
|
||||||
w.writeAll("'\x1b[0m\r\n") catch {};
|
w.writeAll("' or escape to close") catch {};
|
||||||
|
} else {
|
||||||
|
w.writeAll(" Press esc to close") catch {};
|
||||||
}
|
}
|
||||||
|
w.writeAll("\x1b[0m\r\n") catch {};
|
||||||
var parser: pty.Parser = .{ .buf = .init(self.allocator) };
|
var parser: pty.Parser = .{ .buf = .init(self.allocator) };
|
||||||
defer parser.buf.deinit();
|
defer parser.buf.deinit();
|
||||||
_ = self.vt.vt.processOutput(&parser, msg.written()) catch {};
|
_ = self.vt.vt.processOutput(&parser, msg.written()) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restart(self: *Self) !void {
|
|
||||||
// Kill the old pty actor if still alive
|
|
||||||
if (self.vt.pty_pid) |pid| {
|
|
||||||
pid.send(.{"quit"}) catch {};
|
|
||||||
pid.deinit();
|
|
||||||
self.vt.pty_pid = null;
|
|
||||||
}
|
|
||||||
// Re-spawn the child process and a fresh pty actor
|
|
||||||
try self.vt.vt.spawn();
|
|
||||||
self.vt.pty_pid = try pty.spawn(self.allocator, &self.vt.vt);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
|
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
|
||||||
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
|
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
|
||||||
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
|
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
|
||||||
|
|
@ -509,6 +534,17 @@ const Vt = struct {
|
||||||
};
|
};
|
||||||
var global_vt: ?Vt = null;
|
var global_vt: ?Vt = null;
|
||||||
|
|
||||||
|
pub fn is_vt_running() bool {
|
||||||
|
return if (global_vt) |vt| !vt.process_exited else false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_running_cmd(writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||||
|
const cmd_argv = if (global_vt) |vt| vt.vt.cmd.argv else &.{};
|
||||||
|
if (cmd_argv.len > 0) {
|
||||||
|
_ = argv.write(writer, cmd_argv) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Platform-specific pty actor: POSIX uses tp.file_descriptor + SIGCHLD,
|
// Platform-specific pty actor: POSIX uses tp.file_descriptor + SIGCHLD,
|
||||||
// Windows uses tp.file_stream with IOCP overlapped reads on the ConPTY output pipe.
|
// Windows uses tp.file_stream with IOCP overlapped reads on the ConPTY output pipe.
|
||||||
const pty = if (builtin.os.tag == .windows) pty_windows else pty_posix;
|
const pty = if (builtin.os.tag == .windows) pty_windows else pty_posix;
|
||||||
|
|
|
||||||
|
|
@ -1516,9 +1516,13 @@ const cmds = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run_task_in_terminal(self: *Self, ctx: Ctx) Result {
|
pub fn run_task_in_terminal(self: *Self, ctx: Ctx) Result {
|
||||||
|
var buf: [tp.max_message_size]u8 = undefined;
|
||||||
|
std.log.debug("run_task_in_terminal: {s}", .{if (ctx.args.buf.len > 0) ctx.args.to_json(&buf) catch "(error)" else "(none)"});
|
||||||
const expansion = @import("expansion.zig");
|
const expansion = @import("expansion.zig");
|
||||||
var task: []const u8 = undefined;
|
var task: []const u8 = undefined;
|
||||||
if (!try ctx.args.match(.{tp.extract(&task)})) return;
|
var on_exit: @import("config").TerminalOnExit = self.config_.terminal_on_exit;
|
||||||
|
if (!(try ctx.args.match(.{tp.extract(&task)}) or
|
||||||
|
try ctx.args.match(.{ tp.extract(&task), tp.extract(&on_exit) }))) return;
|
||||||
const args = expansion.expand_cbor(self.allocator, ctx.args.buf) catch |e| switch (e) {
|
const args = expansion.expand_cbor(self.allocator, ctx.args.buf) catch |e| switch (e) {
|
||||||
error.NotFound => return error.Stop,
|
error.NotFound => return error.Stop,
|
||||||
else => |e_| return e_,
|
else => |e_| return e_,
|
||||||
|
|
@ -1528,8 +1532,7 @@ const cmds = struct {
|
||||||
if (!try cbor.match(args, .{tp.extract(&cmd)}))
|
if (!try cbor.match(args, .{tp.extract(&cmd)}))
|
||||||
cmd = task;
|
cmd = task;
|
||||||
call_add_task(task);
|
call_add_task(task);
|
||||||
var buf: [tp.max_message_size]u8 = undefined;
|
try command.executeName("open_terminal", try command.fmtbuf(&buf, .{ cmd, on_exit }));
|
||||||
try command.executeName("open_terminal", try command.fmtbuf(&buf, .{cmd}));
|
|
||||||
}
|
}
|
||||||
pub const run_task_in_terminal_meta: Meta = .{
|
pub const run_task_in_terminal_meta: Meta = .{
|
||||||
.description = "Run a task in terminal",
|
.description = "Run a task in terminal",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue