native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #26 from onevcat/feature/ghostty-prompt-title-and-config-actions

feat: implement prompt-title and open-config Ghostty callbacks

authored by

Wei Wang and committed by
GitHub
e7af7dc6 e737361f

+83 -4
+11
supacode/Features/Terminal/Models/TerminalTabManager.swift
··· 30 30 tabs[index].title = title 31 31 } 32 32 33 + func overrideTitle(_ id: TerminalTabID, title: String) { 34 + guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 35 + tabs[index].title = title 36 + tabs[index].isTitleLocked = true 37 + } 38 + 39 + func clearTitleOverride(_ id: TerminalTabID) { 40 + guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 41 + tabs[index].isTitleLocked = false 42 + } 43 + 33 44 func updateDirty(_ id: TerminalTabID, isDirty: Bool) { 34 45 guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 35 46 tabs[index].isDirty = isDirty
+53
supacode/Features/Terminal/Models/WorktreeTerminalState.swift
··· 656 656 guard let self, let view else { return } 657 657 self.handleCloseRequest(for: view, processAlive: processAlive) 658 658 } 659 + view.bridge.onPromptTitle = { [weak self] promptType in 660 + guard let self else { return } 661 + self.handlePromptTitle(promptType, tabId: tabId) 662 + } 659 663 view.onFocusChange = { [weak self, weak view] focused in 660 664 guard let self, let view, focused else { return } 661 665 self.focusedSurfaceIdByTab[tabId] = view.id ··· 699 703 private func currentFocusedSurfaceId() -> UUID? { 700 704 guard let selectedTabId = tabManager.selectedTabId else { return nil } 701 705 return focusedSurfaceIdByTab[selectedTabId] 706 + } 707 + 708 + private func handlePromptTitle( 709 + _ promptType: ghostty_action_prompt_title_e, 710 + tabId: TerminalTabID 711 + ) { 712 + guard let surfaceId = focusedSurfaceIdByTab[tabId], 713 + let window = surfaces[surfaceId]?.window 714 + else { return } 715 + switch promptType { 716 + case GHOSTTY_PROMPT_TITLE_SURFACE, GHOSTTY_PROMPT_TITLE_TAB: 717 + // Prowl is a single-window app so there is no per-surface window title to set. 718 + // Both surface and tab title prompts are treated as tab title changes for now. 719 + // Consider removing GHOSTTY_PROMPT_TITLE_SURFACE support entirely. 720 + promptTabTitle(for: tabId, in: window) 721 + default: 722 + break 723 + } 724 + } 725 + 726 + private func promptTabTitle(for tabId: TerminalTabID, in window: NSWindow) { 727 + guard let tabIndex = tabManager.tabs.firstIndex(where: { $0.id == tabId }) else { return } 728 + 729 + let alert = NSAlert() 730 + alert.messageText = "Change Tab Title" 731 + alert.informativeText = "Leave blank to restore the default." 732 + alert.alertStyle = .informational 733 + 734 + let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24)) 735 + textField.stringValue = tabManager.tabs[tabIndex].title 736 + alert.accessoryView = textField 737 + 738 + alert.addButton(withTitle: "OK") 739 + alert.addButton(withTitle: "Cancel") 740 + alert.window.initialFirstResponder = textField 741 + 742 + alert.beginSheetModal(for: window) { [weak self] response in 743 + MainActor.assumeIsolated { 744 + guard response == .alertFirstButtonReturn else { return } 745 + guard let self else { return } 746 + let newTitle = textField.stringValue 747 + if newTitle.isEmpty { 748 + self.tabManager.clearTitleOverride(tabId) 749 + self.updateTabTitle(for: tabId) 750 + } else { 751 + self.tabManager.overrideTitle(tabId, title: newTitle) 752 + } 753 + } 754 + } 702 755 } 703 756 704 757 private func updateTabTitle(for tabId: TerminalTabID) {
+16
supacode/Infrastructure/Ghostty/GhosttyRuntime.swift
··· 369 369 runtime.reloadConfig(soft: soft, target: target) 370 370 } 371 371 } 372 + if action.tag == GHOSTTY_ACTION_OPEN_CONFIG, target.tag == GHOSTTY_TARGET_APP { 373 + openGhosttyConfig() 374 + return true 375 + } 372 376 guard target.tag == GHOSTTY_TARGET_SURFACE else { return false } 373 377 guard let surface = target.target.surface else { return false } 374 378 guard let bridge = surfaceBridge(fromSurface: surface) else { return false } 375 379 return bridge.handleAction(target: target, action: action) 380 + } 381 + 382 + private static func openGhosttyConfig() { 383 + let configStr = ghostty_config_open_path() 384 + defer { ghostty_string_free(configStr) } 385 + guard let ptr = configStr.ptr else { return } 386 + let path = String(data: Data(bytes: ptr, count: Int(configStr.len)), encoding: .utf8) ?? "" 387 + guard !path.isEmpty else { return } 388 + let process = Process() 389 + process.executableURL = URL(fileURLWithPath: "/usr/bin/open") 390 + process.arguments = ["-t", path] 391 + try? process.run() 376 392 } 377 393 378 394 private static func readClipboard(
+3 -2
supacode/Infrastructure/Ghostty/GhosttySurfaceBridge.swift
··· 17 17 var onCommandPaletteToggle: (() -> Bool)? 18 18 var onProgressReport: ((ghostty_action_progress_report_state_e) -> Void)? 19 19 var onDesktopNotification: ((String, String) -> Void)? 20 + var onPromptTitle: ((ghostty_action_prompt_title_e) -> Void)? 20 21 private var progressResetTask: Task<Void, Never>? 21 22 22 23 deinit { ··· 175 176 return true 176 177 177 178 case GHOSTTY_ACTION_PROMPT_TITLE: 178 - state.promptTitle = action.action.prompt_title 179 + onPromptTitle?(action.action.prompt_title) 179 180 return true 180 181 181 182 case GHOSTTY_ACTION_PWD: ··· 422 423 return true 423 424 424 425 case GHOSTTY_ACTION_OPEN_CONFIG: 425 - state.openConfigCount += 1 426 + // Handled at app level in GhosttyRuntime.handleAction. 426 427 return true 427 428 428 429 case GHOSTTY_ACTION_PRESENT_TERMINAL:
-2
supacode/Infrastructure/Ghostty/GhosttySurfaceState.swift
··· 6 6 final class GhosttySurfaceState { 7 7 var title: String? 8 8 var pwd: String? 9 - var promptTitle: ghostty_action_prompt_title_e? 10 9 var progressState: ghostty_action_progress_report_state_e? 11 10 var progressValue: Int? 12 11 var commandExitCode: Int? ··· 44 43 var reloadConfigSoft: Bool? 45 44 var configChangeCount: Int = 0 46 45 var bellCount: Int = 0 47 - var openConfigCount: Int = 0 48 46 var presentTerminalCount: Int = 0 49 47 var resetWindowSizeCount: Int = 0 50 48 var quitTimer: ghostty_action_quit_timer_e?