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 tabManager = TabManager()
|
||||||
var tabBar: NSStackView!
|
var tabBar: NSStackView!
|
||||||
var contentView: NSView!
|
var contentView: NSView!
|
||||||
|
var splitView: NSSplitView?
|
||||||
|
var activePaneIndex: Int = 0
|
||||||
|
var panes: [TerminalSession] = []
|
||||||
var historyPanel: HistorySearchPanel!
|
var historyPanel: HistorySearchPanel!
|
||||||
var snippetPanel: SnippetPanel!
|
var snippetPanel: SnippetPanel!
|
||||||
var aiPanel: AIGeneratorPanel!
|
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: "New Tab", action: #selector(addNewTab), keyEquivalent: "t"))
|
||||||
fileMenu.addItem(NSMenuItem(title: "Close Tab", action: #selector(closeCurrentTab), keyEquivalent: "w"))
|
fileMenu.addItem(NSMenuItem(title: "Close Tab", action: #selector(closeCurrentTab), keyEquivalent: "w"))
|
||||||
fileMenu.addItem(NSMenuItem.separator())
|
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 Screen", action: #selector(clearScreen), keyEquivalent: "k"))
|
||||||
fileMenu.addItem(NSMenuItem(title: "Clear Scrollback", action: #selector(clearScrollback), keyEquivalent: "K"))
|
fileMenu.addItem(NSMenuItem(title: "Clear Scrollback", action: #selector(clearScrollback), keyEquivalent: "K"))
|
||||||
|
|
||||||
@@ -500,6 +507,8 @@ class MainWindowController: NSWindowController {
|
|||||||
File:
|
File:
|
||||||
Cmd+T - New Tab
|
Cmd+T - New Tab
|
||||||
Cmd+W - Close Tab
|
Cmd+W - Close Tab
|
||||||
|
Cmd+D - Split Pane Vertically
|
||||||
|
Cmd+Shift+D - Split Pane Horizontally
|
||||||
Cmd+K - Clear Screen
|
Cmd+K - Clear Screen
|
||||||
Cmd+Shift+K - Clear Scrollback
|
Cmd+Shift+K - Clear Scrollback
|
||||||
|
|
||||||
@@ -531,6 +540,97 @@ class MainWindowController: NSWindowController {
|
|||||||
alert.runModal()
|
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() {
|
@objc func clearHistory() {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = "Clear Command History?"
|
alert.messageText = "Clear Command History?"
|
||||||
|
|||||||
@@ -402,11 +402,20 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
|
|||||||
|
|
||||||
let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
// Check for aliases
|
// Check for aliases - match the first word
|
||||||
var actualCommand = trimmed
|
var actualCommand = trimmed
|
||||||
if let aliasCommand = TerminalSettings.shared.commandAliases[trimmed] {
|
let components = trimmed.split(separator: " ", maxSplits: 1)
|
||||||
actualCommand = aliasCommand
|
if let firstWord = components.first {
|
||||||
appendText("(alias: \(aliasCommand))\n")
|
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
|
// Track directory changes
|
||||||
|
|||||||
Reference in New Issue
Block a user