config: add `command-palette-entry` config option
parent
d419e5c922
commit
dbe6035da0
|
|
@ -104,7 +104,7 @@ pub fn updateConfig(self: *CommandPalette, config: *const configpkg.Config) !voi
|
|||
_ = 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) {
|
||||
|
|
|
|||
|
|
@ -1975,6 +1975,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
|
||||
|
|
@ -2784,6 +2806,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,
|
||||
|
|
@ -6114,6 +6139,141 @@ 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 {
|
||||
const input_ = input orelse {
|
||||
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.expectEqual(
|
||||
inputpkg.Binding.Action{ .text = "ale bydle" },
|
||||
list.value.items[0].action,
|
||||
);
|
||||
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[0].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