diff --git a/include/ghostty.h b/include/ghostty.h index d6e6fba70..0ad15cf69 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -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); diff --git a/macos/Sources/Features/App Intents/Entities/CommandEntity.swift b/macos/Sources/Features/App Intents/Entities/CommandEntity.swift index f7abcc6de..3c7745e7c 100644 --- a/macos/Sources/Features/App Intents/Entities/CommandEntity.swift +++ b/macos/Sources/Features/App Intents/Entities/CommandEntity.swift @@ -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) } } } diff --git a/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift b/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift index 6efb588cd..e0237f257 100644 --- a/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift +++ b/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift @@ -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( diff --git a/macos/Sources/Ghostty/Ghostty.Surface.swift b/macos/Sources/Ghostty/Ghostty.Surface.swift index c7198e147..e86952e50 100644 --- a/macos/Sources/Ghostty/Ghostty.Surface.swift +++ b/macos/Sources/Ghostty/Ghostty.Surface.swift @@ -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? = 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 } - } } } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 1cb9231bc..64900cef1 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -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.