Add split panes feature and fix alias matching
- Split Panes: Cmd+D (vertical) and Cmd+Shift+D (horizontal) - Close panes with Cmd+W when multiple panes exist - Fixed alias matching to work with command arguments - Aliases now match first word only (e.g., 'gs status' works with gs=git) Split panes allow multiple terminal sessions side-by-side in the same tab
This commit is contained in:
Binary file not shown.
@@ -4,6 +4,9 @@ class MainWindowController: NSWindowController {
|
||||
var tabManager = TabManager()
|
||||
var tabBar: NSStackView!
|
||||
var contentView: NSView!
|
||||
var splitView: NSSplitView?
|
||||
var activePaneIndex: Int = 0
|
||||
var panes: [TerminalSession] = []
|
||||
var historyPanel: HistorySearchPanel!
|
||||
var snippetPanel: SnippetPanel!
|
||||
var aiPanel: AIGeneratorPanel!
|
||||
@@ -153,6 +156,10 @@ class MainWindowController: NSWindowController {
|
||||
fileMenu.addItem(NSMenuItem(title: "New Tab", action: #selector(addNewTab), keyEquivalent: "t"))
|
||||
fileMenu.addItem(NSMenuItem(title: "Close Tab", action: #selector(closeCurrentTab), keyEquivalent: "w"))
|
||||
fileMenu.addItem(NSMenuItem.separator())
|
||||
fileMenu.addItem(NSMenuItem(title: "Split Pane Vertically", action: #selector(splitPaneVertically), keyEquivalent: "d"))
|
||||
fileMenu.addItem(NSMenuItem(title: "Split Pane Horizontally", action: #selector(splitPaneHorizontally), keyEquivalent: "D"))
|
||||
fileMenu.addItem(NSMenuItem(title: "Close Pane", action: #selector(closePane), keyEquivalent: "w"))
|
||||
fileMenu.addItem(NSMenuItem.separator())
|
||||
fileMenu.addItem(NSMenuItem(title: "Clear Screen", action: #selector(clearScreen), keyEquivalent: "k"))
|
||||
fileMenu.addItem(NSMenuItem(title: "Clear Scrollback", action: #selector(clearScrollback), keyEquivalent: "K"))
|
||||
|
||||
@@ -500,6 +507,8 @@ class MainWindowController: NSWindowController {
|
||||
File:
|
||||
Cmd+T - New Tab
|
||||
Cmd+W - Close Tab
|
||||
Cmd+D - Split Pane Vertically
|
||||
Cmd+Shift+D - Split Pane Horizontally
|
||||
Cmd+K - Clear Screen
|
||||
Cmd+Shift+K - Clear Scrollback
|
||||
|
||||
@@ -531,6 +540,97 @@ class MainWindowController: NSWindowController {
|
||||
alert.runModal()
|
||||
}
|
||||
|
||||
@objc func splitPaneVertically() {
|
||||
guard let currentSession = tabManager.getCurrentSession() else { return }
|
||||
|
||||
// Create split view if it doesn't exist
|
||||
if splitView == nil {
|
||||
splitView = NSSplitView(frame: contentView.bounds)
|
||||
splitView?.isVertical = true
|
||||
splitView?.dividerStyle = .thin
|
||||
splitView?.autoresizingMask = [.width, .height]
|
||||
|
||||
// Move current session to split view
|
||||
currentSession.scrollView.removeFromSuperview()
|
||||
splitView?.addArrangedSubview(currentSession.scrollView)
|
||||
panes = [currentSession]
|
||||
}
|
||||
|
||||
// Create new pane
|
||||
let newSession = TerminalSession(title: "Pane \(panes.count + 1)")
|
||||
newSession.setupTextView(frame: CGRect(x: 0, y: 0, width: contentView.bounds.width / 2, height: contentView.bounds.height))
|
||||
newSession.startShell()
|
||||
|
||||
splitView?.addArrangedSubview(newSession.scrollView)
|
||||
panes.append(newSession)
|
||||
activePaneIndex = panes.count - 1
|
||||
|
||||
// Add split view to content if not already there
|
||||
if splitView?.superview == nil {
|
||||
contentView.subviews.forEach { $0.removeFromSuperview() }
|
||||
contentView.addSubview(splitView!)
|
||||
}
|
||||
|
||||
newSession.textView.window?.makeFirstResponder(newSession.textView)
|
||||
}
|
||||
|
||||
@objc func splitPaneHorizontally() {
|
||||
guard let currentSession = tabManager.getCurrentSession() else { return }
|
||||
|
||||
// Create split view if it doesn't exist
|
||||
if splitView == nil {
|
||||
splitView = NSSplitView(frame: contentView.bounds)
|
||||
splitView?.isVertical = false
|
||||
splitView?.dividerStyle = .thin
|
||||
splitView?.autoresizingMask = [.width, .height]
|
||||
|
||||
// Move current session to split view
|
||||
currentSession.scrollView.removeFromSuperview()
|
||||
splitView?.addArrangedSubview(currentSession.scrollView)
|
||||
panes = [currentSession]
|
||||
}
|
||||
|
||||
// Create new pane
|
||||
let newSession = TerminalSession(title: "Pane \(panes.count + 1)")
|
||||
newSession.setupTextView(frame: CGRect(x: 0, y: 0, width: contentView.bounds.width, height: contentView.bounds.height / 2))
|
||||
newSession.startShell()
|
||||
|
||||
splitView?.addArrangedSubview(newSession.scrollView)
|
||||
panes.append(newSession)
|
||||
activePaneIndex = panes.count - 1
|
||||
|
||||
// Add split view to content if not already there
|
||||
if splitView?.superview == nil {
|
||||
contentView.subviews.forEach { $0.removeFromSuperview() }
|
||||
contentView.addSubview(splitView!)
|
||||
}
|
||||
|
||||
newSession.textView.window?.makeFirstResponder(newSession.textView)
|
||||
}
|
||||
|
||||
@objc func closePane() {
|
||||
guard panes.count > 1, activePaneIndex < panes.count else { return }
|
||||
|
||||
let pane = panes[activePaneIndex]
|
||||
pane.cleanup()
|
||||
pane.scrollView.removeFromSuperview()
|
||||
panes.remove(at: activePaneIndex)
|
||||
|
||||
if panes.count == 1 {
|
||||
// Only one pane left, remove split view
|
||||
splitView?.removeFromSuperview()
|
||||
splitView = nil
|
||||
|
||||
let remainingSession = panes[0]
|
||||
remainingSession.scrollView.removeFromSuperview()
|
||||
remainingSession.scrollView.frame = contentView.bounds
|
||||
contentView.addSubview(remainingSession.scrollView)
|
||||
panes = []
|
||||
}
|
||||
|
||||
activePaneIndex = min(activePaneIndex, panes.count - 1)
|
||||
}
|
||||
|
||||
@objc func clearHistory() {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Clear Command History?"
|
||||
|
||||
@@ -402,11 +402,20 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
|
||||
|
||||
let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
// Check for aliases
|
||||
// Check for aliases - match the first word
|
||||
var actualCommand = trimmed
|
||||
if let aliasCommand = TerminalSettings.shared.commandAliases[trimmed] {
|
||||
actualCommand = aliasCommand
|
||||
appendText("(alias: \(aliasCommand))\n")
|
||||
let components = trimmed.split(separator: " ", maxSplits: 1)
|
||||
if let firstWord = components.first {
|
||||
let firstWordStr = String(firstWord)
|
||||
if let aliasCommand = TerminalSettings.shared.commandAliases[firstWordStr] {
|
||||
// Replace the alias with the actual command, keeping any arguments
|
||||
if components.count > 1 {
|
||||
actualCommand = aliasCommand + " " + String(components[1])
|
||||
} else {
|
||||
actualCommand = aliasCommand
|
||||
}
|
||||
appendText("(alias: \(firstWordStr) → \(aliasCommand))\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Track directory changes
|
||||
|
||||
Reference in New Issue
Block a user