commit
fa47db5363
|
|
@ -94,9 +94,8 @@ pub fn deinit(self: *CommandPalette) void {
|
|||
|
||||
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));
|
||||
_ = self.search.as(gtk.Widget).grabFocus();
|
||||
}
|
||||
|
||||
pub fn updateConfig(self: *CommandPalette, config: *const configpkg.Config) !void {
|
||||
|
|
@ -104,13 +103,17 @@ pub fn updateConfig(self: *CommandPalette, config: *const configpkg.Config) !voi
|
|||
self.source.removeAll();
|
||||
_ = self.arena.reset(.retain_capacity);
|
||||
|
||||
// TODO: Allow user-configured palette entries
|
||||
for (inputpkg.command.defaults) |command| {
|
||||
for (config.@"command-palette-entry".value.items) |command| {
|
||||
// Filter out actions that are not implemented
|
||||
// or don't make sense for GTK
|
||||
switch (command.action) {
|
||||
.close_all_windows,
|
||||
.toggle_secure_input,
|
||||
.check_for_updates,
|
||||
.redo,
|
||||
.undo,
|
||||
.reset_window_size,
|
||||
.toggle_window_float_on_top,
|
||||
=> continue,
|
||||
|
||||
else => {},
|
||||
|
|
|
|||
|
|
@ -2062,6 +2062,28 @@ keybind: Keybinds = .{},
|
|||
/// Example: `cursor`, `no-cursor`, `sudo`, `no-sudo`, `title`, `no-title`
|
||||
@"shell-integration-features": ShellIntegrationFeatures = .{},
|
||||
|
||||
/// Custom entries into the command palette.
|
||||
///
|
||||
/// Each entry requires the title, the corresponding action, and an optional
|
||||
/// description. Each field should be prefixed with the field name, a colon
|
||||
/// (`:`), and then the specified value. The syntax for actions is identical
|
||||
/// to the one for keybind actions. Whitespace in between fields is ignored.
|
||||
///
|
||||
/// ```ini
|
||||
/// command-palette-entry = title:Reset Font Style, action:csi:0m
|
||||
/// command-palette-entry = title:Crash on Main Thread,description:Causes a crash on the main (UI) thread.,action:crash:main
|
||||
/// ```
|
||||
///
|
||||
/// By default, the command palette is preloaded with most actions that might
|
||||
/// be useful in an interactive setting yet do not have easily accessible or
|
||||
/// memorizable shortcuts. The default entries can be cleared by setting this
|
||||
/// setting to an empty value:
|
||||
///
|
||||
/// ```ini
|
||||
/// command-palette-entry =
|
||||
/// ```
|
||||
@"command-palette-entry": RepeatableCommand = .{},
|
||||
|
||||
/// Sets the reporting format for OSC sequences that request color information.
|
||||
/// Ghostty currently supports OSC 10 (foreground), OSC 11 (background), and
|
||||
/// OSC 4 (256 color palette) queries, and by default the reported values
|
||||
|
|
@ -2871,6 +2893,9 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||
// Add our default keybindings
|
||||
try result.keybind.init(alloc);
|
||||
|
||||
// Add our default command palette entries
|
||||
try result.@"command-palette-entry".init(alloc);
|
||||
|
||||
// Add our default link for URL detection
|
||||
try result.link.links.append(alloc, .{
|
||||
.regex = url.regex,
|
||||
|
|
@ -6210,6 +6235,150 @@ pub const ShellIntegrationFeatures = packed struct {
|
|||
title: bool = true,
|
||||
};
|
||||
|
||||
pub const RepeatableCommand = struct {
|
||||
value: std.ArrayListUnmanaged(inputpkg.Command) = .empty,
|
||||
|
||||
pub fn init(self: *RepeatableCommand, alloc: Allocator) !void {
|
||||
self.value = .empty;
|
||||
try self.value.appendSlice(alloc, inputpkg.command.defaults);
|
||||
}
|
||||
|
||||
pub fn parseCLI(
|
||||
self: *RepeatableCommand,
|
||||
alloc: Allocator,
|
||||
input_: ?[]const u8,
|
||||
) !void {
|
||||
// Unset or empty input clears the list
|
||||
const input = input_ orelse "";
|
||||
if (input.len == 0) {
|
||||
self.value.clearRetainingCapacity();
|
||||
return;
|
||||
}
|
||||
|
||||
const cmd = try cli.args.parseAutoStruct(
|
||||
inputpkg.Command,
|
||||
alloc,
|
||||
input,
|
||||
);
|
||||
try self.value.append(alloc, cmd);
|
||||
}
|
||||
|
||||
/// Deep copy of the struct. Required by Config.
|
||||
pub fn clone(self: *const RepeatableCommand, alloc: Allocator) Allocator.Error!RepeatableCommand {
|
||||
const value = try self.value.clone(alloc);
|
||||
for (value.items) |*item| {
|
||||
item.* = try item.clone(alloc);
|
||||
}
|
||||
|
||||
return .{ .value = value };
|
||||
}
|
||||
|
||||
/// Compare if two of our value are equal. Required by Config.
|
||||
pub fn equal(self: RepeatableCommand, other: RepeatableCommand) bool {
|
||||
if (self.value.items.len != other.value.items.len) return false;
|
||||
for (self.value.items, other.value.items) |a, b| {
|
||||
if (!a.equal(b)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Used by Formatter
|
||||
pub fn formatEntry(self: RepeatableCommand, formatter: anytype) !void {
|
||||
if (self.value.items.len == 0) {
|
||||
try formatter.formatEntry(void, {});
|
||||
return;
|
||||
}
|
||||
|
||||
var buf: [4096]u8 = undefined;
|
||||
for (self.value.items) |item| {
|
||||
const str = if (item.description.len > 0) std.fmt.bufPrint(
|
||||
&buf,
|
||||
"title:{s},description:{s},action:{}",
|
||||
.{ item.title, item.description, item.action },
|
||||
) else std.fmt.bufPrint(
|
||||
&buf,
|
||||
"title:{s},action:{}",
|
||||
.{ item.title, item.action },
|
||||
);
|
||||
try formatter.formatEntry([]const u8, str catch return error.OutOfMemory);
|
||||
}
|
||||
}
|
||||
|
||||
test "RepeatableCommand parseCLI" {
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var list: RepeatableCommand = .{};
|
||||
try list.parseCLI(alloc, "title:Foo,action:ignore");
|
||||
try list.parseCLI(alloc, "title:Bar,description:bobr,action:text:ale bydle");
|
||||
try list.parseCLI(alloc, "title:Quux,description:boo,action:increase_font_size:2.5");
|
||||
|
||||
try testing.expectEqual(@as(usize, 3), list.value.items.len);
|
||||
|
||||
try testing.expectEqual(inputpkg.Binding.Action.ignore, list.value.items[0].action);
|
||||
try testing.expectEqualStrings("Foo", list.value.items[0].title);
|
||||
|
||||
try testing.expect(list.value.items[1].action == .text);
|
||||
try testing.expectEqualStrings("ale bydle", list.value.items[1].action.text);
|
||||
try testing.expectEqualStrings("Bar", list.value.items[1].title);
|
||||
try testing.expectEqualStrings("bobr", list.value.items[1].description);
|
||||
|
||||
try testing.expectEqual(
|
||||
inputpkg.Binding.Action{ .increase_font_size = 2.5 },
|
||||
list.value.items[2].action,
|
||||
);
|
||||
try testing.expectEqualStrings("Quux", list.value.items[2].title);
|
||||
try testing.expectEqualStrings("boo", list.value.items[2].description);
|
||||
|
||||
try list.parseCLI(alloc, "");
|
||||
try testing.expectEqual(@as(usize, 0), list.value.items.len);
|
||||
}
|
||||
|
||||
test "RepeatableCommand formatConfig empty" {
|
||||
const testing = std.testing;
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
var list: RepeatableCommand = .{};
|
||||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||
try std.testing.expectEqualSlices(u8, "a = \n", buf.items);
|
||||
}
|
||||
|
||||
test "RepeatableCommand formatConfig single item" {
|
||||
const testing = std.testing;
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var list: RepeatableCommand = .{};
|
||||
try list.parseCLI(alloc, "title:Bobr, action:text:Bober");
|
||||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||
try std.testing.expectEqualSlices(u8, "a = title:Bobr,action:text:Bober\n", buf.items);
|
||||
}
|
||||
|
||||
test "RepeatableCommand formatConfig multiple items" {
|
||||
const testing = std.testing;
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var list: RepeatableCommand = .{};
|
||||
try list.parseCLI(alloc, "title:Bobr, action:text:kurwa");
|
||||
try list.parseCLI(alloc, "title:Ja, description: pierdole, action:text:jakie bydle");
|
||||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||
try std.testing.expectEqualSlices(u8, "a = title:Bobr,action:text:kurwa\na = title:Ja,description:pierdole,action:text:jakie bydle\n", buf.items);
|
||||
}
|
||||
};
|
||||
|
||||
/// OSC 4, 10, 11, and 12 default color reporting format.
|
||||
pub const OSCColorReportFormat = enum {
|
||||
none,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const Action = @import("Binding.zig").Action;
|
|||
pub const Command = struct {
|
||||
action: Action,
|
||||
title: [:0]const u8,
|
||||
description: [:0]const u8,
|
||||
description: [:0]const u8 = "",
|
||||
|
||||
/// ghostty_command_s
|
||||
pub const C = extern struct {
|
||||
|
|
@ -28,6 +28,21 @@ pub const Command = struct {
|
|||
description: [*:0]const u8,
|
||||
};
|
||||
|
||||
pub fn clone(self: *const Command, alloc: Allocator) Allocator.Error!Command {
|
||||
return .{
|
||||
.action = try self.action.clone(alloc),
|
||||
.title = try alloc.dupeZ(u8, self.title),
|
||||
.description = try alloc.dupeZ(u8, self.description),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn equal(self: Command, other: Command) bool {
|
||||
if (self.action.hash() != other.action.hash()) return false;
|
||||
if (!std.mem.eql(u8, self.title, other.title)) return false;
|
||||
if (!std.mem.eql(u8, self.description, other.description)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Convert this command to a C struct.
|
||||
pub fn comptimeCval(self: Command) C {
|
||||
assert(@inComptime());
|
||||
|
|
|
|||
Loading…
Reference in New Issue