2026-01-25 12:46:33 +00:00
// C o n t e n t V i e w . s w i f t
// M a i n S w i f t U I c o n t a i n e r f o r N e o n V i s i o n E d i t o r . H o s t s t h e s i n g l e - d o c u m e n t e d i t o r U I ,
// t o o l b a r a c t i o n s , A I i n t e g r a t i o n , s y n t a x h i g h l i g h t i n g , l i n e n u m b e r s , a n d s i d e b a r T O C .
// MARK: - I m p o r t s
2025-08-25 07:39:12 +00:00
import SwiftUI
2026-01-25 12:46:33 +00:00
import Foundation
2026-02-07 10:51:52 +00:00
import UniformTypeIdentifiers
#if os ( macOS )
import AppKit
#elseif canImport ( UIKit )
import UIKit
#endif
2026-01-17 12:04:11 +00:00
#if USE_FOUNDATION_MODELS
import FoundationModels
#endif
2025-08-25 07:39:12 +00:00
2026-01-17 11:11:26 +00:00
2026-01-25 12:46:33 +00:00
// U t i l i t y : q u i c k w i d t h c a l c u l a t i o n f o r s t r i n g s w i t h a g i v e n f o n t ( A p p K i t - b a s e d )
2025-09-25 09:01:45 +00:00
extension String {
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2025-09-25 09:01:45 +00:00
func width ( usingFont font : NSFont ) -> CGFloat {
let attributes = [ NSAttributedString . Key . font : font ]
let size = ( self as NSString ) . size ( withAttributes : attributes )
return size . width
}
2026-02-07 10:51:52 +00:00
#endif
2025-09-25 09:01:45 +00:00
}
2025-08-27 11:34:59 +00:00
2026-01-25 13:29:46 +00:00
// MARK: - R o o t v i e w f o r t h e e d i t o r .
// M a n a g e s t h e e d i t o r a r e a , t o o l b a r , p o p o v e r s , a n d b r i d g e s t o t h e v i e w m o d e l f o r f i l e I / O a n d m e t r i c s .
2025-09-25 09:01:45 +00:00
struct ContentView : View {
2026-01-25 12:46:33 +00:00
// E n v i r o n m e n t - p r o v i d e d v i e w m o d e l a n d t h e m e / e r r o r b i n d i n g s
2026-02-06 18:59:53 +00:00
@ EnvironmentObject var viewModel : EditorViewModel
@ Environment ( \ . colorScheme ) var colorScheme
2026-02-07 10:51:52 +00:00
#if os ( iOS )
@ Environment ( \ . horizontalSizeClass ) var horizontalSizeClass
#endif
#if os ( macOS )
2026-02-06 18:59:53 +00:00
@ Environment ( \ . openWindow ) var openWindow
2026-02-07 10:51:52 +00:00
#endif
2026-02-06 18:59:53 +00:00
@ Environment ( \ . showGrokError ) var showGrokError
@ Environment ( \ . grokErrorMessage ) var grokErrorMessage
2026-01-17 11:11:26 +00:00
2026-01-25 12:46:33 +00:00
// S i n g l e - d o c u m e n t f a l l b a c k s t a t e ( u s e d w h e n n o t a b m o d e l i s s e l e c t e d )
2026-02-06 18:59:53 +00:00
@ State var selectedModel : AIModel = . appleIntelligence
@ State var singleContent : String = " "
@ State var singleLanguage : String = " swift "
@ State var caretStatus : String = " Ln 1, Col 1 "
@ State var editorFontSize : CGFloat = 14
@ State var lastProviderUsed : String = " Apple "
2026-01-17 11:11:26 +00:00
2026-01-25 12:46:33 +00:00
// P e r s i s t e d A P I t o k e n s f o r e x t e r n a l p r o v i d e r s
2026-02-07 22:56:52 +00:00
@ State var grokAPIToken : String = SecureTokenStore . token ( for : . grok )
@ State var openAIAPIToken : String = SecureTokenStore . token ( for : . openAI )
@ State var geminiAPIToken : String = SecureTokenStore . token ( for : . gemini )
@ State var anthropicAPIToken : String = SecureTokenStore . token ( for : . anthropic )
2026-01-25 12:46:33 +00:00
2026-01-25 19:02:59 +00:00
// D e b o u n c e h a n d l e f o r i n l i n e c o m p l e t i o n
2026-02-06 18:59:53 +00:00
@ State var lastCompletionWorkItem : DispatchWorkItem ?
@ State var isAutoCompletionEnabled : Bool = false
@ State var enableTranslucentWindow : Bool = UserDefaults . standard . bool ( forKey : " EnableTranslucentWindow " )
2026-01-20 23:46:04 +00:00
2026-01-25 19:02:59 +00:00
// A d d e d m i s s i n g p o p o v e r U I s t a t e
2026-02-06 18:59:53 +00:00
@ State var showAISelectorPopover : Bool = false
@ State var showAPISettings : Bool = false
@ State var showFindReplace : Bool = false
@ State var findQuery : String = " "
@ State var replaceQuery : String = " "
@ State var findUsesRegex : Bool = false
@ State var findCaseSensitive : Bool = false
@ State var findStatusMessage : String = " "
@ State var showProjectStructureSidebar : Bool = false
2026-02-07 10:51:52 +00:00
@ State var showCompactSidebarSheet : Bool = false
2026-02-06 18:59:53 +00:00
@ State var projectRootFolderURL : URL ? = nil
@ State var projectTreeNodes : [ ProjectTreeNode ] = [ ]
2026-02-06 19:20:03 +00:00
@ State var pendingCloseTabID : UUID ? = nil
@ State var showUnsavedCloseDialog : Bool = false
2026-02-07 10:51:52 +00:00
@ State var showIOSFileImporter : Bool = false
@ State var showIOSFileExporter : Bool = false
@ State var iosExportDocument : PlainTextDocument = PlainTextDocument ( text : " " )
@ State var iosExportFilename : String = " Untitled.txt "
@ State var iosExportTabID : UUID ? = nil
Add broad language support + Find/Replace panel; minor helpers
Languages:
- Extend picker, detection, TOC, and syntax highlighting for:
swift, python, javascript, typescript, java, kotlin, go, ruby, rust, sql,
html, css, c, cpp, objective-c, json, xml, yaml, toml, ini, markdown,
bash, zsh, powershell, plain
Editor:
- Add Find/Replace sheet with Find Next, Replace, Replace All
- New toolbar button (magnifying glass) to open Find/Replace
- Implement find/replace helpers operating on the active NSTextView
- Small NSRange helper for cleaner optional handling
Syntax highlighting:
- Add lightweight regex patterns for Java, Kotlin, Go, Ruby, Rust, TypeScript,
Objective‑C, SQL, XML, YAML, TOML, INI
- Keep performance-friendly patterns consistent with existing approach
TOC:
- Add TOC generation for Java, Kotlin, Go, Ruby, Rust, TypeScript, Objective‑C
Detection:
- Extend heuristics for XML, YAML, TOML/INI, SQL, Go, Java, Kotlin, TypeScript,
Ruby, Rust, Objective‑C, INI
Testing:
- Verify language picker shows all new entries and switching updates highlighting
- Paste snippets of each language; ensure heuristics pick a sensible default
- Open Find/Replace; test Find Next, Replace, Replace All; verify selection/scroll
- Check large files still perform acceptably with lightweight patterns
2026-02-04 15:21:56 +00:00
2026-02-05 21:30:21 +00:00
#if USE_FOUNDATION_MODELS
2026-02-06 18:59:53 +00:00
var appleModelAvailable : Bool { true }
2026-02-05 21:30:21 +00:00
#else
2026-02-06 18:59:53 +00:00
var appleModelAvailable : Bool { false }
2026-02-05 21:30:21 +00:00
#endif
2026-02-06 18:59:53 +00:00
var activeProviderName : String { lastProviderUsed }
2026-02-05 21:30:21 +00:00
2026-02-07 22:56:52 +00:00
// / P r o m p t s t h e u s e r f o r a G r o k t o k e n i f n o n e i s s a v e d . P e r s i s t s t o K e y c h a i n .
2026-01-25 12:46:33 +00:00
// / R e t u r n s t r u e i f a t o k e n i s p r e s e n t / w a s s a v e d ; f a l s e i f c a n c e l l e d o r e m p t y .
2026-01-20 23:46:04 +00:00
private func promptForGrokTokenIfNeeded ( ) -> Bool {
if ! grokAPIToken . trimmingCharacters ( in : . whitespacesAndNewlines ) . isEmpty { return true }
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-01-20 23:46:04 +00:00
let alert = NSAlert ( )
alert . messageText = " Grok API Token Required "
alert . informativeText = " Enter your Grok API token to enable suggestions. You can obtain this from your Grok account. "
alert . alertStyle = . informational
alert . addButton ( withTitle : " Save " )
alert . addButton ( withTitle : " Cancel " )
let input = NSSecureTextField ( frame : NSRect ( x : 0 , y : 0 , width : 280 , height : 24 ) )
input . placeholderString = " sk-... "
alert . accessoryView = input
let response = alert . runModal ( )
if response = = . alertFirstButtonReturn {
let token = input . stringValue . trimmingCharacters ( in : . whitespacesAndNewlines )
if token . isEmpty { return false }
grokAPIToken = token
2026-02-07 22:56:52 +00:00
SecureTokenStore . setToken ( token , for : . grok )
2026-01-20 23:46:04 +00:00
return true
}
2026-02-07 10:51:52 +00:00
#endif
2026-01-20 23:46:04 +00:00
return false
}
2026-02-07 22:56:52 +00:00
// / P r o m p t s t h e u s e r f o r a n O p e n A I t o k e n i f n o n e i s s a v e d . P e r s i s t s t o K e y c h a i n .
2026-01-25 12:46:33 +00:00
// / R e t u r n s t r u e i f a t o k e n i s p r e s e n t / w a s s a v e d ; f a l s e i f c a n c e l l e d o r e m p t y .
private func promptForOpenAITokenIfNeeded ( ) -> Bool {
if ! openAIAPIToken . trimmingCharacters ( in : . whitespacesAndNewlines ) . isEmpty { return true }
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-01-25 12:46:33 +00:00
let alert = NSAlert ( )
alert . messageText = " OpenAI API Token Required "
alert . informativeText = " Enter your OpenAI API token to enable suggestions. "
alert . alertStyle = . informational
alert . addButton ( withTitle : " Save " )
alert . addButton ( withTitle : " Cancel " )
let input = NSSecureTextField ( frame : NSRect ( x : 0 , y : 0 , width : 280 , height : 24 ) )
input . placeholderString = " sk-... "
alert . accessoryView = input
let response = alert . runModal ( )
if response = = . alertFirstButtonReturn {
let token = input . stringValue . trimmingCharacters ( in : . whitespacesAndNewlines )
if token . isEmpty { return false }
openAIAPIToken = token
2026-02-07 22:56:52 +00:00
SecureTokenStore . setToken ( token , for : . openAI )
2026-01-25 12:46:33 +00:00
return true
}
2026-02-07 10:51:52 +00:00
#endif
2026-01-25 12:46:33 +00:00
return false
}
2026-02-07 22:56:52 +00:00
// / P r o m p t s t h e u s e r f o r a G e m i n i t o k e n i f n o n e i s s a v e d . P e r s i s t s t o K e y c h a i n .
2026-01-25 12:46:33 +00:00
// / R e t u r n s t r u e i f a t o k e n i s p r e s e n t / w a s s a v e d ; f a l s e i f c a n c e l l e d o r e m p t y .
private func promptForGeminiTokenIfNeeded ( ) -> Bool {
if ! geminiAPIToken . trimmingCharacters ( in : . whitespacesAndNewlines ) . isEmpty { return true }
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-01-25 12:46:33 +00:00
let alert = NSAlert ( )
alert . messageText = " Gemini API Key Required "
alert . informativeText = " Enter your Gemini API key to enable suggestions. "
alert . alertStyle = . informational
alert . addButton ( withTitle : " Save " )
alert . addButton ( withTitle : " Cancel " )
let input = NSSecureTextField ( frame : NSRect ( x : 0 , y : 0 , width : 280 , height : 24 ) )
input . placeholderString = " AIza... "
alert . accessoryView = input
let response = alert . runModal ( )
if response = = . alertFirstButtonReturn {
let token = input . stringValue . trimmingCharacters ( in : . whitespacesAndNewlines )
if token . isEmpty { return false }
geminiAPIToken = token
2026-02-07 22:56:52 +00:00
SecureTokenStore . setToken ( token , for : . gemini )
2026-01-25 12:46:33 +00:00
return true
}
2026-02-07 10:51:52 +00:00
#endif
2026-01-25 12:46:33 +00:00
return false
}
2026-02-07 22:56:52 +00:00
// / P r o m p t s t h e u s e r f o r a n A n t h r o p i c A P I t o k e n i f n o n e i s s a v e d . P e r s i s t s t o K e y c h a i n .
2026-02-04 13:11:28 +00:00
// / R e t u r n s t r u e i f a t o k e n i s p r e s e n t / w a s s a v e d ; f a l s e i f c a n c e l l e d o r e m p t y .
private func promptForAnthropicTokenIfNeeded ( ) -> Bool {
if ! anthropicAPIToken . trimmingCharacters ( in : . whitespacesAndNewlines ) . isEmpty { return true }
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-02-04 13:11:28 +00:00
let alert = NSAlert ( )
alert . messageText = " Anthropic API Token Required "
alert . informativeText = " Enter your Anthropic API token to enable suggestions. "
alert . alertStyle = . informational
alert . addButton ( withTitle : " Save " )
alert . addButton ( withTitle : " Cancel " )
let input = NSSecureTextField ( frame : NSRect ( x : 0 , y : 0 , width : 280 , height : 24 ) )
input . placeholderString = " sk-ant-... "
alert . accessoryView = input
let response = alert . runModal ( )
if response = = . alertFirstButtonReturn {
let token = input . stringValue . trimmingCharacters ( in : . whitespacesAndNewlines )
if token . isEmpty { return false }
anthropicAPIToken = token
2026-02-07 22:56:52 +00:00
SecureTokenStore . setToken ( token , for : . anthropic )
2026-02-04 13:11:28 +00:00
return true
}
2026-02-07 10:51:52 +00:00
#endif
2026-02-04 13:11:28 +00:00
return false
}
2026-01-25 19:02:59 +00:00
private func performInlineCompletion ( ) {
Task {
await performInlineCompletionAsync ( )
}
}
private func performInlineCompletionAsync ( ) async {
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-01-25 19:02:59 +00:00
guard let textView = NSApp . keyWindow ? . firstResponder as ? NSTextView else { return }
let sel = textView . selectedRange ( )
guard sel . length = = 0 else { return }
let loc = sel . location
guard loc > 0 , loc <= ( textView . string as NSString ) . length else { return }
let nsText = textView . string as NSString
let prevChar = nsText . substring ( with : NSRange ( location : loc - 1 , length : 1 ) )
var nextChar : String ? = nil
if loc < nsText . length {
nextChar = nsText . substring ( with : NSRange ( location : loc , length : 1 ) )
}
// A u t o - c l o s e b r a c e s / b r a c k e t s / p a r e n s i f n o t a l r e a d y c l o s e d
let pairs : [ String : String ] = [ " { " : " } " , " ( " : " ) " , " [ " : " ] " ]
if let closing = pairs [ prevChar ] {
if nextChar != closing {
// I n s e r t c l o s i n g a n d m o v e c a r e t b a c k b e t w e e n p a i r
let insertion = closing
textView . insertText ( insertion , replacementRange : sel )
textView . setSelectedRange ( NSRange ( location : loc , length : 0 ) )
return
}
}
// I f p r e v i o u s c h a r i s ' { ' a n d l a n g u a g e i s s w i f t , j a v a s c r i p t , c , o r c p p , i n s e r t c o d e b l o c k s c a f f o l d
if prevChar = = " { " && [ " swift " , " javascript " , " c " , " cpp " ] . contains ( currentLanguage ) {
// G e t c u r r e n t l i n e i n d e n t a t i o n
let fullText = textView . string as NSString
let lineRange = fullText . lineRange ( for : NSRange ( location : loc - 1 , length : 0 ) )
let lineText = fullText . substring ( with : lineRange )
let indentPrefix = lineText . prefix ( while : { $0 = = " " || $0 = = " \t " } )
let indentString = String ( indentPrefix )
let indentLevel = indentString . count
let indentSpaces = " " // 4 s p a c e s
// B u i l d s c a f f o l d s t r i n g
let scaffold = " \n \( indentString ) \( indentSpaces ) \n \( indentString ) } "
2026-01-25 12:46:33 +00:00
2026-01-25 19:02:59 +00:00
// I n s e r t s c a f f o l d a t c a r e t p o s i t i o n
textView . insertText ( scaffold , replacementRange : NSRange ( location : loc , length : 0 ) )
// M o v e c a r e t t o i n d e n t e d e m p t y l i n e
let newCaretLocation = loc + 1 + indentLevel + indentSpaces . count
textView . setSelectedRange ( NSRange ( location : newCaretLocation , length : 0 ) )
return
}
// M o d e l - b a c k e d c o m p l e t i o n a t t e m p t
let doc = textView . string
// L i m i t t h e p r e f i x c o n t e x t l e n g t h t o 2 0 0 0 U T F - 1 6 c o d e u n i t s m a x f o r p e r f o r m a n c e
let nsDoc = doc as NSString
let prefixStart = max ( 0 , loc - 2000 )
let prefixRange = NSRange ( location : prefixStart , length : loc - prefixStart )
let contextPrefix = nsDoc . substring ( with : prefixRange )
let suggestion = await generateModelCompletion ( prefix : contextPrefix , language : currentLanguage )
guard ! suggestion . isEmpty else { return }
// I n s e r t s u g g e s t i o n a f t e r c a r e t w i t h o u t d u p l i c a t i n g e x i s t i n g t e x t a n d w i t h o u t s c r o l l i n g
await MainActor . run {
let currentText = textView . string as NSString
let insertionRange = NSRange ( location : sel . location , length : 0 )
// C h e c k f o r d u p l i c a t i o n : s k i p i f s u g g e s t i o n p r e f i x m a t c h e s n e x t c h a r a c t e r s a f t e r c a r e t
let nextRangeLength = min ( suggestion . count , currentText . length - sel . location )
let nextText = nextRangeLength > 0 ? currentText . substring ( with : NSRange ( location : sel . location , length : nextRangeLength ) ) : " "
if nextText . starts ( with : suggestion ) {
// A l r e a d y p r e s e n t , d o n o t h i n g
return
}
// I n s e r t t h e s u g g e s t i o n
textView . insertText ( suggestion , replacementRange : insertionRange )
// R e s t o r e t h e s e l e c t i o n t o a f t e r i n s e r t e d t e x t
textView . setSelectedRange ( NSRange ( location : sel . location + ( suggestion as NSString ) . length , length : 0 ) )
// S c r o l l t o v i s i b l e r a n g e o f i n s e r t e d t e x t
textView . scrollRangeToVisible ( NSRange ( location : sel . location + ( suggestion as NSString ) . length , length : 0 ) )
}
2026-02-07 10:51:52 +00:00
#else
// i O S i n l i n e c o m p l e t i o n h o o k c a n b e a d d e d f o r U I T e x t V i e w s e l e c t i o n A P I s .
return
#endif
2026-01-25 19:02:59 +00:00
}
2026-02-05 21:30:21 +00:00
private func externalModelCompletion ( prefix : String , language : String ) async -> String {
// T r y G r o k
if ! grokAPIToken . isEmpty {
2026-01-25 19:02:59 +00:00
do {
2026-02-05 21:30:21 +00:00
let url = URL ( string : " https://api.x.ai/v1/chat/completions " ) !
var request = URLRequest ( url : url )
request . httpMethod = " POST "
request . setValue ( " Bearer \( grokAPIToken ) " , forHTTPHeaderField : " Authorization " )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
2026-01-25 19:02:59 +00:00
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
2026-02-05 21:30:21 +00:00
let body : [ String : Any ] = [
" model " : " grok-2-latest " ,
" messages " : [ [ " role " : " user " , " content " : prompt ] ] ,
" temperature " : 0.5 ,
" max_tokens " : 64 ,
" n " : 1 ,
" stop " : [ " " ]
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if 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 {
return sanitizeCompletion ( content )
}
2026-02-07 22:56:52 +00:00
} catch {
debugLog ( " [Completion][Fallback][Grok] request failed " )
}
2026-02-05 21:30:21 +00:00
}
// T r y O p e n A I
if ! openAIAPIToken . isEmpty {
do {
let url = URL ( string : " https://api.openai.com/v1/chat/completions " ) !
var request = URLRequest ( url : url )
request . httpMethod = " POST "
request . setValue ( " Bearer \( openAIAPIToken ) " , forHTTPHeaderField : " Authorization " )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
" model " : " gpt-4o-mini " ,
" messages " : [ [ " role " : " user " , " content " : prompt ] ] ,
" temperature " : 0.5 ,
" max_tokens " : 64 ,
" n " : 1 ,
" stop " : [ " " ]
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if 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 {
return sanitizeCompletion ( content )
}
2026-02-07 22:56:52 +00:00
} catch {
debugLog ( " [Completion][Fallback][OpenAI] request failed " )
}
2026-02-05 21:30:21 +00:00
}
// T r y G e m i n i
if ! geminiAPIToken . isEmpty {
do {
let model = " gemini-1.5-flash-latest "
2026-02-07 22:56:52 +00:00
let endpoint = " https://generativelanguage.googleapis.com/v1beta/models/ \( model ) :generateContent "
2026-02-05 21:30:21 +00:00
guard let url = URL ( string : endpoint ) else { return " " }
var request = URLRequest ( url : url )
request . httpMethod = " POST "
2026-02-07 22:56:52 +00:00
request . setValue ( geminiAPIToken , forHTTPHeaderField : " x-goog-api-key " )
2026-02-05 21:30:21 +00:00
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
" contents " : [ [ " parts " : [ [ " text " : prompt ] ] ] ] ,
" generationConfig " : [ " temperature " : 0.5 , " maxOutputTokens " : 64 ]
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if let json = try JSONSerialization . jsonObject ( with : data ) as ? [ String : Any ] ,
let candidates = json [ " candidates " ] as ? [ [ String : Any ] ] ,
let first = candidates . first ,
let content = first [ " content " ] as ? [ String : Any ] ,
let parts = content [ " parts " ] as ? [ [ String : Any ] ] ,
let text = parts . first ? [ " text " ] as ? String {
return sanitizeCompletion ( text )
}
2026-02-07 22:56:52 +00:00
} catch {
debugLog ( " [Completion][Fallback][Gemini] request failed " )
}
2026-02-05 21:30:21 +00:00
}
// T r y A n t h r o p i c
if ! anthropicAPIToken . isEmpty {
do {
let url = URL ( string : " https://api.anthropic.com/v1/messages " ) !
var request = URLRequest ( url : url )
request . httpMethod = " POST "
request . setValue ( anthropicAPIToken , forHTTPHeaderField : " x-api-key " )
request . setValue ( " 2023-06-01 " , forHTTPHeaderField : " anthropic-version " )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
" model " : " claude-3-5-haiku-latest " ,
" max_tokens " : 64 ,
" temperature " : 0.5 ,
" messages " : [ [ " role " : " user " , " content " : prompt ] ]
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if let json = try JSONSerialization . jsonObject ( with : data ) as ? [ String : Any ] ,
let contentArr = json [ " content " ] as ? [ [ String : Any ] ] ,
let first = contentArr . first ,
let text = first [ " text " ] as ? String {
return sanitizeCompletion ( text )
}
if let json = try JSONSerialization . jsonObject ( with : data ) as ? [ String : Any ] ,
let message = json [ " message " ] as ? [ String : Any ] ,
let contentArr = message [ " content " ] as ? [ [ String : Any ] ] ,
let first = contentArr . first ,
let text = first [ " text " ] as ? String {
return sanitizeCompletion ( text )
}
2026-02-07 22:56:52 +00:00
} catch {
debugLog ( " [Completion][Fallback][Anthropic] request failed " )
}
2026-02-05 21:30:21 +00:00
}
return " "
}
private func appleModelCompletion ( prefix : String , language : String ) async -> String {
let client = AppleIntelligenceAIClient ( )
var aggregated = " "
var firstChunk : String ?
for await chunk in client . streamSuggestions ( prompt : " Continue the following \( language ) code snippet with a few lines or tokens of code only. Do not add prose or explanations. \n \n \( prefix ) \n \n Completion: " ) {
if firstChunk = = nil , ! chunk . trimmingCharacters ( in : . whitespacesAndNewlines ) . isEmpty {
firstChunk = chunk
break
} else {
aggregated += chunk
2026-01-25 19:02:59 +00:00
}
2026-02-05 21:30:21 +00:00
}
let candidate = sanitizeCompletion ( ( firstChunk ? ? aggregated ) )
await MainActor . run { lastProviderUsed = " Apple " }
return candidate
}
private func generateModelCompletion ( prefix : String , language : String ) async -> String {
switch selectedModel {
case . appleIntelligence :
return await appleModelCompletion ( prefix : prefix , language : language )
2026-01-20 23:46:04 +00:00
case . grok :
2026-02-05 21:30:21 +00:00
if grokAPIToken . isEmpty {
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Grok (fallback to Apple) " }
return res
}
2026-02-04 13:11:28 +00:00
do {
let url = URL ( string : " https://api.x.ai/v1/chat/completions " ) !
var request = URLRequest ( url : url )
request . httpMethod = " POST "
request . setValue ( " Bearer \( grokAPIToken ) " , forHTTPHeaderField : " Authorization " )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
" model " : " grok-2-latest " ,
2026-02-05 21:30:21 +00:00
" messages " : [ [ " role " : " user " , " content " : prompt ] ] ,
2026-02-04 13:11:28 +00:00
" temperature " : 0.5 ,
" max_tokens " : 64 ,
" n " : 1 ,
" stop " : [ " " ]
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if 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 {
2026-02-05 21:30:21 +00:00
await MainActor . run { lastProviderUsed = " Grok " }
2026-02-04 13:11:28 +00:00
return sanitizeCompletion ( content )
}
2026-02-05 21:30:21 +00:00
// I f n o c o n t e n t , f a l l b a c k t o A p p l e
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Grok (fallback to Apple) " }
return res
2026-02-04 13:11:28 +00:00
} catch {
2026-02-07 22:56:52 +00:00
debugLog ( " [Completion][Grok] request failed " )
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Grok (fallback to Apple) " }
return res
2026-02-04 13:11:28 +00:00
}
2026-01-25 12:46:33 +00:00
case . openAI :
2026-02-05 21:30:21 +00:00
if openAIAPIToken . isEmpty {
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " OpenAI (fallback to Apple) " }
return res
}
2026-01-25 19:02:59 +00:00
do {
let url = URL ( string : " https://api.openai.com/v1/chat/completions " ) !
var request = URLRequest ( url : url )
request . httpMethod = " POST "
request . setValue ( " Bearer \( openAIAPIToken ) " , forHTTPHeaderField : " Authorization " )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
" model " : " gpt-4o-mini " ,
2026-02-05 21:30:21 +00:00
" messages " : [ [ " role " : " user " , " content " : prompt ] ] ,
2026-01-25 19:02:59 +00:00
" temperature " : 0.5 ,
" max_tokens " : 64 ,
" n " : 1 ,
" stop " : [ " " ]
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if 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 {
2026-02-05 21:30:21 +00:00
await MainActor . run { lastProviderUsed = " OpenAI " }
2026-01-25 19:02:59 +00:00
return sanitizeCompletion ( content )
}
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " OpenAI (fallback to Apple) " }
return res
2026-01-25 19:02:59 +00:00
} catch {
2026-02-07 22:56:52 +00:00
debugLog ( " [Completion][OpenAI] request failed " )
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " OpenAI (fallback to Apple) " }
return res
2026-01-25 19:02:59 +00:00
}
2026-01-25 12:46:33 +00:00
case . gemini :
2026-02-05 21:30:21 +00:00
if geminiAPIToken . isEmpty {
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Gemini (fallback to Apple) " }
return res
}
2026-02-04 13:11:28 +00:00
do {
let model = " gemini-1.5-flash-latest "
2026-02-07 22:56:52 +00:00
let endpoint = " https://generativelanguage.googleapis.com/v1beta/models/ \( model ) :generateContent "
2026-02-05 21:30:21 +00:00
guard let url = URL ( string : endpoint ) else {
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Gemini (fallback to Apple) " }
return res
}
2026-02-04 13:11:28 +00:00
var request = URLRequest ( url : url )
request . httpMethod = " POST "
2026-02-07 22:56:52 +00:00
request . setValue ( geminiAPIToken , forHTTPHeaderField : " x-goog-api-key " )
2026-02-04 13:11:28 +00:00
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
2026-02-05 21:30:21 +00:00
" contents " : [ [ " parts " : [ [ " text " : prompt ] ] ] ] ,
" generationConfig " : [ " temperature " : 0.5 , " maxOutputTokens " : 64 ]
2026-02-04 13:11:28 +00:00
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if let json = try JSONSerialization . jsonObject ( with : data ) as ? [ String : Any ] ,
let candidates = json [ " candidates " ] as ? [ [ String : Any ] ] ,
let first = candidates . first ,
let content = first [ " content " ] as ? [ String : Any ] ,
let parts = content [ " parts " ] as ? [ [ String : Any ] ] ,
let text = parts . first ? [ " text " ] as ? String {
2026-02-05 21:30:21 +00:00
await MainActor . run { lastProviderUsed = " Gemini " }
2026-02-04 13:11:28 +00:00
return sanitizeCompletion ( text )
}
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Gemini (fallback to Apple) " }
return res
2026-02-04 13:11:28 +00:00
} catch {
2026-02-07 22:56:52 +00:00
debugLog ( " [Completion][Gemini] request failed " )
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Gemini (fallback to Apple) " }
return res
2026-02-04 13:11:28 +00:00
}
case . anthropic :
2026-02-05 21:30:21 +00:00
if anthropicAPIToken . isEmpty {
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Anthropic (fallback to Apple) " }
return res
}
2026-02-04 13:11:28 +00:00
do {
let url = URL ( string : " https://api.anthropic.com/v1/messages " ) !
var request = URLRequest ( url : url )
request . httpMethod = " POST "
request . setValue ( anthropicAPIToken , forHTTPHeaderField : " x-api-key " )
request . setValue ( " 2023-06-01 " , forHTTPHeaderField : " anthropic-version " )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
let prompt = " " "
Continue the following \ ( language ) code snippet with a few lines or tokens of code only . Do not add prose or explanations .
\ ( prefix )
Completion :
" " "
let body : [ String : Any ] = [
" model " : " claude-3-5-haiku-latest " ,
" max_tokens " : 64 ,
" temperature " : 0.5 ,
2026-02-05 21:30:21 +00:00
" messages " : [ [ " role " : " user " , " content " : prompt ] ]
2026-02-04 13:11:28 +00:00
]
request . httpBody = try JSONSerialization . data ( withJSONObject : body , options : [ ] )
let ( data , _ ) = try await URLSession . shared . data ( for : request )
if let json = try JSONSerialization . jsonObject ( with : data ) as ? [ String : Any ] ,
let contentArr = json [ " content " ] as ? [ [ String : Any ] ] ,
let first = contentArr . first ,
let text = first [ " text " ] as ? String {
2026-02-05 21:30:21 +00:00
await MainActor . run { lastProviderUsed = " Anthropic " }
2026-02-04 13:11:28 +00:00
return sanitizeCompletion ( text )
}
if let json = try JSONSerialization . jsonObject ( with : data ) as ? [ String : Any ] ,
let message = json [ " message " ] as ? [ String : Any ] ,
let contentArr = message [ " content " ] as ? [ [ String : Any ] ] ,
let first = contentArr . first ,
let text = first [ " text " ] as ? String {
2026-02-05 21:30:21 +00:00
await MainActor . run { lastProviderUsed = " Anthropic " }
2026-02-04 13:11:28 +00:00
return sanitizeCompletion ( text )
}
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Anthropic (fallback to Apple) " }
return res
2026-02-04 13:11:28 +00:00
} catch {
2026-02-07 22:56:52 +00:00
debugLog ( " [Completion][Anthropic] request failed " )
2026-02-05 21:30:21 +00:00
let res = await appleModelCompletion ( prefix : prefix , language : language )
await MainActor . run { lastProviderUsed = " Anthropic (fallback to Apple) " }
return res
2026-02-04 13:11:28 +00:00
}
2026-01-20 23:46:04 +00:00
}
2026-01-25 19:02:59 +00:00
}
2026-01-25 12:46:33 +00:00
2026-01-25 19:02:59 +00:00
private func sanitizeCompletion ( _ raw : String ) -> String {
// R e m o v e c o d e f e n c e s a n d p r o s e , k e e p f i r s t f e w l i n e s o f c o d e o n l y
var result = raw . trimmingCharacters ( in : . whitespacesAndNewlines )
// R e m o v e o p e n i n g a n d c l o s i n g c o d e f e n c e s i f p r e s e n t
while result . hasPrefix ( " ``` " ) {
if let fenceEndIndex = result . firstIndex ( of : " \n " ) {
result = String ( result [ fenceEndIndex . . . ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
} else {
break
}
}
if let closingFenceRange = result . range ( of : " ``` " ) {
result = String ( result [ . . < closingFenceRange . lowerBound ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
}
// T a k e o n l y u p t o 2 l i n e s t o a v o i d b i g i n s e r t i o n s
let lines = result . components ( separatedBy : . newlines )
if lines . count > 2 {
result = lines . prefix ( 2 ) . joined ( separator : " \n " )
}
2026-01-25 12:46:33 +00:00
2026-01-25 19:02:59 +00:00
return result
2026-01-20 23:46:04 +00:00
}
2026-02-07 22:56:52 +00:00
private func debugLog ( _ message : String ) {
#if DEBUG
print ( message )
#endif
}
2026-02-07 10:51:52 +00:00
@ ViewBuilder
private var platformLayout : some View {
#if os ( macOS )
2026-01-25 19:02:59 +00:00
Group {
2026-02-07 10:51:52 +00:00
if shouldUseSplitView {
2026-01-25 19:02:59 +00:00
NavigationSplitView {
sidebarView
} detail : {
editorView
}
. navigationSplitViewColumnWidth ( min : 200 , ideal : 250 , max : 600 )
2026-02-06 13:29:34 +00:00
. background ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color . clear ) )
2026-01-25 19:02:59 +00:00
} else {
editorView
}
2025-09-25 09:01:45 +00:00
}
. frame ( minWidth : 600 , minHeight : 400 )
2026-02-07 10:51:52 +00:00
#else
NavigationStack {
Group {
if shouldUseSplitView {
NavigationSplitView {
sidebarView
} detail : {
editorView
}
. navigationSplitViewColumnWidth ( min : 200 , ideal : 250 , max : 600 )
. background ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color . clear ) )
} else {
editorView
}
}
}
. frame ( maxWidth : . infinity , maxHeight : . infinity , alignment : . topLeading )
#endif
}
// L a y o u t : N a v i g a t i o n S p l i t V i e w w i t h o p t i o n a l s i d e b a r a n d t h e p r i m a r y c o d e e d i t o r .
var body : some View {
platformLayout
2025-09-25 09:01:45 +00:00
. alert ( " AI Error " , isPresented : showGrokError ) {
Button ( " OK " ) { }
} message : {
Text ( grokErrorMessage . wrappedValue )
}
2026-02-06 18:59:53 +00:00
. navigationTitle ( " Neon Vision Editor " )
2026-01-25 12:46:33 +00:00
. sheet ( isPresented : $ showAPISettings ) {
APISupportSettingsView (
grokAPIToken : $ grokAPIToken ,
openAIAPIToken : $ openAIAPIToken ,
2026-02-04 13:11:28 +00:00
geminiAPIToken : $ geminiAPIToken ,
anthropicAPIToken : $ anthropicAPIToken
2026-01-25 12:46:33 +00:00
)
}
Add broad language support + Find/Replace panel; minor helpers
Languages:
- Extend picker, detection, TOC, and syntax highlighting for:
swift, python, javascript, typescript, java, kotlin, go, ruby, rust, sql,
html, css, c, cpp, objective-c, json, xml, yaml, toml, ini, markdown,
bash, zsh, powershell, plain
Editor:
- Add Find/Replace sheet with Find Next, Replace, Replace All
- New toolbar button (magnifying glass) to open Find/Replace
- Implement find/replace helpers operating on the active NSTextView
- Small NSRange helper for cleaner optional handling
Syntax highlighting:
- Add lightweight regex patterns for Java, Kotlin, Go, Ruby, Rust, TypeScript,
Objective‑C, SQL, XML, YAML, TOML, INI
- Keep performance-friendly patterns consistent with existing approach
TOC:
- Add TOC generation for Java, Kotlin, Go, Ruby, Rust, TypeScript, Objective‑C
Detection:
- Extend heuristics for XML, YAML, TOML/INI, SQL, Go, Java, Kotlin, TypeScript,
Ruby, Rust, Objective‑C, INI
Testing:
- Verify language picker shows all new entries and switching updates highlighting
- Paste snippets of each language; ensure heuristics pick a sensible default
- Open Find/Replace; test Find Next, Replace, Replace All; verify selection/scroll
- Check large files still perform acceptably with lightweight patterns
2026-02-04 15:21:56 +00:00
. sheet ( isPresented : $ showFindReplace ) {
2026-02-06 18:59:53 +00:00
FindReplacePanel (
findQuery : $ findQuery ,
replaceQuery : $ replaceQuery ,
useRegex : $ findUsesRegex ,
caseSensitive : $ findCaseSensitive ,
statusMessage : $ findStatusMessage ,
onFindNext : { findNext ( ) } ,
onReplace : { replaceSelection ( ) } ,
onReplaceAll : { replaceAll ( ) }
)
2026-02-07 10:51:52 +00:00
#if canImport ( UIKit )
. frame ( maxWidth : 420 )
#else
Add broad language support + Find/Replace panel; minor helpers
Languages:
- Extend picker, detection, TOC, and syntax highlighting for:
swift, python, javascript, typescript, java, kotlin, go, ruby, rust, sql,
html, css, c, cpp, objective-c, json, xml, yaml, toml, ini, markdown,
bash, zsh, powershell, plain
Editor:
- Add Find/Replace sheet with Find Next, Replace, Replace All
- New toolbar button (magnifying glass) to open Find/Replace
- Implement find/replace helpers operating on the active NSTextView
- Small NSRange helper for cleaner optional handling
Syntax highlighting:
- Add lightweight regex patterns for Java, Kotlin, Go, Ruby, Rust, TypeScript,
Objective‑C, SQL, XML, YAML, TOML, INI
- Keep performance-friendly patterns consistent with existing approach
TOC:
- Add TOC generation for Java, Kotlin, Go, Ruby, Rust, TypeScript, Objective‑C
Detection:
- Extend heuristics for XML, YAML, TOML/INI, SQL, Go, Java, Kotlin, TypeScript,
Ruby, Rust, Objective‑C, INI
Testing:
- Verify language picker shows all new entries and switching updates highlighting
- Paste snippets of each language; ensure heuristics pick a sensible default
- Open Find/Replace; test Find Next, Replace, Replace All; verify selection/scroll
- Check large files still perform acceptably with lightweight patterns
2026-02-04 15:21:56 +00:00
. frame ( width : 420 )
2026-02-07 10:51:52 +00:00
#endif
Add broad language support + Find/Replace panel; minor helpers
Languages:
- Extend picker, detection, TOC, and syntax highlighting for:
swift, python, javascript, typescript, java, kotlin, go, ruby, rust, sql,
html, css, c, cpp, objective-c, json, xml, yaml, toml, ini, markdown,
bash, zsh, powershell, plain
Editor:
- Add Find/Replace sheet with Find Next, Replace, Replace All
- New toolbar button (magnifying glass) to open Find/Replace
- Implement find/replace helpers operating on the active NSTextView
- Small NSRange helper for cleaner optional handling
Syntax highlighting:
- Add lightweight regex patterns for Java, Kotlin, Go, Ruby, Rust, TypeScript,
Objective‑C, SQL, XML, YAML, TOML, INI
- Keep performance-friendly patterns consistent with existing approach
TOC:
- Add TOC generation for Java, Kotlin, Go, Ruby, Rust, TypeScript, Objective‑C
Detection:
- Extend heuristics for XML, YAML, TOML/INI, SQL, Go, Java, Kotlin, TypeScript,
Ruby, Rust, Objective‑C, INI
Testing:
- Verify language picker shows all new entries and switching updates highlighting
- Paste snippets of each language; ensure heuristics pick a sensible default
- Open Find/Replace; test Find Next, Replace, Replace All; verify selection/scroll
- Check large files still perform acceptably with lightweight patterns
2026-02-04 15:21:56 +00:00
}
2026-02-07 10:51:52 +00:00
#if os ( iOS )
. sheet ( isPresented : $ showCompactSidebarSheet ) {
NavigationStack {
SidebarView ( content : currentContent , language : currentLanguage )
. navigationTitle ( " Sidebar " )
. toolbar {
ToolbarItem ( placement : . topBarTrailing ) {
Button ( " Done " ) {
showCompactSidebarSheet = false
}
}
}
}
. presentationDetents ( [ . medium , . large ] )
}
#endif
2026-02-06 19:20:03 +00:00
. confirmationDialog ( " Save changes before closing? " , isPresented : $ showUnsavedCloseDialog , titleVisibility : . visible ) {
Button ( " Save " ) { saveAndClosePendingTab ( ) }
Button ( " Don't Save " , role : . destructive ) { discardAndClosePendingTab ( ) }
Button ( " Cancel " , role : . cancel ) {
pendingCloseTabID = nil
}
} message : {
if let pendingCloseTabID ,
let tab = viewModel . tabs . first ( where : { $0 . id = = pendingCloseTabID } ) {
Text ( " \" \( tab . name ) \" has unsaved changes. " )
} else {
Text ( " This file has unsaved changes. " )
}
}
2026-02-07 10:51:52 +00:00
#if canImport ( UIKit )
. fileImporter (
isPresented : $ showIOSFileImporter ,
allowedContentTypes : [ . text , . plainText , . sourceCode , . json , . xml , . yaml ] ,
allowsMultipleSelection : false
) { result in
handleIOSImportResult ( result )
}
. fileExporter (
isPresented : $ showIOSFileExporter ,
document : iosExportDocument ,
contentType : . plainText ,
defaultFilename : iosExportFilename
) { result in
handleIOSExportResult ( result )
}
#endif
2026-01-25 19:02:59 +00:00
. onAppear {
// S t a r t w i t h s i d e b a r c o l l a p s e d b y d e f a u l t
viewModel . showSidebar = false
2026-02-06 18:59:53 +00:00
showProjectStructureSidebar = false
2026-02-06 13:29:34 +00:00
// R e s t o r e B r a i n D u m p m o d e f r o m d e f a u l t s
if UserDefaults . standard . object ( forKey : " BrainDumpModeEnabled " ) != nil {
viewModel . isBrainDumpMode = UserDefaults . standard . bool ( forKey : " BrainDumpModeEnabled " )
}
2026-02-06 18:59:53 +00:00
applyWindowTranslucency ( enableTranslucentWindow )
2026-01-25 19:02:59 +00:00
}
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
2026-02-07 10:51:52 +00:00
private var shouldUseSplitView : Bool {
#if os ( macOS )
return viewModel . showSidebar && ! viewModel . isBrainDumpMode
#else
// K e e p i P h o n e l a y o u t s i n g l e - c o l u m n t o a v o i d h o r i z o n t a l c l i p p i n g .
return viewModel . showSidebar && ! viewModel . isBrainDumpMode && horizontalSizeClass = = . regular
#endif
}
2026-01-25 12:46:33 +00:00
// S i d e b a r s h o w s a l i g h t w e i g h t t a b l e o f c o n t e n t s ( T O C ) d e r i v e d f r o m t h e c u r r e n t d o c u m e n t .
2025-09-25 09:01:45 +00:00
@ ViewBuilder
2026-02-06 18:59:53 +00:00
var sidebarView : some View {
2025-09-25 09:01:45 +00:00
if viewModel . showSidebar && ! viewModel . isBrainDumpMode {
2026-01-17 11:11:26 +00:00
SidebarView ( content : currentContent ,
language : currentLanguage )
. frame ( minWidth : 200 , idealWidth : 250 , maxWidth : 600 )
2025-09-25 09:01:45 +00:00
. animation ( . spring ( ) , value : viewModel . showSidebar )
. safeAreaInset ( edge : . bottom ) {
Divider ( )
}
2026-02-06 13:29:34 +00:00
. background ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color . clear ) )
2026-01-25 19:02:59 +00:00
} else {
EmptyView ( )
2025-09-25 09:01:45 +00:00
}
}
2026-01-17 11:11:26 +00:00
2026-01-25 12:46:33 +00:00
// B i n d i n g s t h a t r e s o l v e t o t h e a c t i v e t a b ( i f p r e s e n t ) o r f a l l b a c k s i n g l e - d o c u m e n t s t a t e .
2026-02-06 18:59:53 +00:00
var currentContentBinding : Binding < String > {
2026-01-17 11:11:26 +00:00
if let tab = viewModel . selectedTab {
return Binding (
get : { tab . content } ,
set : { newValue in viewModel . updateTabContent ( tab : tab , content : newValue ) }
)
} else {
return $ singleContent
}
}
2026-02-06 18:59:53 +00:00
var currentLanguageBinding : Binding < String > {
2026-01-17 11:11:26 +00:00
if let selectedID = viewModel . selectedTabID , let idx = viewModel . tabs . firstIndex ( where : { $0 . id = = selectedID } ) {
return Binding (
get : { viewModel . tabs [ idx ] . language } ,
set : { newValue in viewModel . tabs [ idx ] . language = newValue }
)
} else {
return $ singleLanguage
}
}
2026-02-06 18:59:53 +00:00
var currentContent : String { currentContentBinding . wrappedValue }
var currentLanguage : String { currentLanguageBinding . wrappedValue }
2026-01-17 11:11:26 +00:00
2026-01-25 12:46:33 +00:00
// / D e t e c t s l a n g u a g e u s i n g A p p l e F o u n d a t i o n M o d e l s w h e n a v a i l a b l e , w i t h a h e u r i s t i c f a l l b a c k .
// / R e t u r n s a s u p p o r t e d l a n g u a g e s t r i n g u s e d b y s y n t a x h i g h l i g h t i n g a n d t h e l a n g u a g e p i c k e r .
2026-01-17 12:04:11 +00:00
private func detectLanguageWithAppleIntelligence ( _ text : String ) async -> String {
// S u p p o r t e d l a n g u a g e s i n o u r p i c k e r
2026-02-05 21:30:21 +00:00
let supported = [ " swift " , " python " , " javascript " , " typescript " , " java " , " kotlin " , " go " , " ruby " , " rust " , " sql " , " html " , " css " , " cpp " , " objective-c " , " csharp " , " json " , " xml " , " yaml " , " toml " , " ini " , " markdown " , " bash " , " zsh " , " powershell " , " standard " , " plain " ]
2026-01-17 12:04:11 +00:00
#if USE_FOUNDATION_MODELS
2026-02-05 21:30:21 +00:00
// A t t e m p t a l i g h t w e i g h t m o d e l - b a s e d d e t e c t i o n v i a A p p l e I n t e l l i g e n c e A I C l i e n t i f a v a i l a b l e
2026-01-17 12:04:11 +00:00
do {
2026-02-05 21:30:21 +00:00
let client = AppleIntelligenceAIClient ( )
var response = " "
for await chunk in client . streamSuggestions ( prompt : " Detect the programming or markup language of the following snippet and answer with one of: \( supported . joined ( separator : " , " ) ) . If none match, reply with 'swift'. \n \n Snippet: \n \n \( text ) \n \n Answer: " ) {
response += chunk
}
2026-01-17 12:04:11 +00:00
let detectedRaw = response . trimmingCharacters ( in : CharacterSet . whitespacesAndNewlines ) . lowercased ( )
if let match = supported . first ( where : { detectedRaw . contains ( $0 ) } ) {
return match
}
}
#endif
// H e u r i s t i c f a l l b a c k
let lower = text . lowercased ( )
2026-02-05 21:30:21 +00:00
// N o r m a l i z e c o m m o n C # i n d i c a t o r s t o " c s h a r p " t o e n s u r e t h e p i c k e r h a s a m a t c h i n g t a g
if lower . contains ( " c# " ) || lower . contains ( " c sharp " ) || lower . range ( of : # " \ bcs \ b " # , options : . regularExpression ) != nil || lower . contains ( " .cs " ) {
return " csharp "
}
// C # s t r o n g h e u r i s t i c
if lower . contains ( " using system " ) || lower . contains ( " namespace " ) || lower . contains ( " public class " ) || lower . contains ( " public static void main " ) || lower . contains ( " static void main " ) || lower . contains ( " console.writeline " ) || lower . contains ( " console.readline " ) || lower . contains ( " class program " ) || lower . contains ( " get; set; " ) || lower . contains ( " list< " ) || lower . contains ( " dictionary< " ) || lower . contains ( " ienumerable< " ) || lower . range ( of : # " \ [[A-Za-z_][A-Za-z0-9_]* \ ] " # , options : . regularExpression ) != nil {
return " csharp "
}
2026-01-17 12:04:11 +00:00
if lower . contains ( " import swift " ) || lower . contains ( " struct " ) || lower . contains ( " func " ) {
return " swift "
}
if lower . contains ( " def " ) || ( lower . contains ( " class " ) && lower . contains ( " : " ) ) {
return " python "
}
if lower . contains ( " function " ) || lower . contains ( " const " ) || lower . contains ( " let " ) || lower . contains ( " => " ) {
return " javascript "
}
Add broad language support + Find/Replace panel; minor helpers
Languages:
- Extend picker, detection, TOC, and syntax highlighting for:
swift, python, javascript, typescript, java, kotlin, go, ruby, rust, sql,
html, css, c, cpp, objective-c, json, xml, yaml, toml, ini, markdown,
bash, zsh, powershell, plain
Editor:
- Add Find/Replace sheet with Find Next, Replace, Replace All
- New toolbar button (magnifying glass) to open Find/Replace
- Implement find/replace helpers operating on the active NSTextView
- Small NSRange helper for cleaner optional handling
Syntax highlighting:
- Add lightweight regex patterns for Java, Kotlin, Go, Ruby, Rust, TypeScript,
Objective‑C, SQL, XML, YAML, TOML, INI
- Keep performance-friendly patterns consistent with existing approach
TOC:
- Add TOC generation for Java, Kotlin, Go, Ruby, Rust, TypeScript, Objective‑C
Detection:
- Extend heuristics for XML, YAML, TOML/INI, SQL, Go, Java, Kotlin, TypeScript,
Ruby, Rust, Objective‑C, INI
Testing:
- Verify language picker shows all new entries and switching updates highlighting
- Paste snippets of each language; ensure heuristics pick a sensible default
- Open Find/Replace; test Find Next, Replace, Replace All; verify selection/scroll
- Check large files still perform acceptably with lightweight patterns
2026-02-04 15:21:56 +00:00
// X M L
if lower . contains ( " <?xml " ) || ( lower . contains ( " </ " ) && lower . contains ( " > " ) ) {
return " xml "
}
// Y A M L
if lower . contains ( " : " ) && ( lower . contains ( " - " ) || lower . contains ( " \n " ) ) && ! lower . contains ( " ; " ) {
return " yaml "
}
// T O M L / I N I
if lower . range ( of : # " ^ \ [[^ \ ]]+ \ ] " # , options : [ . regularExpression , . anchored ] ) != nil || ( lower . contains ( " = " ) && lower . contains ( " \n [ " ) ) {
return lower . contains ( " toml " ) ? " toml " : " ini "
}
// S Q L
if lower . range ( of : # " \ b(select|insert|update|delete|create \ s+table|from|where|join) \ b " # , options : . regularExpression ) != nil {
return " sql "
}
// G o
if lower . contains ( " package " ) && lower . contains ( " func " ) {
return " go "
}
// J a v a
if lower . contains ( " public class " ) || lower . contains ( " public static void main " ) {
return " java "
}
// K o t l i n
if ( lower . contains ( " fun " ) || lower . contains ( " val " ) ) || ( lower . contains ( " var " ) && lower . contains ( " : " ) ) {
return " kotlin "
}
// T y p e S c r i p t
if lower . contains ( " interface " ) || ( lower . contains ( " type " ) && lower . contains ( " : " ) ) || lower . contains ( " : string " ) {
return " typescript "
}
// R u b y
if lower . contains ( " def " ) || ( lower . contains ( " end " ) && lower . contains ( " class " ) ) {
return " ruby "
}
// R u s t
if lower . contains ( " fn " ) || lower . contains ( " let mut " ) || lower . contains ( " pub struct " ) {
return " rust "
}
// O b j e c t i v e - C
if lower . contains ( " @interface " ) || lower . contains ( " @implementation " ) || lower . contains ( " #import " ) {
return " objective-c "
}
// I N I
if lower . range ( of : # " ^;.*$ " # , options : . regularExpression ) != nil || lower . range ( of : # " ^ \ w+ \ s*= \ s*.*$ " # , options : . regularExpression ) != nil {
return " ini "
}
2026-01-17 12:04:11 +00:00
if lower . contains ( " <html " ) || lower . contains ( " <div " ) || lower . contains ( " </ " ) {
return " html "
}
2026-02-05 21:30:21 +00:00
// S t r i c t e r C - f a m i l y d e t e c t i o n t o a v o i d m i s c l a s s i f y i n g C #
if lower . contains ( " #include " ) || lower . range ( of : # " ^ \ s*(int|void) \ s+main \ s* \( " #, options: .regularExpression) != nil {
return " cpp "
2026-01-17 12:04:11 +00:00
}
if lower . contains ( " class " ) && ( lower . contains ( " :: " ) || lower . contains ( " template< " ) ) {
return " cpp "
}
if lower . contains ( " ; " ) && lower . contains ( " : " ) && lower . contains ( " { " ) && lower . contains ( " } " ) && lower . contains ( " color: " ) {
return " css "
}
2026-01-23 11:49:52 +00:00
// S h e l l d e t e c t i o n ( b a s h / z s h )
2026-02-05 21:30:21 +00:00
if lower . contains ( " #!/bin/bash " ) || lower . contains ( " #!/usr/bin/env bash " ) || lower . contains ( " declare -a " ) || lower . contains ( " [[ " ) || lower . contains ( " ]] " ) || lower . contains ( " $( " ) {
2026-01-23 11:49:52 +00:00
return " bash "
}
if lower . contains ( " #!/bin/zsh " ) || lower . contains ( " #!/usr/bin/env zsh " ) || lower . contains ( " typeset " ) || lower . contains ( " autoload -Uz " ) || lower . contains ( " setopt " ) {
return " zsh "
}
// G e n e r i c P O S I X s h f a l l b a c k
if lower . contains ( " #!/bin/sh " ) || lower . contains ( " #!/usr/bin/env sh " ) || lower . contains ( " fi " ) || lower . contains ( " do " ) || lower . contains ( " done " ) || lower . contains ( " esac " ) {
return " bash "
}
2026-02-04 13:11:28 +00:00
// P o w e r S h e l l d e t e c t i o n
if lower . contains ( " write-host " ) || lower . contains ( " param( " ) || lower . contains ( " $psversiontable " ) || lower . range ( of : # " \ b(Get|Set|New|Remove|Add|Clear|Write)-[A-Za-z]+ \ b " # , options : . regularExpression ) != nil {
return " powershell "
}
2026-02-05 21:30:21 +00:00
return " standard "
2026-01-17 12:04:11 +00:00
}
2026-01-25 13:29:46 +00:00
// MARK: M a i n e d i t o r s t a c k : h o s t s t h e N S T e x t V i e w - b a c k e d e d i t o r , s t a t u s l i n e , a n d t o o l b a r .
2025-09-25 09:01:45 +00:00
@ ViewBuilder
2026-02-06 18:59:53 +00:00
var editorView : some View {
HStack ( spacing : 0 ) {
VStack ( spacing : 0 ) {
2026-02-06 19:20:03 +00:00
if ! viewModel . isBrainDumpMode {
tabBarView
}
2026-02-06 18:59:53 +00:00
// S i n g l e e d i t o r ( n o T a b V i e w )
CustomTextEditor (
text : currentContentBinding ,
language : currentLanguage ,
colorScheme : colorScheme ,
fontSize : editorFontSize ,
isLineWrapEnabled : $ viewModel . isLineWrapEnabled ,
translucentBackgroundEnabled : enableTranslucentWindow
)
. id ( currentLanguage )
. frame ( maxWidth : viewModel . isBrainDumpMode ? 800 : . infinity )
. frame ( maxHeight : . infinity )
. padding ( . horizontal , viewModel . isBrainDumpMode ? 100 : 0 )
. padding ( . vertical , viewModel . isBrainDumpMode ? 40 : 0 )
. background (
Group {
if enableTranslucentWindow {
Color . clear . background ( . ultraThinMaterial )
} else {
Color . clear
}
2026-02-06 13:29:34 +00:00
}
2026-02-06 18:59:53 +00:00
)
if ! viewModel . isBrainDumpMode {
wordCountView
2026-02-06 13:29:34 +00:00
}
2026-02-06 18:59:53 +00:00
}
2026-01-17 11:11:26 +00:00
2026-02-06 18:59:53 +00:00
if showProjectStructureSidebar && ! viewModel . isBrainDumpMode {
Divider ( )
ProjectStructureSidebarView (
rootFolderURL : projectRootFolderURL ,
nodes : projectTreeNodes ,
selectedFileURL : viewModel . selectedTab ? . fileURL ,
translucentBackgroundEnabled : enableTranslucentWindow ,
2026-02-07 10:51:52 +00:00
onOpenFile : { openFileFromToolbar ( ) } ,
2026-02-06 18:59:53 +00:00
onOpenFolder : { openProjectFolder ( ) } ,
onOpenProjectFile : { openProjectFile ( url : $0 ) } ,
onRefreshTree : { refreshProjectTree ( ) }
)
. frame ( minWidth : 220 , idealWidth : 260 , maxWidth : 340 )
2025-09-25 09:01:45 +00:00
}
}
2026-02-07 10:51:52 +00:00
. frame ( maxWidth : . infinity , maxHeight : . infinity , alignment : . topLeading )
2026-01-17 11:11:26 +00:00
. onReceive ( NotificationCenter . default . publisher ( for : . caretPositionDidChange ) ) { notif in
2026-01-25 12:46:33 +00:00
// U p d a t e s t a t u s l i n e w h e n c a r e t m o v e s
2026-01-17 11:11:26 +00:00
if let line = notif . userInfo ? [ " line " ] as ? Int , let col = notif . userInfo ? [ " column " ] as ? Int {
caretStatus = " Ln \( line ) , Col \( col ) "
}
}
2026-01-17 12:04:11 +00:00
. onReceive ( NotificationCenter . default . publisher ( for : . pastedText ) ) { notif in
if let pasted = notif . object as ? String {
2026-02-06 13:29:34 +00:00
let result = LanguageDetector . shared . detect ( text : pasted , name : nil , fileURL : nil )
currentLanguageBinding . wrappedValue = result . lang = = " plain " ? " swift " : result . lang
2026-01-17 12:04:11 +00:00
}
}
2026-02-07 10:51:52 +00:00
. onReceive ( NotificationCenter . default . publisher ( for : . clearEditorRequested ) ) { _ in
clearEditorContent ( )
}
. onReceive ( NotificationCenter . default . publisher ( for : . toggleCodeCompletionRequested ) ) { _ in
isAutoCompletionEnabled . toggle ( )
}
. onReceive ( NotificationCenter . default . publisher ( for : . showFindReplaceRequested ) ) { _ in
showFindReplace = true
}
. onReceive ( NotificationCenter . default . publisher ( for : . toggleProjectStructureSidebarRequested ) ) { _ in
showProjectStructureSidebar . toggle ( )
}
. onReceive ( NotificationCenter . default . publisher ( for : . showAPISettingsRequested ) ) { _ in
showAISelectorPopover = false
showAPISettings = true
}
. onReceive ( NotificationCenter . default . publisher ( for : . selectAIModelRequested ) ) { notif in
guard let modelRawValue = notif . object as ? String ,
let model = AIModel ( rawValue : modelRawValue ) else { return }
selectedModel = model
}
#if os ( macOS )
2026-01-25 19:02:59 +00:00
. onReceive ( NotificationCenter . default . publisher ( for : NSText . didChangeNotification ) ) { _ in
2026-02-06 13:29:34 +00:00
guard isAutoCompletionEnabled && ! viewModel . isBrainDumpMode else { return }
2026-01-25 19:02:59 +00:00
lastCompletionWorkItem ? . cancel ( )
2026-01-20 23:46:04 +00:00
let work = DispatchWorkItem {
2026-01-25 19:02:59 +00:00
performInlineCompletion ( )
2026-01-20 23:46:04 +00:00
}
2026-01-25 19:02:59 +00:00
lastCompletionWorkItem = work
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.35 , execute : work )
2026-01-20 23:46:04 +00:00
}
2026-02-07 10:51:52 +00:00
#endif
2026-02-06 13:29:34 +00:00
. onChange ( of : enableTranslucentWindow ) { _ , newValue in
2026-02-06 18:59:53 +00:00
applyWindowTranslucency ( newValue )
2026-02-06 13:29:34 +00:00
}
2025-09-25 09:01:45 +00:00
. toolbar {
2026-02-06 18:59:53 +00:00
editorToolbarContent
2025-08-27 11:33:45 +00:00
}
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-02-06 13:29:34 +00:00
. toolbarBackground ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color ( nsColor : . windowBackgroundColor ) ) , for : . windowToolbar )
2026-02-07 10:51:52 +00:00
#else
. toolbarBackground ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color ( . systemBackground ) ) , for : . navigationBar )
#endif
2025-08-27 11:34:59 +00:00
}
2026-01-25 12:46:33 +00:00
// S t a t u s l i n e : c a r e t l o c a t i o n + l i v e w o r d c o u n t f r o m t h e v i e w m o d e l .
2025-09-25 09:01:45 +00:00
@ ViewBuilder
2026-02-06 18:59:53 +00:00
var wordCountView : some View {
2025-09-25 09:01:45 +00:00
HStack {
Spacer ( )
2026-01-17 11:11:26 +00:00
Text ( " \( caretStatus ) • Words: \( viewModel . wordCount ( for : currentContent ) ) " )
2025-09-25 09:01:45 +00:00
. font ( . system ( size : 12 ) )
. foregroundColor ( . secondary )
. padding ( . bottom , 8 )
. padding ( . trailing , 16 )
2025-08-26 11:22:53 +00:00
}
2026-02-06 13:29:34 +00:00
. background ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color . clear ) )
2025-08-26 11:22:53 +00:00
}
Add broad language support + Find/Replace panel; minor helpers
Languages:
- Extend picker, detection, TOC, and syntax highlighting for:
swift, python, javascript, typescript, java, kotlin, go, ruby, rust, sql,
html, css, c, cpp, objective-c, json, xml, yaml, toml, ini, markdown,
bash, zsh, powershell, plain
Editor:
- Add Find/Replace sheet with Find Next, Replace, Replace All
- New toolbar button (magnifying glass) to open Find/Replace
- Implement find/replace helpers operating on the active NSTextView
- Small NSRange helper for cleaner optional handling
Syntax highlighting:
- Add lightweight regex patterns for Java, Kotlin, Go, Ruby, Rust, TypeScript,
Objective‑C, SQL, XML, YAML, TOML, INI
- Keep performance-friendly patterns consistent with existing approach
TOC:
- Add TOC generation for Java, Kotlin, Go, Ruby, Rust, TypeScript, Objective‑C
Detection:
- Extend heuristics for XML, YAML, TOML/INI, SQL, Go, Java, Kotlin, TypeScript,
Ruby, Rust, Objective‑C, INI
Testing:
- Verify language picker shows all new entries and switching updates highlighting
- Paste snippets of each language; ensure heuristics pick a sensible default
- Open Find/Replace; test Find Next, Replace, Replace All; verify selection/scroll
- Check large files still perform acceptably with lightweight patterns
2026-02-04 15:21:56 +00:00
2026-02-06 19:20:03 +00:00
@ ViewBuilder
var tabBarView : some View {
ScrollView ( . horizontal , showsIndicators : false ) {
HStack ( spacing : 6 ) {
ForEach ( viewModel . tabs ) { tab in
HStack ( spacing : 6 ) {
Button {
viewModel . selectedTabID = tab . id
} label : {
Text ( tab . name + ( tab . isDirty ? " • " : " " ) )
. lineLimit ( 1 )
. font ( . system ( size : 12 , weight : viewModel . selectedTabID = = tab . id ? . semibold : . regular ) )
}
. buttonStyle ( . plain )
Button {
requestCloseTab ( tab )
} label : {
Image ( systemName : " xmark " )
. font ( . system ( size : 10 , weight : . bold ) )
}
. buttonStyle ( . plain )
. help ( " Close \( tab . name ) " )
}
. padding ( . horizontal , 10 )
. padding ( . vertical , 6 )
. background (
RoundedRectangle ( cornerRadius : 8 , style : . continuous )
. fill ( viewModel . selectedTabID = = tab . id ? Color . accentColor . opacity ( 0.18 ) : Color . secondary . opacity ( 0.10 ) )
)
}
}
. padding ( . horizontal , 10 )
. padding ( . vertical , 6 )
}
2026-02-07 10:51:52 +00:00
#if os ( macOS )
2026-02-06 19:20:03 +00:00
. background ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color ( nsColor : . windowBackgroundColor ) ) )
2026-02-07 10:51:52 +00:00
#else
. background ( enableTranslucentWindow ? AnyShapeStyle ( . ultraThinMaterial ) : AnyShapeStyle ( Color ( . systemBackground ) ) )
#endif
2026-02-06 19:20:03 +00:00
}
2026-01-17 11:11:26 +00:00
}