@@ -44,6 +44,24 @@ enum SQLSyntaxHighlighter {
4444 "UPPER",
4545 ]
4646
47+ // Pre-sorted arrays for completions
48+ static let sortedKeywords: [String] = keywords.sorted()
49+ static let sortedFunctions: [String] = functions.sorted()
50+
51+ // Pre-compiled regexes (created once, reused every keystroke)
52+ private static let commentRegex = try! NSRegularExpression(pattern: "--[^\n]*", options: [.caseInsensitive])
53+ private static let singleQuoteRegex = try! NSRegularExpression(pattern: "'[^']*(?:''[^']*)*'", options: [.caseInsensitive])
54+ private static let doubleQuoteRegex = try! NSRegularExpression(pattern: "\"[^\"]*(?:\"\"[^\"]*)*\"", options: [.caseInsensitive])
55+ private static let numberRegex = try! NSRegularExpression(pattern: "\\b\\d+(?:\\.\\d+)?\\b", options: [])
56+ private static let keywordRegex: NSRegularExpression = {
57+ let pattern = "\\b(?:" + sortedKeywords.joined(separator: "|") + ")\\b"
58+ return try! NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
59+ }()
60+ private static let functionRegex: NSRegularExpression = {
61+ let pattern = "\\b(?:" + sortedFunctions.joined(separator: "|") + ")\\b"
62+ return try! NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
63+ }()
64+
4765 /// Applies SQL syntax highlighting to an NSTextStorage.
4866 static func highlight(_ text: String, in textStorage: NSTextStorage, baseFont: NSFont) {
4967 let fullRange = NSRange(location: 0, length: textStorage.length)
@@ -59,42 +77,39 @@ enum SQLSyntaxHighlighter {
5977 var protectedRanges: [NSRange] = []
6078
6179 // 1. Comments (-- to end of line)
62− applyPattern("--[^\n]*", to: textStorage, text: nsText,
63− color: .secondaryLabelColor, protectedRanges: &protectedRanges)
80+ applyRegex(commentRegex, to: textStorage, text: nsText,
81+ color: .secondaryLabelColor, protectedRanges: &protectedRanges)
6482
6583 // 2. Single-quoted strings
66− applyPattern("'[^']*(?:''[^']*)*'", to: textStorage, text: nsText,
67− color: .systemRed, protectedRanges: &protectedRanges)
84+ applyRegex(singleQuoteRegex, to: textStorage, text: nsText,
85+ color: .systemRed, protectedRanges: &protectedRanges)
6886
6987 // 3. Double-quoted identifiers
70− applyPattern("\"[^\"]*(?:\"\"[^\"]*)*\"", to: textStorage, text: nsText,
71− color: .systemRed, protectedRanges: &protectedRanges)
88+ applyRegex(doubleQuoteRegex, to: textStorage, text: nsText,
89+ color: .systemRed, protectedRanges: &protectedRanges)
7290
7391 // 4. Numbers (outside protected ranges)
74− applyUnprotectedPattern("\\b\\d+(?:\\.\\d+)?\\b", to: textStorage, text: nsText,
75− color: .systemPurple, protectedRanges: protectedRanges)
92+ applyUnprotectedRegex(numberRegex, to: textStorage, text: nsText,
93+ color: .systemPurple, protectedRanges: protectedRanges)
7694
7795 // 5. Keywords (outside protected ranges)
78− let keywordPattern = "\\b(?:" + keywords.sorted().joined(separator: "|") + ")\\b"
79− applyUnprotectedKeywordPattern(keywordPattern, to: textStorage, text: nsText,
80− color: .systemBlue, bold: true, baseFont: baseFont,
81− protectedRanges: protectedRanges)
96+ applyUnprotectedKeywordRegex(keywordRegex, to: textStorage, text: nsText,
97+ color: .systemBlue, bold: true, baseFont: baseFont,
98+ protectedRanges: protectedRanges)
8299
83100 // 6. Functions (outside protected ranges)
84− let functionPattern = "\\b(?:" + functions.sorted().joined(separator: "|") + ")\\b"
85− applyUnprotectedKeywordPattern(functionPattern, to: textStorage, text: nsText,
86− color: .systemTeal, bold: false, baseFont: baseFont,
87− protectedRanges: protectedRanges)
101+ applyUnprotectedKeywordRegex(functionRegex, to: textStorage, text: nsText,
102+ color: .systemTeal, bold: false, baseFont: baseFont,
103+ protectedRanges: protectedRanges)
88104 }
89105
90− private static func applyPattern(
91− _ pattern: String,
106+ private static func applyRegex(
107+ _ regex: NSRegularExpression,
92108 to textStorage: NSTextStorage,
93109 text: NSString,
94110 color: NSColor,
95111 protectedRanges: inout [NSRange]
96112 ) {
97− guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return }
98113 let matches = regex.matches(in: text as String, range: NSRange(location: 0, length: text.length))
99114 for match in matches {
100115 textStorage.addAttribute(.foregroundColor, value: color, range: match.range)
@@ -102,22 +117,21 @@ enum SQLSyntaxHighlighter {
102117 }
103118 }
104119
105− private static func applyUnprotectedPattern(
106− _ pattern: String,
120+ private static func applyUnprotectedRegex(
121+ _ regex: NSRegularExpression,
107122 to textStorage: NSTextStorage,
108123 text: NSString,
109124 color: NSColor,
110125 protectedRanges: [NSRange]
111126 ) {
112− guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return }
113127 let matches = regex.matches(in: text as String, range: NSRange(location: 0, length: text.length))
114128 for match in matches where !isProtected(match.range, by: protectedRanges) {
115129 textStorage.addAttribute(.foregroundColor, value: color, range: match.range)
116130 }
117131 }
118132
119− private static func applyUnprotectedKeywordPattern(
120− _ pattern: String,
133+ private static func applyUnprotectedKeywordRegex(
134+ _ regex: NSRegularExpression,
121135 to textStorage: NSTextStorage,
122136 text: NSString,
123137 color: NSColor,
@@ -125,7 +139,6 @@ enum SQLSyntaxHighlighter {
125139 baseFont: NSFont,
126140 protectedRanges: [NSRange]
127141 ) {
128− guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return }
129142 let boldFont = NSFont.monospacedSystemFont(ofSize: baseFont.pointSize, weight: .bold)
130143 let matches = regex.matches(in: text as String, range: NSRange(location: 0, length: text.length))
131144 for match in matches where !isProtected(match.range, by: protectedRanges) {
@@ -151,6 +164,12 @@ enum SQLSyntaxHighlighter {
151164
152165enum JSONSyntaxHighlighter {
153166
167+ // Pre-compiled regexes (created once, reused every call)
168+ private static let stringRegex = try! NSRegularExpression(pattern: "\"(?:[^\"\\\\]|\\\\.)*\"", options: [])
169+ private static let boolRegex = try! NSRegularExpression(pattern: "\\b(true|false)\\b", options: [])
170+ private static let nullRegex = try! NSRegularExpression(pattern: "\\bnull\\b", options: [])
171+ private static let numberRegex = try! NSRegularExpression(pattern: "-?\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?\\b", options: [])
172+
154173 /// Creates a syntax-highlighted AttributedString from a JSON string.
155174 static func highlight(_ json: String, font: NSFont? = nil) -> AttributedString {
156175 let resolvedFont = font ?? NSFont.monospacedSystemFont(ofSize: NSFont.smallSystemFontSize, weight: .regular)
@@ -168,45 +187,37 @@ enum JSONSyntaxHighlighter {
168187 var stringRanges: [NSRange] = []
169188
170189 // 1. Double-quoted strings — distinguish keys from values
171− if let regex = try? NSRegularExpression(pattern: "\"(?:[^\"\\\\]|\\\\.)*\"", options: []) {
172− let matches = regex.matches(in: json, range: fullRange)
173− for match in matches {
174− stringRanges.append(match.range)
175−
176− let isKey = isJSONKey(after: match.range, in: nsString)
177− let color: NSColor = isKey ? .systemCyan : .systemRed
178− attributed.addAttribute(.foregroundColor, value: color, range: match.range)
179− if isKey {
180− attributed.addAttribute(
181− .font,
182− value: NSFont.monospacedSystemFont(ofSize: resolvedFont.pointSize, weight: .medium),
183− range: match.range)
184− }
190+ let matches = stringRegex.matches(in: json, range: fullRange)
191+ for match in matches {
192+ stringRanges.append(match.range)
193+
194+ let isKey = isJSONKey(after: match.range, in: nsString)
195+ let color: NSColor = isKey ? .systemCyan : .systemRed
196+ attributed.addAttribute(.foregroundColor, value: color, range: match.range)
197+ if isKey {
198+ attributed.addAttribute(
199+ .font,
200+ value: NSFont.monospacedSystemFont(ofSize: resolvedFont.pointSize, weight: .medium),
201+ range: match.range)
185202 }
186203 }
187204
188205 // 2. Booleans (outside strings)
189− if let regex = try? NSRegularExpression(pattern: "\\b(true|false)\\b", options: []) {
190− for match in regex.matches(in: json, range: fullRange)
191− where !isInString(match.range, stringRanges: stringRanges) {
192− attributed.addAttribute(.foregroundColor, value: NSColor.systemOrange, range: match.range)
193− }
206+ for match in boolRegex.matches(in: json, range: fullRange)
207+ where !isInString(match.range, stringRanges: stringRanges) {
208+ attributed.addAttribute(.foregroundColor, value: NSColor.systemOrange, range: match.range)
194209 }
195210
196211 // 3. Null (outside strings)
197− if let regex = try? NSRegularExpression(pattern: "\\bnull\\b", options: []) {
198− for match in regex.matches(in: json, range: fullRange)
199− where !isInString(match.range, stringRanges: stringRanges) {
200− attributed.addAttribute(.foregroundColor, value: NSColor.secondaryLabelColor, range: match.range)
201− }
212+ for match in nullRegex.matches(in: json, range: fullRange)
213+ where !isInString(match.range, stringRanges: stringRanges) {
214+ attributed.addAttribute(.foregroundColor, value: NSColor.secondaryLabelColor, range: match.range)
202215 }
203216
204217 // 4. Numbers (outside strings)
205− if let regex = try? NSRegularExpression(pattern: "-?\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?\\b", options: []) {
206− for match in regex.matches(in: json, range: fullRange)
207− where !isInString(match.range, stringRanges: stringRanges) {
208− attributed.addAttribute(.foregroundColor, value: NSColor.systemPurple, range: match.range)
209− }
218+ for match in numberRegex.matches(in: json, range: fullRange)
219+ where !isInString(match.range, stringRanges: stringRanges) {
220+ attributed.addAttribute(.foregroundColor, value: NSColor.systemPurple, range: match.range)
210221 }
211222
212223 return AttributedString(attributed)