import Cocoa 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] textView.backgroundColor = .black textView.textColor = .green textView.font = NSFont.monospacedSystemFont(ofSize: 14, weight: .regular) textView.isEditable = true textView.delegate = self scrollView.documentView = textView self.view = scrollView } override func viewDidLoad() { super.viewDidLoad() appendText("Simple Terminal v1.0\n") appendText("Type commands and press Enter\n") appendText("Type 'exit' to quit\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) { // Strip ANSI escape codes for simpler display 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 attr = NSAttributedString(string: cleaned, attributes: [ .foregroundColor: NSColor.green, .font: NSFont.monospacedSystemFont(ofSize: 14, weight: .regular) ]) textView.textStorage?.append(attr) textView.scrollToEndOfDocument(nil) } func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString: String?) -> Bool { // Don't allow editing before prompt if range.location < promptPosition { return false } // Handle Enter key 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() } } deinit { shellTask?.terminate() } } class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! func applicationDidFinishLaunching(_ notification: Notification) { window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), styleMask: [.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false ) window.center() window.title = "Simple Terminal" let viewController = TerminalViewController() window.contentViewController = viewController window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } let app = NSApplication.shared let delegate = AppDelegate() app.delegate = delegate app.run()