Neon-Vision-Editor/Neon Vision Editor/EditorViewModel.swift

167 lines
5.1 KiB
Swift
Raw Normal View History

2025-09-25 09:00:22 +00:00
import SwiftUI
import Combine
2025-09-25 09:00:22 +00:00
import UniformTypeIdentifiers
struct TabData: Identifiable {
let id = UUID()
var name: String
var content: String
var language: String
var fileURL: URL?
}
@MainActor
2025-09-25 09:00:22 +00:00
class EditorViewModel: ObservableObject {
@Published var tabs: [TabData] = []
@Published var selectedTabID: UUID?
2025-09-25 09:00:22 +00:00
@Published var showSidebar: Bool = true
@Published var isBrainDumpMode: Bool = false
@Published var showingRename: Bool = false
@Published var renameText: String = ""
2026-01-17 11:11:26 +00:00
@Published var isLineWrapEnabled: Bool = true
var selectedTab: TabData? {
get { tabs.first(where: { $0.id == selectedTabID }) }
set { selectedTabID = newValue?.id }
}
private let languageMap: [String: String] = [
"swift": "swift",
"py": "python",
"js": "javascript",
"html": "html",
"css": "css",
"c": "c",
"cpp": "cpp",
"h": "c",
"cs": "csharp",
"json": "json",
"md": "markdown",
"sh": "bash",
"bash": "bash",
"zsh": "zsh"
]
init() {
addNewTab()
}
func addNewTab() {
let newTab = TabData(name: "Untitled \(tabs.count + 1)", content: "", language: "swift", fileURL: nil)
2025-09-25 09:00:22 +00:00
tabs.append(newTab)
selectedTabID = newTab.id
2025-09-25 09:00:22 +00:00
}
func renameTab(tab: TabData, newName: String) {
if let index = tabs.firstIndex(where: { $0.id == tab.id }) {
tabs[index].name = newName
2025-09-25 09:00:22 +00:00
}
}
func updateTabContent(tab: TabData, content: String) {
if let index = tabs.firstIndex(where: { $0.id == tab.id }) {
tabs[index].content = content
}
}
func updateTabLanguage(tab: TabData, language: String) {
if let index = tabs.firstIndex(where: { $0.id == tab.id }) {
tabs[index].language = language
}
}
func closeTab(tab: TabData) {
tabs.removeAll { $0.id == tab.id }
if tabs.isEmpty {
addNewTab()
} else if selectedTabID == tab.id {
selectedTabID = tabs.first?.id
}
}
func saveFile(tab: TabData) {
guard let index = tabs.firstIndex(where: { $0.id == tab.id }) else { return }
if let url = tabs[index].fileURL {
2025-09-25 09:00:22 +00:00
do {
try tabs[index].content.write(to: url, atomically: true, encoding: .utf8)
2025-09-25 09:00:22 +00:00
} catch {
print("Error saving file: \(error)")
}
} else {
saveFileAs(tab: tab)
}
}
func saveFileAs(tab: TabData) {
guard let index = tabs.firstIndex(where: { $0.id == tab.id }) else { return }
let panel = NSSavePanel()
panel.nameFieldStringValue = tabs[index].name
2026-01-25 13:06:31 +00:00
let mdType = UTType(filenameExtension: "md") ?? .plainText
panel.allowedContentTypes = [
.text,
.swiftSource,
.pythonScript,
.javaScript,
.html,
.css,
.cSource,
.json,
mdType,
(UTType(filenameExtension: "cs") ?? .text)
]
2026-01-17 11:11:26 +00:00
if panel.runModal() == .OK, let url = panel.url {
do {
try tabs[index].content.write(to: url, atomically: true, encoding: .utf8)
tabs[index].fileURL = url
tabs[index].name = url.lastPathComponent
} catch {
print("Error saving file: \(error)")
2025-09-25 09:00:22 +00:00
}
}
}
func openFile() {
let panel = NSOpenPanel()
// Allow opening any file type, including hidden dotfiles like .zshrc
panel.allowedContentTypes = []
panel.allowsOtherFileTypes = true
panel.allowsMultipleSelection = false
panel.canChooseDirectories = false
panel.showsHiddenFiles = true
2026-01-17 11:11:26 +00:00
if panel.runModal() == .OK, let url = panel.url {
do {
let content = try String(contentsOf: url, encoding: .utf8)
let newTab = TabData(name: url.lastPathComponent,
content: content,
language: languageMap[url.pathExtension.lowercased()] ?? "swift",
fileURL: url)
tabs.append(newTab)
selectedTabID = newTab.id
} catch {
print("Error opening file: \(error)")
}
}
}
func openFile(url: URL) {
do {
let content = try String(contentsOf: url, encoding: .utf8)
let newTab = TabData(name: url.lastPathComponent,
content: content,
language: languageMap[url.pathExtension.lowercased()] ?? "swift",
fileURL: url)
tabs.append(newTab)
selectedTabID = newTab.id
} catch {
print("Error opening file: \(error)")
}
}
func wordCount(for text: String) -> Int {
text.components(separatedBy: .whitespacesAndNewlines)
.filter { !$0.isEmpty }.count
}
}