import SwiftUI import AppKit import SwiftTerm /// Full terminal emulator view (SwiftTerm): cursor addressing, colors, /// scrollback, selection, and full-screen apps like vim/htop. /// Keyboard goes straight to the pty (Tab completion, arrows, Ctrl+C, Esc); /// pty output is fed in via PTYSession.onData. Dracula themed. struct SwiftTermView: NSViewRepresentable { @ObservedObject var term: PTYSession func makeCoordinator() -> Coordinator { Coordinator(term: term) } func makeNSView(context: Context) -> TerminalView { let view = TerminalView(frame: .zero, font: .monospacedSystemFont(ofSize: 13, weight: .regular)) view.terminalDelegate = context.coordinator view.nativeForegroundColor = Dracula.nsColor(for: nil) view.nativeBackgroundColor = Dracula.nsBackground let palette = Dracula.ansiPalette if palette.count == 16 { view.installColors(palette) } context.coordinator.view = view term.onData = { [weak view] data in let bytes = Array(data) DispatchQueue.main.async { view?.feed(byteArray: bytes[...]) } } // Sync current size once the view exists (later resizes via delegate). DispatchQueue.main.async { term.setWinsize(cols: view.terminal.cols, rows: view.terminal.rows) } return view } func updateNSView(_ view: TerminalView, context: Context) { context.coordinator.term = term } final class Coordinator: NSObject, TerminalViewDelegate { var term: PTYSession? weak var view: TerminalView? init(term: PTYSession) { self.term = term } func send(source: TerminalView, data: ArraySlice) { term?.sendBytes(Data(data)) } func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) { term?.setWinsize(cols: newCols, rows: newRows) } func setTerminalTitle(source: TerminalView, title: String) {} func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {} func scrolled(source: TerminalView, position: Double) {} func rangeChanged(source: TerminalView, startY: Int, endY: Int) {} } }