Fix command modified keys. Add plan for splitscreen

This commit is contained in:
2026-03-13 20:25:18 +11:00
parent 1e30e9bf9e
commit 8ecb7d4382
9 changed files with 1044 additions and 482 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -15,7 +15,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1485207FA11756EC2DF4F08B"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
@@ -28,7 +28,42 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "036FDAECD12C0A679DA1F5D6"
BuildableName = "CommandNotchTests.xctest"
BlueprintName = "CommandNotchTests"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1C8D00CBB29219BD347E9CC4"
BuildableName = "CommandNotchUITests.xctest"
BlueprintName = "CommandNotchUITests"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -44,12 +79,14 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1485207FA11756EC2DF4F08B"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -61,12 +98,14 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1485207FA11756EC2DF4F08B"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -15,7 +15,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1485207FA11756EC2DF4F08B"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
@@ -28,13 +28,48 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "036FDAECD12C0A679DA1F5D6"
BuildableName = "CommandNotchTests.xctest"
BlueprintName = "CommandNotchTests"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1C8D00CBB29219BD347E9CC4"
BuildableName = "CommandNotchUITests.xctest"
BlueprintName = "CommandNotchUITests"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "1"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
@@ -44,12 +79,14 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1485207FA11756EC2DF4F08B"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -61,12 +98,14 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1485207FA11756EC2DF4F08B"
BlueprintIdentifier = "D5585E5732CD067DF6EF0C69"
BuildableName = "CommandNotch.app"
BlueprintName = "CommandNotch"
ReferencedContainer = "container:CommandNotch.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -0,0 +1,31 @@
import AppKit
import Carbon.HIToolbox
import SwiftTerm
enum TerminalCommandArrowBehavior {
private static let relevantModifiers: NSEvent.ModifierFlags = [.command, .control, .option, .shift]
private static let lineKill: [UInt8] = [0x15]
private static let clearScreen: [UInt8] = [0x0c]
static func sequence(
for modifierFlags: NSEvent.ModifierFlags,
keyCode: UInt16,
applicationCursor: Bool
) -> [UInt8]? {
let flags = modifierFlags.intersection(relevantModifiers)
guard flags == [.command] else { return nil }
switch Int(keyCode) {
case kVK_LeftArrow:
return applicationCursor ? EscapeSequences.moveHomeApp : EscapeSequences.moveHomeNormal
case kVK_RightArrow:
return applicationCursor ? EscapeSequences.moveEndApp : EscapeSequences.moveEndNormal
case kVK_Delete:
return lineKill
case kVK_ANSI_L:
return clearScreen
default:
return nil
}
}
}

View File

@@ -9,6 +9,7 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
let id = UUID()
let terminalView: TerminalView
private var process: LocalProcess?
private var keyEventMonitor: Any?
private let backgroundColor = NSColor.black
private let configuredShellPath: String
@@ -26,10 +27,17 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)
terminalView.font = font
applyTheme(theme)
installCommandArrowMonitor()
startShell()
}
deinit {
if let keyEventMonitor {
NSEvent.removeMonitor(keyEventMonitor)
}
}
// MARK: - Shell management
private func startShell() {
@@ -58,6 +66,26 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
return ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh"
}
private func installCommandArrowMonitor() {
keyEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
guard let self else { return event }
guard let window = self.terminalView.window else { return event }
guard event.window === window else { return event }
guard window.firstResponder === self.terminalView else { return event }
guard let sequence = TerminalCommandArrowBehavior.sequence(
for: event.modifierFlags,
keyCode: event.keyCode,
applicationCursor: self.terminalView.getTerminal().applicationCursor
) else {
return event
}
self.terminalView.send(data: sequence[...])
return nil
}
}
func updateFontSize(_ size: CGFloat) {
terminalView.font = NSFont.monospacedSystemFont(ofSize: size, weight: .regular)
}

View File

@@ -0,0 +1,57 @@
import AppKit
import Carbon.HIToolbox
import XCTest
import SwiftTerm
@testable import CommandNotch
final class TerminalCommandArrowBehaviorTests: XCTestCase {
func testCommandLeftUsesHomeSequence() {
let sequence = TerminalCommandArrowBehavior.sequence(
for: [.command],
keyCode: UInt16(kVK_LeftArrow),
applicationCursor: false
)
XCTAssertEqual(sequence, EscapeSequences.moveHomeNormal)
}
func testCommandRightUsesApplicationEndSequence() {
let sequence = TerminalCommandArrowBehavior.sequence(
for: [.command],
keyCode: UInt16(kVK_RightArrow),
applicationCursor: true
)
XCTAssertEqual(sequence, EscapeSequences.moveEndApp)
}
func testOptionLeftKeepsSwiftTermWordNavigationPath() {
let sequence = TerminalCommandArrowBehavior.sequence(
for: [.option],
keyCode: UInt16(kVK_LeftArrow),
applicationCursor: false
)
XCTAssertNil(sequence)
}
func testCommandDeleteUsesLineKillSequence() {
let sequence = TerminalCommandArrowBehavior.sequence(
for: [.command],
keyCode: UInt16(kVK_Delete),
applicationCursor: false
)
XCTAssertEqual(sequence, [0x15])
}
func testCommandLUsesClearScreenSequence() {
let sequence = TerminalCommandArrowBehavior.sequence(
for: [.command],
keyCode: UInt16(kVK_ANSI_L),
applicationCursor: false
)
XCTAssertEqual(sequence, [0x0c])
}
}

View File

@@ -14,6 +14,45 @@ packages:
SwiftTerm:
url: https://github.com/migueldeicaza/SwiftTerm.git
from: "1.2.0"
schemes:
CommandNotch:
build:
targets:
CommandNotch: all
test:
config: Debug
targets:
- CommandNotchTests
- CommandNotchUITests
run:
config: Debug
profile:
config: Release
analyze:
config: Debug
archive:
config: Release
management:
shared: true
Release-CommandNotch:
build:
targets:
CommandNotch: all
test:
config: Debug
targets:
- CommandNotchTests
- CommandNotchUITests
run:
config: Release
profile:
config: Release
analyze:
config: Debug
archive:
config: Release
management:
shared: true
targets:
CommandNotch:
type: application
@@ -29,9 +68,9 @@ targets:
properties:
CFBundleName: CommandNotch
CFBundleDisplayName: CommandNotch
CFBundleIdentifier: com.commandnotch.app
CFBundleIdentifier: "$(PRODUCT_BUNDLE_IDENTIFIER)"
CFBundleVersion: "1"
CFBundleShortVersionString: "0.2.0"
CFBundleShortVersionString: "0.0.3"
CFBundlePackageType: APPL
CFBundleExecutable: CommandNotch
LSMinimumSystemVersion: "14.0"