Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
dbc351f
Add prompt-aware selection resolver for terminal text editing
teamleaderleo Sep 26, 2026
ff500d1
Address review: collapse, Shift+Delete, Option text, collapse tracking
teamleaderleo Sep 26, 2026
ae9ba81
Drop stale collapse points and document option-as-alt text
teamleaderleo Sep 26, 2026
b6d104f
Never press Right when collapsing a prompt selection
teamleaderleo Sep 27, 2026
0830393
Add terminal.promptSelection and wire prompt selection into the termi…
teamleaderleo Sep 27, 2026
2179dbe
Merge commit 'bdbc19a4cb8d9ed1a2c3d7b21e0caeef0923031b' into feat/pro…
teamleaderleo Sep 27, 2026
b5eb0d0
Bump Ghostty to 1015e149c for the prompt input exports
teamleaderleo Sep 27, 2026
67db742
Match Cmd+A and Cmd+X on a non-optional key string
teamleaderleo Sep 27, 2026
c11ebbb
Pin the GhosttyKit checksum for 1015e149c
teamleaderleo Sep 27, 2026
b38e720
Address review: exact-text Cut, clipboard-read ordering, output selec…
teamleaderleo Sep 27, 2026
537b2c2
Bump Ghostty to f8f8f828a so a right prompt is not input
teamleaderleo Sep 27, 2026
a4c9aad
Pin the GhosttyKit checksum for f8f8f828a
teamleaderleo Sep 27, 2026
32cd76b
Document Tab and paste-read limits of prompt selection
teamleaderleo Sep 27, 2026
9a52873
Bump Ghostty to b93ab4222 so zle's erased spaces are not input
teamleaderleo Sep 27, 2026
f195582
Pin the GhosttyKit checksum for b93ab4222
teamleaderleo Sep 27, 2026
a7d5ee8
Merge main (e1f1cb2fca9a) into feat/prompt-aware-selection
teamleaderleo Sep 27, 2026
7762400
Call the prompt input exports through CmuxTerminalCore's GhosttyKit m…
teamleaderleo Sep 27, 2026
94e3c29
Merge main (de22b47d4876) into feat/prompt-aware-selection
teamleaderleo Sep 27, 2026
ede391a
Merge main (badf9f6a0c67) into feat/prompt-aware-selection
teamleaderleo Sep 27, 2026
e8af053
Stub the prompt input symbols for the Swift package test runners
teamleaderleo Sep 27, 2026
812f3c1
Merge main into prompt-aware selection
teamleaderleo Sep 30, 2026
bca0fc7
test: invalidate stale prompt selection anchors
teamleaderleo Sep 30, 2026
70c339a
fix: invalidate stale prompt selection anchors
teamleaderleo Sep 30, 2026
9fff5d7
Merge main into prompt-aware selection
teamleaderleo Sep 30, 2026
0fc192d
Merge main (ecba57ac7195) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
655301f
docs: deduplicate terminal gesture keys
teamleaderleo Sep 30, 2026
ea178e7
fix: align Bonsplit with terminal sizing APIs
teamleaderleo Sep 30, 2026
1124723
Merge main (40a636edacb3) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
e47cb17
test: add prompt selection dogfood tour
teamleaderleo Sep 30, 2026
60870e5
Merge main (aff066ba78f) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
b8c5533
Merge main (7d177547949) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
6276087
docs: preserve Ghostty fork history in catch-up
teamleaderleo Sep 30, 2026
3e0b7dd
Merge main (e94800760c4) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
1cf4acb
fix: align Bonsplit with current main
teamleaderleo Sep 30, 2026
1c74568
Merge main (e709b69d8c8c) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
342613a
Merge main (a302b3a326b) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
e406d4d
Merge main (b3d644ba8732) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
cd04cd7
Merge main (ea6e02be1680) into feat/prompt-aware-selection
teamleaderleo Sep 30, 2026
d1176dd
Preserve prompt-selection Ghostty gitlink
teamleaderleo Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/build-ghosttykit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ jobs:
-Dtest-filter="unfocused render pacer spaces unfocused frames" \
--summary all

- name: Test Ghostty prompt input
run: |
set -euo pipefail
cd ghostty
zig build test \
-Dapp-runtime=none \
-Demit-macos-app=false \
-Dsentry=false \
-Dtest-filter="promptInput" \
--summary all

- name: Test Ghostty startup input bytes
run: |
set -euo pipefail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,11 @@ enum CmuxEmbeddedConfigSchema {
"default": false,
"description": "Keep textEditingGestures active while a full-screen application has the terminal on the alternate screen. tmux, screen, and zellij keep the outer terminal on the alternate screen even at their shell prompt, so turn this on to use gestures inside a multiplexer. Applications that bind the gesture chords themselves then receive the translated keys."
},
"promptSelection": {
"type": "boolean",
"default": false,
"description": "When true, the shell input at a prompt behaves like a text field for selection: Command-A selects only the command being typed (an empty prompt keeps select-all), Shift-Left/Right and Command-Shift-Left/Right extend the selection, and typing, Delete, or Command-X replace it through arrow and Backspace keys. Needs shell integration (OSC 133) and applies only at a prompt on the main screen, never inside full-screen TUIs or while a command runs. Assumes an emacs-style line editor; in vi command mode the replayed Backspace keys move instead of deleting."
},
"showPasswordInputIndicator": {
"type": "boolean",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ public struct TerminalCatalogSection: SettingCatalogSection {
userDefaultsKey: "terminal.textEditingGestures"
)

/// Whether the shell input at a prompt behaves like a text field for
/// selection: Command-A selects the command being typed, Shift-arrows
/// extend, and typing, Delete, or Command-X replace the selection. Needs
/// shell integration (OSC 133). Off by default, because edits replay
/// arrow and Backspace keys that assume an emacs-style line editor.
public let promptSelection = DefaultsKey<Bool>(
id: "terminal.promptSelection",
defaultValue: false,
userDefaultsKey: "terminal.promptSelection"
)

/// Whether text-editing gestures use the browser-style layout: Command
/// moves and deletes by word like Option, and Control+Left/Right move to
/// the line start and end. Only consulted while ``textEditingGestures`` is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ extension Array where Element == CuratedSettingEntry {
synonyms: "terminal.scrollSpeed scroll speed multiplier wheel mouse trackpad sensitivity faster slower"
),
.init(section: .terminal, id: "copy-on-select", title: String(localized: "settings.terminal.copyOnSelect", defaultValue: "Copy on Selection"), synonyms: "Copy on Selection terminal.copyOnSelect copy on selection select clipboard mouse double click triple click iterm"),
.init(section: .terminal, id: "prompt-selection", title: String(localized: "settings.terminal.promptSelection", defaultValue: "Prompt Text Selection"), synonyms: "Prompt Text Selection terminal.promptSelection prompt text selection select all command a shift arrow cut replace shell input command line text field osc 133"),
.init(section: .terminal, id: "text-editing-gestures", title: String(localized: "settings.terminal.textEditingGestures", defaultValue: "Text Editing Gestures"), synonyms: "Text Editing Gestures terminal.textEditingGestures text editing gestures option alt word line kill readline emacs keybindings command arrow delete"),
.init(
section: .terminal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public struct TerminalSection: View {
@State private var reflowHardWrapOnCopy: DefaultsValueModel<Bool>
@State private var confirmUnsafePaste: DefaultsValueModel<Bool>
@State private var textEditingGestures: DefaultsValueModel<Bool>
@State private var promptSelection: DefaultsValueModel<Bool>
@State private var textEditingCommandMovesByWord: DefaultsValueModel<Bool>
@State private var textEditingGesturesInFullScreenApps: DefaultsValueModel<Bool>
@State private var passwordInputIndicator: DefaultsValueModel<Bool>
Expand Down Expand Up @@ -61,6 +62,7 @@ public struct TerminalSection: View {
_reflowHardWrapOnCopy = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.reflowHardWrapOnCopy))
_confirmUnsafePaste = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.confirmUnsafePaste))
_textEditingGestures = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.textEditingGestures))
_promptSelection = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.promptSelection))
_textEditingCommandMovesByWord = State(
initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.textEditingCommandMovesByWord)
)
Expand Down Expand Up @@ -109,6 +111,7 @@ public struct TerminalSection: View {
reflowHardWrapOnCopy,
confirmUnsafePaste,
textEditingGestures,
promptSelection,
textEditingCommandMovesByWord,
textEditingGesturesInFullScreenApps,
passwordInputIndicator,
Expand Down Expand Up @@ -448,6 +451,19 @@ public struct TerminalSection: View {
.accessibilityIdentifier("SettingsTerminalTextEditingGesturesToggle")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.promptSelection"),
String(localized: "settings.terminal.promptSelection", defaultValue: "Prompt Text Selection"),
subtitle: promptSelection.current
? String(localized: "settings.terminal.promptSelection.subtitleOn", defaultValue: "At a shell prompt, Command-A selects the command you're typing, Shift-arrow keys extend the selection, and typing, Delete, or Command-X replace it. Needs shell integration and an emacs-style line editor.")
: String(localized: "settings.terminal.promptSelection.subtitleOff", defaultValue: "Command-A selects everything in the terminal, and typing never replaces a selection.")
) {
Toggle("", isOn: Binding(get: { promptSelection.current }, set: { promptSelection.set($0) }))
.labelsHidden()
.controlSize(.small)
.accessibilityIdentifier("SettingsTerminalPromptSelectionToggle")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.textEditingCommandMovesByWord"),
String(localized: "settings.terminal.textEditingCommandMovesByWord", defaultValue: "Command Moves by Word"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ struct SettingsRowAnchorResolutionTests {
"terminal.showPasswordInputDots",
"terminal.predictiveLocalEcho",
"terminal.textEditingGestures",
"terminal.promptSelection",
"terminal.textEditingCommandMovesByWord",
"terminal.textEditingGesturesInFullScreenApps",
"terminal.resumeCommands",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,8 @@ bool ghostty_surface_needs_confirm_quit(void *surface) {
return cmux_test_needs_confirm_quit;
}
void ghostty_surface_new(void) {}
void ghostty_surface_prompt_input(void) {}
void ghostty_surface_select_prompt_input(void) {}
bool ghostty_surface_process_exited(void *surface) {
(void)surface;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ void ghostty_surface_mouse_pos(void);
void ghostty_surface_mouse_scroll(void);
bool ghostty_surface_needs_confirm_quit(void *surface);
void ghostty_surface_new(void);
void ghostty_surface_prompt_input(void);
void ghostty_surface_select_prompt_input(void);
bool ghostty_surface_process_exited(void *surface);
void ghostty_surface_process_output(void *surface, const char *data, uintptr_t len);
void ghostty_surface_quicklook_font(void);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
public import GhosttyKit

extension TerminalPromptInputSnapshot {
/// Reads the shell input under the cursor of a live Ghostty surface.
///
/// Takes Ghostty's renderer lock once, so call it for selection gestures,
/// not on every keystroke.
///
/// - Parameter surface: A live runtime surface.
/// - Returns: The snapshot, or `nil` when Ghostty reports no editable
/// prompt: the alternate screen, a running command, no shell
/// integration, or a line without a prompt before its input.
public static func read(from surface: ghostty_surface_t) -> TerminalPromptInputSnapshot? {
var input = ghostty_surface_prompt_input_s()
guard ghostty_surface_prompt_input(surface, &input) else { return nil }
let selection: Range<Int>? = input.has_selection && input.selection_end > input.selection_start
? Int(input.selection_start)..<Int(input.selection_end)
: nil
return TerminalPromptInputSnapshot(
length: Int(input.length),
caret: Int(input.caret),
selection: selection,
selectionOutsideInput: selection == nil && ghostty_surface_has_selection(surface)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public import GhosttyKit

extension TerminalPromptSelection {
/// Selects this selection's caret stops on a live Ghostty surface.
///
/// Never writes a clipboard, whatever `copy-on-select` says.
///
/// - Parameter surface: A live runtime surface.
/// - Returns: Whether Ghostty made the selection. It refuses when the
/// cursor is no longer in editable prompt input or the range falls
/// outside it.
public func select(on surface: ghostty_surface_t) -> Bool {
ghostty_surface_select_prompt_input(
surface,
UInt32(clamping: range.lowerBound),
UInt32(clamping: range.upperBound)
)
}
}
Loading
Loading