Add a web browser tab.
This commit is contained in:
+38
-14
@@ -14,6 +14,7 @@ const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
const vt = @import("ghostty-vt");
|
||||
|
||||
const Terminal = @import("Terminal.zig");
|
||||
const View = @import("View.zig");
|
||||
|
||||
const Window = @This();
|
||||
@@ -42,13 +43,18 @@ updating: bool = false,
|
||||
/// doesn't try to close a tab we're already destroying.
|
||||
closing: bool = false,
|
||||
|
||||
/// A single tab: a view of one or more terminals, plus the sidebar row that
|
||||
/// A single tab: a view of one or more panes, plus the sidebar row that
|
||||
/// selects it.
|
||||
const Tab = struct {
|
||||
window: *Window,
|
||||
view: *View,
|
||||
row: *gtk.ListBoxRow,
|
||||
label: *gtk.Label,
|
||||
|
||||
/// Shows what the tab's focused pane is, so a web view is recognisable in
|
||||
/// the sidebar without reading the title.
|
||||
icon: *gtk.Image,
|
||||
|
||||
name: [16]u8,
|
||||
name_len: usize,
|
||||
|
||||
@@ -185,6 +191,7 @@ pub fn newTab(self: *Window) !void {
|
||||
.view = view,
|
||||
.row = gtk.ListBoxRow.new(),
|
||||
.label = gtk.Label.new("shell"),
|
||||
.icon = gtk.Image.newFromIconName("utilities-terminal-symbolic"),
|
||||
.name = undefined,
|
||||
.name_len = 0,
|
||||
};
|
||||
@@ -195,8 +202,7 @@ pub fn newTab(self: *Window) !void {
|
||||
const row_box = gtk.Box.new(.horizontal, 6);
|
||||
row_box.as(gtk.Widget).addCssClass("vtabs-row");
|
||||
|
||||
const icon = gtk.Image.newFromIconName("utilities-terminal-symbolic");
|
||||
row_box.append(icon.as(gtk.Widget));
|
||||
row_box.append(tab.icon.as(gtk.Widget));
|
||||
|
||||
tab.label.setXalign(0);
|
||||
tab.label.setEllipsize(.end);
|
||||
@@ -217,8 +223,8 @@ pub fn newTab(self: *Window) !void {
|
||||
try self.tabs.append(self.alloc, tab);
|
||||
|
||||
// Only now is `tab` complete enough for the view's callbacks to use, so
|
||||
// this is the first safe moment to give the view its terminal.
|
||||
try view.addPane();
|
||||
// this is the first safe moment to give the view its first pane.
|
||||
try view.addPane(.terminal);
|
||||
|
||||
self.refreshLabel(tab);
|
||||
self.select(tab);
|
||||
@@ -301,6 +307,7 @@ fn refreshLabel(self: *Window, tab: *Tab) void {
|
||||
|
||||
tab.label.setText(buf[0..text.len :0]);
|
||||
tab.label.as(gtk.Widget).setTooltipText(buf[0..text.len :0]);
|
||||
tab.icon.setFromIconName(tab.view.iconName());
|
||||
}
|
||||
|
||||
fn onViewTitle(ctx: ?*anyopaque) void {
|
||||
@@ -342,6 +349,20 @@ fn activeTab(self: *Window) ?*Tab {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The focused terminal of the visible tab, or null when a web pane has focus.
|
||||
fn focusedTerminal(self: *Window) ?*Terminal {
|
||||
const tab = self.activeTab() orelse return null;
|
||||
return tab.view.focusedTerminal();
|
||||
}
|
||||
|
||||
/// Split the visible tab's focused pane, adding a pane of the given kind.
|
||||
fn addPane(self: *Window, kind: View.Kind) void {
|
||||
const tab = self.activeTab() orelse return;
|
||||
tab.view.addPane(kind) catch |err| {
|
||||
std.log.err("failed to open {s} pane: {s}", .{ @tagName(kind), @errorName(err) });
|
||||
};
|
||||
}
|
||||
|
||||
fn selectIndex(self: *Window, index: usize) void {
|
||||
if (index >= self.tabs.items.len) return;
|
||||
self.select(self.tabs.items[index]);
|
||||
@@ -376,22 +397,26 @@ fn onShortcut(
|
||||
return 1;
|
||||
},
|
||||
gdk.KEY_W, gdk.KEY_w => {
|
||||
// Closes the focused terminal. The view raises on_empty when
|
||||
// its last pane goes, which is what closes the tab.
|
||||
// Closes the focused pane. The view raises on_empty when its
|
||||
// last pane goes, which is what closes the tab.
|
||||
if (self.activeTab()) |tab| {
|
||||
if (tab.view.focusedPane()) |pane| tab.view.closePane(pane);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
gdk.KEY_E, gdk.KEY_e => {
|
||||
if (self.activeTab()) |tab| {
|
||||
tab.view.addPane() catch |err| {
|
||||
std.log.err("failed to open terminal: {s}", .{@errorName(err)});
|
||||
};
|
||||
}
|
||||
self.addPane(.terminal);
|
||||
return 1;
|
||||
},
|
||||
gdk.KEY_B, gdk.KEY_b => {
|
||||
self.addPane(.web);
|
||||
return 1;
|
||||
},
|
||||
gdk.KEY_V, gdk.KEY_v => {
|
||||
// Only a terminal needs us to encode a paste for it. A web
|
||||
// pane has its own clipboard handling, so the key is left
|
||||
// alone rather than swallowed here.
|
||||
if (self.focusedTerminal() == null) return 0;
|
||||
self.paste();
|
||||
return 1;
|
||||
},
|
||||
@@ -478,8 +503,7 @@ fn onPasteReady(
|
||||
};
|
||||
defer glib.free(text);
|
||||
|
||||
const tab = self.activeTab() orelse return;
|
||||
const terminal = tab.view.focusedTerminal() orelse return;
|
||||
const terminal = self.focusedTerminal() orelse return;
|
||||
const session = terminal.session;
|
||||
// Coerce to a plain slice: encodePaste dispatches on the exact type.
|
||||
const span: []const u8 = std.mem.span(text);
|
||||
|
||||
Reference in New Issue
Block a user