diff --git a/Neon Vision Editor.xcodeproj/project.pbxproj b/Neon Vision Editor.xcodeproj/project.pbxproj index 38a42a1..e2f8554 100644 --- a/Neon Vision Editor.xcodeproj/project.pbxproj +++ b/Neon Vision Editor.xcodeproj/project.pbxproj @@ -7,25 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - 983EEA302E5F22DA00E19094 /* SwiftData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98EAE6592E5F1E890050E579 /* SwiftData.framework */; }; - 983EEA312E5F22DA00E19094 /* SwiftData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 98EAE6592E5F1E890050E579 /* SwiftData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9825C2092ED77CF3007D8698 /* SwiftData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9825C2082ED77CF3007D8698 /* SwiftData.framework */; }; /* End PBXBuildFile section */ -/* Begin PBXCopyFilesBuildPhase section */ - 983EEA322E5F22DA00E19094 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - dstPath = ""; - dstSubfolder = Frameworks; - files = ( - 983EEA312E5F22DA00E19094 /* SwiftData.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ + 9825C2082ED77CF3007D8698 /* SwiftData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftData.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.1.sdk/System/Library/Frameworks/SwiftData.framework; sourceTree = DEVELOPER_DIR; }; 98EAE6332E5F15E80050E579 /* Neon Vision Editor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Neon Vision Editor.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 98EAE6592E5F1E890050E579 /* SwiftData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftData.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.0.sdk/System/Library/Frameworks/SwiftData.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -40,7 +27,7 @@ 98EAE6302E5F15E80050E579 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - 983EEA302E5F22DA00E19094 /* SwiftData.framework in Frameworks */, + 9825C2092ED77CF3007D8698 /* SwiftData.framework in Frameworks */, ); }; /* End PBXFrameworksBuildPhase section */ @@ -66,7 +53,7 @@ 98EAE6532E5F175B0050E579 /* Frameworks */ = { isa = PBXGroup; children = ( - 98EAE6592E5F1E890050E579 /* SwiftData.framework */, + 9825C2082ED77CF3007D8698 /* SwiftData.framework */, ); name = Frameworks; sourceTree = ""; @@ -81,7 +68,6 @@ 98EAE62F2E5F15E80050E579 /* Sources */, 98EAE6302E5F15E80050E579 /* Frameworks */, 98EAE6312E5F15E80050E579 /* Resources */, - 983EEA322E5F22DA00E19094 /* Embed Frameworks */, ); buildRules = ( ); @@ -101,7 +87,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 2600; - LastUpgradeCheck = 2600; + LastUpgradeCheck = 2610; TargetAttributes = { 98EAE6322E5F15E80050E579 = { CreatedOnToolsVersion = 26.0; @@ -178,6 +164,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = CS727NF72U; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -201,6 +188,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -240,6 +228,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = CS727NF72U; ENABLE_NS_ASSERTIONS = NO; @@ -256,6 +245,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; @@ -265,15 +255,29 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + AUTOMATION_APPLE_EVENTS = NO; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = CS727NF72U; ENABLE_APP_SANDBOX = YES; + ENABLE_FILE_ACCESS_DOWNLOADS_FOLDER = readwrite; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readonly; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + ENABLE_USER_SELECTED_FILES = readwrite; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Neon Vision Editor"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; @@ -297,6 +301,12 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; SDKROOT = auto; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; @@ -316,15 +326,29 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + AUTOMATION_APPLE_EVENTS = NO; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = CS727NF72U; ENABLE_APP_SANDBOX = YES; + ENABLE_FILE_ACCESS_DOWNLOADS_FOLDER = readwrite; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readonly; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + ENABLE_USER_SELECTED_FILES = readwrite; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Neon Vision Editor"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; @@ -348,6 +372,12 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; SDKROOT = auto; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; diff --git a/Neon Vision Editor/AI-Modell.swift b/Neon Vision Editor/AI-Modell.swift new file mode 100644 index 0000000..ca8e1e3 --- /dev/null +++ b/Neon Vision Editor/AI-Modell.swift @@ -0,0 +1,28 @@ +import Foundation + +struct GrokAPIClient { + let apiKey: String + private let baseURL = URL(string: "https://api.x.ai/v1")! + + func generateText(prompt: String, maxTokens: Int = 100) async throws -> String { + var request = URLRequest(url: baseURL.appendingPathComponent("generate")) + request.httpMethod = "POST" + request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let body: [String: Any] = [ + "model": "grok-4", + "prompt": prompt, + "max_tokens": maxTokens + ] + request.httpBody = try JSONSerialization.data(withJSONObject: body) + + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { + throw URLError(.badServerResponse) + } + + let json = try JSONDecoder().decode([String: String].self, from: data) + return json["text"] ?? "" + } +} diff --git a/Neon Vision Editor/Assets.xcassets/AccentColor.colorset/Contents.json b/Neon Vision Editor/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index 2ec281a..0000000 --- a/Neon Vision Editor/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0.694", - "green" : "0.302", - "blue" : "0.831", - "alpha" : "1.0" - } - } - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/Contents.json b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 4029a0d..0000000 --- a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "filename" : "NeonVisionEditor-16.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "filename" : "NeonVisionEditor-32 1.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "filename" : "NeonVisionEditor-32.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "filename" : "NeonVisionEditor-64.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "filename" : "NeonVisionEditor-128.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "filename" : "NeonVisionEditor-256.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "filename" : "NeonVisionEditor-256 1.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "filename" : "NeonVisionEditor-512 1.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "filename" : "NeonVisionEditor-512.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "filename" : "NeonVision Editor.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-128.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-128.png deleted file mode 100644 index 67b1649..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-128.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-16.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-16.png deleted file mode 100644 index 91edd24..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-16.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-256 1.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-256 1.png deleted file mode 100644 index dd84f5f..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-256 1.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-256.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-256.png deleted file mode 100644 index dd84f5f..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-256.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-32 1.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-32 1.png deleted file mode 100644 index 12b35e9..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-32 1.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-32.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-32.png deleted file mode 100644 index 12b35e9..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-32.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-512 1.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-512 1.png deleted file mode 100644 index b374088..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-512 1.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-512.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-512.png deleted file mode 100644 index b374088..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-512.png and /dev/null differ diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-64.png b/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-64.png deleted file mode 100644 index 86e514c..0000000 Binary files a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVisionEditor-64.png and /dev/null differ diff --git a/Neon Vision Editor/ContentView.swift b/Neon Vision Editor/ContentView.swift index a99a16a..e7f3d30 100644 --- a/Neon Vision Editor/ContentView.swift +++ b/Neon Vision Editor/ContentView.swift @@ -1,481 +1,575 @@ +// FIXES APPLIED: Consistent rename and content persistence for tab creation and language updates import SwiftUI -import SwiftData -import UniformTypeIdentifiers -#if os(macOS) import AppKit -#elseif os(iOS) -import UIKit -#endif + +// Extension to calculate string width +extension String { + func width(usingFont font: NSFont) -> CGFloat { + let attributes = [NSAttributedString.Key.font: font] + let size = (self as NSString).size(withAttributes: attributes) + return size.width + } +} struct ContentView: View { - @Environment(\.modelContext) private var modelContext - @Query private var tabs: [Tab] - @State private var selectedTab: Tab? - @State private var showSidebar: Bool = true - @State private var selectedTOCItem: String? = nil - #if os(iOS) - @State private var showingDocumentPicker = false - #endif - - private let languages: [String] = ["Swift", "Python", "C", "C++", "Java", "HTML", "Markdown", "JSON", "Bash"] - private let languageMap: [String: String] = [ - "swift": "swift", "py": "python", "c": "c", "cpp": "cpp", "java": "java", - "html": "html", "htm": "html", "md": "markdown", "json": "json", "sh": "bash" - ] - + @EnvironmentObject private var viewModel: EditorViewModel + @Environment(\.colorScheme) private var colorScheme + @Environment(\.showGrokError) private var showGrokError + @Environment(\.grokErrorMessage) private var grokErrorMessage + @Environment(\.selectedAIModel) private var selectedAIModel + + @State private var singleContent: String = "" + @State private var singleLanguage: String = "swift" + var body: some View { NavigationSplitView { - List(tabs, selection: $selectedTab) { tab in - Text(tab.name) - .tag(tab) - .foregroundColor(.gray) - } - .listStyle(.sidebar) - .frame(minWidth: 200) - .background(Color(.windowBackgroundColor).opacity(0.85)) + sidebarView } detail: { - if let selectedTab = selectedTab { - CustomTextEditor( - text: Binding( - get: { selectedTab.content }, - set: { selectedTab.content = $0 } - ), - language: Binding( - get: { selectedTab.language }, - set: { selectedTab.language = $0 } - ), - isModified: Binding( - get: { selectedTab.isModified }, - set: { selectedTab.isModified = $0 } - ) - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(.textBackgroundColor).opacity(0.85)) - .toolbar { - ToolbarItemGroup { - Picker("Language", selection: Binding( - get: { selectedTab.language }, - set: { selectedTab.language = $0 } - )) { - ForEach(languages, id: \.self) { lang in - Text(lang).tag(lang.lowercased() as String) - } - } - .labelsHidden() - Button(action: { showSidebar.toggle() }) { - Image(systemName: "sidebar.left") - } - Button(action: { openFile() }) { - Image(systemName: "folder.badge.plus") - } - Button(action: { saveFile(for: selectedTab) }) { - Image(systemName: "floppydisk") - } - Button(action: { saveAsFile(for: selectedTab) }) { - Image(systemName: "square.and.arrow.down") - } - } + editorView + } + .frame(minWidth: 600, minHeight: 400) + .background(.ultraThinMaterial) + .overlay(.ultraThinMaterial.opacity(0.2)) // Fallback for liquidGlassEffect + .sheet(isPresented: $viewModel.showingRename) { + renameSheet + } + .alert("AI Error", isPresented: showGrokError) { + Button("OK") { } + } message: { + Text(grokErrorMessage.wrappedValue) + } + .navigationTitle("NeonVision Editor") + } + + @ViewBuilder + private var sidebarView: some View { + if viewModel.showSidebar && !viewModel.isBrainDumpMode { + SidebarView(content: (viewModel.selectedTab?.content ?? singleContent), + language: (viewModel.selectedTab?.language ?? singleLanguage)) + .frame(minWidth: 200, idealWidth: 250) + .background(.ultraThinMaterial) + .overlay(.ultraThinMaterial.opacity(0.2)) + .animation(.spring(), value: viewModel.showSidebar) + .safeAreaInset(edge: .bottom) { + Divider() } - .onChange(of: selectedTab.content) { _, _ in - selectedTab.isModified = true - } - .onChange(of: selectedTab.language) { _, _ in - selectedTab.isModified = true - } - .onAppear { - #if os(macOS) - if let window = NSApplication.shared.windows.first { - window.title = selectedTab.name - } - #endif - } - #if os(macOS) - .onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)) { _ in - if selectedTab.isModified { - promptToSave(for: selectedTab) - } - } - #elseif os(iOS) - .onDisappear { - if selectedTab.isModified { - promptToSave(for: selectedTab) - } - } - .sheet(isPresented: $showingDocumentPicker) { - DocumentPicker { url in - handleDocumentPickerSelection(url) - } - } - #endif - } else { - Text("Select a tab or create a new one") - .frame(maxWidth: .infinity, maxHeight: .infinity) - } } } - - func openFile() { - #if os(macOS) - let panel = NSOpenPanel() - panel.allowedContentTypes = [.text, .sourceCode, .swiftSource, .pythonScript, .html, .cSource, .shellScript, .json, UTType("public.markdown") ?? .text] - panel.allowsMultipleSelection = false - panel.canChooseDirectories = false - panel.canChooseFiles = true - - guard panel.runModal() == .OK, let url = panel.url else { return } - do { - let content = try String(contentsOf: url, encoding: .utf8) - let newTab = Tab( - name: url.lastPathComponent, - content: content, - language: languageMap[url.pathExtension.lowercased()] ?? "plaintext" + + @ViewBuilder + private var editorView: some View { + VStack(spacing: 0) { + tabViewContent + if !viewModel.isBrainDumpMode { + wordCountView + } + } + .toolbar { + ToolbarItem(placement: .primaryAction) { + Picker("Language", selection: Binding( + get: { + viewModel.selectedTab?.language ?? singleLanguage + }, + set: { newLang in + if let selectedID = viewModel.selectedTabID, let idx = viewModel.tabs.firstIndex(where: { $0.id == selectedID }) { + viewModel.tabs[idx].language = newLang + } else { + singleLanguage = newLang + } + } + )) { + ForEach(["swift", "python", "javascript", "html", "css", "c", "cpp", "json", "markdown"], id: \.self) { lang in + Text(lang.capitalized).tag(lang) + } + } + .frame(width: 150) + } + ToolbarItem(placement: .primaryAction) { + Picker("AI Model", selection: selectedAIModel) { + Text("Apple Intelligence").tag(AIModel.appleIntelligence) + Text("Grok").tag(AIModel.grok) + } + .frame(width: 150) + } + ToolbarItemGroup(placement: .automatic) { + Button(action: { viewModel.openFile() }) { + Image(systemName: "folder") + } + Button(action: { if let tab = viewModel.selectedTab { viewModel.saveFile(tab: tab) } }) { + Image(systemName: "square.and.arrow.down") + } + .disabled(viewModel.selectedTab == nil) + Button(action: { viewModel.showSidebar.toggle() }) { + Image(systemName: viewModel.showSidebar ? "sidebar.left" : "sidebar.right") + } + Button(action: { viewModel.isBrainDumpMode.toggle() }) { + Image(systemName: "note.text") + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) // Added to expand editorView + } + + @ViewBuilder + private var tabViewContent: some View { + VStack(spacing: 0) { + CustomTextEditor( + text: Binding( + get: { + if let selID = viewModel.selectedTabID, let tab = viewModel.tabs.first(where: { $0.id == selID }) { + return tab.content + } else { + return singleContent + } + }, + set: { newValue in + if let selID = viewModel.selectedTabID, let tab = viewModel.tabs.first(where: { $0.id == selID }) { + viewModel.updateTabContent(tab: tab, content: newValue) + } else { + singleContent = newValue + } + } + ), + language: viewModel.selectedTab?.language ?? singleLanguage, + colorScheme: colorScheme ) - modelContext.insert(newTab) - selectedTab = newTab - if let window = NSApplication.shared.windows.first { - window.title = newTab.name + .frame(maxWidth: viewModel.isBrainDumpMode ? 800 : .infinity) + .padding(viewModel.isBrainDumpMode ? .horizontal : [], 100) + } + } + + @ViewBuilder + private var wordCountView: some View { + HStack { + Spacer() + Text("Words: \(viewModel.wordCount(for: viewModel.selectedTab?.content ?? ""))") + .font(.system(size: 12)) + .foregroundColor(.secondary) + .padding(.bottom, 8) + .padding(.trailing, 16) + } + } + + @ViewBuilder + private var renameSheet: some View { + VStack { + Text("Rename Tab") + .font(.headline) + TextField("Name", text: $viewModel.renameText) + .textFieldStyle(.roundedBorder) + .padding() + HStack(spacing: 12) { + Button("Cancel") { + viewModel.showingRename = false + } + .buttonStyle(.bordered) + + Button("OK") { + // Ensure we have a selected tab; if not, select the first available tab + if viewModel.selectedTab == nil, let first = viewModel.tabs.first { + viewModel.selectedTabID = first.id + } + if let tab = viewModel.selectedTab { + if viewModel.selectedTabID != tab.id { + viewModel.selectedTabID = tab.id + } + viewModel.renameTab(tab: tab, newName: viewModel.renameText) + } + viewModel.showingRename = false + } + .buttonStyle(.borderedProminent) + .disabled(viewModel.renameText.isEmpty) } - } catch { - NSAlert(error: error).runModal() } - #elseif os(iOS) - showingDocumentPicker = true - #endif - } - - #if os(iOS) - func handleDocumentPickerSelection(_ url: URL) { - do { - let content = try String(contentsOf: url, encoding: .utf8) - let newTab = Tab( - name: url.lastPathComponent, - content: content, - language: languageMap[url.pathExtension.lowercased()] ?? "plaintext" - ) - modelContext.insert(newTab) - selectedTab = newTab - } catch { - let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true) - } - } - #endif - - func saveFile(for tab: Tab) { - #if os(macOS) - guard tab.isModified else { return } - let panel = NSSavePanel() - panel.allowedContentTypes = [.text, .sourceCode, .swiftSource, .pythonScript, .html, .cSource, .shellScript, .json, UTType("public.markdown") ?? .text] - panel.nameFieldStringValue = tab.name - - guard panel.runModal() == .OK, let url = panel.url else { return } - do { - try tab.content.write(to: url, atomically: true, encoding: .utf8) - tab.name = url.lastPathComponent - tab.isModified = false - if let window = NSApplication.shared.windows.first { - window.title = tab.name - } - } catch { - NSAlert(error: error).runModal() - } - #elseif os(iOS) - // iOS save logic (simplified, as iOS file handling is complex) - // Implement UIDocumentPickerViewController for export - #endif - } - - func saveAsFile(for tab: Tab) { - #if os(macOS) - let panel = NSSavePanel() - panel.allowedContentTypes = [.text, .sourceCode, .swiftSource, .pythonScript, .html, .cSource, .shellScript, .json, UTType("public.markdown") ?? .text] - panel.nameFieldStringValue = tab.name - - guard panel.runModal() == .OK, let url = panel.url else { return } - do { - try tab.content.write(to: url, atomically: true, encoding: .utf8) - tab.name = url.lastPathComponent - tab.isModified = false - if let window = NSApplication.shared.windows.first { - window.title = tab.name - } - } catch { - NSAlert(error: error).runModal() - } - #elseif os(iOS) - // iOS save-as logic (simplified) - #endif - } - - func promptToSave(for tab: Tab) { - guard tab.isModified else { return } - #if os(macOS) - let alert = NSAlert() - alert.messageText = "Do you want to save changes to \(tab.name)?" - alert.informativeText = "Your changes will be lost if you don't save them." - alert.addButton(withTitle: "Save") - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Don't Save") - switch alert.runModal() { - case .alertFirstButtonReturn: saveFile(for: tab) - case .alertSecondButtonReturn: break - default: tab.isModified = false - } - #elseif os(iOS) - let alert = UIAlertController(title: "Save Changes?", message: "Do you want to save changes to \(tab.name)?", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Save", style: .default) { _ in saveFile(for: tab) }) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - alert.addAction(UIAlertAction(title: "Don't Save", style: .destructive) { _ in tab.isModified = false }) - UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true) - #endif + .padding() + .frame(width: 320) + .background(.regularMaterial) + .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + .shadow(radius: 12) + .interactiveDismissDisabled(false) + .allowsHitTesting(true) } } -#if os(iOS) -struct DocumentPicker: UIViewControllerRepresentable { - let onSelect: (URL) -> Void - - func makeUIViewController(context: Context) -> UIDocumentPickerViewController { - let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.text, .sourceCode, .swiftSource, .pythonScript, .html, .cSource, .shellScript, .json, UTType("public.markdown") ?? .text]) - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, UIDocumentPickerDelegate { - let parent: DocumentPicker - - init(_ parent: DocumentPicker) { - self.parent = parent - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - if let url = urls.first { - parent.onSelect(url) - } - } - } -} -#endif - -struct CustomTextEditor: View { - @Binding var text: String - @Binding var language: String - @Binding var isModified: Bool - @Environment(\.colorScheme) private var colorScheme: SwiftUI.ColorScheme - +struct SidebarView: View { + let content: String + let language: String + @State private var selectedTOCItem: String? + var body: some View { - #if os(macOS) - CustomTextViewRepresentable( - text: $text, - language: language, - isModified: $isModified, - colorScheme: colorScheme - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(colorScheme == .dark ? Color.black.opacity(0.85) : Color.white.opacity(0.85)) - #elseif os(iOS) - CustomTextViewRepresentableiOS( - text: $text, - language: language, - isModified: $isModified, - colorScheme: colorScheme - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(colorScheme == .dark ? Color.black.opacity(0.85) : Color.white.opacity(0.85)) - #endif + List(generateTableOfContents(), id: \.self, selection: $selectedTOCItem) { item in + Text(item) + .font(.system(size: 13)) + .foregroundColor(.primary) + .padding(.vertical, 4) + .padding(.horizontal, 8) + .onTapGesture { + if let lineNumber = lineNumber(for: item) { + NotificationCenter.default.post(name: .moveCursorToLine, object: lineNumber) + } + } + } + .listStyle(.sidebar) + .frame(maxWidth: .infinity, alignment: .leading) + } + + func generateTableOfContents() -> [String] { + guard !content.isEmpty else { return ["No content available"] } + let lines = content.components(separatedBy: .newlines) + var toc: [String] = [] + + switch language { + case "swift": + toc = lines.enumerated().compactMap { index, line in + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasPrefix("func ") || trimmed.hasPrefix("struct ") || + trimmed.hasPrefix("class ") || trimmed.hasPrefix("enum ") { + return "\(trimmed) (Line \(index + 1))" + } + return nil + } + case "python": + toc = lines.enumerated().compactMap { index, line in + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasPrefix("def ") || trimmed.hasPrefix("class ") { + return "\(trimmed) (Line \(index + 1))" + } + return nil + } + case "javascript": + toc = lines.enumerated().compactMap { index, line in + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasPrefix("function ") || trimmed.hasPrefix("class ") { + return "\(trimmed) (Line \(index + 1))" + } + return nil + } + case "c", "cpp": + toc = lines.enumerated().compactMap { index, line in + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.contains("(") && !trimmed.contains(";") && (trimmed.hasPrefix("void ") || trimmed.hasPrefix("int ") || trimmed.hasPrefix("float ") || trimmed.hasPrefix("double ") || trimmed.hasPrefix("char ") || trimmed.contains("{")) { + return "\(trimmed) (Line \(index + 1))" + } + return nil + } + case "html", "css", "json", "markdown": + toc = lines.enumerated().compactMap { index, line in + let trimmed = line.trimmingCharacters(in: .whitespaces) + if !trimmed.isEmpty && (trimmed.hasPrefix("#") || trimmed.hasPrefix(" Int? { + let lines = content.components(separatedBy: .newlines) + return lines.firstIndex { $0.trimmingCharacters(in: .whitespaces) == item.components(separatedBy: " (Line").first } } } -#if os(macOS) -struct CustomTextViewRepresentable: NSViewRepresentable { +struct CustomTextEditor: NSViewRepresentable { @Binding var text: String let language: String - @Binding var isModified: Bool - let colorScheme: SwiftUI.ColorScheme - + let colorScheme: ColorScheme + func makeNSView(context: Context) -> NSScrollView { - let textView = NSTextView() - textView.isEditable = true - textView.isRichText = false - textView.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular) - textView.delegate = context.coordinator - textView.string = text - updateHighlighting(textView: textView) + // Use AppKit's factory to get a properly configured scroll view + text view + let scrollView = NSTextView.scrollableTextView() + let textView = scrollView.documentView as! NSTextView - let scrollView = NSScrollView() - scrollView.documentView = textView + // Configure text view + textView.isEditable = true + textView.isRulerVisible = false + textView.font = NSFont.monospacedSystemFont(ofSize: 14, weight: .regular) + textView.backgroundColor = .clear + textView.textContainerInset = NSSize(width: 12, height: 12) + textView.textColor = .labelColor + + // Plain text configuration and initial value + textView.isRichText = false + textView.usesRuler = false + textView.usesFindBar = true + textView.allowsUndo = true + textView.isAutomaticQuoteSubstitutionEnabled = false + textView.isAutomaticDataDetectionEnabled = false + textView.isAutomaticLinkDetectionEnabled = false + textView.string = self.text + + // Disable smart replacements/spell checking for code + textView.textContainer?.lineFragmentPadding = 0 + textView.isAutomaticTextReplacementEnabled = false + textView.isAutomaticSpellingCorrectionEnabled = false + + // Sizing behavior: allow vertical growth and wrap to width + textView.minSize = NSSize(width: 0, height: 0) + textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + textView.isVerticallyResizable = true + textView.isHorizontallyResizable = false + textView.autoresizingMask = [.width] + textView.postsFrameChangedNotifications = true + + if let container = textView.textContainer { + container.containerSize = NSSize(width: scrollView.contentSize.width, height: .greatestFiniteMagnitude) + container.widthTracksTextView = true + container.heightTracksTextView = false + } + + // Configure scroll view scrollView.hasVerticalScroller = true - scrollView.hasHorizontalScroller = true + scrollView.hasHorizontalScroller = false + scrollView.autohidesScrollers = true + scrollView.borderType = .noBorder + scrollView.backgroundColor = .clear + + // Keep container width in sync with scroll view size changes + scrollView.contentView.postsBoundsChangedNotifications = true + NotificationCenter.default.addObserver(context.coordinator, + selector: #selector(context.coordinator.scrollViewBoundsDidChange(_:)), + name: NSView.boundsDidChangeNotification, + object: scrollView.contentView) + + // Coordinator and notifications + textView.delegate = context.coordinator + NotificationCenter.default.addObserver(context.coordinator, selector: #selector(context.coordinator.updateTextContainerSize), name: NSView.frameDidChangeNotification, object: textView) + context.coordinator.textView = textView + + // Apply initial syntax highlighting + context.coordinator.applySyntaxHighlighting() + + DispatchQueue.main.async { + textView.window?.makeFirstResponder(textView) + } + return scrollView } - + func updateNSView(_ nsView: NSScrollView, context: Context) { if let textView = nsView.documentView as? NSTextView { - if textView.string != text { - textView.string = text - updateHighlighting(textView: textView) + // Only push SwiftUI -> AppKit when the source of truth changed + if textView.string != self.text { + textView.string = self.text + context.coordinator.applySyntaxHighlighting() } + // Do not write back here. Coordinator's textDidChange handles AppKit -> SwiftUI updates. + + if let container = textView.textContainer { + let width = nsView.contentSize.width + if container.containerSize.width != width { + container.containerSize = NSSize(width: width, height: .greatestFiniteMagnitude) + container.widthTracksTextView = true + } + } + textView.invalidateIntrinsicContentSize() + textView.layoutManager?.ensureLayout(for: textView.textContainer!) } } - + func makeCoordinator() -> Coordinator { Coordinator(self) } - + class Coordinator: NSObject, NSTextViewDelegate { - var parent: CustomTextViewRepresentable - - init(_ parent: CustomTextViewRepresentable) { + var parent: CustomTextEditor + weak var textView: NSTextView? + + init(_ parent: CustomTextEditor) { self.parent = parent + super.init() + NotificationCenter.default.addObserver(self, selector: #selector(moveToLine(_:)), name: .moveCursorToLine, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(streamSuggestion(_:)), name: .streamSuggestion, object: nil) } - + + @objc func moveToLine(_ notification: Notification) { + guard let lineNumber = notification.object as? Int, + let textView = textView, + !parent.text.isEmpty else { return } + + let lines = parent.text.components(separatedBy: .newlines) + guard lineNumber >= 0 && lineNumber < lines.count else { return } + + let lineStart = lines[0.. 0 ? 1 : 0) + textView.setSelectedRange(NSRange(location: lineStart, length: 0)) + textView.scrollRangeToVisible(NSRange(location: lineStart, length: 0)) + } + + @objc func streamSuggestion(_ notification: Notification) { + guard let stream = notification.object as? AsyncStream, + let textView = textView else { return } + + Task { + for await chunk in stream { + textView.textStorage?.append(NSAttributedString(string: chunk)) + textView.scrollToEndOfDocument(nil) + parent.text = textView.string + } + } + } + + @objc func updateTextContainerSize() { + if let tv = textView, let sv = tv.enclosingScrollView { + tv.textContainer?.containerSize = NSSize(width: sv.contentSize.width, height: .greatestFiniteMagnitude) + tv.textContainer?.widthTracksTextView = true + } + } + + @objc func scrollViewBoundsDidChange(_ notification: Notification) { + if let tv = textView, let sv = tv.enclosingScrollView { + tv.textContainer?.containerSize = NSSize(width: sv.contentSize.width, height: .greatestFiniteMagnitude) + tv.textContainer?.widthTracksTextView = true + tv.invalidateIntrinsicContentSize() + tv.layoutManager?.ensureLayout(for: tv.textContainer!) + } + } + func textDidChange(_ notification: Notification) { guard let textView = notification.object as? NSTextView else { return } parent.text = textView.string - parent.isModified = true - parent.updateHighlighting(textView: textView) + + if let container = textView.textContainer, let scrollView = textView.enclosingScrollView { + container.containerSize = NSSize(width: scrollView.contentSize.width, height: .greatestFiniteMagnitude) + container.widthTracksTextView = true + textView.invalidateIntrinsicContentSize() + textView.layoutManager?.ensureLayout(for: container) + } + + applySyntaxHighlighting() } - } - - private func updateHighlighting(textView: NSTextView) { - let attributedString = NSMutableAttributedString(string: textView.string) - let range = NSRange(location: 0, length: textView.string.utf16.count) - - // Base attributes - attributedString.addAttribute(.font, value: NSFont.monospacedSystemFont(ofSize: 12, weight: .regular), range: range) - attributedString.addAttribute(.foregroundColor, value: colorScheme == .dark ? NSColor.white : NSColor.black, range: range) - - // Vibrant Light highlighting - if language == "swift" { - // Keywords (pink: 0.983822 0 0.72776 1) - let keywords = ["func", "class", "struct", "let", "var"] - for keyword in keywords { - let regex = try? NSRegularExpression(pattern: "\\b\(keyword)\\b", options: []) - regex?.enumerateMatches(in: textView.string, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: NSColor(red: 0.983822, green: 0, blue: 0.72776, alpha: 1), range: matchRange) - attributedString.addAttribute(.font, value: NSFont.monospacedSystemFont(ofSize: 12, weight: .semibold), range: matchRange) + + func applySyntaxHighlighting() { + guard let textView = textView else { return } + let fullRange = NSRange(location: 0, length: (textView.string as NSString).length) + // Replace the line below with adaptive label color instead of removing attribute + textView.textStorage?.addAttribute(.foregroundColor, value: NSColor.labelColor, range: fullRange) + let colors = SyntaxColors.fromVibrantLightTheme(colorScheme: parent.colorScheme) + let patterns = getSyntaxPatterns(for: parent.language, colors: colors) + for (pattern, color) in patterns { + guard let regex = try? NSRegularExpression(pattern: pattern, options: [.anchorsMatchLines]) else { continue } + let matches = regex.matches(in: textView.string, range: fullRange) + for match in matches { + textView.textStorage?.addAttribute(.foregroundColor, value: NSColor(color), range: match.range) } } - - // Strings (green: 0 0.743633 0 1) - let stringRegex = try? NSRegularExpression(pattern: "\".*?\"", options: []) - stringRegex?.enumerateMatches(in: textView.string, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: NSColor(red: 0, green: 0.743633, blue: 0, alpha: 1), range: matchRange) - } - - // Comments (gray: 0.36526 0.421879 0.475154 1) - let commentRegex = try? NSRegularExpression(pattern: "//.*?\n|/\\*.*?\\*/", options: [.dotMatchesLineSeparators]) - commentRegex?.enumerateMatches(in: textView.string, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: NSColor(red: 0.36526, green: 0.421879, blue: 0.475154, alpha: 1), range: matchRange) - } - - // Numbers (blue: 0.11 0 0.81 1) - let numberRegex = try? NSRegularExpression(pattern: "\\b\\d+\\.?\\d*\\b", options: []) - numberRegex?.enumerateMatches(in: textView.string, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: NSColor(red: 0.11, green: 0, blue: 0.81, alpha: 1), range: matchRange) - } } - - textView.textStorage?.setAttributedString(attributedString) } } -#elseif os(iOS) -struct CustomTextViewRepresentableiOS: UIViewRepresentable { - @Binding var text: String - let language: String - @Binding var isModified: Bool - let colorScheme: SwiftUI.ColorScheme - func makeUIView(context: Context) -> UITextView { - let textView = UITextView() - textView.isEditable = true - textView.font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) - textView.delegate = context.coordinator - textView.text = text - updateHighlighting(textView: textView) - return textView - } - - func updateUIView(_ uiView: UITextView, context: Context) { - if uiView.text != text { - uiView.text = text - updateHighlighting(textView: uiView) - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, UITextViewDelegate { - var parent: CustomTextViewRepresentableiOS - - init(_ parent: CustomTextViewRepresentableiOS) { - self.parent = parent - } - - func textViewDidChange(_ textView: UITextView) { - parent.text = textView.text - parent.isModified = true - parent.updateHighlighting(textView: textView) - } - } - - private func updateHighlighting(textView: UITextView) { - let attributedString = NSMutableAttributedString(string: textView.text) - let range = NSRange(location: 0, length: textView.text.utf16.count) - - // Base attributes - attributedString.addAttribute(.font, value: UIFont.monospacedSystemFont(ofSize: 12, weight: .regular), range: range) - attributedString.addAttribute(.foregroundColor, value: colorScheme == .dark ? UIColor.white : UIColor.black, range: range) - - // Vibrant Light highlighting - if language == "swift" { - // Keywords (pink: 0.983822 0 0.72776 1) - let keywords = ["func", "class", "struct", "let", "var"] - for keyword in keywords { - let regex = try? NSRegularExpression(pattern: "\\b\(keyword)\\b", options: []) - regex?.enumerateMatches(in: textView.text, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: UIColor(red: 0.983822, green: 0, blue: 0.72776, alpha: 1), range: matchRange) - attributedString.addAttribute(.font, value: UIFont.monospacedSystemFont(ofSize: 12, weight: .semibold), range: matchRange) - } - } - - // Strings (green: 0 0.743633 0 1) - let stringRegex = try? NSRegularExpression(pattern: "\".*?\"", options: []) - stringRegex?.enumerateMatches(in: textView.text, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: UIColor(red: 0, green: 0.743633, blue: 0, alpha: 1), range: matchRange) - } - - // Comments (gray: 0.36526 0.421879 0.475154 1) - let commentRegex = try? NSRegularExpression(pattern: "//.*?\n|/\\*.*?\\*/", options: [.dotMatchesLineSeparators]) - commentRegex?.enumerateMatches(in: textView.text, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: UIColor(red: 0.36526, green: 0.421879, blue: 0.475154, alpha: 1), range: matchRange) - } - - // Numbers (blue: 0.11 0 0.81 1) - let numberRegex = try? NSRegularExpression(pattern: "\\b\\d+\\.?\\d*\\b", options: []) - numberRegex?.enumerateMatches(in: textView.text, options: [], range: range) { match, _, _ in - guard let matchRange = match?.range else { return } - attributedString.addAttribute(.foregroundColor, value: UIColor(red: 0.11, green: 0, blue: 0.81, alpha: 1), range: matchRange) - } - } - - textView.attributedText = attributedString +struct SyntaxColors { + let keyword: Color + let string: Color + let number: Color + let comment: Color + let attribute: Color + let variable: Color + let def: Color + let property: Color + let meta: Color + let tag: Color + let atom: Color + let builtin: Color + let type: Color + + static func fromVibrantLightTheme(colorScheme: ColorScheme) -> SyntaxColors { + let baseColors: [String: (light: Color, dark: Color)] = [ + "keyword": (light: Color(red: 251/255, green: 0/255, blue: 186/255), dark: Color(red: 251/255, green: 0/255, blue: 186/255)), + "string": (light: Color(red: 190/255, green: 0/255, blue: 255/255), dark: Color(red: 190/255, green: 0/255, blue: 255/255)), + "number": (light: Color(red: 28/255, green: 0/255, blue: 207/255), dark: Color(red: 28/255, green: 0/255, blue: 207/255)), + "comment": (light: Color(red: 93/255, green: 108/255, blue: 121/255), dark: Color(red: 150/255, green: 160/255, blue: 170/255)), + "attribute": (light: Color(red: 57/255, green: 0/255, blue: 255/255), dark: Color(red: 57/255, green: 0/255, blue: 255/255)), + "variable": (light: Color(red: 19/255, green: 0/255, blue: 255/255), dark: Color(red: 19/255, green: 0/255, blue: 255/255)), + "def": (light: Color(red: 29/255, green: 196/255, blue: 83/255), dark: Color(red: 29/255, green: 196/255, blue: 83/255)), + "property": (light: Color(red: 29/255, green: 196/255, blue: 83/255), dark: Color(red: 29/255, green: 0/255, blue: 160/255)), + "meta": (light: Color(red: 255/255, green: 16/255, blue: 0/255), dark: Color(red: 255/255, green: 16/255, blue: 0/255)), + "tag": (light: Color(red: 170/255, green: 0/255, blue: 160/255), dark: Color(red: 170/255, green: 0/255, blue: 160/255)), + "atom": (light: Color(red: 28/255, green: 0/255, blue: 207/255), dark: Color(red: 28/255, green: 0/255, blue: 207/255)), + "builtin": (light: Color(red: 255/255, green: 130/255, blue: 0/255), dark: Color(red: 255/255, green: 130/255, blue: 0/255)), + "type": (light: Color(red: 170/255, green: 0/255, blue: 160/255), dark: Color(red: 170/255, green: 0/255, blue: 160/255)) + ] + + return SyntaxColors( + keyword: colorScheme == .dark ? baseColors["keyword"]!.dark : baseColors["keyword"]!.light, + string: colorScheme == .dark ? baseColors["string"]!.dark : baseColors["string"]!.light, + number: colorScheme == .dark ? baseColors["number"]!.dark : baseColors["number"]!.light, + comment: colorScheme == .dark ? baseColors["comment"]!.dark : baseColors["comment"]!.light, + attribute: colorScheme == .dark ? baseColors["attribute"]!.dark : baseColors["attribute"]!.light, + variable: colorScheme == .dark ? baseColors["variable"]!.dark : baseColors["variable"]!.light, + def: colorScheme == .dark ? baseColors["def"]!.dark : baseColors["def"]!.light, + property: colorScheme == .dark ? baseColors["property"]!.dark : baseColors["property"]!.light, + meta: colorScheme == .dark ? baseColors["meta"]!.dark : baseColors["meta"]!.light, + tag: colorScheme == .dark ? baseColors["tag"]!.dark : baseColors["tag"]!.light, + atom: colorScheme == .dark ? baseColors["atom"]!.dark : baseColors["atom"]!.light, + builtin: colorScheme == .dark ? baseColors["builtin"]!.dark : baseColors["builtin"]!.light, + type: colorScheme == .dark ? baseColors["type"]!.dark : baseColors["type"]!.light + ) } } -#endif + +func getSyntaxPatterns(for language: String, colors: SyntaxColors) -> [String: Color] { + switch language { + case "swift": + return [ + "\\b(func|struct|class|enum|protocol|extension|if|else|for|while|switch|case|default|guard|defer|throw|try|catch|return|init|deinit)\\b": colors.keyword, + "\"[^\"]*\"": colors.string, + "\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number, + "//.*": colors.comment, + "/\\*([^*]|(\\*+[^*/]))*\\*+/": colors.comment, + "@\\w+": colors.attribute, + "\\b(var|let)\\b": colors.variable, + "\\b(String|Int|Double|Bool)\\b": colors.type + ] + case "python": + return [ + "\\b(def|class|if|else|for|while|try|except|with|as|import|from)\\b": colors.keyword, + "\\b(int|str|float|bool|list|dict)\\b": colors.type, + "\"[^\"]*\"|'[^']*'": colors.string, + "\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number, + "#.*": colors.comment + ] + case "javascript": + return [ + "\\b(function|var|let|const|if|else|for|while|do|try|catch)\\b": colors.keyword, + "\\b(Number|String|Boolean|Object|Array)\\b": colors.type, + "\"[^\"]*\"|'[^']*'|\\`[^\\`]*\\`": colors.string, + "\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number, + "//.*|/\\*([^*]|(\\*+[^*/]))*\\*+/": colors.comment + ] + case "html": + return ["<[^>]+>": colors.tag] + case "css": + return ["\\b([a-zA-Z-]+\\s*:\\s*[^;]+;)": colors.property] + case "c", "cpp": + return [ + "\\b(int|float|double|char|void|if|else|for|while|do|switch|case|return)\\b": colors.keyword, + "\\b(int|float|double|char)\\b": colors.type, + "\"[^\"]*\"": colors.string, + "\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number, + "//.*|/\\*([^*]|(\\*+[^*/]))*\\*+/": colors.comment + ] + case "json": + return [ + "\"[^\"]+\"\\s*:": colors.property, + "\"[^\"]*\"": colors.string, + "\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number, + "\\b(true|false|null)\\b": colors.keyword + ] + case "markdown": + return [ + "^#+\\s*[^#]+": colors.keyword, + "\\*\\*[^\\*\\*]+\\*\\*": colors.def, + "\\_[^\\_]+\\_": colors.def + ] + default: + return [:] + } +} + +extension Notification.Name { + static let moveCursorToLine = Notification.Name("moveCursorToLine") + static let streamSuggestion = Notification.Name("streamSuggestion") +} + diff --git a/Neon Vision Editor/EditorViewModel.swift b/Neon Vision Editor/EditorViewModel.swift index b2674db..f9e9ff4 100644 --- a/Neon Vision Editor/EditorViewModel.swift +++ b/Neon Vision Editor/EditorViewModel.swift @@ -1,48 +1,84 @@ import SwiftUI -import SwiftData +import Combine import UniformTypeIdentifiers +struct TabData: Identifiable { + let id = UUID() + var name: String + var content: String + var language: String + var fileURL: URL? +} + +@MainActor class EditorViewModel: ObservableObject { - @Published var tabs: [Tab] = [] - @Published var selectedTab: Tab? + @Published var tabs: [TabData] = [] + @Published var selectedTabID: UUID? @Published var showSidebar: Bool = true - - func addNewTab(context: ModelContext) { - let newTab = Tab(name: "New Tab \(tabs.count + 1)", content: "", language: "swift") - tabs.append(newTab) - context.insert(newTab) - selectedTab = newTab - try? context.save() + @Published var isBrainDumpMode: Bool = false + @Published var showingRename: Bool = false + @Published var renameText: String = "" + + var selectedTab: TabData? { + get { tabs.first(where: { $0.id == selectedTabID }) } + set { selectedTabID = newValue?.id } } - - func openFile() { - let openPanel = NSOpenPanel() - openPanel.allowedContentTypes = [.text, .sourceCode, .swiftSource, .pythonScript, .javaScript, .html, .css, .cSource, .cppSource, .json, .markdown] - openPanel.allowsMultipleSelection = false - openPanel.canChooseDirectories = false - openPanel.canChooseFiles = true - - guard openPanel.runModal() == .OK, let url = openPanel.url else { return } - do { - let content = try String(contentsOf: url, encoding: .utf8) - let language = languageMap[url.pathExtension.lowercased()] ?? "plaintext" - let newTab = Tab(name: url.lastPathComponent, content: content, language: language, fileURL: url) - tabs.append(newTab) - selectedTab = newTab - if let context = try? ModelContext(ModelContainer(for: Tab.self)) { - context.insert(newTab) - try? context.save() - } - } catch { - print("Error opening file: \(error)") + + private let languageMap: [String: String] = [ + "swift": "swift", + "py": "python", + "js": "javascript", + "html": "html", + "css": "css", + "c": "c", + "cpp": "cpp", + "h": "c", + "json": "json", + "md": "markdown" + ] + + init() { + addNewTab() + } + + func addNewTab() { + let newTab = TabData(name: "Untitled \(tabs.count + 1)", content: "", language: "swift", fileURL: nil) + tabs.append(newTab) + selectedTabID = newTab.id + } + + func renameTab(tab: TabData, newName: String) { + if let index = tabs.firstIndex(where: { $0.id == tab.id }) { + tabs[index].name = newName } } - - func saveFile(tab: Tab) { - if let url = tab.fileURL { + + 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 { do { - try tab.content.write(to: url, atomically: true, encoding: .utf8) - print("Saved to \(url.path)") + try tabs[index].content.write(to: url, atomically: true, encoding: .utf8) } catch { print("Error saving file: \(error)") } @@ -50,28 +86,51 @@ class EditorViewModel: ObservableObject { saveFileAs(tab: tab) } } - - func saveFileAs(tab: Tab) { - let savePanel = NSSavePanel() - savePanel.allowedContentTypes = [.text, .sourceCode, .swiftSource, .pythonScript, .javaScript, .html, .css, .cSource, .cppSource, .json, .markdown] - savePanel.nameFieldStringValue = tab.name - - guard savePanel.runModal() == .OK, let url = savePanel.url else { return } - do { - try tab.content.write(to: url, atomically: true, encoding: .utf8) - tab.fileURL = url - tab.name = url.lastPathComponent - if let context = try? ModelContext(ModelContainer(for: Tab.self)) { - try? context.save() + + func saveFileAs(tab: TabData) { + guard let index = tabs.firstIndex(where: { $0.id == tab.id }) else { return } + let panel = NSSavePanel() + panel.nameFieldStringValue = tabs[index].name + panel.allowedContentTypes = [.text, .swiftSource, .pythonScript, .javaScript, .html, .css, .cSource, .json, UTType(importedAs: "public.markdown")] + + Task { + 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)") + } } - print("Saved as \(url.path)") - } catch { - print("Error saving file: \(error)") } } - - let languageMap: [String: String] = [ - "swift": "swift", "py": "python", "js": "javascript", "html": "html", "css": "css", - "c": "c", "cpp": "cpp", "json": "json", "md": "markdown" - ] -} \ No newline at end of file + + func openFile() { + let panel = NSOpenPanel() + panel.allowedContentTypes = [.text, .sourceCode, .swiftSource, .pythonScript, .javaScript, .html, .css, .cSource, .json, UTType(importedAs: "public.markdown")] + panel.allowsMultipleSelection = false + panel.canChooseDirectories = false + + Task { + 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 wordCount(for text: String) -> Int { + text.components(separatedBy: .whitespacesAndNewlines) + .filter { !$0.isEmpty }.count + } +} diff --git a/Neon Vision Editor/GrokAPIClient.swift b/Neon Vision Editor/GrokAPIClient.swift deleted file mode 100644 index c064f82..0000000 --- a/Neon Vision Editor/GrokAPIClient.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - -class GrokAPIClient { - private let apiKey: String - private let baseURL = "https://api.x.ai/v1" - - init(apiKey: String) { - self.apiKey = apiKey - } - - func generateText(prompt: String, model: String = "grok-3-beta", maxTokens: Int = 500) async throws -> String { - let url = URL(string: "\(baseURL)/chat/completions")! - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let body: [String: Any] = [ - "model": model, - "messages": [ - ["role": "user", "content": prompt] - ], - "max_tokens": maxTokens - ] - request.httpBody = try JSONSerialization.data(withJSONObject: body) - - let (data, response) = try await URLSession.shared.data(for: request) - guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { - throw NSError(domain: "GrokAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "API request failed"]) - } - - let json = try JSONDecoder().decode(GrokResponse.self, from: data) - return json.choices.first?.message.content ?? "" - } -} - -struct GrokResponse: Codable { - struct Choice: Codable { - struct Message: Codable { - let content: String - } - let message: Message - } - let choices: [Choice] -} \ No newline at end of file diff --git a/Neon Vision Editor/NeonVisionEditorApp.swift b/Neon Vision Editor/NeonVisionEditorApp.swift index 712a783..56c3c53 100644 --- a/Neon Vision Editor/NeonVisionEditorApp.swift +++ b/Neon Vision Editor/NeonVisionEditorApp.swift @@ -1,112 +1,159 @@ import SwiftUI -import SwiftData +import FoundationModels -class AppDelegate: NSObject, NSApplicationDelegate { - var viewModel: EditorViewModel? - var modelContext: ModelContext? - - func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { - guard let viewModel = viewModel, let modelContext = modelContext else { - return .terminateNow - } - - for tab in viewModel.tabs { - if !tab.content.isEmpty && tab.fileURL == nil { - let alert = NSAlert() - alert.messageText = "Save changes to \"\(tab.name)\" before quitting?" - alert.informativeText = "Your changes will be lost if you don't save them." - alert.addButton(withTitle: "Save") - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Don't Save") - alert.alertStyle = .warning - - switch alert.runModal() { - case .alertFirstButtonReturn: // Save - viewModel.saveFileAs(tab: tab) - case .alertSecondButtonReturn: // Cancel - return .terminateCancel - case .alertThirdButtonReturn: // Don't Save - break - default: - break - } - } - } - - // Clear all tabs on quit - for tab in viewModel.tabs { - modelContext.delete(tab) - } - try? modelContext.save() - - return .terminateNow - } +enum AIModel: String, Identifiable { + case appleIntelligence = "Apple Intelligence" + case grok = "Grok" + + var id: String { rawValue } } @main struct NeonVisionEditorApp: App { @StateObject private var viewModel = EditorViewModel() - @State private var appDelegate = AppDelegate() - @State private var modelContainer: ModelContainer? - + @State private var showGrokError: Bool = false + @State private var grokErrorMessage: String = "" + @State private var selectedAIModel: AIModel = .appleIntelligence + var body: some Scene { WindowGroup { ContentView() .environmentObject(viewModel) - .environment(\.modelContext, modelContainer?.mainContext ?? ModelContext(ModelContainer(for: Tab.self))) - .frame(minWidth: 1000, minHeight: 600) - .onAppear { - if modelContainer == nil { - do { - let container = try ModelContainer(for: Tab.self) - modelContainer = container - appDelegate.viewModel = viewModel - appDelegate.modelContext = container.mainContext - NSApplication.shared.delegate = appDelegate - } catch { - print("Failed to create ModelContainer: \(error)") - } - } + .environment(\.showGrokError, $showGrokError) + .environment(\.grokErrorMessage, $grokErrorMessage) + .environment(\.selectedAIModel, $selectedAIModel) + .frame(minWidth: 600, minHeight: 400) + .background(.ultraThinMaterial) + .overlay(.ultraThinMaterial.opacity(0.2)) // Fallback for liquidGlassEffect + .task { + // Pre-warm Apple Intelligence model + let session = LanguageModelSession(model: SystemLanguageModel()) + session.prewarm() } } .defaultSize(width: 1000, height: 600) .commands { - CommandGroup(replacing: .newItem) { + CommandMenu("File") { Button("New Tab") { - if let context = modelContainer?.mainContext { - viewModel.addNewTab(context: context) - } + viewModel.addNewTab() } .keyboardShortcut("t", modifiers: .command) - } - CommandMenu("File") { + Button("Open File...") { viewModel.openFile() } .keyboardShortcut("o", modifiers: .command) - + Button("Save") { - if let selectedTab = viewModel.selectedTab { - viewModel.saveFile(tab: selectedTab) + if let tab = viewModel.selectedTab { + viewModel.saveFile(tab: tab) } } .keyboardShortcut("s", modifiers: .command) .disabled(viewModel.selectedTab == nil) - + Button("Save As...") { - if let selectedTab = viewModel.selectedTab { - viewModel.saveFileAs(tab: selectedTab) + if let tab = viewModel.selectedTab { + viewModel.saveFileAs(tab: tab) } } .disabled(viewModel.selectedTab == nil) - } - - CommandMenu("View") { - Button(viewModel.showSidebar ? "Hide Sidebar" : "Show Sidebar") { - viewModel.showSidebar.toggle() + + Button("Rename") { + viewModel.showingRename = true + viewModel.renameText = viewModel.selectedTab?.name ?? "Untitled" } - .keyboardShortcut("b", modifiers: [.command, .shift]) + .disabled(viewModel.selectedTab == nil) + + Button("Close Tab") { + if let tab = viewModel.selectedTab { + viewModel.closeTab(tab: tab) + } + } + .keyboardShortcut("w", modifiers: .command) + .disabled(viewModel.selectedTab == nil) + } + + CommandMenu("Language") { + ForEach(["swift", "python", "javascript", "html", "css", "c", "cpp", "json", "markdown"], id: \.self) { lang in + Button(lang.capitalized) { + if let tab = viewModel.selectedTab { + viewModel.updateTabLanguage(tab: tab, language: lang) + } + } + .disabled(viewModel.selectedTab == nil) + } + } + + CommandMenu("View") { + Toggle("Toggle Sidebar", isOn: $viewModel.showSidebar) + .keyboardShortcut("s", modifiers: [.command, .option]) + + Toggle("Brain Dump Mode", isOn: $viewModel.isBrainDumpMode) + .keyboardShortcut("d", modifiers: [.command, .shift]) + } + + CommandMenu("Tools") { + Button("Suggest Code") { + Task { + if let tab = viewModel.selectedTab { + switch selectedAIModel { + case .appleIntelligence: + let session = LanguageModelSession(model: SystemLanguageModel()) + let prompt = "System: Output a code suggestion for this \(tab.language) code.\nUser: \(tab.content.prefix(1000))" + do { + let suggestion = try await session.respond(to: prompt) + viewModel.updateTabContent(tab: tab, content: tab.content + "\n\n// Apple Intelligence Suggestion:\n" + suggestion.content) + } catch { + grokErrorMessage = error.localizedDescription + showGrokError = true + } + case .grok: + let client = GrokAPIClient(apiKey: "your-xai-api-key") // Replace with your xAI API key from https://x.ai/api + let prompt = "Suggest improvements for this \(tab.language) code: \(tab.content.prefix(1000))" + do { + let suggestion = try await client.generateText(prompt: prompt, maxTokens: 200) + viewModel.updateTabContent(tab: tab, content: tab.content + "\n\n// Grok Suggestion:\n" + suggestion) + } catch { + grokErrorMessage = error.localizedDescription + showGrokError = true + } + } + } + } + } + .keyboardShortcut("g", modifiers: [.command, .shift]) + .disabled(viewModel.selectedTab == nil) } } } } + +struct ShowGrokErrorKey: EnvironmentKey { + static let defaultValue: Binding = .constant(false) +} + +struct GrokErrorMessageKey: EnvironmentKey { + static let defaultValue: Binding = .constant("") +} + +struct SelectedAIModelKey: EnvironmentKey { + static let defaultValue: Binding = .constant(.appleIntelligence) +} + +extension EnvironmentValues { + var showGrokError: Binding { + get { self[ShowGrokErrorKey.self] } + set { self[ShowGrokErrorKey.self] = newValue } + } + + var grokErrorMessage: Binding { + get { self[GrokErrorMessageKey.self] } + set { self[GrokErrorMessageKey.self] = newValue } + } + + var selectedAIModel: Binding { + get { self[SelectedAIModelKey.self] } + set { self[SelectedAIModelKey.self] = newValue } + } +} diff --git a/Neon Vision Editor/Recources/AppIcon.icon/Assets/5.png b/Neon Vision Editor/Recources/AppIcon.icon/Assets/5.png new file mode 100644 index 0000000..014792f Binary files /dev/null and b/Neon Vision Editor/Recources/AppIcon.icon/Assets/5.png differ diff --git a/Neon Vision Editor/Recources/AppIcon.icon/icon.json b/Neon Vision Editor/Recources/AppIcon.icon/icon.json new file mode 100644 index 0000000..5cd5e34 --- /dev/null +++ b/Neon Vision Editor/Recources/AppIcon.icon/icon.json @@ -0,0 +1,29 @@ +{ + "fill" : { + "automatic-gradient" : "extended-srgb:0.00000,0.53333,1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "image-name" : "5.png", + "name" : "5" + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/Neon Vision Editor/Recources/Assets.xcassets/AccentColor.colorset/Contents.json b/Neon Vision Editor/Recources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..8229368 --- /dev/null +++ b/Neon Vision Editor/Recources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.772", + "green" : "0.532", + "red" : "0.898" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.816", + "green" : "0.261", + "red" : "0.569" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Neon Vision Editor/Assets.xcassets/Contents.json b/Neon Vision Editor/Recources/Assets.xcassets/Contents.json similarity index 100% rename from Neon Vision Editor/Assets.xcassets/Contents.json rename to Neon Vision Editor/Recources/Assets.xcassets/Contents.json diff --git a/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/Contents.json b/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/Contents.json new file mode 100644 index 0000000..d99a7b7 --- /dev/null +++ b/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "NeonVision Editor.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "NeonVision Editor 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVision Editor.png b/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/NeonVision Editor 1.png similarity index 100% rename from Neon Vision Editor/Assets.xcassets/AppIcon.appiconset/NeonVision Editor.png rename to Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/NeonVision Editor 1.png diff --git a/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/NeonVision Editor.png b/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/NeonVision Editor.png new file mode 100644 index 0000000..dd6e0bc Binary files /dev/null and b/Neon Vision Editor/Recources/Assets.xcassets/NeonVision Editor.imageset/NeonVision Editor.png differ