Add command history navigation with up/down arrows

- Up arrow: Navigate to previous commands in history
- Down arrow: Navigate to next commands (or back to current input)
- Works exactly like standard terminal behavior
- Preserves current input when starting navigation
- Resets on command execution
This commit is contained in:
2026-01-31 23:29:39 +00:00
parent b9570620da
commit 5cf9701bc9
3 changed files with 82 additions and 0 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -12,6 +12,9 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
var title: String
var currentWorkingDirectory: String = "~"
private var currentSuggestion: String?
private var historyIndex: Int = -1
private var historyBuffer: [String] = []
private var currentInput: String = ""
init(title: String = "Terminal") {
self.sessionID = UUID()
@@ -232,6 +235,80 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
return cleaned
}
func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
// Handle up arrow - previous command in history
if commandSelector == #selector(NSResponder.moveUp(_:)) {
navigateHistory(direction: .previous)
return true
}
// Handle down arrow - next command in history
if commandSelector == #selector(NSResponder.moveDown(_:)) {
navigateHistory(direction: .next)
return true
}
return false
}
private enum HistoryDirection {
case previous, next
}
private func navigateHistory(direction: HistoryDirection) {
// Load history if not already loaded
if historyBuffer.isEmpty {
historyBuffer = HistoryManager.shared.getRecentCommands(100)
}
guard !historyBuffer.isEmpty else { return }
// Save current input if we're starting to navigate
if historyIndex == -1 {
currentInput = getCurrentCommand()
}
// Navigate history
switch direction {
case .previous:
if historyIndex < historyBuffer.count - 1 {
historyIndex += 1
}
case .next:
if historyIndex > -1 {
historyIndex -= 1
}
}
// Update command line
let newCommand: String
if historyIndex == -1 {
// Back to original input
newCommand = currentInput
} else {
// Show history command
newCommand = historyBuffer[historyIndex]
}
// Replace current command with history command
replaceCurrentCommand(with: newCommand)
}
private func replaceCurrentCommand(with newCommand: String) {
let currentLength = textView.string.count
let commandLength = currentLength - promptPosition
if commandLength > 0 {
let range = NSRange(location: promptPosition, length: commandLength)
textView.textStorage?.replaceCharacters(in: range, with: newCommand)
} else {
textView.insertText(newCommand, replacementRange: NSRange(location: promptPosition, length: 0))
}
// Move cursor to end
textView.setSelectedRange(NSRange(location: textView.string.count, length: 0))
}
func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString: String?) -> Bool {
// Don't allow editing before the prompt
if range.location < promptPosition {
@@ -402,6 +479,11 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines)
// Reset history navigation
historyIndex = -1
currentInput = ""
historyBuffer = [] // Reload on next navigation
// Check for aliases - match the first word
var actualCommand = trimmed
let components = trimmed.split(separator: " ", maxSplits: 1)