Use pane TTY fallback for tmux Shift+Enter
This commit is contained in:
parent
f4c99d34f3
commit
8336aae865
3 changed files with 97 additions and 26 deletions
|
|
@ -1930,12 +1930,10 @@ class GhosttyApp {
|
||||||
modifierFlags: NSEvent.ModifierFlags,
|
modifierFlags: NSEvent.ModifierFlags,
|
||||||
isInsideTmux: Bool,
|
isInsideTmux: Bool,
|
||||||
userConfigDefinesShiftEnterBinding: Bool,
|
userConfigDefinesShiftEnterBinding: Bool,
|
||||||
ghosttyHasBinding: Bool,
|
|
||||||
hasMarkedText: Bool
|
hasMarkedText: Bool
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
guard isInsideTmux else { return false }
|
guard isInsideTmux else { return false }
|
||||||
guard !userConfigDefinesShiftEnterBinding else { return false }
|
guard !userConfigDefinesShiftEnterBinding else { return false }
|
||||||
guard !ghosttyHasBinding else { return false }
|
|
||||||
guard !hasMarkedText else { return false }
|
guard !hasMarkedText else { return false }
|
||||||
|
|
||||||
let normalizedModifiers = terminalKeyboardCopyModeNormalizedModifiers(modifierFlags)
|
let normalizedModifiers = terminalKeyboardCopyModeNormalizedModifiers(modifierFlags)
|
||||||
|
|
@ -6236,27 +6234,44 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
|
||||||
event: NSEvent,
|
event: NSEvent,
|
||||||
surface: ghostty_surface_t
|
surface: ghostty_surface_t
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
guard !GhosttyApp.shared.userConfigDefinesShiftEnterBinding else { return false }
|
let userConfigDefinesShiftEnterBinding = GhosttyApp.shared.userConfigDefinesShiftEnterBinding
|
||||||
|
guard !userConfigDefinesShiftEnterBinding else { return false }
|
||||||
let normalizedModifiers = terminalKeyboardCopyModeNormalizedModifiers(event.modifierFlags)
|
let normalizedModifiers = terminalKeyboardCopyModeNormalizedModifiers(event.modifierFlags)
|
||||||
guard normalizedModifiers == [.shift] else { return false }
|
guard normalizedModifiers == [.shift] else { return false }
|
||||||
guard event.keyCode == 36 || event.keyCode == 76 else { return false }
|
guard event.keyCode == 36 || event.keyCode == 76 else { return false }
|
||||||
guard let terminalSurface else { return false }
|
guard let terminalSurface else { return false }
|
||||||
let tabId = terminalSurface.tabId
|
let tabId = terminalSurface.tabId
|
||||||
let panelId = terminalSurface.id
|
let panelId = terminalSurface.id
|
||||||
let isInsideTmux = AppDelegate.shared?
|
guard let tab = AppDelegate.shared?
|
||||||
.tabManagerFor(tabId: tabId)?
|
.tabManagerFor(tabId: tabId)?
|
||||||
.tabs
|
.tabs
|
||||||
.first(where: { $0.id == tabId })?
|
.first(where: { $0.id == tabId }) else {
|
||||||
.panelIsInsideTmux(panelId: panelId) ?? false
|
return false
|
||||||
let ghosttyHasBinding = ghosttyBindingFlags(for: event, surface: surface) != nil
|
}
|
||||||
return GhosttyApp.shouldRemapShiftEnterForTmux(
|
let reportedInsideTmux = tab.panelIsInsideTmux(panelId: panelId)
|
||||||
|
// Shell-side tmux telemetry can lag behind pane focus changes, so fall back to
|
||||||
|
// the current foreground process on the pane TTY before deciding whether to remap.
|
||||||
|
let detectedInsideTmux = tab.surfaceTTYNames[panelId].map {
|
||||||
|
TerminalSSHSessionDetector.isInsideTmux(forTTY: $0)
|
||||||
|
} ?? false
|
||||||
|
let isInsideTmux = reportedInsideTmux || detectedInsideTmux
|
||||||
|
if detectedInsideTmux != reportedInsideTmux {
|
||||||
|
AppDelegate.shared?
|
||||||
|
.tabManagerFor(tabId: tabId)?
|
||||||
|
.updateSurfaceTmuxState(
|
||||||
|
tabId: tabId,
|
||||||
|
surfaceId: panelId,
|
||||||
|
isInsideTmux: detectedInsideTmux
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let shouldRemap = GhosttyApp.shouldRemapShiftEnterForTmux(
|
||||||
keyCode: event.keyCode,
|
keyCode: event.keyCode,
|
||||||
modifierFlags: event.modifierFlags,
|
modifierFlags: event.modifierFlags,
|
||||||
isInsideTmux: isInsideTmux,
|
isInsideTmux: isInsideTmux,
|
||||||
userConfigDefinesShiftEnterBinding: false,
|
userConfigDefinesShiftEnterBinding: userConfigDefinesShiftEnterBinding,
|
||||||
ghosttyHasBinding: ghosttyHasBinding,
|
|
||||||
hasMarkedText: hasMarkedText()
|
hasMarkedText: hasMarkedText()
|
||||||
)
|
)
|
||||||
|
return shouldRemap
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,24 @@ enum TerminalSSHSessionDetector {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func isInsideTmux(forTTY ttyName: String) -> Bool {
|
||||||
|
let normalizedTTY = normalizeTTYName(ttyName)
|
||||||
|
guard !normalizedTTY.isEmpty else { return false }
|
||||||
|
return isInsideTmuxForTesting(
|
||||||
|
ttyName: normalizedTTY,
|
||||||
|
processes: processSnapshots(forTTY: normalizedTTY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isInsideTmuxForTesting(
|
||||||
|
ttyName: String,
|
||||||
|
processes: [ProcessSnapshot]
|
||||||
|
) -> Bool {
|
||||||
|
let normalizedTTY = normalizeTTYName(ttyName)
|
||||||
|
guard !normalizedTTY.isEmpty else { return false }
|
||||||
|
return processes.contains { isForegroundProcess($0, ttyName: normalizedTTY, executableName: "tmux") }
|
||||||
|
}
|
||||||
|
|
||||||
static func detectForTesting(
|
static func detectForTesting(
|
||||||
ttyName: String,
|
ttyName: String,
|
||||||
processes: [ProcessSnapshot],
|
processes: [ProcessSnapshot],
|
||||||
|
|
@ -474,8 +492,16 @@ enum TerminalSSHSessionDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func isForegroundSSHProcess(_ process: ProcessSnapshot, ttyName: String) -> Bool {
|
private static func isForegroundSSHProcess(_ process: ProcessSnapshot, ttyName: String) -> Bool {
|
||||||
|
isForegroundProcess(process, ttyName: ttyName, executableName: "ssh")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func isForegroundProcess(
|
||||||
|
_ process: ProcessSnapshot,
|
||||||
|
ttyName: String,
|
||||||
|
executableName: String
|
||||||
|
) -> Bool {
|
||||||
normalizeTTYName(process.tty) == normalizeTTYName(ttyName) &&
|
normalizeTTYName(process.tty) == normalizeTTYName(ttyName) &&
|
||||||
process.executableName == "ssh" &&
|
process.executableName == executableName &&
|
||||||
process.pgid > 0 &&
|
process.pgid > 0 &&
|
||||||
process.tpgid > 0 &&
|
process.tpgid > 0 &&
|
||||||
process.pgid == process.tpgid
|
process.pgid == process.tpgid
|
||||||
|
|
|
||||||
|
|
@ -2559,7 +2559,6 @@ final class GhosttyMouseFocusTests: XCTestCase {
|
||||||
modifierFlags: [.shift],
|
modifierFlags: [.shift],
|
||||||
isInsideTmux: true,
|
isInsideTmux: true,
|
||||||
userConfigDefinesShiftEnterBinding: false,
|
userConfigDefinesShiftEnterBinding: false,
|
||||||
ghosttyHasBinding: false,
|
|
||||||
hasMarkedText: false
|
hasMarkedText: false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2570,7 +2569,6 @@ final class GhosttyMouseFocusTests: XCTestCase {
|
||||||
modifierFlags: [.shift],
|
modifierFlags: [.shift],
|
||||||
isInsideTmux: false,
|
isInsideTmux: false,
|
||||||
userConfigDefinesShiftEnterBinding: false,
|
userConfigDefinesShiftEnterBinding: false,
|
||||||
ghosttyHasBinding: false,
|
|
||||||
hasMarkedText: false
|
hasMarkedText: false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2581,18 +2579,6 @@ final class GhosttyMouseFocusTests: XCTestCase {
|
||||||
modifierFlags: [.shift],
|
modifierFlags: [.shift],
|
||||||
isInsideTmux: true,
|
isInsideTmux: true,
|
||||||
userConfigDefinesShiftEnterBinding: true,
|
userConfigDefinesShiftEnterBinding: true,
|
||||||
ghosttyHasBinding: false,
|
|
||||||
hasMarkedText: false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
XCTAssertFalse(
|
|
||||||
GhosttyApp.shouldRemapShiftEnterForTmux(
|
|
||||||
keyCode: 36,
|
|
||||||
modifierFlags: [.shift],
|
|
||||||
isInsideTmux: true,
|
|
||||||
userConfigDefinesShiftEnterBinding: false,
|
|
||||||
ghosttyHasBinding: true,
|
|
||||||
hasMarkedText: false
|
hasMarkedText: false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2603,12 +2589,56 @@ final class GhosttyMouseFocusTests: XCTestCase {
|
||||||
modifierFlags: [.shift, .command],
|
modifierFlags: [.shift, .command],
|
||||||
isInsideTmux: true,
|
isInsideTmux: true,
|
||||||
userConfigDefinesShiftEnterBinding: false,
|
userConfigDefinesShiftEnterBinding: false,
|
||||||
ghosttyHasBinding: false,
|
|
||||||
hasMarkedText: false
|
hasMarkedText: false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testForegroundTmuxProcessOnTTYIsDetected() {
|
||||||
|
let processes = [
|
||||||
|
TerminalSSHSessionDetector.ProcessSnapshot(
|
||||||
|
pid: 47486,
|
||||||
|
pgid: 47486,
|
||||||
|
tpgid: 48365,
|
||||||
|
tty: "ttys089",
|
||||||
|
executableName: "login"
|
||||||
|
),
|
||||||
|
TerminalSSHSessionDetector.ProcessSnapshot(
|
||||||
|
pid: 47487,
|
||||||
|
pgid: 47487,
|
||||||
|
tpgid: 48365,
|
||||||
|
tty: "ttys089",
|
||||||
|
executableName: "zsh"
|
||||||
|
),
|
||||||
|
TerminalSSHSessionDetector.ProcessSnapshot(
|
||||||
|
pid: 48365,
|
||||||
|
pgid: 48365,
|
||||||
|
tpgid: 48365,
|
||||||
|
tty: "ttys089",
|
||||||
|
executableName: "tmux"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
XCTAssertTrue(
|
||||||
|
TerminalSSHSessionDetector.isInsideTmuxForTesting(
|
||||||
|
ttyName: "ttys089",
|
||||||
|
processes: processes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
XCTAssertFalse(
|
||||||
|
TerminalSSHSessionDetector.isInsideTmuxForTesting(
|
||||||
|
ttyName: "ttys090",
|
||||||
|
processes: processes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
XCTAssertFalse(
|
||||||
|
TerminalSSHSessionDetector.isInsideTmuxForTesting(
|
||||||
|
ttyName: "ttys089",
|
||||||
|
processes: processes.filter { $0.executableName != "tmux" }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func testLoadedCJKScanPathsSkipsReleaseAppSupportWhenTaggedConfigExists() throws {
|
func testLoadedCJKScanPathsSkipsReleaseAppSupportWhenTaggedConfigExists() throws {
|
||||||
let appSupport = FileManager.default.temporaryDirectory
|
let appSupport = FileManager.default.temporaryDirectory
|
||||||
.appendingPathComponent("cmux-test-cjk-app-support-\(UUID().uuidString)")
|
.appendingPathComponent("cmux-test-cjk-app-support-\(UUID().uuidString)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue