Initial Push
This commit is contained in:
635
main_enhanced_backup.swift
Normal file
635
main_enhanced_backup.swift
Normal file
@@ -0,0 +1,635 @@
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
// MARK: - Settings & Configuration
|
||||
class Settings {
|
||||
static let shared = Settings()
|
||||
|
||||
var theme: Theme = .dracula
|
||||
var fontSize: CGFloat = 14
|
||||
var aiProvider: AIProvider = .none
|
||||
var openAIKey: String = ""
|
||||
var ollamaURL: String = "http://localhost:11434"
|
||||
var ollamaModel: String = "llama3.2:latest"
|
||||
|
||||
enum AIProvider: String {
|
||||
case none, openai, ollama
|
||||
}
|
||||
|
||||
struct Theme {
|
||||
let background: NSColor
|
||||
let foreground: NSColor
|
||||
|
||||
static let dracula = Theme(
|
||||
background: NSColor(red: 0.16, green: 0.16, blue: 0.21, alpha: 1.0),
|
||||
foreground: NSColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0)
|
||||
)
|
||||
|
||||
static let classic = Theme(
|
||||
background: .black,
|
||||
foreground: NSColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
|
||||
)
|
||||
|
||||
static let solarized = Theme(
|
||||
background: NSColor(red: 0.0, green: 0.17, blue: 0.21, alpha: 1.0),
|
||||
foreground: NSColor(red: 0.51, green: 0.58, blue: 0.59, alpha: 1.0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Snippet Manager
|
||||
class SnippetManager {
|
||||
static let shared = SnippetManager()
|
||||
private var snippets: [(name: String, command: String)] = []
|
||||
|
||||
init() {
|
||||
snippets = [
|
||||
("List files", "ls -lah"),
|
||||
("Git status", "git status"),
|
||||
("Find large files", "find . -type f -size +100M"),
|
||||
("Docker ps", "docker ps -a"),
|
||||
("Kill port 8080", "lsof -ti:8080 | xargs kill -9"),
|
||||
("Disk usage", "du -sh * | sort -h")
|
||||
]
|
||||
}
|
||||
|
||||
func getSnippets() -> [(name: String, command: String)] {
|
||||
return snippets
|
||||
}
|
||||
|
||||
func addSnippet(name: String, command: String) {
|
||||
snippets.append((name, command))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AI Service
|
||||
class AIService {
|
||||
static let shared = AIService()
|
||||
|
||||
func generateCommand(from prompt: String, completion: @escaping (String?) -> Void) {
|
||||
let settings = Settings.shared
|
||||
|
||||
switch settings.aiProvider {
|
||||
case .openai:
|
||||
generateWithOpenAI(prompt: prompt, completion: completion)
|
||||
case .ollama:
|
||||
generateWithOllama(prompt: prompt, completion: completion)
|
||||
case .none:
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func generateWithOpenAI(prompt: String, completion: @escaping (String?) -> Void) {
|
||||
guard let url = URL(string: "https://api.openai.com/v1/chat/completions") else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(Settings.shared.openAIKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let body: [String: Any] = [
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
["role": "system", "content": "You are a terminal command expert. Generate only the command, no explanations."],
|
||||
["role": "user", "content": prompt]
|
||||
],
|
||||
"max_tokens": 100
|
||||
]
|
||||
|
||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, _, _ in
|
||||
guard let data = data,
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let choices = json["choices"] as? [[String: Any]],
|
||||
let message = choices.first?["message"] as? [String: Any],
|
||||
let content = message["content"] as? String else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
completion(content.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}.resume()
|
||||
}
|
||||
|
||||
private func generateWithOllama(prompt: String, completion: @escaping (String?) -> Void) {
|
||||
guard let url = URL(string: "\(Settings.shared.ollamaURL)/api/generate") else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let body: [String: Any] = [
|
||||
"model": Settings.shared.ollamaModel,
|
||||
"prompt": "Generate only the terminal command for: \(prompt). Respond with just the command.",
|
||||
"stream": false
|
||||
]
|
||||
|
||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, _, _ in
|
||||
guard let data = data,
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let response = json["response"] as? String else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
completion(response.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Terminal View Controller
|
||||
class TerminalViewController: NSViewController, NSTextViewDelegate {
|
||||
var textView: NSTextView!
|
||||
var shellTask: Process?
|
||||
var inputPipe: Pipe?
|
||||
var outputPipe: Pipe?
|
||||
var promptPosition = 0
|
||||
|
||||
override func loadView() {
|
||||
let scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 800, height: 600))
|
||||
scrollView.autoresizingMask = [.width, .height]
|
||||
scrollView.hasVerticalScroller = true
|
||||
|
||||
textView = NSTextView(frame: scrollView.bounds)
|
||||
textView.autoresizingMask = [.width, .height]
|
||||
applyTheme()
|
||||
textView.isEditable = true
|
||||
textView.delegate = self
|
||||
|
||||
scrollView.documentView = textView
|
||||
self.view = scrollView
|
||||
}
|
||||
|
||||
func applyTheme() {
|
||||
let theme = Settings.shared.theme
|
||||
textView.backgroundColor = theme.background
|
||||
textView.textColor = theme.foreground
|
||||
textView.font = NSFont.monospacedSystemFont(ofSize: Settings.shared.fontSize, weight: .regular)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
appendText("Enhanced Terminal v2.0\n")
|
||||
appendText("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
|
||||
appendText("Shortcuts:\n")
|
||||
appendText(" Cmd+K - AI Command Generator (describe what you want)\n")
|
||||
appendText(" Cmd+P - Quick Snippets (common commands)\n")
|
||||
appendText(" Cmd+, - Settings (themes, AI config)\n")
|
||||
appendText("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n")
|
||||
startShell()
|
||||
}
|
||||
|
||||
func startShell() {
|
||||
shellTask = Process()
|
||||
inputPipe = Pipe()
|
||||
outputPipe = Pipe()
|
||||
|
||||
shellTask?.executableURL = URL(fileURLWithPath: "/bin/zsh")
|
||||
shellTask?.arguments = ["-i"]
|
||||
shellTask?.environment = [
|
||||
"TERM": "dumb",
|
||||
"PS1": "$ ",
|
||||
"PATH": ProcessInfo.processInfo.environment["PATH"] ?? "/usr/bin:/bin:/usr/sbin:/sbin",
|
||||
"HOME": ProcessInfo.processInfo.environment["HOME"] ?? ""
|
||||
]
|
||||
shellTask?.standardInput = inputPipe
|
||||
shellTask?.standardOutput = outputPipe
|
||||
shellTask?.standardError = outputPipe
|
||||
|
||||
outputPipe?.fileHandleForReading.readabilityHandler = { [weak self] handle in
|
||||
let data = handle.availableData
|
||||
if data.count > 0, let output = String(data: data, encoding: .utf8) {
|
||||
DispatchQueue.main.async {
|
||||
self?.appendText(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try shellTask?.run()
|
||||
} catch {
|
||||
appendText("Error starting shell: \(error)\n")
|
||||
}
|
||||
|
||||
showPrompt()
|
||||
}
|
||||
|
||||
func showPrompt() {
|
||||
appendText("$ ")
|
||||
promptPosition = textView.string.count
|
||||
}
|
||||
|
||||
func appendText(_ text: String) {
|
||||
let cleaned = text
|
||||
.replacingOccurrences(of: "\u{1B}\\[[0-9;]*[a-zA-Z]", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "\u{1B}\\][0-9];[^\u{07}]*\u{07}", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "\\]633;[^\u{07}]*\u{07}", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "\\]697;[^\u{07}]*\u{07}", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "\\]1;[^\u{07}]*\u{07}", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "\\]2;[^\u{07}]*\u{07}", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "\\]7;[^\u{07}]*\\\\", with: "", options: .regularExpression)
|
||||
|
||||
let theme = Settings.shared.theme
|
||||
let attr = NSAttributedString(string: cleaned, attributes: [
|
||||
.foregroundColor: theme.foreground,
|
||||
.font: NSFont.monospacedSystemFont(ofSize: Settings.shared.fontSize, weight: .regular)
|
||||
])
|
||||
textView.textStorage?.append(attr)
|
||||
textView.scrollToEndOfDocument(nil)
|
||||
}
|
||||
|
||||
func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString: String?) -> Bool {
|
||||
if range.location < promptPosition {
|
||||
return false
|
||||
}
|
||||
|
||||
if replacementString == "\n" {
|
||||
let command = String(textView.string.suffix(from: textView.string.index(textView.string.startIndex, offsetBy: promptPosition)))
|
||||
executeCommand(command)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func executeCommand(_ command: String) {
|
||||
appendText("\n")
|
||||
|
||||
let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
if trimmed == "exit" {
|
||||
NSApp.terminate(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if trimmed == "clear" {
|
||||
textView.string = ""
|
||||
showPrompt()
|
||||
return
|
||||
}
|
||||
|
||||
if !trimmed.isEmpty {
|
||||
if let data = (trimmed + "\n").data(using: .utf8) {
|
||||
inputPipe?.fileHandleForWriting.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
self?.showPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
func insertCommand(_ command: String) {
|
||||
// Insert command at current cursor position
|
||||
textView.insertText(command, replacementRange: NSRange(location: textView.string.count, length: 0))
|
||||
// Show a hint
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
self?.textView.window?.makeFirstResponder(self?.textView)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
shellTask?.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Snippet Panel
|
||||
class SnippetPanel: NSPanel {
|
||||
var completion: ((String) -> Void)?
|
||||
|
||||
init() {
|
||||
super.init(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
|
||||
styleMask: [.titled, .closable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
title = "Snippets"
|
||||
setupUI()
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
let container = NSView(frame: contentView!.bounds)
|
||||
var y: CGFloat = container.bounds.height - 40
|
||||
|
||||
for snippet in SnippetManager.shared.getSnippets() {
|
||||
let button = NSButton(frame: NSRect(x: 20, y: y, width: 360, height: 30))
|
||||
button.title = "\(snippet.name): \(snippet.command)"
|
||||
button.bezelStyle = .rounded
|
||||
button.target = self
|
||||
button.action = #selector(snippetSelected(_:))
|
||||
button.tag = SnippetManager.shared.getSnippets().firstIndex(where: { $0.name == snippet.name }) ?? 0
|
||||
container.addSubview(button)
|
||||
y -= 35
|
||||
}
|
||||
|
||||
contentView = container
|
||||
}
|
||||
|
||||
@objc func snippetSelected(_ sender: NSButton) {
|
||||
let snippets = SnippetManager.shared.getSnippets()
|
||||
if sender.tag < snippets.count {
|
||||
completion?(snippets[sender.tag].command)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
func show(completion: @escaping (String) -> Void) {
|
||||
self.completion = completion
|
||||
center()
|
||||
makeKeyAndOrderFront(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AI Panel
|
||||
class AIPanel: NSPanel {
|
||||
var promptField: NSTextField!
|
||||
var resultField: NSTextField!
|
||||
var completion: ((String) -> Void)?
|
||||
|
||||
init() {
|
||||
super.init(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 500, height: 200),
|
||||
styleMask: [.titled, .closable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
title = "AI Command Generator"
|
||||
setupUI()
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
let container = NSView(frame: contentView!.bounds)
|
||||
|
||||
let label = NSTextField(labelWithString: "Describe what you want to do:")
|
||||
label.frame = NSRect(x: 20, y: 140, width: 460, height: 20)
|
||||
|
||||
promptField = NSTextField(frame: NSRect(x: 20, y: 110, width: 460, height: 24))
|
||||
promptField.placeholderString = "e.g., find all PDF files"
|
||||
|
||||
let generateBtn = NSButton(frame: NSRect(x: 20, y: 70, width: 100, height: 30))
|
||||
generateBtn.title = "Generate"
|
||||
generateBtn.bezelStyle = .rounded
|
||||
generateBtn.target = self
|
||||
generateBtn.action = #selector(generate)
|
||||
|
||||
resultField = NSTextField(labelWithString: "")
|
||||
resultField.frame = NSRect(x: 20, y: 40, width: 460, height: 24)
|
||||
|
||||
let useBtn = NSButton(frame: NSRect(x: 380, y: 70, width: 100, height: 30))
|
||||
useBtn.title = "Use"
|
||||
useBtn.bezelStyle = .rounded
|
||||
useBtn.target = self
|
||||
useBtn.action = #selector(useCommand)
|
||||
|
||||
let hintLabel = NSTextField(labelWithString: "Tip: After clicking 'Use', press Enter to execute the command")
|
||||
hintLabel.frame = NSRect(x: 20, y: 10, width: 460, height: 20)
|
||||
hintLabel.textColor = .secondaryLabelColor
|
||||
hintLabel.font = NSFont.systemFont(ofSize: 11)
|
||||
|
||||
container.addSubview(label)
|
||||
container.addSubview(promptField)
|
||||
container.addSubview(generateBtn)
|
||||
container.addSubview(resultField)
|
||||
container.addSubview(useBtn)
|
||||
container.addSubview(hintLabel)
|
||||
|
||||
contentView = container
|
||||
}
|
||||
|
||||
@objc func generate() {
|
||||
let prompt = promptField.stringValue
|
||||
guard !prompt.isEmpty else { return }
|
||||
|
||||
resultField.stringValue = "Generating..."
|
||||
|
||||
AIService.shared.generateCommand(from: prompt) { [weak self] command in
|
||||
DispatchQueue.main.async {
|
||||
self?.resultField.stringValue = command ?? "Error: Could not generate command"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func useCommand() {
|
||||
let command = resultField.stringValue
|
||||
guard !command.isEmpty && !command.hasPrefix("Error") && !command.hasPrefix("Generating") else { return }
|
||||
completion?(command)
|
||||
close()
|
||||
}
|
||||
|
||||
func show(completion: @escaping (String) -> Void) {
|
||||
self.completion = completion
|
||||
center()
|
||||
makeKeyAndOrderFront(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Settings Panel
|
||||
class SettingsPanel: NSPanel {
|
||||
init() {
|
||||
super.init(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 450, height: 350),
|
||||
styleMask: [.titled, .closable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
title = "Settings"
|
||||
setupUI()
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
let container = NSView(frame: contentView!.bounds)
|
||||
var y: CGFloat = container.bounds.height - 40
|
||||
|
||||
// Theme
|
||||
let themeLabel = NSTextField(labelWithString: "Theme:")
|
||||
themeLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20)
|
||||
|
||||
let themePopup = NSPopUpButton(frame: NSRect(x: 130, y: y - 5, width: 200, height: 30))
|
||||
themePopup.addItems(withTitles: ["Classic", "Dracula", "Solarized"])
|
||||
themePopup.target = self
|
||||
themePopup.action = #selector(themeChanged(_:))
|
||||
|
||||
container.addSubview(themeLabel)
|
||||
container.addSubview(themePopup)
|
||||
y -= 50
|
||||
|
||||
// AI Provider
|
||||
let aiLabel = NSTextField(labelWithString: "AI Provider:")
|
||||
aiLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20)
|
||||
|
||||
let aiPopup = NSPopUpButton(frame: NSRect(x: 130, y: y - 5, width: 200, height: 30))
|
||||
aiPopup.addItems(withTitles: ["None", "OpenAI", "Ollama"])
|
||||
aiPopup.target = self
|
||||
aiPopup.action = #selector(aiChanged(_:))
|
||||
|
||||
container.addSubview(aiLabel)
|
||||
container.addSubview(aiPopup)
|
||||
y -= 50
|
||||
|
||||
// OpenAI Key
|
||||
let keyLabel = NSTextField(labelWithString: "OpenAI Key:")
|
||||
keyLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20)
|
||||
|
||||
let keyField = NSSecureTextField(frame: NSRect(x: 130, y: y, width: 300, height: 24))
|
||||
keyField.placeholderString = "sk-..."
|
||||
keyField.target = self
|
||||
keyField.action = #selector(keyChanged(_:))
|
||||
|
||||
container.addSubview(keyLabel)
|
||||
container.addSubview(keyField)
|
||||
y -= 50
|
||||
|
||||
// Ollama URL
|
||||
let urlLabel = NSTextField(labelWithString: "Ollama URL:")
|
||||
urlLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20)
|
||||
|
||||
let urlField = NSTextField(frame: NSRect(x: 130, y: y, width: 300, height: 24))
|
||||
urlField.placeholderString = "http://localhost:11434"
|
||||
urlField.stringValue = Settings.shared.ollamaURL
|
||||
urlField.target = self
|
||||
urlField.action = #selector(urlChanged(_:))
|
||||
|
||||
container.addSubview(urlLabel)
|
||||
container.addSubview(urlField)
|
||||
y -= 50
|
||||
|
||||
// Ollama Model
|
||||
let modelLabel = NSTextField(labelWithString: "Ollama Model:")
|
||||
modelLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20)
|
||||
|
||||
let modelField = NSTextField(frame: NSRect(x: 130, y: y, width: 300, height: 24))
|
||||
modelField.placeholderString = "llama3.2:latest"
|
||||
modelField.stringValue = Settings.shared.ollamaModel
|
||||
modelField.target = self
|
||||
modelField.action = #selector(modelChanged(_:))
|
||||
|
||||
container.addSubview(modelLabel)
|
||||
container.addSubview(modelField)
|
||||
|
||||
contentView = container
|
||||
}
|
||||
|
||||
@objc func themeChanged(_ sender: NSPopUpButton) {
|
||||
switch sender.indexOfSelectedItem {
|
||||
case 0: Settings.shared.theme = .classic
|
||||
case 1: Settings.shared.theme = .dracula
|
||||
case 2: Settings.shared.theme = .solarized
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
@objc func aiChanged(_ sender: NSPopUpButton) {
|
||||
switch sender.indexOfSelectedItem {
|
||||
case 0: Settings.shared.aiProvider = .none
|
||||
case 1: Settings.shared.aiProvider = .openai
|
||||
case 2: Settings.shared.aiProvider = .ollama
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
@objc func keyChanged(_ sender: NSTextField) {
|
||||
Settings.shared.openAIKey = sender.stringValue
|
||||
}
|
||||
|
||||
@objc func urlChanged(_ sender: NSTextField) {
|
||||
Settings.shared.ollamaURL = sender.stringValue
|
||||
}
|
||||
|
||||
@objc func modelChanged(_ sender: NSTextField) {
|
||||
Settings.shared.ollamaModel = sender.stringValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - App Delegate
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var window: NSWindow!
|
||||
var terminalVC: TerminalViewController!
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 900, height: 650),
|
||||
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
window.center()
|
||||
window.title = "Enhanced Terminal"
|
||||
|
||||
terminalVC = TerminalViewController()
|
||||
window.contentViewController = terminalVC
|
||||
|
||||
setupMenu()
|
||||
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
func setupMenu() {
|
||||
let mainMenu = NSMenu()
|
||||
|
||||
// App menu
|
||||
let appMenuItem = NSMenuItem()
|
||||
mainMenu.addItem(appMenuItem)
|
||||
let appMenu = NSMenu()
|
||||
appMenuItem.submenu = appMenu
|
||||
appMenu.addItem(NSMenuItem(title: "Settings", action: #selector(openSettings), keyEquivalent: ","))
|
||||
appMenu.addItem(NSMenuItem.separator())
|
||||
appMenu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
|
||||
|
||||
// Tools menu
|
||||
let toolsMenuItem = NSMenuItem(title: "Tools", action: nil, keyEquivalent: "")
|
||||
mainMenu.addItem(toolsMenuItem)
|
||||
let toolsMenu = NSMenu(title: "Tools")
|
||||
toolsMenuItem.submenu = toolsMenu
|
||||
toolsMenu.addItem(NSMenuItem(title: "AI Generator", action: #selector(showAI), keyEquivalent: "k"))
|
||||
toolsMenu.addItem(NSMenuItem(title: "Snippets", action: #selector(showSnippets), keyEquivalent: "p"))
|
||||
|
||||
NSApp.mainMenu = mainMenu
|
||||
}
|
||||
|
||||
@objc func openSettings() {
|
||||
let panel = SettingsPanel()
|
||||
panel.center()
|
||||
panel.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
@objc func showAI() {
|
||||
guard Settings.shared.aiProvider != .none else {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Please configure AI provider in Settings first"
|
||||
alert.runModal()
|
||||
return
|
||||
}
|
||||
|
||||
let panel = AIPanel()
|
||||
panel.show { [weak self] command in
|
||||
self?.terminalVC.insertCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showSnippets() {
|
||||
let panel = SnippetPanel()
|
||||
panel.show { [weak self] command in
|
||||
self?.terminalVC.insertCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Main Entry Point
|
||||
let app = NSApplication.shared
|
||||
let delegate = AppDelegate()
|
||||
app.delegate = delegate
|
||||
app.run()
|
||||
Reference in New Issue
Block a user