2026-01-17 11:11:26 +00:00
// S i m p l i f i e d : S i n g l e - d o c u m e n t e d i t o r w i t h o u t t a b s ; r e m o v e d A I M o d e l r e f e r e n c e s a n d f i x e d c o m p i l e e r r o r s
2025-08-25 07:39:12 +00:00
import SwiftUI
2025-08-26 17:25:39 +00:00
import AppKit
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
enum AIModel : String , CaseIterable , Identifiable {
case appleIntelligence
case grok
var id : String { rawValue }
}
2025-09-25 09:01:45 +00:00
// E x t e n s i o n t o c a l c u l a t e s t r i n g w i d t h
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
}
}
2025-08-27 11:34:59 +00:00
2025-09-25 09:01:45 +00:00
struct ContentView : View {
@ EnvironmentObject private var viewModel : EditorViewModel
@ Environment ( \ . colorScheme ) private var colorScheme
@ Environment ( \ . showGrokError ) private var showGrokError
@ Environment ( \ . grokErrorMessage ) private var grokErrorMessage
2026-01-17 11:11:26 +00:00
// 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 i n c a s e t h e v i e w m o d e l d o e s n ' t e x p o s e o n e
@ State private var selectedModel : AIModel = . appleIntelligence
2025-09-25 09:01:45 +00:00
@ State private var singleContent : String = " "
@ State private var singleLanguage : String = " swift "
2026-01-17 11:11:26 +00:00
@ State private var caretStatus : String = " Ln 1, Col 1 "
@ State private var editorFontSize : CGFloat = 14
2025-08-27 11:33:45 +00:00
var body : some View {
2025-08-27 11:34:59 +00:00
NavigationSplitView {
2025-09-25 09:01:45 +00:00
sidebarView
2025-08-27 11:34:59 +00:00
} detail : {
2025-09-25 09:01:45 +00:00
editorView
}
2026-01-17 11:11:26 +00:00
. navigationSplitViewColumnWidth ( min : 200 , ideal : 250 , max : 600 )
2025-09-25 09:01:45 +00:00
. frame ( minWidth : 600 , minHeight : 400 )
. alert ( " AI Error " , isPresented : showGrokError ) {
Button ( " OK " ) { }
} message : {
Text ( grokErrorMessage . wrappedValue )
}
. navigationTitle ( " NeonVision Editor " )
}
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
@ ViewBuilder
private var sidebarView : some View {
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-01-17 11:11:26 +00:00
private var currentContentBinding : Binding < String > {
if let tab = viewModel . selectedTab {
return Binding (
get : { tab . content } ,
set : { newValue in viewModel . updateTabContent ( tab : tab , content : newValue ) }
)
} else {
return $ singleContent
}
}
private var currentLanguageBinding : Binding < String > {
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
}
}
private var currentContent : String { currentContentBinding . wrappedValue }
private var currentLanguage : String { currentLanguageBinding . wrappedValue }
2026-01-17 12:04:11 +00:00
// A p p l e F o u n d a t i o n M o d e l s - p o w e r e d l a n g u a g e d e t e c t i o n w i t h h e u r i s t i c f a l l b a c k
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
let supported = [ " swift " , " python " , " javascript " , " html " , " css " , " c " , " cpp " , " json " , " markdown " ]
// T r y o n - d e v i c e F o u n d a t i o n M o d e l f i r s t
#if USE_FOUNDATION_MODELS
do {
// C r e a t e a s m a l l , f a s t m o d e l s u i t a b l e f o r c l a s s i f i c a t i o n
// N O T E : A d j u s t t h e i n i t i a l i z e r a n d e n u m c a s e s t o m a t c h y o u r S D K .
let model = try FMTextModel ( . small )
let 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: "
let response = try await model . generate ( prompt )
let detectedRaw = response . trimmingCharacters ( in : CharacterSet . whitespacesAndNewlines ) . lowercased ( )
if let match = supported . first ( where : { detectedRaw . contains ( $0 ) } ) {
return match
}
} catch {
// F a l l t h r o u g h t o h e u r i s t i c
}
#endif
// H e u r i s t i c f a l l b a c k
let lower = text . lowercased ( )
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 "
}
if lower . contains ( " <html " ) || lower . contains ( " <div " ) || lower . contains ( " </ " ) {
return " html "
}
if lower . contains ( " { " ) && lower . contains ( " } " ) && lower . contains ( " : " ) && ! lower . contains ( " ; " ) && ! lower . contains ( " function " ) {
return " json "
}
if lower . contains ( " # " ) || lower . contains ( " ## " ) {
return " markdown "
}
if lower . contains ( " #include " ) || lower . contains ( " int " ) || lower . contains ( " void " ) {
return " c "
}
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 "
}
return " swift "
}
2025-09-25 09:01:45 +00:00
@ ViewBuilder
private var editorView : some View {
VStack ( spacing : 0 ) {
2026-01-17 11:11:26 +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
)
2026-01-17 11:36:31 +00:00
. id ( currentLanguage )
2026-01-17 11:11:26 +00:00
. frame ( maxWidth : viewModel . isBrainDumpMode ? 800 : . infinity )
. frame ( maxHeight : . infinity )
. padding ( . horizontal , viewModel . isBrainDumpMode ? 100 : 0 )
. padding ( . vertical , viewModel . isBrainDumpMode ? 40 : 0 )
2025-09-25 09:01:45 +00:00
if ! viewModel . isBrainDumpMode {
wordCountView
}
}
2026-01-17 11:11:26 +00:00
. onReceive ( NotificationCenter . default . publisher ( for : . caretPositionDidChange ) ) { notif in
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 {
Task { @ MainActor in
let detected = await detectLanguageWithAppleIntelligence ( pasted )
currentLanguageBinding . wrappedValue = detected
}
}
}
2025-09-25 09:01:45 +00:00
. toolbar {
2026-01-17 11:11:26 +00:00
ToolbarItemGroup ( placement : . primaryAction ) {
HStack ( spacing : 8 ) {
Picker ( " AI Model " , selection : $ selectedModel ) {
Text ( " Apple Intelligence " ) . tag ( AIModel . appleIntelligence )
Text ( " Grok " ) . tag ( AIModel . grok )
}
. labelsHidden ( )
. controlSize ( . large )
. frame ( width : 170 )
. padding ( . vertical , 2 )
Picker ( " Language " , selection : currentLanguageBinding ) {
ForEach ( [ " swift " , " python " , " javascript " , " html " , " css " , " c " , " cpp " , " json " , " markdown " ] , id : \ . self ) { lang in
Text ( lang . capitalized ) . tag ( lang )
2025-08-27 11:34:59 +00:00
}
}
2026-01-17 11:11:26 +00:00
. labelsHidden ( )
. controlSize ( . large )
. frame ( width : 140 )
. padding ( . vertical , 2 )
Divider ( )
Button ( action : { editorFontSize = max ( 8 , editorFontSize - 1 ) } ) {
Image ( systemName : " textformat.size.smaller " )
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
. help ( " Decrease Font Size " )
Button ( action : { editorFontSize = min ( 48 , editorFontSize + 1 ) } ) {
Image ( systemName : " textformat.size.larger " )
}
. help ( " Increase Font Size " )
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
}
ToolbarItemGroup ( placement : . automatic ) {
Button ( action : { viewModel . openFile ( ) } ) {
Image ( systemName : " folder " )
2025-08-27 11:33:45 +00:00
}
2026-01-17 11:11:26 +00:00
Button ( action : {
if let tab = viewModel . selectedTab { viewModel . saveFile ( tab : tab ) }
} ) {
2025-09-25 09:01:45 +00:00
Image ( systemName : " square.and.arrow.down " )
2025-08-27 11:33:45 +00:00
}
2025-09-25 09:01:45 +00:00
. disabled ( viewModel . selectedTab = = nil )
Button ( action : { viewModel . showSidebar . toggle ( ) } ) {
Image ( systemName : viewModel . showSidebar ? " sidebar.left " : " sidebar.right " )
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
Button ( action : { viewModel . isBrainDumpMode . toggle ( ) } ) {
Image ( systemName : " note.text " )
2025-08-27 11:34:59 +00:00
}
2025-08-27 11:33:45 +00:00
}
}
2026-01-17 11:11:26 +00:00
. toolbarBackground ( . visible , for : . windowToolbar )
. toolbarBackground ( Color ( nsColor : . windowBackgroundColor ) , for : . windowToolbar )
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
@ ViewBuilder
private var wordCountView : some View {
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
}
}
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
struct SidebarView : View {
let content : String
let language : String
@ State private var selectedTOCItem : String ?
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
var body : some View {
List ( generateTableOfContents ( ) , id : \ . self , selection : $ selectedTOCItem ) { item in
2026-01-17 11:11:26 +00:00
Button ( action : {
// E x p e c t i t e m f o r m a t : " . . . ( L i n e N ) "
if let startRange = item . range ( of : " (Line " ) ,
let endRange = item . range ( of : " ) " , range : startRange . upperBound . . < item . endIndex ) {
let numberStr = item [ startRange . upperBound . . < endRange . lowerBound ]
if let lineOneBased = Int ( numberStr . trimmingCharacters ( in : . whitespaces ) ) , lineOneBased > 0 {
DispatchQueue . main . async {
NotificationCenter . default . post ( name : . moveCursorToLine , object : lineOneBased )
}
2025-09-25 09:01:45 +00:00
}
}
2026-01-17 11:11:26 +00:00
} ) {
Text ( item )
. font ( . system ( size : 13 ) )
. foregroundColor ( . primary )
. padding ( . vertical , 4 )
. padding ( . horizontal , 8 )
. tag ( item )
}
. buttonStyle ( . plain )
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
. listStyle ( . sidebar )
. frame ( maxWidth : . infinity , alignment : . leading )
2026-01-17 11:11:26 +00:00
. onChange ( of : selectedTOCItem ) { oldValue , newValue in
guard let item = newValue else { return }
if let startRange = item . range ( of : " (Line " ) ,
let endRange = item . range ( of : " ) " , range : startRange . upperBound . . < item . endIndex ) {
let numberStr = item [ startRange . upperBound . . < endRange . lowerBound ]
if let lineOneBased = Int ( numberStr . trimmingCharacters ( in : . whitespaces ) ) , lineOneBased > 0 {
DispatchQueue . main . async {
NotificationCenter . default . post ( name : . moveCursorToLine , object : lineOneBased )
}
}
}
}
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
func generateTableOfContents ( ) -> [ String ] {
guard ! content . isEmpty else { return [ " No content available " ] }
let lines = content . components ( separatedBy : . newlines )
var toc : [ String ] = [ ]
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
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
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
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 ( " <h " ) ) {
return " \( trimmed ) (Line \( index + 1 ) ) "
}
return nil
}
default :
return [ " Unsupported language " ]
2025-08-25 07:39:12 +00:00
}
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
return toc . isEmpty ? [ " No headers found " ] : toc
2025-08-25 07:39:12 +00:00
}
2026-01-17 11:11:26 +00:00
}
final class AcceptingTextView : NSTextView {
override var acceptsFirstResponder : Bool { true }
override var mouseDownCanMoveWindow : Bool { false }
override var isOpaque : Bool { false }
override func insertText ( _ insertString : Any , replacementRange : NSRange ) {
guard let s = insertString as ? String else {
super . insertText ( insertString , replacementRange : replacementRange )
return
}
if s = = " \n " {
// A u t o - i n d e n t : c o p y l e a d i n g w h i t e s p a c e f r o m c u r r e n t l i n e
let ns = ( string as NSString )
let sel = selectedRange ( )
let lineRange = ns . lineRange ( for : NSRange ( location : sel . location , length : 0 ) )
let currentLine = ns . substring ( with : NSRange ( location : lineRange . location , length : sel . location - lineRange . location ) )
let indent = currentLine . prefix { $0 = = " " || $0 = = " \t " }
super . insertText ( " \n " + indent , replacementRange : replacementRange )
return
}
// B r a c k e t / q u o t e p a i r i n g
let pairs : [ String : String ] = [ " ( " : " ) " , " [ " : " ] " , " { " : " } " , " \" " : " \" " , " ' " : " ' " ]
if let closing = pairs [ s ] {
let sel = selectedRange ( )
super . insertText ( s + closing , replacementRange : replacementRange )
setSelectedRange ( NSRange ( location : sel . location + 1 , length : 0 ) )
return
}
super . insertText ( insertString , replacementRange : replacementRange )
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:36:31 +00:00
override func paste ( _ sender : Any ? ) {
// R e m e m b e r t h e i n s e r t i o n s t a r t
let start = selectedRange ( ) . location
2026-01-17 12:04:11 +00:00
let pastedString = NSPasteboard . general . string ( forType : . string )
2026-01-17 11:36:31 +00:00
super . paste ( sender )
2026-01-17 12:04:11 +00:00
if let pastedString , ! pastedString . isEmpty {
NotificationCenter . default . post ( name : . pastedText , object : pastedString )
}
2026-01-17 11:36:31 +00:00
// R e s t o r e c a r e t t o t h e s t a r t o f t h e p a s t e d c o n t e n t a n d k e e p t h a t v i s i b l e
let range = NSRange ( location : start , length : 0 )
setSelectedRange ( range )
scrollRangeToVisible ( range )
}
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
struct CustomTextEditor : NSViewRepresentable {
2025-08-27 11:34:59 +00:00
@ Binding var text : String
let language : String
2025-09-25 09:01:45 +00:00
let colorScheme : ColorScheme
2026-01-17 11:11:26 +00:00
let fontSize : CGFloat
@ Binding var isLineWrapEnabled : Bool
private func applyWrapMode ( isWrapped : Bool , textView : NSTextView , scrollView : NSScrollView ) {
if isWrapped {
// W r a p : t r a c k t h e t e x t v i e w w i d t h , n o h o r i z o n t a l s c r o l l i n g
textView . isHorizontallyResizable = false
textView . textContainer ? . widthTracksTextView = true
textView . textContainer ? . heightTracksTextView = false
scrollView . hasHorizontalScroller = false
// E n s u r e t h e c o n t a i n e r w i d t h m a t c h e s t h e v i s i b l e c o n t e n t w i d t h
let contentWidth = scrollView . contentSize . width
let width = contentWidth > 0 ? contentWidth : scrollView . frame . size . width
textView . textContainer ? . containerSize = NSSize ( width : width , height : CGFloat . greatestFiniteMagnitude )
} else {
// N o w r a p : a l l o w h o r i z o n t a l e x p a n s i o n a n d h o r i z o n t a l s c r o l l i n g
textView . isHorizontallyResizable = true
textView . textContainer ? . widthTracksTextView = false
textView . textContainer ? . heightTracksTextView = false
scrollView . hasHorizontalScroller = true
textView . textContainer ? . containerSize = NSSize ( width : CGFloat . greatestFiniteMagnitude , height : CGFloat . greatestFiniteMagnitude )
}
}
2025-08-26 17:25:39 +00:00
func makeNSView ( context : Context ) -> NSScrollView {
2026-01-17 11:36:31 +00:00
// U s e A c c e p t i n g T e x t V i e w i n s i d e a s c r o l l v i e w i n s t e a d o f N S T e x t V i e w f a c t o r y m e t h o d
let scrollView = NSScrollView ( )
2026-01-17 11:11:26 +00:00
scrollView . drawsBackground = false
scrollView . autohidesScrollers = true
scrollView . hasVerticalScroller = true
scrollView . contentView . postsBoundsChangedNotifications = true
2025-09-25 09:01:45 +00:00
2026-01-17 11:36:31 +00:00
let textView = AcceptingTextView ( frame : . zero )
2026-01-17 11:11:26 +00:00
textView . isEditable = true
2025-08-27 11:34:59 +00:00
textView . isRichText = false
2025-09-25 09:01:45 +00:00
textView . usesFindBar = true
2026-01-17 11:11:26 +00:00
textView . isVerticallyResizable = true
textView . isHorizontallyResizable = false
textView . font = NSFont . monospacedSystemFont ( ofSize : fontSize , weight : . regular )
textView . backgroundColor = . textBackgroundColor
textView . textContainerInset = NSSize ( width : 12 , height : 12 )
textView . minSize = NSSize ( width : 0 , height : 0 )
textView . maxSize = NSSize ( width : CGFloat . greatestFiniteMagnitude , height : CGFloat . greatestFiniteMagnitude )
textView . isSelectable = true
2025-09-25 09:01:45 +00:00
textView . allowsUndo = true
2026-01-17 11:11:26 +00:00
textView . textColor = . labelColor
textView . insertionPointColor = . controlAccentColor
textView . drawsBackground = true
textView . isAutomaticTextCompletionEnabled = false
// D i s a b l e s m a r t s u b s t i t u t i o n s / d e t e c t i o n s t h a t c a n i n t e r f e r e w i t h s e l e c t i o n w h e n r e c o l o r i n g
2025-09-25 09:01:45 +00:00
textView . isAutomaticQuoteSubstitutionEnabled = false
2026-01-17 11:11:26 +00:00
textView . isAutomaticDashSubstitutionEnabled = false
2025-09-25 09:01:45 +00:00
textView . isAutomaticDataDetectionEnabled = false
textView . isAutomaticLinkDetectionEnabled = false
2026-01-17 11:11:26 +00:00
textView . isGrammarCheckingEnabled = false
textView . isContinuousSpellCheckingEnabled = false
textView . smartInsertDeleteEnabled = false
2025-09-25 09:01:45 +00:00
2026-01-17 11:36:31 +00:00
// E m b e d t h e t e x t v i e w i n t h e s c r o l l v i e w
scrollView . documentView = textView
// C o n f i g u r e t h e t e x t v i e w d e l e g a t e
2026-01-17 11:11:26 +00:00
textView . delegate = context . coordinator
2025-09-25 09:01:45 +00:00
2026-01-17 11:11:26 +00:00
// A d d l i n e n u m b e r r u l e r
scrollView . hasVerticalRuler = true
scrollView . rulersVisible = true
scrollView . verticalRulerView = LineNumberRulerView ( textView : textView )
2025-09-25 09:01:45 +00:00
2026-01-17 11:11:26 +00:00
// A p p l y w r a p p i n g m o d e c o n f i g u r a t i o n
applyWrapMode ( isWrapped : isLineWrapEnabled , textView : textView , scrollView : scrollView )
2025-09-25 09:01:45 +00:00
2026-01-17 11:11:26 +00:00
// S e e d i n i t i a l t e x t
textView . string = text
DispatchQueue . main . async { [ weak scrollView , weak textView ] in
guard let sv = scrollView , let tv = textView else { return }
sv . window ? . makeFirstResponder ( tv )
}
context . coordinator . scheduleHighlightIfNeeded ( currentText : text )
2025-09-25 09:01:45 +00:00
2026-01-17 11:11:26 +00:00
NotificationCenter . default . addObserver ( forName : NSView . boundsDidChangeNotification , object : scrollView . contentView , queue : . main ) { [ weak textView , weak scrollView ] _ in
guard let tv = textView , let sv = scrollView else { return }
if tv . textContainer ? . widthTracksTextView = = true {
tv . textContainer ? . containerSize . width = sv . contentSize . width
if let container = tv . textContainer {
tv . layoutManager ? . ensureLayout ( for : container )
}
}
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
context . coordinator . textView = textView
2025-08-26 17:25:39 +00:00
return scrollView
2025-08-25 07:39:12 +00:00
}
2026-01-17 11:11:26 +00:00
2025-08-26 17:25:39 +00:00
func updateNSView ( _ nsView : NSScrollView , context : Context ) {
2025-08-27 11:34:59 +00:00
if let textView = nsView . documentView as ? NSTextView {
2026-01-17 11:11:26 +00:00
if textView . string != text {
textView . string = text
}
if textView . font ? . pointSize != fontSize {
textView . font = NSFont . monospacedSystemFont ( ofSize : fontSize , weight : . regular )
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
// K e e p t h e t e x t c o n t a i n e r w i d t h i n s y n c & r e l a y o u t
applyWrapMode ( isWrapped : isLineWrapEnabled , textView : textView , scrollView : nsView )
2025-09-25 09:01:45 +00:00
if let container = textView . textContainer {
2026-01-17 11:11:26 +00:00
textView . layoutManager ? . ensureLayout ( for : container )
2025-08-27 11:34:59 +00:00
}
2025-09-25 09:01:45 +00:00
textView . invalidateIntrinsicContentSize ( )
2026-01-17 11:11:26 +00:00
// O n l y s c h e d u l e h i g h l i g h t i f n e e d e d ( e . g . , l a n g u a g e / c o l o r s c h e m e c h a n g e s o r e x t e r n a l t e x t u p d a t e s )
2026-01-17 11:36:31 +00:00
context . coordinator . parent = self
2026-01-17 11:11:26 +00:00
context . coordinator . scheduleHighlightIfNeeded ( )
2025-08-26 09:49:52 +00:00
}
}
2026-01-17 11:11:26 +00:00
2025-08-26 09:49:52 +00:00
func makeCoordinator ( ) -> Coordinator {
Coordinator ( self )
}
2026-01-17 11:11:26 +00:00
2025-08-26 09:49:52 +00:00
class Coordinator : NSObject , NSTextViewDelegate {
2025-09-25 09:01:45 +00:00
var parent : CustomTextEditor
weak var textView : NSTextView ?
2026-01-17 11:11:26 +00:00
private let highlightQueue = DispatchQueue ( label : " NeonVision.SyntaxHighlight " , qos : . userInitiated )
private var pendingHighlight : DispatchWorkItem ?
private var lastHighlightedText : String = " "
private var lastLanguage : String ?
private var lastColorScheme : ColorScheme ?
2025-09-25 09:01:45 +00:00
init ( _ parent : CustomTextEditor ) {
2025-08-26 09:49:52 +00:00
self . parent = parent
2025-09-25 09:01:45 +00:00
super . init ( )
NotificationCenter . default . addObserver ( self , selector : #selector ( moveToLine ( _ : ) ) , name : . moveCursorToLine , object : nil )
NotificationCenter . default . addObserver ( self , selector : #selector ( streamSuggestion ( _ : ) ) , name : . streamSuggestion , object : nil )
2025-08-26 09:49:52 +00:00
}
2026-01-17 11:11:26 +00:00
deinit {
NotificationCenter . default . removeObserver ( self )
2025-08-25 07:39:12 +00:00
}
2026-01-17 11:11:26 +00:00
func scheduleHighlightIfNeeded ( currentText : String ? = nil ) {
guard textView != nil else { return }
// D e f e r h i g h l i g h t i n g w h i l e a m o d a l p a n e l i s p r e s e n t e d ( e . g . , N S S a v e P a n e l )
if NSApp . modalWindow != nil {
pendingHighlight ? . cancel ( )
let work = DispatchWorkItem { [ weak self ] in
self ? . scheduleHighlightIfNeeded ( currentText : currentText )
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:11:26 +00:00
pendingHighlight = work
highlightQueue . asyncAfter ( deadline : . now ( ) + 0.3 , execute : work )
return
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:11:26 +00:00
let lang = parent . language
let scheme = parent . colorScheme
let text = currentText ? ? textView ? . string ? ? " "
if text = = lastHighlightedText && lastLanguage = = lang && lastColorScheme = = scheme {
return
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:11:26 +00:00
rehighlight ( )
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
func rehighlight ( ) {
guard let textView = textView else { return }
// S n a p s h o t c u r r e n t s t a t e
let textSnapshot = textView . string
let language = parent . language
let scheme = parent . colorScheme
let selected = textView . selectedRange ( )
let colors = SyntaxColors . fromVibrantLightTheme ( colorScheme : scheme )
let patterns = getSyntaxPatterns ( for : language , colors : colors )
// C a n c e l a n y i n - f l i g h t w o r k
pendingHighlight ? . cancel ( )
let work = DispatchWorkItem { [ weak self ] in
// C o m p u t e m a t c h e s o f f t h e m a i n t h r e a d
let nsText = textSnapshot as NSString
let fullRange = NSRange ( location : 0 , length : nsText . length )
var coloredRanges : [ ( NSRange , Color ) ] = [ ]
for ( pattern , color ) in patterns {
guard let regex = try ? NSRegularExpression ( pattern : pattern , options : [ . anchorsMatchLines ] ) else { continue }
let matches = regex . matches ( in : textSnapshot , range : fullRange )
for match in matches {
coloredRanges . append ( ( match . range , color ) )
}
}
DispatchQueue . main . async { [ weak self ] in
guard let self = self , let tv = self . textView else { return }
// D i s c a r d i f t e x t c h a n g e d s i n c e w e s t a r t e d
guard tv . string = = textSnapshot else { return }
tv . textStorage ? . beginEditing ( )
// C l e a r p r e v i o u s c o l o r i n g a n d a p p l y b a s e c o l o r
tv . textStorage ? . removeAttribute ( . foregroundColor , range : fullRange )
tv . textStorage ? . addAttribute ( . foregroundColor , value : tv . textColor ? ? NSColor . labelColor , range : fullRange )
// A p p l y c o l o r e d r a n g e s
for ( range , color ) in coloredRanges {
tv . textStorage ? . addAttribute ( . foregroundColor , value : NSColor ( color ) , range : range )
}
tv . textStorage ? . endEditing ( )
// R e s t o r e s e l e c t i o n o n l y i f i t h a s n ' t c h a n g e d s i n c e w e s t a r t e d
if NSEqualRanges ( tv . selectedRange ( ) , selected ) {
tv . setSelectedRange ( selected )
}
// U p d a t e l a s t h i g h l i g h t e d s t a t e
self . lastHighlightedText = textSnapshot
self . lastLanguage = language
self . lastColorScheme = scheme
}
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:11:26 +00:00
pendingHighlight = work
// D e b o u n c e s l i g h t l y t o a v o i d t h r a s h i n g w h i l e t y p i n g
highlightQueue . asyncAfter ( deadline : . now ( ) + 0.12 , execute : work )
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
func textDidChange ( _ notification : Notification ) {
guard let textView = notification . object as ? NSTextView else { return }
parent . text = textView . string
2026-01-17 11:11:26 +00:00
updateCaretStatusAndHighlight ( )
scheduleHighlightIfNeeded ( currentText : parent . text )
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:11:26 +00:00
func textViewDidChangeSelection ( _ notification : Notification ) {
updateCaretStatusAndHighlight ( )
}
private func updateCaretStatusAndHighlight ( ) {
guard let tv = textView else { return }
let ns = tv . string as NSString
let sel = tv . selectedRange ( )
let location = sel . location
let prefix = ns . substring ( to : min ( location , ns . length ) )
let line = prefix . reduce ( 1 ) { $1 = = " \n " ? $0 + 1 : $0 }
let col : Int = {
if let lastNL = prefix . lastIndex ( of : " \n " ) {
return prefix . distance ( from : lastNL , to : prefix . endIndex ) - 1
} else {
return prefix . count
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
} ( )
NotificationCenter . default . post ( name : . caretPositionDidChange , object : nil , userInfo : [ " line " : line , " column " : col ] )
// H i g h l i g h t c u r r e n t l i n e
let lineRange = ns . lineRange ( for : NSRange ( location : location , length : 0 ) )
let fullRange = NSRange ( location : 0 , length : ns . length )
tv . textStorage ? . beginEditing ( )
tv . textStorage ? . removeAttribute ( . backgroundColor , range : fullRange )
tv . textStorage ? . addAttribute ( . backgroundColor , value : NSColor . selectedTextBackgroundColor . withAlphaComponent ( 0.12 ) , range : lineRange )
tv . textStorage ? . endEditing ( )
2025-08-26 17:25:39 +00:00
}
2025-08-27 11:34:59 +00:00
2026-01-17 11:11:26 +00:00
@objc func moveToLine ( _ notification : Notification ) {
guard let lineOneBased = notification . object as ? Int ,
let textView = textView else { return }
2025-08-26 17:25:39 +00:00
2026-01-17 11:11:26 +00:00
// I f t h e r e ' s n o t e x t , n o t h i n g t o d o
let currentText = textView . string
guard ! currentText . isEmpty else { return }
2025-08-27 11:34:59 +00:00
2026-01-17 11:11:26 +00:00
// C a n c e l a n y i n - f l i g h t h i g h l i g h t t o p r e v e n t i t f r o m r e s t o r i n g a n o l d s e l e c t i o n
pendingHighlight ? . cancel ( )
2025-09-25 09:01:45 +00:00
2026-01-17 11:11:26 +00:00
// W o r k w i t h N S S t r i n g / U T F - 1 6 i n d i c e s t o m a t c h N S T e x t V i e w e x p e c t a t i o n s
let ns = currentText as NSString
let totalLength = ns . length
2025-08-25 07:39:12 +00:00
2026-01-17 11:11:26 +00:00
// C l a m p t a r g e t l i n e t o a v a i l a b l e l i n e c o u n t ( 1 - b a s e d i n p u t )
let linesArray = currentText . components ( separatedBy : . newlines )
let clampedLineIndex = max ( 1 , min ( lineOneBased , linesArray . count ) ) - 1 // 0 - b a s e d i n d e x
// C o m p u t e t h e U T F - 1 6 l o c a t i o n b y s u m m i n g U T F - 1 6 l e n g t h s o f p r e c e d i n g l i n e s + n e w l i n e c h a r a c t e r s
var location = 0
if clampedLineIndex > 0 {
for i in 0. . < ( clampedLineIndex ) {
let lineNSString = linesArray [ i ] as NSString
location += lineNSString . length
// A d d o n e f o r t h e n e w l i n e t h a t s e p a r a t e s l i n e s , a s c o m p o n e n t s ( s e p a r a t e d B y : ) d r o p s s e p a r a t o r s
location += 1
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
}
// S a f e t y c l a m p
location = max ( 0 , min ( location , totalLength ) )
2025-08-27 11:34:59 +00:00
2026-01-17 11:11:26 +00:00
// M o v e c a r e t a n d s c r o l l i n t o v i e w o n t h e m a i n t h r e a d
DispatchQueue . main . async { [ weak self ] in
guard let self = self , let tv = self . textView else { return }
tv . window ? . makeFirstResponder ( tv )
// E n s u r e l a y o u t i s u p - t o - d a t e b e f o r e s c r o l l i n g
if let container = tv . textContainer {
tv . layoutManager ? . ensureLayout ( for : container )
2025-09-25 09:01:45 +00:00
}
2026-01-17 11:11:26 +00:00
tv . setSelectedRange ( NSRange ( location : location , length : 0 ) )
tv . scrollRangeToVisible ( NSRange ( location : location , length : 0 ) )
// S t r o n g e r h i g h l i g h t f o r t h e e n t i r e t a r g e t l i n e
let lineRange = ns . lineRange ( for : NSRange ( location : location , length : 0 ) )
let fullRange = NSRange ( location : 0 , length : totalLength )
tv . textStorage ? . beginEditing ( )
tv . textStorage ? . removeAttribute ( . backgroundColor , range : fullRange )
tv . textStorage ? . addAttribute ( . backgroundColor , value : NSColor . selectedTextBackgroundColor . withAlphaComponent ( 0.18 ) , range : lineRange )
tv . textStorage ? . endEditing ( )
}
}
@objc func streamSuggestion ( _ notification : Notification ) {
guard let stream = notification . object as ? AsyncStream < String > ,
let textView = textView else { return }
Task {
for await chunk in stream {
textView . textStorage ? . append ( NSAttributedString ( string : chunk ) )
textView . scrollToEndOfDocument ( nil )
parent . text = textView . string
2025-08-27 11:34:59 +00:00
}
}
}
}
2025-08-25 07:39:12 +00:00
}
2025-08-27 11:34:59 +00:00
2026-01-17 11:11:26 +00:00
final class LineNumberRulerView : NSRulerView {
private weak var textView : NSTextView ?
private let font = NSFont . monospacedSystemFont ( ofSize : 11 , weight : . regular )
private let textColor = NSColor . secondaryLabelColor
private let inset : CGFloat = 4
init ( textView : NSTextView ) {
self . textView = textView
super . init ( scrollView : textView . enclosingScrollView , orientation : . verticalRuler )
self . clientView = textView
self . ruleThickness = 44
NotificationCenter . default . addObserver ( self , selector : #selector ( redraw ) , name : NSText . didChangeNotification , object : textView )
NotificationCenter . default . addObserver ( self , selector : #selector ( redraw ) , name : NSView . boundsDidChangeNotification , object : scrollView ? . contentView )
}
required init ( coder : NSCoder ) { fatalError ( " init(coder:) has not been implemented " ) }
@objc private func redraw ( ) { needsDisplay = true }
override func drawHashMarksAndLabels ( in rect : NSRect ) {
guard let tv = textView , let lm = tv . layoutManager , let tc = tv . textContainer else { return }
let ctx = NSString ( string : tv . string )
let visibleGlyphRange = lm . glyphRange ( forBoundingRect : tv . visibleRect , in : tc )
var lineNumber = 1
if visibleGlyphRange . location > 0 {
let charIndex = lm . characterIndexForGlyph ( at : visibleGlyphRange . location )
let prefix = ctx . substring ( to : charIndex )
lineNumber = prefix . reduce ( 1 ) { $1 = = " \n " ? $0 + 1 : $0 }
}
var glyphIndex = visibleGlyphRange . location
while glyphIndex < visibleGlyphRange . upperBound {
var effectiveRange = NSRange ( location : 0 , length : 0 )
let lineRect = lm . lineFragmentRect ( forGlyphAt : glyphIndex , effectiveRange : & effectiveRange , withoutAdditionalLayout : true )
let y = ( lineRect . minY - tv . visibleRect . origin . y ) + 2 - tv . textContainerInset . height
let numberString = " \( lineNumber ) " as NSString
let attributes : [ NSAttributedString . Key : Any ] = [ . font : font , . foregroundColor : textColor ]
let size = numberString . size ( withAttributes : attributes )
let drawPoint = NSPoint ( x : bounds . maxX - size . width - inset , y : y )
numberString . draw ( at : drawPoint , withAttributes : attributes )
glyphIndex = effectiveRange . upperBound
lineNumber += 1
}
}
2025-09-25 09:01:45 +00:00
}
2025-08-25 07:39:12 +00:00
2025-09-25 09:01:45 +00:00
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
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
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 ) )
]
2026-01-17 11:11:26 +00:00
2025-09-25 09:01:45 +00:00
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
)
2025-08-26 17:25:39 +00:00
}
2025-09-25 09:01:45 +00:00
}
2025-08-27 11:34:59 +00:00
2025-09-25 09:01:45 +00:00
func getSyntaxPatterns ( for language : String , colors : SyntaxColors ) -> [ String : Color ] {
switch language {
case " swift " :
return [
2026-01-17 11:11:26 +00:00
// K e y w o r d s ( e x t e n d e d t o i n c l u d e ` i m p o r t ` )
" \\ b(func|struct|class|enum|protocol|extension|if|else|for|while|switch|case|default|guard|defer|throw|try|catch|return|init|deinit|import) \\ b " : colors . keyword ,
// S t r i n g s a n d C h a r a c t e r s
2025-09-25 09:01:45 +00:00
" \" [^ \" ]* \" " : colors . string ,
2026-01-17 11:11:26 +00:00
" '[^' \\ ](?: \\ .[^' \\ ])*' " : colors . string ,
// N u m b e r s
2025-09-25 09:01:45 +00:00
" \\ b([0-9]+( \\ .[0-9]+)?) \\ b " : colors . number ,
2026-01-17 11:11:26 +00:00
// C o m m e n t s ( s i n g l e a n d m u l t i - l i n e )
2025-09-25 09:01:45 +00:00
" //.* " : colors . comment ,
" / \\ *([^*]|( \\ *+[^*/]))* \\ *+/ " : colors . comment ,
2026-01-17 11:11:26 +00:00
// D o c u m e n t a t i o n m a r k u p ( t r i p l e s l a s h a n d d o c b l o c k s )
" (?m)^(///).*$ " : colors . comment ,
" / \\ * \\ *([ \\ s \\ S]*?) \\ *+/ " : colors . comment ,
// D o c u m e n t a t i o n k e y w o r d s i n s i d e d o c s ( e . g . , - P a r a m e t e r : , - R e t u r n s : )
" (?m) \\ - \\ s*(Parameter|Parameters|Returns|Throws|Note|Warning|See \\ salso) \\ s*: " : colors . meta ,
// M a r k s / T O D O / F I X M E
" (?m)// \\ s*(MARK|TODO|FIXME) \\ s*:.*$ " : colors . meta ,
// U R L s
" https?://[A-Za-z0-9._~:/?#@!$&'()*+,;=%-]+ " : colors . atom ,
" file://[A-Za-z0-9._~:/?#@!$&'()*+,;=%-]+ " : colors . atom ,
// P r e p r o c e s s o r s t a t e m e n t s ( c o n d i t i o n a l s a n d d i r e c t i v e s )
" (?m)^#(if|elseif|else|endif|warning|error|available) \\ b.*$ " : colors . keyword ,
// A t t r i b u t e s l i k e @ a v a i l a b l e , @ M a i n A c t o r , e t c .
2025-09-25 09:01:45 +00:00
" @ \\ w+ " : colors . attribute ,
2026-01-17 11:11:26 +00:00
// V a r i a b l e d e c l a r a t i o n s
2025-09-25 09:01:45 +00:00
" \\ b(var|let) \\ b " : colors . variable ,
2026-01-17 11:11:26 +00:00
// C o m m o n S w i f t t y p e s
" \\ b(String|Int|Double|Bool) \\ b " : colors . type ,
// R e g e x l i t e r a l s a n d c o m p o n e n t s ( S w i f t / … / )
" /[^/ \\ n]*/ " : colors . builtin , // w h o l e r e g e x l i t e r a l
" \\ ( \\ ?<([A-Za-z_][A-Za-z0-9_]*)> " : colors . def , // n a m e d c a p t u r e s t a r t ( ? < n a m e >
" \\ [[^ \\ ]]* \\ ] " : colors . property , // c h a r a c t e r c l a s s e s
" [|*+?] " : colors . meta , // r e g e x o p e r a t o r s
// C o m m o n S w i f t U I p r o p e r t y n a m e s l i k e ` b o d y `
" \\ bbody \\ b " : colors . property ,
// P r o j e c t - s p e c i f i c i d e n t i f i e r y o u m e n t i o n e d : ` v i e w M o d e l `
" \\ bviewModel \\ b " : colors . property
2025-09-25 09:01:45 +00:00
]
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 [ : ]
2025-08-26 17:25:39 +00:00
}
2025-09-25 09:01:45 +00:00
}
2025-08-26 17:25:39 +00:00
2025-09-25 09:01:45 +00:00
extension Notification . Name {
static let moveCursorToLine = Notification . Name ( " moveCursorToLine " )
2026-01-17 11:11:26 +00:00
static let streamSuggestion = Notification . Name ( " streamSuggestion " )
static let caretPositionDidChange = Notification . Name ( " caretPositionDidChange " )
2026-01-17 12:04:11 +00:00
static let pastedText = Notification . Name ( " pastedText " )
2025-08-27 11:34:59 +00:00
}
2026-01-17 11:11:26 +00:00