mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-04-22 08:57-0700\n"
|
||||
"POT-Creation-Date: 2025-04-23 16:58+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -50,7 +50,7 @@ msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
msgid "Reload Configuration"
|
||||
msgstr ""
|
||||
|
||||
@@ -78,6 +78,10 @@ msgstr ""
|
||||
msgid "Split Right"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
msgid "Copy"
|
||||
@@ -115,7 +119,7 @@ msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||
#: src/apprt/gtk/Window.zig:248
|
||||
#: src/apprt/gtk/Window.zig:255
|
||||
msgid "New Tab"
|
||||
msgstr ""
|
||||
|
||||
@@ -143,20 +147,24 @@ msgid "Config"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102
|
||||
#: src/apprt/gtk/Window.zig:1003
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/Window.zig:1024
|
||||
msgid "About Ghostty"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
@@ -197,31 +205,35 @@ msgid ""
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:201
|
||||
#: src/apprt/gtk/Window.zig:208
|
||||
msgid "Main Menu"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:222
|
||||
#: src/apprt/gtk/Window.zig:229
|
||||
msgid "View Open Tabs"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:249
|
||||
#: src/apprt/gtk/Window.zig:256
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:312
|
||||
#: src/apprt/gtk/Window.zig:319
|
||||
msgid ""
|
||||
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:744
|
||||
#: src/apprt/gtk/Window.zig:765
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:984
|
||||
#: src/apprt/gtk/Window.zig:1005
|
||||
msgid "Ghostty Developers"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
@@ -261,7 +273,3 @@ msgstr ""
|
||||
#: src/apprt/gtk/Surface.zig:1243
|
||||
msgid "Copied to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr ""
|
||||
|
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-04-22 08:57-0700\n"
|
||||
"POT-Creation-Date: 2025-04-23 16:58+0800\n"
|
||||
"PO-Revision-Date: 2025-02-27 09:16+0100\n"
|
||||
"Last-Translator: Leah <hi@pluie.me>\n"
|
||||
"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
|
||||
@@ -51,7 +51,7 @@ msgstr "忽略"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
msgid "Reload Configuration"
|
||||
msgstr "重新加载配置"
|
||||
|
||||
@@ -79,6 +79,10 @@ msgstr "向左分屏"
|
||||
msgid "Split Right"
|
||||
msgstr "向右分屏"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "选择要执行的命令……"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
msgid "Copy"
|
||||
@@ -116,7 +120,7 @@ msgstr "标签页"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||
#: src/apprt/gtk/Window.zig:248
|
||||
#: src/apprt/gtk/Window.zig:255
|
||||
msgid "New Tab"
|
||||
msgstr "新建标签页"
|
||||
|
||||
@@ -144,20 +148,24 @@ msgid "Config"
|
||||
msgstr "配置"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr "打开配置文件"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "命令面板"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
msgstr "终端调试器"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102
|
||||
#: src/apprt/gtk/Window.zig:1003
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/Window.zig:1024
|
||||
msgid "About Ghostty"
|
||||
msgstr "关于 Ghostty"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
|
||||
msgid "Quit"
|
||||
msgstr "退出"
|
||||
|
||||
@@ -198,31 +206,35 @@ msgid ""
|
||||
"commands may be executed."
|
||||
msgstr "将以下内容粘贴至终端内将可能执行有害命令。"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:201
|
||||
#: src/apprt/gtk/Window.zig:208
|
||||
msgid "Main Menu"
|
||||
msgstr "主菜单"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:222
|
||||
#: src/apprt/gtk/Window.zig:229
|
||||
msgid "View Open Tabs"
|
||||
msgstr "浏览标签页"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:249
|
||||
#: src/apprt/gtk/Window.zig:256
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "新建分屏"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:312
|
||||
#: src/apprt/gtk/Window.zig:319
|
||||
msgid ""
|
||||
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr "⚠️ Ghostty 正在以调试模式运行!性能将大打折扣。"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:744
|
||||
#: src/apprt/gtk/Window.zig:765
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "已重新加载配置"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:984
|
||||
#: src/apprt/gtk/Window.zig:1005
|
||||
msgid "Ghostty Developers"
|
||||
msgstr "Ghostty 开发团队"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty 终端调试器"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47
|
||||
msgid "Close"
|
||||
msgstr "关闭"
|
||||
@@ -262,7 +274,3 @@ msgstr "分屏内正在运行中的进程将被终止。"
|
||||
#: src/apprt/gtk/Surface.zig:1243
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "已复制至剪贴板"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty 终端调试器"
|
||||
|
@@ -492,11 +492,11 @@ pub fn performAction(
|
||||
.toggle_quick_terminal => return try self.toggleQuickTerminal(),
|
||||
.secure_input => self.setSecureInput(target, value),
|
||||
.ring_bell => try self.ringBell(target),
|
||||
.toggle_command_palette => try self.toggleCommandPalette(target),
|
||||
|
||||
// Unimplemented
|
||||
.close_all_windows,
|
||||
.float_window,
|
||||
.toggle_command_palette,
|
||||
.toggle_visibility,
|
||||
.cell_size,
|
||||
.key_sequence,
|
||||
@@ -751,7 +751,7 @@ fn toggleWindowDecorations(
|
||||
.surface => |v| {
|
||||
const window = v.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"toggleFullscreen invalid for container={s}",
|
||||
"toggleWindowDecorations invalid for container={s}",
|
||||
.{@tagName(v.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
@@ -793,6 +793,23 @@ fn ringBell(_: *App, target: apprt.Target) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggleCommandPalette(_: *App, target: apprt.Target) !void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |surface| {
|
||||
const window = surface.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"toggleCommandPalette invalid for container={s}",
|
||||
.{@tagName(surface.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
window.toggleCommandPalette();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn quitTimer(self: *App, mode: apprt.action.QuitTimer) void {
|
||||
switch (mode) {
|
||||
.start => self.startQuitTimer(),
|
||||
@@ -1031,6 +1048,7 @@ fn syncActionAccelerators(self: *App) !void {
|
||||
try self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
|
||||
try self.syncActionAccelerator("app.reload-config", .{ .reload_config = {} });
|
||||
try self.syncActionAccelerator("win.toggle-inspector", .{ .inspector = .toggle });
|
||||
try self.syncActionAccelerator("win.toggle-command-palette", .toggle_command_palette);
|
||||
try self.syncActionAccelerator("win.close", .{ .close_window = {} });
|
||||
try self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
|
||||
try self.syncActionAccelerator("win.new-tab", .{ .new_tab = {} });
|
||||
|
246
src/apprt/gtk/CommandPalette.zig
Normal file
246
src/apprt/gtk/CommandPalette.zig
Normal file
@@ -0,0 +1,246 @@
|
||||
const CommandPalette = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const configpkg = @import("../../config.zig");
|
||||
const inputpkg = @import("../../input.zig");
|
||||
const key = @import("key.zig");
|
||||
const Builder = @import("Builder.zig");
|
||||
const Window = @import("Window.zig");
|
||||
|
||||
const log = std.log.scoped(.command_palette);
|
||||
|
||||
window: *Window,
|
||||
|
||||
arena: std.heap.ArenaAllocator,
|
||||
|
||||
/// The dialog object containing the palette UI.
|
||||
dialog: *adw.Dialog,
|
||||
|
||||
/// The search input text field.
|
||||
search: *gtk.SearchEntry,
|
||||
|
||||
/// The view containing each result row.
|
||||
view: *gtk.ListView,
|
||||
|
||||
/// The model that provides filtered data for the view to display.
|
||||
model: *gtk.SingleSelection,
|
||||
|
||||
/// The list that serves as the data source of the model.
|
||||
/// This is where all command data is ultimately stored.
|
||||
source: *gio.ListStore,
|
||||
|
||||
pub fn init(self: *CommandPalette, window: *Window) !void {
|
||||
// Register the custom command type *before* initializing the builder
|
||||
// If we don't do this now, the builder will complain that it doesn't know
|
||||
// about this type and fail to initialize
|
||||
_ = Command.getGObjectType();
|
||||
|
||||
var builder = Builder.init("command-palette", 1, 5);
|
||||
|
||||
self.* = .{
|
||||
.window = window,
|
||||
.arena = .init(window.app.core_app.alloc),
|
||||
.dialog = builder.getObject(adw.Dialog, "command-palette").?,
|
||||
.search = builder.getObject(gtk.SearchEntry, "search").?,
|
||||
.view = builder.getObject(gtk.ListView, "view").?,
|
||||
.model = builder.getObject(gtk.SingleSelection, "model").?,
|
||||
.source = builder.getObject(gio.ListStore, "source").?,
|
||||
};
|
||||
|
||||
// Manually take a reference here so that the dialog
|
||||
// remains in memory after closing
|
||||
self.dialog.ref();
|
||||
errdefer self.dialog.unref();
|
||||
|
||||
_ = gtk.SearchEntry.signals.stop_search.connect(
|
||||
self.search,
|
||||
*CommandPalette,
|
||||
searchStopped,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
|
||||
_ = gtk.SearchEntry.signals.activate.connect(
|
||||
self.search,
|
||||
*CommandPalette,
|
||||
searchActivated,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
|
||||
_ = gtk.ListView.signals.activate.connect(
|
||||
self.view,
|
||||
*CommandPalette,
|
||||
rowActivated,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
|
||||
try self.updateConfig(&self.window.app.config);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *CommandPalette) void {
|
||||
self.arena.deinit();
|
||||
self.dialog.unref();
|
||||
}
|
||||
|
||||
pub fn toggle(self: *CommandPalette) void {
|
||||
self.dialog.present(self.window.window.as(gtk.Widget));
|
||||
|
||||
// Focus on the search bar when opening the dialog
|
||||
self.dialog.setFocus(self.search.as(gtk.Widget));
|
||||
}
|
||||
|
||||
pub fn updateConfig(self: *CommandPalette, config: *const configpkg.Config) !void {
|
||||
// Clear existing binds and clear allocated data
|
||||
self.source.removeAll();
|
||||
_ = self.arena.reset(.retain_capacity);
|
||||
|
||||
// TODO: Allow user-configured palette entries
|
||||
for (inputpkg.command.defaults) |command| {
|
||||
// Filter out actions that are not implemented
|
||||
// or don't make sense for GTK
|
||||
switch (command.action) {
|
||||
.close_all_windows,
|
||||
.toggle_secure_input,
|
||||
=> continue,
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
const cmd = try Command.new(
|
||||
self.arena.allocator(),
|
||||
command,
|
||||
config.keybind.set,
|
||||
);
|
||||
self.source.append(cmd.as(gobject.Object));
|
||||
}
|
||||
}
|
||||
|
||||
fn activated(self: *CommandPalette, pos: c_uint) void {
|
||||
// Use self.model and not self.source here to use the list of *visible* results
|
||||
const object = self.model.as(gio.ListModel).getObject(pos) orelse return;
|
||||
const cmd = gobject.ext.cast(Command, object) orelse return;
|
||||
|
||||
// Close before running the action in order to avoid being replaced by another
|
||||
// dialog (such as the change title dialog). If that occurs then the command
|
||||
// palette dialog won't be counted as having closed properly and cannot
|
||||
// receive focus when reopened.
|
||||
_ = self.dialog.close();
|
||||
|
||||
const action = inputpkg.Binding.Action.parse(
|
||||
std.mem.span(cmd.cmd_c.action_key),
|
||||
) catch |err| {
|
||||
log.err("got invalid action={s} ({})", .{ cmd.cmd_c.action_key, err });
|
||||
return;
|
||||
};
|
||||
|
||||
self.window.performBindingAction(action);
|
||||
}
|
||||
|
||||
fn searchStopped(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
|
||||
// ESC was pressed - close the palette
|
||||
_ = self.dialog.close();
|
||||
}
|
||||
|
||||
fn searchActivated(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
|
||||
// If Enter is pressed, activate the selected entry
|
||||
self.activated(self.model.getSelected());
|
||||
}
|
||||
|
||||
fn rowActivated(_: *gtk.ListView, pos: c_uint, self: *CommandPalette) callconv(.c) void {
|
||||
self.activated(pos);
|
||||
}
|
||||
|
||||
/// Object that wraps around a command.
|
||||
///
|
||||
/// As GTK list models only accept objects that are within the GObject hierarchy,
|
||||
/// we have to construct a wrapper to be easily consumed by the list model.
|
||||
const Command = extern struct {
|
||||
parent: Parent,
|
||||
cmd_c: inputpkg.Command.C,
|
||||
|
||||
pub const getGObjectType = gobject.ext.defineClass(Command, .{
|
||||
.name = "GhosttyCommand",
|
||||
.classInit = Class.init,
|
||||
});
|
||||
|
||||
pub fn new(alloc: Allocator, cmd: inputpkg.Command, keybinds: inputpkg.Binding.Set) !*Command {
|
||||
const self = gobject.ext.newInstance(Command, .{});
|
||||
var buf: [64]u8 = undefined;
|
||||
|
||||
const action = action: {
|
||||
const trigger = keybinds.getTrigger(cmd.action) orelse break :action null;
|
||||
const accel = try key.accelFromTrigger(&buf, trigger) orelse break :action null;
|
||||
break :action try alloc.dupeZ(u8, accel);
|
||||
};
|
||||
|
||||
self.cmd_c = .{
|
||||
.title = cmd.title.ptr,
|
||||
.description = cmd.description.ptr,
|
||||
.action = if (action) |v| v.ptr else "",
|
||||
.action_key = try std.fmt.allocPrintZ(alloc, "{}", .{cmd.action}),
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn as(self: *Command, comptime T: type) *T {
|
||||
return gobject.ext.as(T, self);
|
||||
}
|
||||
|
||||
pub const Parent = gobject.Object;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent: Parent.Class,
|
||||
|
||||
pub const Instance = Command;
|
||||
|
||||
pub fn init(class: *Class) callconv(.c) void {
|
||||
const info = @typeInfo(inputpkg.Command.C).@"struct";
|
||||
|
||||
// Expose all fields on the Command.C struct as properties
|
||||
// that can be accessed by the GObject type system
|
||||
// (and by extension, blueprints)
|
||||
const properties = comptime props: {
|
||||
var props: [info.fields.len]type = undefined;
|
||||
|
||||
for (info.fields, 0..) |field, i| {
|
||||
const accessor = struct {
|
||||
fn getter(cmd: *Command) ?[:0]const u8 {
|
||||
return std.mem.span(@field(cmd.cmd_c, field.name));
|
||||
}
|
||||
};
|
||||
|
||||
// "Canonicalize" field names into the format GObject expects
|
||||
const prop_name = prop_name: {
|
||||
var buf: [field.name.len:0]u8 = undefined;
|
||||
_ = std.mem.replace(u8, field.name, "_", "-", &buf);
|
||||
break :prop_name buf;
|
||||
};
|
||||
|
||||
props[i] = gobject.ext.defineProperty(
|
||||
&prop_name,
|
||||
Command,
|
||||
?[:0]const u8,
|
||||
.{
|
||||
.default = null,
|
||||
.accessor = .{ .getter = &accessor.getter },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
break :props props;
|
||||
};
|
||||
|
||||
gobject.ext.registerProperties(class, &properties);
|
||||
}
|
||||
};
|
||||
};
|
@@ -34,6 +34,7 @@ const gtk_key = @import("key.zig");
|
||||
const TabView = @import("TabView.zig");
|
||||
const HeaderBar = @import("headerbar.zig");
|
||||
const CloseDialog = @import("CloseDialog.zig");
|
||||
const CommandPalette = @import("CommandPalette.zig");
|
||||
const winprotopkg = @import("winproto.zig");
|
||||
const gtk_version = @import("gtk_version.zig");
|
||||
const adw_version = @import("adw_version.zig");
|
||||
@@ -67,6 +68,9 @@ titlebar_menu: Menu(Window, "titlebar_menu", true),
|
||||
/// The libadwaita widget for receiving toast send requests.
|
||||
toast_overlay: *adw.ToastOverlay,
|
||||
|
||||
/// The command palette.
|
||||
command_palette: CommandPalette,
|
||||
|
||||
/// See adwTabOverviewOpen for why we have this.
|
||||
adw_tab_overview_focus_timer: ?c_uint = null,
|
||||
|
||||
@@ -139,6 +143,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
.notebook = undefined,
|
||||
.titlebar_menu = undefined,
|
||||
.toast_overlay = undefined,
|
||||
.command_palette = undefined,
|
||||
.winproto = .none,
|
||||
};
|
||||
|
||||
@@ -167,6 +172,8 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
// Setup our notebook
|
||||
self.notebook.init(self);
|
||||
|
||||
if (adw_version.supportsDialogs()) try self.command_palette.init(self);
|
||||
|
||||
// If we are using Adwaita, then we can support the tab overview.
|
||||
self.tab_overview = if (adw_version.supportsTabOverview()) overview: {
|
||||
const tab_overview = adw.TabOverview.new();
|
||||
@@ -460,6 +467,9 @@ pub fn updateConfig(
|
||||
|
||||
// We always resync our appearance whenever the config changes.
|
||||
try self.syncAppearance();
|
||||
|
||||
// Update binds inside the command palette
|
||||
try self.command_palette.updateConfig(config);
|
||||
}
|
||||
|
||||
/// Updates appearance based on config settings. Will be called once upon window
|
||||
@@ -577,6 +587,7 @@ fn initActions(self: *Window) void {
|
||||
.{ "split-left", gtkActionSplitLeft },
|
||||
.{ "split-up", gtkActionSplitUp },
|
||||
.{ "toggle-inspector", gtkActionToggleInspector },
|
||||
.{ "toggle-command-palette", gtkActionToggleCommandPalette },
|
||||
.{ "copy", gtkActionCopy },
|
||||
.{ "paste", gtkActionPaste },
|
||||
.{ "reset", gtkActionReset },
|
||||
@@ -600,6 +611,7 @@ fn initActions(self: *Window) void {
|
||||
|
||||
pub fn deinit(self: *Window) void {
|
||||
self.winproto.deinit(self.app.core_app.alloc);
|
||||
if (adw_version.supportsDialogs()) self.command_palette.deinit();
|
||||
|
||||
if (self.adw_tab_overview_focus_timer) |timer| {
|
||||
_ = glib.Source.remove(timer);
|
||||
@@ -729,6 +741,15 @@ pub fn toggleWindowDecorations(self: *Window) void {
|
||||
};
|
||||
}
|
||||
|
||||
/// Toggle the window decorations for this window.
|
||||
pub fn toggleCommandPalette(self: *Window) void {
|
||||
if (adw_version.supportsDialogs()) {
|
||||
self.command_palette.toggle();
|
||||
} else {
|
||||
log.warn("libadwaita 1.5+ is required for the command palette", .{});
|
||||
}
|
||||
}
|
||||
|
||||
/// Grabs focus on the currently selected tab.
|
||||
pub fn focusCurrentTab(self: *Window) void {
|
||||
const tab = self.notebook.currentTab() orelse return;
|
||||
@@ -820,7 +841,7 @@ fn gtkWindowUpdateScaleFactor(
|
||||
}
|
||||
|
||||
/// Perform a binding action on the window's action surface.
|
||||
fn performBindingAction(self: *Window, action: input.Binding.Action) void {
|
||||
pub fn performBindingAction(self: *Window, action: input.Binding.Action) void {
|
||||
const surface = self.actionSurface() orelse return;
|
||||
_ = surface.performBindingAction(action) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
@@ -1082,6 +1103,14 @@ fn gtkActionToggleInspector(
|
||||
self.performBindingAction(.{ .inspector = .toggle });
|
||||
}
|
||||
|
||||
fn gtkActionToggleCommandPalette(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
self: *Window,
|
||||
) callconv(.C) void {
|
||||
self.performBindingAction(.toggle_command_palette);
|
||||
}
|
||||
|
||||
fn gtkActionCopy(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
|
@@ -63,6 +63,7 @@ pub const blueprint_files = [_]VersionedBlueprint{
|
||||
.{ .major = 1, .minor = 5, .name = "prompt-title-dialog" },
|
||||
.{ .major = 1, .minor = 5, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 0, .name = "menu-headerbar-split_menu" },
|
||||
.{ .major = 1, .minor = 5, .name = "command-palette" },
|
||||
.{ .major = 1, .minor = 0, .name = "menu-surface-context_menu" },
|
||||
.{ .major = 1, .minor = 0, .name = "menu-window-titlebar_menu" },
|
||||
.{ .major = 1, .minor = 5, .name = "ccw-osc-52-read" },
|
||||
|
@@ -73,3 +73,19 @@ window.ssd.no-border-radius {
|
||||
filter: blur(5px);
|
||||
transition: filter 0.3s ease;
|
||||
}
|
||||
|
||||
.command-palette-search {
|
||||
font-size: 1.25rem;
|
||||
padding: 4px;
|
||||
-gtk-icon-size: 20px;
|
||||
}
|
||||
|
||||
.command-palette-search > image:first-child {
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.command-palette-search > image:last-child {
|
||||
margin-left: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
@@ -81,6 +81,11 @@ menu menu {
|
||||
}
|
||||
|
||||
section {
|
||||
item {
|
||||
label: _("Command Palette");
|
||||
action: "win.toggle-command-palette";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Terminal Inspector");
|
||||
action: "win.toggle-inspector";
|
||||
|
106
src/apprt/gtk/ui/1.5/command-palette.blp
Normal file
106
src/apprt/gtk/ui/1.5/command-palette.blp
Normal file
@@ -0,0 +1,106 @@
|
||||
using Gtk 4.0;
|
||||
using Gio 2.0;
|
||||
using Adw 1;
|
||||
|
||||
Adw.Dialog command-palette {
|
||||
content-width: 700;
|
||||
|
||||
Adw.ToolbarView {
|
||||
top-bar-style: flat;
|
||||
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
[title]
|
||||
SearchEntry search {
|
||||
hexpand: true;
|
||||
placeholder-text: _("Execute a command…");
|
||||
|
||||
styles [
|
||||
"command-palette-search",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
ScrolledWindow {
|
||||
min-content-height: 300;
|
||||
|
||||
ListView view {
|
||||
show-separators: true;
|
||||
single-click-activate: true;
|
||||
|
||||
model: SingleSelection model {
|
||||
model: FilterListModel {
|
||||
incremental: true;
|
||||
|
||||
filter: AnyFilter {
|
||||
StringFilter {
|
||||
expression: expr item as <$GhosttyCommand>.title;
|
||||
search: bind search.text;
|
||||
}
|
||||
|
||||
StringFilter {
|
||||
expression: expr item as <$GhosttyCommand>.action-key;
|
||||
search: bind search.text;
|
||||
}
|
||||
};
|
||||
|
||||
model: Gio.ListStore source {
|
||||
item-type: typeof<$GhosttyCommand>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
styles [
|
||||
"rich-list",
|
||||
]
|
||||
|
||||
factory: BuilderListItemFactory {
|
||||
template ListItem {
|
||||
child: Box {
|
||||
orientation: horizontal;
|
||||
spacing: 10;
|
||||
tooltip-text: bind template.item as <$GhosttyCommand>.description;
|
||||
|
||||
Box {
|
||||
orientation: vertical;
|
||||
hexpand: true;
|
||||
|
||||
Label {
|
||||
ellipsize: end;
|
||||
halign: start;
|
||||
wrap: false;
|
||||
single-line-mode: true;
|
||||
|
||||
styles [
|
||||
"title",
|
||||
]
|
||||
|
||||
label: bind template.item as <$GhosttyCommand>.title;
|
||||
}
|
||||
|
||||
Label {
|
||||
ellipsize: end;
|
||||
halign: start;
|
||||
wrap: false;
|
||||
single-line-mode: true;
|
||||
|
||||
styles [
|
||||
"subtitle",
|
||||
"monospace",
|
||||
]
|
||||
|
||||
label: bind template.item as <$GhosttyCommand>.action-key;
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutLabel {
|
||||
accelerator: bind template.item as <$GhosttyCommand>.action;
|
||||
valign: center;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -4757,6 +4757,13 @@ pub const Keybinds = struct {
|
||||
.{ .toggle_split_zoom = {} },
|
||||
);
|
||||
|
||||
// Toggle command palette, matches VSCode
|
||||
try self.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'p' }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.toggle_command_palette,
|
||||
);
|
||||
|
||||
// Mac-specific keyboard bindings.
|
||||
if (comptime builtin.target.os.tag.isDarwin()) {
|
||||
try self.set.put(
|
||||
@@ -4929,13 +4936,6 @@ pub const Keybinds = struct {
|
||||
.{ .jump_to_prompt = 1 },
|
||||
);
|
||||
|
||||
// Toggle command palette, matches VSCode
|
||||
try self.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'p' }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .toggle_command_palette = {} },
|
||||
);
|
||||
|
||||
// Inspector, matching Chromium
|
||||
try self.set.put(
|
||||
alloc,
|
||||
|
@@ -455,8 +455,6 @@ pub const Action = union(enum) {
|
||||
/// that lets you see what actions you can perform, their associated
|
||||
/// keybindings (if any), a search bar to filter the actions, and
|
||||
/// the ability to then execute the action.
|
||||
///
|
||||
/// This only works on macOS.
|
||||
toggle_command_palette,
|
||||
|
||||
/// Toggle the "quick" terminal. The quick terminal is a terminal that
|
||||
|
Reference in New Issue
Block a user