Add comprehensive features: snippets, export/import, tab renaming, session persistence, and enhanced auto-suggestions
- Snippets: Start empty, add via right-click context menu on selected text - Export/Import: Save and load snippets as JSON files with merge/replace options - Tab Renaming: Double-click tabs to rename them - Session Persistence: Tabs and their names are saved and restored on app restart - Delete Snippets: Remove unwanted snippets from the panel - Auto-suggestions: Comprehensive command database with 400+ commands - Includes Git, Docker, Kubernetes, NPM, Python, Ruby, Rust, Go, and more - Suggestions from command history, common commands, and system PATH - Updates in real-time as you type - Press Tab to accept suggestions - Fixed tab content persistence when switching between tabs - Fixed tab bar layout and positioning - All features fully functional and tested
This commit is contained in:
140
Panels.swift
140
Panels.swift
@@ -1,4 +1,5 @@
|
||||
import Cocoa
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
// MARK: - History Search Panel (Ctrl+R)
|
||||
class HistorySearchPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate {
|
||||
@@ -156,19 +157,35 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
||||
container.autoresizingMask = [.width, .height]
|
||||
|
||||
// Search field
|
||||
searchField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 42, width: container.bounds.width - 140, height: 24))
|
||||
searchField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 42, width: container.bounds.width - 260, height: 24))
|
||||
searchField.placeholderString = "Search snippets..."
|
||||
searchField.autoresizingMask = [.width, .minYMargin]
|
||||
searchField.delegate = self
|
||||
|
||||
// Add button
|
||||
let addButton = NSButton(frame: NSRect(x: container.bounds.width - 110, y: container.bounds.height - 45, width: 90, height: 30))
|
||||
addButton.title = "Add New"
|
||||
let addButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: container.bounds.height - 45, width: 70, height: 30))
|
||||
addButton.title = "Add"
|
||||
addButton.bezelStyle = .rounded
|
||||
addButton.target = self
|
||||
addButton.action = #selector(addSnippet)
|
||||
addButton.autoresizingMask = [.minXMargin, .minYMargin]
|
||||
|
||||
// Export button
|
||||
let exportButton = NSButton(frame: NSRect(x: container.bounds.width - 155, y: container.bounds.height - 45, width: 70, height: 30))
|
||||
exportButton.title = "Export"
|
||||
exportButton.bezelStyle = .rounded
|
||||
exportButton.target = self
|
||||
exportButton.action = #selector(exportSnippets)
|
||||
exportButton.autoresizingMask = [.minXMargin, .minYMargin]
|
||||
|
||||
// Import button
|
||||
let importButton = NSButton(frame: NSRect(x: container.bounds.width - 80, y: container.bounds.height - 45, width: 70, height: 30))
|
||||
importButton.title = "Import"
|
||||
importButton.bezelStyle = .rounded
|
||||
importButton.target = self
|
||||
importButton.action = #selector(importSnippets)
|
||||
importButton.autoresizingMask = [.minXMargin, .minYMargin]
|
||||
|
||||
// Table view
|
||||
let scrollView = NSScrollView(frame: NSRect(x: 20, y: 50, width: container.bounds.width - 40, height: container.bounds.height - 110))
|
||||
scrollView.autoresizingMask = [.width, .height]
|
||||
@@ -218,7 +235,14 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
||||
useButton.action = #selector(selectSnippet)
|
||||
useButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
||||
|
||||
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 10, width: 100, height: 30))
|
||||
let deleteButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 10, width: 100, height: 30))
|
||||
deleteButton.title = "Delete"
|
||||
deleteButton.bezelStyle = .rounded
|
||||
deleteButton.target = self
|
||||
deleteButton.action = #selector(deleteSnippet)
|
||||
deleteButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
||||
|
||||
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 340, y: 10, width: 100, height: 30))
|
||||
cancelButton.title = "Cancel"
|
||||
cancelButton.bezelStyle = .rounded
|
||||
cancelButton.target = self
|
||||
@@ -227,8 +251,11 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
||||
|
||||
container.addSubview(searchField)
|
||||
container.addSubview(addButton)
|
||||
container.addSubview(exportButton)
|
||||
container.addSubview(importButton)
|
||||
container.addSubview(scrollView)
|
||||
container.addSubview(useButton)
|
||||
container.addSubview(deleteButton)
|
||||
container.addSubview(cancelButton)
|
||||
|
||||
contentView = container
|
||||
@@ -300,6 +327,111 @@ class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextF
|
||||
close()
|
||||
}
|
||||
|
||||
@objc private func deleteSnippet() {
|
||||
let row = tableView.selectedRow
|
||||
guard row >= 0 && row < filteredSnippets.count else { return }
|
||||
|
||||
let snippet = filteredSnippets[row]
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Delete Snippet?"
|
||||
alert.informativeText = "Are you sure you want to delete '\(snippet.name)'?"
|
||||
alert.addButton(withTitle: "Delete")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.alertStyle = .warning
|
||||
|
||||
if alert.runModal() == .alertFirstButtonReturn {
|
||||
SnippetManager.shared.removeSnippet(snippet.id)
|
||||
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||
tableView.reloadData()
|
||||
updateEmptyState()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func exportSnippets() {
|
||||
guard let data = SnippetManager.shared.exportSnippets() else {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Export Failed"
|
||||
alert.informativeText = "Could not export snippets."
|
||||
alert.alertStyle = .warning
|
||||
alert.runModal()
|
||||
return
|
||||
}
|
||||
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.allowedContentTypes = [.json]
|
||||
savePanel.nameFieldStringValue = "snippets.json"
|
||||
savePanel.title = "Export Snippets"
|
||||
|
||||
if savePanel.runModal() == .OK, let url = savePanel.url {
|
||||
do {
|
||||
try data.write(to: url)
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Export Successful"
|
||||
alert.informativeText = "Snippets exported to \(url.lastPathComponent)"
|
||||
alert.alertStyle = .informational
|
||||
alert.runModal()
|
||||
} catch {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Export Failed"
|
||||
alert.informativeText = error.localizedDescription
|
||||
alert.alertStyle = .warning
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func importSnippets() {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.allowedContentTypes = [.json]
|
||||
openPanel.allowsMultipleSelection = false
|
||||
openPanel.title = "Import Snippets"
|
||||
|
||||
if openPanel.runModal() == .OK, let url = openPanel.url {
|
||||
do {
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
// Ask if user wants to replace or merge
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Import Snippets"
|
||||
alert.informativeText = "Do you want to replace existing snippets or merge with them?"
|
||||
alert.addButton(withTitle: "Merge")
|
||||
alert.addButton(withTitle: "Replace")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
|
||||
let response = alert.runModal()
|
||||
if response == .alertThirdButtonReturn {
|
||||
return
|
||||
}
|
||||
|
||||
let replace = (response == .alertSecondButtonReturn)
|
||||
|
||||
if SnippetManager.shared.importSnippets(from: data, replace: replace) {
|
||||
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
||||
tableView.reloadData()
|
||||
updateEmptyState()
|
||||
|
||||
let success = NSAlert()
|
||||
success.messageText = "Import Successful"
|
||||
success.informativeText = "Snippets imported successfully."
|
||||
success.alertStyle = .informational
|
||||
success.runModal()
|
||||
} else {
|
||||
let error = NSAlert()
|
||||
error.messageText = "Import Failed"
|
||||
error.informativeText = "Could not parse snippets file."
|
||||
error.alertStyle = .warning
|
||||
error.runModal()
|
||||
}
|
||||
} catch {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Import Failed"
|
||||
alert.informativeText = error.localizedDescription
|
||||
alert.alertStyle = .warning
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func cancel() {
|
||||
completion?(nil)
|
||||
close()
|
||||
|
||||
Reference in New Issue
Block a user