Merge pull request #10363 from manaflow-ai/issue-10103-copy-mode-selection-bleed

Fix terminal Copy Mode selection scope across split panes
This commit is contained in:
Austin Wang
2026-08-18 21:46:09 -07:00
committed by GitHub
5 changed files with 36 additions and 1 deletions
@@ -27,6 +27,9 @@ public protocol TerminalSurfaceNativeViewing: NSView, TerminalSurfaceHosting {
@discardableResult
func toggleKeyboardCopyMode() -> Bool
/// Ends keyboard copy mode and clears its selection when focus leaves the view.
func cancelKeyboardCopyMode()
/// Re-applies the window background for the active surface.
func applyWindowBackgroundIfActive()
@@ -21,6 +21,9 @@ extension TerminalSurface {
/// Applies a focus state to the runtime surface (deduplicated).
@MainActor
public func setFocus(_ focused: Bool, force: Bool = false) {
if !focused {
surfaceView.cancelKeyboardCopyMode()
}
// Only send focus events when the state changes to avoid redundant
// prompt redraws with zsh themes like Powerlevel10k.
guard force || focused != desiredFocusState else { return }
@@ -9,7 +9,8 @@ final class FakeTerminalSurfaceNativeView: NSView {
weak var attachedController: (any TerminalSurfaceControlling)?
var attachedSurfaceController: (any TerminalSurfaceControlling)? { attachedController }
var currentKeyStateIndicatorText: String? { nil }
var isKeyboardCopyModeActive: Bool { false }
var isKeyboardCopyModeActive = false
private(set) var keyboardCopyModeCancellationCount = 0
var shouldDeferRuntimeInput = false
var runtimeInputDeferralResponses: [Bool] = []
var runtimeInputDeferralCallCount = 0
@@ -18,6 +19,10 @@ final class FakeTerminalSurfaceNativeView: NSView {
var mobileMouseButtonEvents: [String] = []
func toggleKeyboardCopyMode() -> Bool { false }
func cancelKeyboardCopyMode() {
keyboardCopyModeCancellationCount += 1
isKeyboardCopyModeActive = false
}
func applyWindowBackgroundIfActive() {}
func forceRefreshSurface() -> Bool { true }
func runtimeSurfaceDidBecomeReady() {}
@@ -184,6 +184,18 @@ struct TerminalSurfaceExplicitInputTests {
#expect(fixture.paneHost.explicitInputCount == 1)
}
@Test func losingFocusCancelsKeyboardCopyModeOnTheSurface() {
let fixture = makeFixture()
defer { fixture.surface.releaseSurfaceForTesting() }
fixture.nativeView.isKeyboardCopyModeActive = true
fixture.surface.setFocus(true)
fixture.surface.setFocus(false)
#expect(fixture.nativeView.keyboardCopyModeCancellationCount == 1)
#expect(!fixture.nativeView.isKeyboardCopyModeActive)
}
@Test func mobileGesturesNotifyPaneHost() {
let fixture = makeFixture()
defer { fixture.surface.releaseSurfaceForTesting() }
+12
View File
@@ -4521,6 +4521,14 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
return true
}
func cancelKeyboardCopyMode() {
guard keyboardCopyModeActive else { return }
if let surface {
_ = GhosttyRuntimeCInterop.clearSelection(surface)
}
setKeyboardCopyModeActive(false)
}
private func setKeyboardCopyModeActive(_ active: Bool) {
keyboardCopyModeInputState.reset()
keyboardCopyModeSelectionKind = nil
@@ -5341,6 +5349,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
imeConsumedKeyUps.removeAll()
manualNamedKeyConsumedKeyUps.removeAll()
desiredFocus = false
cancelKeyboardCopyMode()
terminalSurface?.hostedView.cancelSuppressedFirstResponderFocusReapply()
terminalSurface?.recordExternalFocusState(false)
}
@@ -10105,6 +10114,9 @@ final class GhosttySurfaceScrollView: NSView {
func setActive(_ active: Bool) {
let wasActive = isActive
if !active {
surfaceView.cancelKeyboardCopyMode()
}
isActive = active
#if DEBUG
if wasActive != active {