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:
2026-01-31 23:27:54 +00:00
parent 7edf5d7186
commit b9570620da
4 changed files with 113 additions and 4 deletions

View File

@@ -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?"