Compare commits

...

8 commits

5 changed files with 104 additions and 19 deletions

View file

@ -30,8 +30,8 @@
.hash = "fuzzig-0.1.1-Ji0xivxIAQBD0g8O_NV_0foqoPf3elsg9Sc3pNfdVH4D",
},
.vaxis = .{
.url = "git+https://github.com/neurocyte/libvaxis?ref=main#855da7eb7e0991b360e1dcb630691465b725c761",
.hash = "vaxis-0.5.1-BWNV_OxtCQD40bC4JBVvCa4GjVRU4ESeFmYOUPcsuROG",
.url = "git+https://github.com/neurocyte/libvaxis?ref=main#1f16837cc6444f9323ac21a7988860f6a424b9d0",
.hash = "vaxis-0.5.1-BWNV_BazCQACrQg5CxqJoXx6A0SusCFlc8Rr-vgePzLr",
},
.zeit = .{
.url = "git+https://github.com/rockorager/zeit?ref=zig-0.15#ed2ca60db118414bda2b12df2039e33bad3b0b88",

View file

@ -40,8 +40,6 @@
["ctrl+shift+f", "find_in_files"],
["ctrl+shift+l", "toggle_panel"],
["alt+shift+p", "open_command_palette"],
["ctrl+shift+page_up", "terminal_scroll_up"],
["ctrl+shift+page_down", "terminal_scroll_down"],
["alt+n", "goto_next_file_or_diagnostic"],
["alt+p", "goto_prev_file_or_diagnostic"],
["alt+l", "toggle_panel"],
@ -65,8 +63,8 @@
["ctrl+shift+tab", "previous_tab"],
["ctrl+page_down", "next_tab"],
["ctrl+page_up", "previous_tab"],
["ctrl+shift+page_down", "move_tab_next"],
["ctrl+shift+page_up", "move_tab_previous"],
["ctrl+shift+page_down", "move_tab_next_or_scroll_terminal_down"],
["ctrl+shift+page_up", "move_tab_previous_or_scroll_terminal_up"],
["ctrl+k e", "switch_buffers"],
["alt+shift+v", "clipboard_history"],
["ctrl+0", "reset_fontsize"],
@ -353,6 +351,7 @@
["alt+f9", "overlay_next_widget_style"],
["alt+!", "add_task"],
["ctrl+j", "toggle_panel"],
["ctrl+shift+j", "toggle_maximize_panel"],
["ctrl+q", "quit"],
["ctrl+w", "close_file"],
["ctrl+shift+f", "find_in_files"],
@ -599,11 +598,13 @@
["ctrl+8", "focus_split", 7],
["ctrl+`", "focus_terminal"],
["ctrl+j", "toggle_panel"],
["ctrl+shift+page_down", "terminal_scroll_down"],
["ctrl+shift+page_up", "terminal_scroll_up"],
["ctrl+shift+j", "toggle_maximize_panel"],
["ctrl+shift+p", "open_command_palette"],
["alt+shift+p", "open_command_palette"],
["alt+x", "open_command_palette"],
["alt+!", "run_task"],
["ctrl+shift+j", "toggle_maximize_panel"],
["alt+f9", "panel_next_widget_style"],
["ctrl+shift+q", "quit_without_saving"],
["ctrl+alt+shift+r", "restart"]

View file

@ -34,6 +34,7 @@ const style = struct {
\\open_recent_project
\\find_in_files
\\open_command_palette
\\focus_terminal
\\run_task
\\add_task
\\open_config
@ -52,6 +53,7 @@ const style = struct {
\\open_recent_project
\\find_in_files
\\open_command_palette
\\focus_terminal
\\run_task
\\add_task
\\open_config

View file

@ -1014,7 +1014,7 @@ const cmds = struct {
vt.focus();
}
}
pub const focus_terminal_meta: Meta = .{ .description = "Focus terminal panel" };
pub const focus_terminal_meta: Meta = .{ .description = "Terminal" };
pub fn close_find_in_files_results(self: *Self, _: Ctx) Result {
if (self.file_list_type == .find_in_files)
@ -1591,6 +1591,22 @@ const cmds = struct {
}
pub const move_tab_previous_meta: Meta = .{ .description = "Move tab to previous position" };
pub fn move_tab_next_or_scroll_terminal_down(self: *Self, _: Ctx) Result {
if (self.is_panel_view_showing(terminal_view))
try command.executeName("terminal_scroll_down", .{})
else
_ = try self.widgets_widget.msg(.{"move_tab_next"});
}
pub const move_tab_next_or_scroll_terminal_down_meta: Meta = .{ .description = "Move tab next or scroll terminal down" };
pub fn move_tab_previous_or_scroll_terminal_up(self: *Self, _: Ctx) Result {
if (self.is_panel_view_showing(terminal_view))
try command.executeName("terminal_scroll_up", .{})
else
_ = try self.widgets_widget.msg(.{"move_tab_previous"});
}
pub const move_tab_previous_or_scroll_terminal_up_meta: Meta = .{ .description = "Move tab previous or scroll terminal up" };
pub fn place_next_tab(self: *Self, ctx: Ctx) Result {
var pos: enum { before, after } = undefined;
var buffer_ref: Buffer.Ref = undefined;

View file

@ -189,7 +189,7 @@ pub fn shutdown(allocator: Allocator) void {
}
}
pub fn render(self: *Self, _: *const Widget.Theme) bool {
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
// Drain the vt event queue.
while (self.vt.vt.tryEvent()) |event| {
switch (event) {
@ -206,9 +206,48 @@ pub fn render(self: *Self, _: *const Widget.Theme) bool {
self.vt.title.clearRetainingCapacity();
self.vt.title.appendSlice(self.allocator, t) catch {};
},
.color_change => |cc| {
self.vt.app_fg = cc.fg;
self.vt.app_bg = cc.bg;
self.vt.app_cursor = cc.cursor;
},
.osc_copy => |text| {
// Terminal app wrote to clipboard via OSC 52.
// Add to flow clipboard history and forward to system clipboard.
const owned = tui.clipboard_allocator().dupe(u8, text) catch break;
tui.clipboard_clear_all();
tui.clipboard_start_group();
tui.clipboard_add_chunk(owned);
tui.clipboard_send_to_system() catch {};
},
.osc_paste_request => {
// Terminal app requested clipboard contents via OSC 52.
// Assemble from flow clipboard history and respond.
if (tui.clipboard_get_history()) |history| {
var buf: std.Io.Writer.Allocating = .init(self.allocator);
defer buf.deinit();
var first = true;
for (history) |chunk| {
if (first) first = false else buf.writer.writeByte('\n') catch break;
buf.writer.writeAll(chunk.text) catch break;
}
self.vt.vt.respondOsc52Paste(buf.written());
}
},
}
}
// Update the terminal's fg/bg color cache from the current theme so that
// OSC 10/11 colour queries return accurate values.
if (theme.editor.fg) |fg| {
const c = fg.color;
self.vt.vt.fg_color = .{ @truncate(c >> 16), @truncate(c >> 8), @truncate(c) };
}
if (theme.editor.bg) |bg| {
const c = bg.color;
self.vt.vt.bg_color = .{ @truncate(c >> 16), @truncate(c >> 8), @truncate(c) };
}
// Blit the terminal's front screen into our vaxis.Window.
self.vt.vt.draw(self.allocator, self.plane.window, self.focused) catch |e| {
std.log.err("terminal_view: draw failed: {}", .{e});
@ -276,6 +315,10 @@ const Vt = struct {
pty_pid: ?tp.pid = null,
cwd: std.ArrayListUnmanaged(u8) = .empty,
title: std.ArrayListUnmanaged(u8) = .empty,
/// App-specified override colours (from OSC 10/11/12). null = use theme.
app_fg: ?[3]u8 = null,
app_bg: ?[3]u8 = null,
app_cursor: ?[3]u8 = null,
fn init(allocator: std.mem.Allocator, argv: []const []const u8, env: std.process.EnvMap, rows: u16, cols: u16) !void {
const home = env.get("HOME") orelse "/tmp";
@ -363,17 +406,23 @@ const pty = struct {
}
fn deinit(self: *@This()) void {
std.log.debug("terminal: pty actor deinit (pid={?})", .{self.vt.cmd.pid});
self.fd.deinit();
self.parser.buf.deinit();
self.parent.deinit();
self.allocator.destroy(self);
std.log.debug("terminal: pty destroyed", .{});
}
fn start(self: *@This()) tp.result {
errdefer self.deinit();
self.fd = tp.file_descriptor.init("pty", self.pty_fd) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.fd.wait_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
self.fd = tp.file_descriptor.init("pty", self.pty_fd) catch |e| {
std.log.debug("terminal: pty fd init failed: {}", .{e});
return tp.exit_error(e, @errorReturnTrace());
};
self.fd.wait_read() catch |e| {
std.log.debug("terminal: pty initial wait_read failed: {}", .{e});
return tp.exit_error(e, @errorReturnTrace());
};
tp.receive(&self.receiver);
}
@ -382,14 +431,28 @@ const pty = struct {
if (try m.match(.{ "fd", "pty", "read_ready" })) {
self.read_and_process() catch |e| return switch (e) {
error.Terminated => tp.exit_normal(),
error.InputOutput => tp.exit_normal(),
error.SendFailed => tp.exit_normal(),
error.Unexpected => tp.exit_normal(),
error.Terminated => {
std.log.debug("terminal: pty exiting: read loop terminated (process exited)", .{});
return tp.exit_normal();
},
error.InputOutput => {
std.log.debug("terminal: pty exiting: EIO on read (process exited)", .{});
return tp.exit_normal();
},
error.SendFailed => {
std.log.debug("terminal: pty exiting: send to parent failed", .{});
return tp.exit_normal();
},
error.Unexpected => {
std.log.debug("terminal: pty exiting: unexpected error (see preceding log)", .{});
return tp.exit_normal();
},
};
} else if (try m.match(.{"quit"})) {
std.log.debug("terminal: pty exiting: received quit", .{});
return tp.exit_normal();
} else {
std.log.debug("terminal: pty exiting: unexpected message", .{});
return tp.unexpected(m);
}
}
@ -402,6 +465,7 @@ const pty = struct {
error.WouldBlock => break,
error.InputOutput => {
const code = self.vt.cmd.wait();
std.log.debug("terminal: read EIO, process exited with code={d}", .{code});
self.vt.event_queue.push(.{ .exited = code });
self.parent.send(.{ "terminal_view", "output" }) catch {};
return error.InputOutput;
@ -420,12 +484,13 @@ const pty = struct {
error.LockViolation,
error.Unexpected,
=> {
std.log.err("terminal_view: read unexpected: {}", .{e});
std.log.debug("terminal: read unexpected error: {} (pid={?})", .{ e, self.vt.cmd.pid });
return error.Unexpected;
},
};
if (n == 0) {
const code = self.vt.cmd.wait();
std.log.debug("terminal: read returned 0 bytes (EOF), process exited with code={d}", .{code});
self.vt.event_queue.push(.{ .exited = code });
self.parent.send(.{ "terminal_view", "output" }) catch {};
return error.Terminated;
@ -439,11 +504,12 @@ const pty = struct {
error.OutOfMemory,
error.Utf8InvalidStartByte,
=> {
std.log.err("terminal_view: processOutput unexpected: {}", .{e});
std.log.debug("terminal: processOutput error: {} (pid={?})", .{ e, self.vt.cmd.pid });
return error.Unexpected;
},
}) {
.exited => {
std.log.debug("terminal: processOutput returned .exited (process EOF)", .{});
return error.Terminated;
},
.running => {},
@ -452,7 +518,7 @@ const pty = struct {
self.fd.wait_read() catch |e| switch (e) {
error.ThespianFileDescriptorWaitReadFailed => {
std.log.err("terminal_view: wait_read unexpected: {}", .{e});
std.log.debug("terminal: wait_read failed: {} (pid={?})", .{ e, self.vt.cmd.pid });
return error.Unexpected;
},
};