Add 5 new features: edit snippets, clear scrollback, category filter, find in terminal, startup commands
1. Edit Snippets: Edit button to modify existing snippets 2. Clear Scrollback: Cmd+Shift+K to clear all terminal output 3. Category Filter: Dropdown to filter snippets by category 4. Find in Terminal: Cmd+F to search terminal output (built-in) 5. Startup Commands: Configure commands to run when opening new tabs All features tested and working
This commit is contained in:
Binary file not shown.
@@ -141,6 +141,7 @@ class MainWindowController: NSWindowController {
|
|||||||
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: "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"))
|
||||||
|
|
||||||
// Edit menu
|
// Edit menu
|
||||||
let editMenuItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "")
|
let editMenuItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "")
|
||||||
@@ -152,6 +153,8 @@ class MainWindowController: NSWindowController {
|
|||||||
editMenu.addItem(NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"))
|
editMenu.addItem(NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"))
|
||||||
editMenu.addItem(NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"))
|
editMenu.addItem(NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"))
|
||||||
editMenu.addItem(NSMenuItem.separator())
|
editMenu.addItem(NSMenuItem.separator())
|
||||||
|
editMenu.addItem(NSMenuItem(title: "Find...", action: #selector(showFindPanel), keyEquivalent: "f"))
|
||||||
|
editMenu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
let searchHistoryItem = NSMenuItem(title: "Search History", action: #selector(searchHistory), keyEquivalent: "r")
|
let searchHistoryItem = NSMenuItem(title: "Search History", action: #selector(searchHistory), keyEquivalent: "r")
|
||||||
searchHistoryItem.keyEquivalentModifierMask = [.command]
|
searchHistoryItem.keyEquivalentModifierMask = [.command]
|
||||||
@@ -453,6 +456,28 @@ class MainWindowController: NSWindowController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func clearScrollback() {
|
||||||
|
if let session = tabManager.getCurrentSession() {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "Clear Scrollback?"
|
||||||
|
alert.informativeText = "This will clear all terminal output history."
|
||||||
|
alert.addButton(withTitle: "Clear")
|
||||||
|
alert.addButton(withTitle: "Cancel")
|
||||||
|
alert.alertStyle = .warning
|
||||||
|
|
||||||
|
if alert.runModal() == .alertFirstButtonReturn {
|
||||||
|
session.textView.string = ""
|
||||||
|
session.showPrompt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func showFindPanel() {
|
||||||
|
if let session = tabManager.getCurrentSession() {
|
||||||
|
session.textView.performTextFinderAction(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc func clearHistory() {
|
@objc func clearHistory() {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = "Clear Command History?"
|
alert.messageText = "Clear Command History?"
|
||||||
|
|||||||
163
Panels.swift
163
Panels.swift
@@ -136,6 +136,7 @@ class HistorySearchPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, N
|
|||||||
class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate {
|
class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate {
|
||||||
private var tableView: NSTableView!
|
private var tableView: NSTableView!
|
||||||
private var searchField: NSTextField!
|
private var searchField: NSTextField!
|
||||||
|
private var categoryPopup: NSPopUpButton!
|
||||||
private var filteredSnippets: [SnippetManager.Snippet] = []
|
private var filteredSnippets: [SnippetManager.Snippet] = []
|
||||||
private var completion: ((SnippetManager.Snippet?) -> Void)?
|
private var completion: ((SnippetManager.Snippet?) -> Void)?
|
||||||
|
|
||||||
@@ -157,13 +158,24 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
container.autoresizingMask = [.width, .height]
|
container.autoresizingMask = [.width, .height]
|
||||||
|
|
||||||
// Search field
|
// Search field
|
||||||
searchField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 42, width: container.bounds.width - 260, height: 24))
|
searchField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 42, width: container.bounds.width - 400, height: 24))
|
||||||
searchField.placeholderString = "Search snippets..."
|
searchField.placeholderString = "Search snippets..."
|
||||||
searchField.autoresizingMask = [.width, .minYMargin]
|
searchField.autoresizingMask = [.width, .minYMargin]
|
||||||
searchField.delegate = self
|
searchField.delegate = self
|
||||||
|
|
||||||
|
// Category filter
|
||||||
|
let categoryLabel = NSTextField(labelWithString: "Category:")
|
||||||
|
categoryLabel.frame = NSRect(x: container.bounds.width - 370, y: container.bounds.height - 40, width: 70, height: 20)
|
||||||
|
categoryLabel.autoresizingMask = [.minXMargin, .minYMargin]
|
||||||
|
|
||||||
|
categoryPopup = NSPopUpButton(frame: NSRect(x: container.bounds.width - 295, y: container.bounds.height - 45, width: 120, height: 30))
|
||||||
|
categoryPopup.autoresizingMask = [.minXMargin, .minYMargin]
|
||||||
|
categoryPopup.target = self
|
||||||
|
categoryPopup.action = #selector(categoryChanged)
|
||||||
|
updateCategoryFilter()
|
||||||
|
|
||||||
// Add button
|
// Add button
|
||||||
let addButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: container.bounds.height - 45, width: 70, height: 30))
|
let addButton = NSButton(frame: NSRect(x: container.bounds.width - 165, y: container.bounds.height - 45, width: 50, height: 30))
|
||||||
addButton.title = "Add"
|
addButton.title = "Add"
|
||||||
addButton.bezelStyle = .rounded
|
addButton.bezelStyle = .rounded
|
||||||
addButton.target = self
|
addButton.target = self
|
||||||
@@ -171,19 +183,21 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
addButton.autoresizingMask = [.minXMargin, .minYMargin]
|
addButton.autoresizingMask = [.minXMargin, .minYMargin]
|
||||||
|
|
||||||
// Export button
|
// Export button
|
||||||
let exportButton = NSButton(frame: NSRect(x: container.bounds.width - 155, y: container.bounds.height - 45, width: 70, height: 30))
|
let exportButton = NSButton(frame: NSRect(x: container.bounds.width - 110, y: container.bounds.height - 45, width: 50, height: 30))
|
||||||
exportButton.title = "Export"
|
exportButton.title = "↓"
|
||||||
exportButton.bezelStyle = .rounded
|
exportButton.bezelStyle = .rounded
|
||||||
exportButton.target = self
|
exportButton.target = self
|
||||||
exportButton.action = #selector(exportSnippets)
|
exportButton.action = #selector(exportSnippets)
|
||||||
|
exportButton.toolTip = "Export snippets"
|
||||||
exportButton.autoresizingMask = [.minXMargin, .minYMargin]
|
exportButton.autoresizingMask = [.minXMargin, .minYMargin]
|
||||||
|
|
||||||
// Import button
|
// Import button
|
||||||
let importButton = NSButton(frame: NSRect(x: container.bounds.width - 80, y: container.bounds.height - 45, width: 70, height: 30))
|
let importButton = NSButton(frame: NSRect(x: container.bounds.width - 55, y: container.bounds.height - 45, width: 50, height: 30))
|
||||||
importButton.title = "Import"
|
importButton.title = "↑"
|
||||||
importButton.bezelStyle = .rounded
|
importButton.bezelStyle = .rounded
|
||||||
importButton.target = self
|
importButton.target = self
|
||||||
importButton.action = #selector(importSnippets)
|
importButton.action = #selector(importSnippets)
|
||||||
|
importButton.toolTip = "Import snippets"
|
||||||
importButton.autoresizingMask = [.minXMargin, .minYMargin]
|
importButton.autoresizingMask = [.minXMargin, .minYMargin]
|
||||||
|
|
||||||
// Table view
|
// Table view
|
||||||
@@ -235,14 +249,21 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
useButton.action = #selector(selectSnippet)
|
useButton.action = #selector(selectSnippet)
|
||||||
useButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
useButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
||||||
|
|
||||||
let deleteButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 10, width: 100, height: 30))
|
let editButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 10, width: 100, height: 30))
|
||||||
|
editButton.title = "Edit"
|
||||||
|
editButton.bezelStyle = .rounded
|
||||||
|
editButton.target = self
|
||||||
|
editButton.action = #selector(editSnippet)
|
||||||
|
editButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
||||||
|
|
||||||
|
let deleteButton = NSButton(frame: NSRect(x: container.bounds.width - 340, y: 10, width: 100, height: 30))
|
||||||
deleteButton.title = "Delete"
|
deleteButton.title = "Delete"
|
||||||
deleteButton.bezelStyle = .rounded
|
deleteButton.bezelStyle = .rounded
|
||||||
deleteButton.target = self
|
deleteButton.target = self
|
||||||
deleteButton.action = #selector(deleteSnippet)
|
deleteButton.action = #selector(deleteSnippet)
|
||||||
deleteButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
deleteButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
||||||
|
|
||||||
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 340, y: 10, width: 100, height: 30))
|
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 450, y: 10, width: 100, height: 30))
|
||||||
cancelButton.title = "Cancel"
|
cancelButton.title = "Cancel"
|
||||||
cancelButton.bezelStyle = .rounded
|
cancelButton.bezelStyle = .rounded
|
||||||
cancelButton.target = self
|
cancelButton.target = self
|
||||||
@@ -250,11 +271,14 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
||||||
|
|
||||||
container.addSubview(searchField)
|
container.addSubview(searchField)
|
||||||
|
container.addSubview(categoryLabel)
|
||||||
|
container.addSubview(categoryPopup)
|
||||||
container.addSubview(addButton)
|
container.addSubview(addButton)
|
||||||
container.addSubview(exportButton)
|
container.addSubview(exportButton)
|
||||||
container.addSubview(importButton)
|
container.addSubview(importButton)
|
||||||
container.addSubview(scrollView)
|
container.addSubview(scrollView)
|
||||||
container.addSubview(useButton)
|
container.addSubview(useButton)
|
||||||
|
container.addSubview(editButton)
|
||||||
container.addSubview(deleteButton)
|
container.addSubview(deleteButton)
|
||||||
container.addSubview(cancelButton)
|
container.addSubview(cancelButton)
|
||||||
|
|
||||||
@@ -272,13 +296,48 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlTextDidChange(_ obj: Notification) {
|
private func updateCategoryFilter() {
|
||||||
|
categoryPopup.removeAllItems()
|
||||||
|
categoryPopup.addItem(withTitle: "All Categories")
|
||||||
|
|
||||||
|
let categories = Set(SnippetManager.shared.getAllSnippets().map { $0.category }).sorted()
|
||||||
|
categoryPopup.addItems(withTitles: categories)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func categoryChanged() {
|
||||||
|
filterSnippets()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func filterSnippets() {
|
||||||
let query = searchField.stringValue
|
let query = searchField.stringValue
|
||||||
filteredSnippets = SnippetManager.shared.searchSnippets(query)
|
let selectedCategory = categoryPopup.titleOfSelectedItem ?? "All Categories"
|
||||||
|
|
||||||
|
var snippets = SnippetManager.shared.getAllSnippets()
|
||||||
|
|
||||||
|
// Filter by category
|
||||||
|
if selectedCategory != "All Categories" {
|
||||||
|
snippets = snippets.filter { $0.category == selectedCategory }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by search query
|
||||||
|
if !query.isEmpty {
|
||||||
|
snippets = snippets.filter {
|
||||||
|
$0.name.localizedCaseInsensitiveContains(query) ||
|
||||||
|
$0.command.localizedCaseInsensitiveContains(query) ||
|
||||||
|
$0.category.localizedCaseInsensitiveContains(query) ||
|
||||||
|
$0.tags.contains { $0.localizedCaseInsensitiveContains(query) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredSnippets = snippets
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func controlTextDidChange(_ obj: Notification) {
|
||||||
|
filterSnippets()
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func addSnippet() {
|
@objc private func addSnippet() {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = "Add New Snippet"
|
alert.messageText = "Add New Snippet"
|
||||||
@@ -317,6 +376,7 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
|
updateCategoryFilter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +387,62 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func editSnippet() {
|
||||||
|
let row = tableView.selectedRow
|
||||||
|
guard row >= 0 && row < filteredSnippets.count else { return }
|
||||||
|
|
||||||
|
let snippet = filteredSnippets[row]
|
||||||
|
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "Edit Snippet"
|
||||||
|
alert.informativeText = "Update snippet details:"
|
||||||
|
|
||||||
|
let nameField = NSTextField(frame: NSRect(x: 0, y: 90, width: 300, height: 24))
|
||||||
|
nameField.stringValue = snippet.name
|
||||||
|
nameField.placeholderString = "Name"
|
||||||
|
|
||||||
|
let commandField = NSTextField(frame: NSRect(x: 0, y: 60, width: 300, height: 24))
|
||||||
|
commandField.stringValue = snippet.command
|
||||||
|
commandField.placeholderString = "Command"
|
||||||
|
|
||||||
|
let categoryField = NSTextField(frame: NSRect(x: 0, y: 30, width: 300, height: 24))
|
||||||
|
categoryField.stringValue = snippet.category
|
||||||
|
categoryField.placeholderString = "Category"
|
||||||
|
|
||||||
|
let descField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
|
||||||
|
descField.stringValue = snippet.description
|
||||||
|
descField.placeholderString = "Description"
|
||||||
|
|
||||||
|
let view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 120))
|
||||||
|
view.addSubview(nameField)
|
||||||
|
view.addSubview(commandField)
|
||||||
|
view.addSubview(categoryField)
|
||||||
|
view.addSubview(descField)
|
||||||
|
|
||||||
|
alert.accessoryView = view
|
||||||
|
alert.addButton(withTitle: "Save")
|
||||||
|
alert.addButton(withTitle: "Cancel")
|
||||||
|
|
||||||
|
if alert.runModal() == .alertFirstButtonReturn {
|
||||||
|
// Remove old snippet
|
||||||
|
SnippetManager.shared.removeSnippet(snippet.id)
|
||||||
|
|
||||||
|
// Add updated snippet
|
||||||
|
let updatedSnippet = SnippetManager.Snippet(
|
||||||
|
name: nameField.stringValue.isEmpty ? snippet.name : nameField.stringValue,
|
||||||
|
command: commandField.stringValue.isEmpty ? snippet.command : commandField.stringValue,
|
||||||
|
description: descField.stringValue,
|
||||||
|
category: categoryField.stringValue.isEmpty ? snippet.category : categoryField.stringValue
|
||||||
|
)
|
||||||
|
SnippetManager.shared.addSnippet(updatedSnippet)
|
||||||
|
|
||||||
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||||
|
tableView.reloadData()
|
||||||
|
updateEmptyState()
|
||||||
|
updateCategoryFilter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func deleteSnippet() {
|
@objc private func deleteSnippet() {
|
||||||
let row = tableView.selectedRow
|
let row = tableView.selectedRow
|
||||||
guard row >= 0 && row < filteredSnippets.count else { return }
|
guard row >= 0 && row < filteredSnippets.count else { return }
|
||||||
@@ -344,6 +460,7 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
|
updateCategoryFilter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +526,7 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
|
updateCategoryFilter()
|
||||||
|
|
||||||
let success = NSAlert()
|
let success = NSAlert()
|
||||||
success.messageText = "Import Successful"
|
success.messageText = "Import Successful"
|
||||||
@@ -441,6 +559,7 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
|||||||
self.completion = completion
|
self.completion = completion
|
||||||
center()
|
center()
|
||||||
searchField.stringValue = ""
|
searchField.stringValue = ""
|
||||||
|
updateCategoryFilter()
|
||||||
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
@@ -650,7 +769,7 @@ class AIGeneratorPanel: NSPanel, NSTextFieldDelegate {
|
|||||||
class SettingsWindowController: NSWindowController {
|
class SettingsWindowController: NSWindowController {
|
||||||
convenience init() {
|
convenience init() {
|
||||||
let window = NSWindow(
|
let window = NSWindow(
|
||||||
contentRect: NSRect(x: 0, y: 0, width: 500, height: 450),
|
contentRect: NSRect(x: 0, y: 0, width: 500, height: 550),
|
||||||
styleMask: [.titled, .closable],
|
styleMask: [.titled, .closable],
|
||||||
backing: .buffered,
|
backing: .buffered,
|
||||||
defer: false
|
defer: false
|
||||||
@@ -773,8 +892,23 @@ class SettingsWindowController: NSWindowController {
|
|||||||
|
|
||||||
yPos -= 50
|
yPos -= 50
|
||||||
|
|
||||||
|
// Startup commands
|
||||||
|
let startupLabel = NSTextField(labelWithString: "Startup Commands:")
|
||||||
|
startupLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
||||||
|
|
||||||
|
let startupField = NSTextField(frame: NSRect(x: 150, y: yPos, width: 320, height: 24))
|
||||||
|
startupField.placeholderString = "Commands to run on new tab (comma separated)"
|
||||||
|
startupField.stringValue = TerminalSettings.shared.startupCommands.joined(separator: ", ")
|
||||||
|
startupField.target = self
|
||||||
|
startupField.action = #selector(startupCommandsChanged(_:))
|
||||||
|
|
||||||
|
container.addSubview(startupLabel)
|
||||||
|
container.addSubview(startupField)
|
||||||
|
|
||||||
|
yPos -= 50
|
||||||
|
|
||||||
// Info label
|
// Info label
|
||||||
let infoLabel = NSTextField(wrappingLabelWithString: "Note: For Ollama, make sure it's running locally. For OpenAI, you need an API key from platform.openai.com")
|
let infoLabel = NSTextField(wrappingLabelWithString: "Note: For Ollama, make sure it's running locally. For OpenAI, you need an API key from platform.openai.com\n\nStartup commands will run automatically when opening a new tab.")
|
||||||
infoLabel.frame = NSRect(x: 20, y: 20, width: 460, height: 40)
|
infoLabel.frame = NSRect(x: 20, y: 20, width: 460, height: 40)
|
||||||
infoLabel.textColor = .secondaryLabelColor
|
infoLabel.textColor = .secondaryLabelColor
|
||||||
infoLabel.font = NSFont.systemFont(ofSize: 11)
|
infoLabel.font = NSFont.systemFont(ofSize: 11)
|
||||||
@@ -825,4 +959,9 @@ class SettingsWindowController: NSWindowController {
|
|||||||
TerminalSettings.shared.ollamaModel = sender.stringValue
|
TerminalSettings.shared.ollamaModel = sender.stringValue
|
||||||
AIManager.shared.updateService()
|
AIManager.shared.updateService()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func startupCommandsChanged(_ sender: NSTextField) {
|
||||||
|
let commands = sender.stringValue.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
|
TerminalSettings.shared.startupCommands = commands.filter { !$0.isEmpty }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class TerminalSettings {
|
|||||||
var ollamaModel: String = "llama3.2:latest"
|
var ollamaModel: String = "llama3.2:latest"
|
||||||
var enableAutoSuggestions: Bool = true
|
var enableAutoSuggestions: Bool = true
|
||||||
var maxHistorySize: Int = 1000
|
var maxHistorySize: Int = 1000
|
||||||
|
var startupCommands: [String] = []
|
||||||
|
|
||||||
enum AIProvider: String, CaseIterable {
|
enum AIProvider: String, CaseIterable {
|
||||||
case none = "None"
|
case none = "None"
|
||||||
|
|||||||
@@ -179,6 +179,13 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
|
|||||||
// Wait a bit for shell to initialize, then show prompt
|
// Wait a bit for shell to initialize, then show prompt
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
self?.showPrompt()
|
self?.showPrompt()
|
||||||
|
|
||||||
|
// Run startup commands
|
||||||
|
for command in TerminalSettings.shared.startupCommands {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
self?.executeCommand(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
appendText("Error starting shell: \(error)\n")
|
appendText("Error starting shell: \(error)\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user