mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-12-28 09:04:40 +00:00
macOS: Don't duplicate command palette entries for terminal commands (#10069)
This is a regression introduced when we added macOS support for custom entries. I mistakingly thought that only custom entries were in the config, but we do initialize it with all!
This commit is contained in:
@@ -1050,7 +1050,6 @@ void ghostty_surface_set_color_scheme(ghostty_surface_t,
|
||||
ghostty_color_scheme_e);
|
||||
ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t,
|
||||
ghostty_input_mods_e);
|
||||
void ghostty_surface_commands(ghostty_surface_t, ghostty_command_s**, size_t*);
|
||||
bool ghostty_surface_key(ghostty_surface_t, ghostty_input_key_s);
|
||||
bool ghostty_surface_key_is_binding(ghostty_surface_t, ghostty_input_key_s);
|
||||
void ghostty_surface_text(ghostty_surface_t, const char*, uintptr_t);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AppIntents
|
||||
import Cocoa
|
||||
|
||||
// MARK: AppEntity
|
||||
|
||||
@@ -94,23 +95,23 @@ struct CommandQuery: EntityQuery {
|
||||
|
||||
@MainActor
|
||||
func entities(for identifiers: [CommandEntity.ID]) async throws -> [CommandEntity] {
|
||||
guard let appDelegate = NSApp.delegate as? AppDelegate else { return [] }
|
||||
let commands = appDelegate.ghostty.config.commandPaletteEntries
|
||||
|
||||
// Extract unique terminal IDs to avoid fetching duplicates
|
||||
let terminalIds = Set(identifiers.map(\.terminalId))
|
||||
let terminals = try await TerminalEntity.defaultQuery.entities(for: Array(terminalIds))
|
||||
|
||||
// Build a cache of terminals and their available commands
|
||||
// This avoids repeated command fetching for the same terminal
|
||||
typealias Tuple = (terminal: TerminalEntity, commands: [Ghostty.Command])
|
||||
let commandMap: [TerminalEntity.ID: Tuple] =
|
||||
// Build a lookup from terminal ID to terminal entity
|
||||
let terminalMap: [TerminalEntity.ID: TerminalEntity] =
|
||||
terminals.reduce(into: [:]) { result, terminal in
|
||||
guard let commands = try? terminal.surfaceModel?.commands() else { return }
|
||||
result[terminal.id] = (terminal: terminal, commands: commands)
|
||||
result[terminal.id] = terminal
|
||||
}
|
||||
|
||||
|
||||
// Map each identifier to its corresponding CommandEntity. If a command doesn't
|
||||
// exist it maps to nil and is removed via compactMap.
|
||||
return identifiers.compactMap { id in
|
||||
guard let (terminal, commands) = commandMap[id.terminalId],
|
||||
guard let terminal = terminalMap[id.terminalId],
|
||||
let command = commands.first(where: { $0.actionKey == id.actionKey }) else {
|
||||
return nil
|
||||
}
|
||||
@@ -121,8 +122,8 @@ struct CommandQuery: EntityQuery {
|
||||
|
||||
@MainActor
|
||||
func suggestedEntities() async throws -> [CommandEntity] {
|
||||
guard let terminal = commandPaletteIntent?.terminal,
|
||||
let surface = terminal.surfaceModel else { return [] }
|
||||
return try surface.commands().map { CommandEntity($0, for: terminal) }
|
||||
guard let appDelegate = NSApp.delegate as? AppDelegate,
|
||||
let terminal = commandPaletteIntent?.terminal else { return [] }
|
||||
return appDelegate.ghostty.config.commandPaletteEntries.map { CommandEntity($0, for: terminal) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ struct TerminalCommandPaletteView: View {
|
||||
// Sort the rest. We replace ":" with a character that sorts before space
|
||||
// so that "Foo:" sorts before "Foo Bar:". Use sortKey as a tie-breaker
|
||||
// for stable ordering when titles are equal.
|
||||
options.append(contentsOf: (jumpOptions + terminalOptions + customEntries).sorted { a, b in
|
||||
options.append(contentsOf: (jumpOptions + terminalOptions).sorted { a, b in
|
||||
let aNormalized = a.title.replacingOccurrences(of: ":", with: "\t")
|
||||
let bNormalized = b.title.replacingOccurrences(of: ":", with: "\t")
|
||||
let comparison = aNormalized.localizedCaseInsensitiveCompare(bNormalized)
|
||||
@@ -83,11 +83,11 @@ struct TerminalCommandPaletteView: View {
|
||||
/// Commands for installing or canceling available updates.
|
||||
private var updateOptions: [CommandOption] {
|
||||
var options: [CommandOption] = []
|
||||
|
||||
|
||||
guard let updateViewModel, updateViewModel.state.isInstallable else {
|
||||
return options
|
||||
}
|
||||
|
||||
|
||||
// We override the update available one only because we want to properly
|
||||
// convey it'll go all the way through.
|
||||
let title: String
|
||||
@@ -96,7 +96,7 @@ struct TerminalCommandPaletteView: View {
|
||||
} else {
|
||||
title = updateViewModel.text
|
||||
}
|
||||
|
||||
|
||||
options.append(CommandOption(
|
||||
title: title,
|
||||
description: updateViewModel.description,
|
||||
@@ -106,37 +106,19 @@ struct TerminalCommandPaletteView: View {
|
||||
) {
|
||||
(NSApp.delegate as? AppDelegate)?.updateController.installUpdate()
|
||||
})
|
||||
|
||||
|
||||
options.append(CommandOption(
|
||||
title: "Cancel or Skip Update",
|
||||
description: "Dismiss the current update process"
|
||||
) {
|
||||
updateViewModel.state.cancel()
|
||||
})
|
||||
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
/// Commands exposed by the terminal surface.
|
||||
private var terminalOptions: [CommandOption] {
|
||||
guard let surface = surfaceView.surfaceModel else { return [] }
|
||||
do {
|
||||
return try surface.commands().map { c in
|
||||
CommandOption(
|
||||
title: c.title,
|
||||
description: c.description,
|
||||
symbols: ghosttyConfig.keyboardShortcut(for: c.action)?.keyList,
|
||||
) {
|
||||
onAction(c.action)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom commands from the command-palette-entry configuration.
|
||||
private var customEntries: [CommandOption] {
|
||||
private var terminalOptions: [CommandOption] {
|
||||
guard let appDelegate = NSApp.delegate as? AppDelegate else { return [] }
|
||||
return appDelegate.ghostty.config.commandPaletteEntries.map { c in
|
||||
CommandOption(
|
||||
|
||||
@@ -134,16 +134,5 @@ extension Ghostty {
|
||||
ghostty_surface_binding_action(surface, cString, UInt(len - 1))
|
||||
}
|
||||
}
|
||||
|
||||
/// Command options for this surface.
|
||||
@MainActor
|
||||
func commands() throws -> [Command] {
|
||||
var ptr: UnsafeMutablePointer<ghostty_command_s>? = nil
|
||||
var count: Int = 0
|
||||
ghostty_surface_commands(surface, &ptr, &count)
|
||||
guard let ptr else { throw Error.apiFailed }
|
||||
let buffer = UnsafeBufferPointer(start: ptr, count: count)
|
||||
return Array(buffer).map { Command(cValue: $0) }.filter { $0.isSupported }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1700,23 +1700,6 @@ pub const CAPI = struct {
|
||||
return @intCast(@as(input.Mods.Backing, @bitCast(result)));
|
||||
}
|
||||
|
||||
/// Returns the current possible commands for a surface
|
||||
/// in the output parameter. The memory is owned by libghostty
|
||||
/// and doesn't need to be freed.
|
||||
export fn ghostty_surface_commands(
|
||||
surface: *Surface,
|
||||
out: *[*]const input.Command.C,
|
||||
len: *usize,
|
||||
) void {
|
||||
// In the future we may use this information to filter
|
||||
// some commands.
|
||||
_ = surface;
|
||||
|
||||
const commands = input.command.defaultsC;
|
||||
out.* = commands.ptr;
|
||||
len.* = commands.len;
|
||||
}
|
||||
|
||||
/// Send this for raw keypresses (i.e. the keyDown event on macOS).
|
||||
/// This will handle the keymap translation and send the appropriate
|
||||
/// key and char events.
|
||||
|
||||
Reference in New Issue
Block a user