Skip to content

Commit 66280cb

Browse files
author
oil3
committed
pure swiftui actually very possible
1 parent ca58844 commit 66280cb

File tree

7 files changed

+118
-152
lines changed

7 files changed

+118
-152
lines changed

CodeColors.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
34B4DB372CBCADFD001F31C6 /* QuickCodeColorLook.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 34B4DB272CBCADFD001F31C6 /* QuickCodeColorLook.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1616
34B4DB3D2CBCC51D001F31C6 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B4DB2C2CBCADFD001F31C6 /* PreviewViewController.swift */; };
1717
34B4DB3E2CBCC5F3001F31C6 /* PreviewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B4DB2E2CBCADFD001F31C6 /* PreviewProvider.swift */; };
18+
34E745012CBE141900EB6061 /* SyntaxRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E745002CBE141900EB6061 /* SyntaxRules.swift */; };
19+
34E745032CBE15D200EB6061 /* SyntaxHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E745022CBE15D200EB6061 /* SyntaxHighlighter.swift */; };
1820
/* End PBXBuildFile section */
1921

2022
/* Begin PBXContainerItemProxy section */
@@ -54,6 +56,8 @@
5456
34B4DB2E2CBCADFD001F31C6 /* PreviewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewProvider.swift; sourceTree = "<group>"; };
5557
34B4DB332CBCADFD001F31C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5658
34B4DB342CBCADFD001F31C6 /* QuickCodeColorLook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickCodeColorLook.entitlements; sourceTree = "<group>"; };
59+
34E745002CBE141900EB6061 /* SyntaxRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyntaxRules.swift; sourceTree = "<group>"; };
60+
34E745022CBE15D200EB6061 /* SyntaxHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyntaxHighlighter.swift; sourceTree = "<group>"; };
5761
/* End PBXFileReference section */
5862

5963
/* Begin PBXFrameworksBuildPhase section */
@@ -127,6 +131,8 @@
127131
children = (
128132
34B4DB2C2CBCADFD001F31C6 /* PreviewViewController.swift */,
129133
34B4DB2E2CBCADFD001F31C6 /* PreviewProvider.swift */,
134+
34E745002CBE141900EB6061 /* SyntaxRules.swift */,
135+
34E745022CBE15D200EB6061 /* SyntaxHighlighter.swift */,
130136
34B4DB332CBCADFD001F31C6 /* Info.plist */,
131137
34B4DB342CBCADFD001F31C6 /* QuickCodeColorLook.entitlements */,
132138
);
@@ -242,6 +248,8 @@
242248
isa = PBXSourcesBuildPhase;
243249
buildActionMask = 2147483647;
244250
files = (
251+
34E745032CBE15D200EB6061 /* SyntaxHighlighter.swift in Sources */,
252+
34E745012CBE141900EB6061 /* SyntaxRules.swift in Sources */,
245253
34B4DB3D2CBCC51D001F31C6 /* PreviewViewController.swift in Sources */,
246254
34B4DB3E2CBCC5F3001F31C6 /* PreviewProvider.swift in Sources */,
247255
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Bucket
3+
uuid = "60919A6F-13ED-4CF9-8607-2F6AA9EC1D1D"
4+
type = "1"
5+
version = "2.0">
6+
</Bucket>

QuickCodeColorLook/Info.plist

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,5 @@
2121
<key>NSExtensionPrincipalClass</key>
2222
<string>$(PRODUCT_MODULE_NAME).PreviewViewController</string>
2323
</dict>
24-
<key>NSExtensionPrincipalClass</key>
25-
<string>$(PRODUCT_MODULE_NAME).PreviewProvider</string>
2624
</dict>
2725
</plist>
Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
// PreviewProvider.swift
2-
3-
import QuickLookUI
41
import QuickLook
2+
import QuickLookUI
53
import SwiftUI
4+
import AppKit
65

76
class PreviewProvider: QLPreviewProvider {
87
func providePreview(for request: QLFilePreviewRequest, handler: @escaping (QLPreviewReply?, Error?) -> Void) {
98

109
DispatchQueue.global(qos: .userInitiated).async {
10+
// Define the maximum file size (e.g., 5 MB)
11+
let maxFileSize: Int64 = 5 * 1024 * 1024 // 5 MB
12+
13+
// Check file size
14+
let fileSize = (try? request.fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0
15+
if Int64(fileSize) > maxFileSize {
16+
DispatchQueue.main.async {
17+
handler(nil, nil) // Skip processing large files
18+
}
19+
return
20+
}
21+
1122
// Read the file content
1223
guard let content = try? String(contentsOf: request.fileURL, encoding: .utf8) else {
1324
handler(nil, nil)
@@ -28,12 +39,21 @@ class PreviewProvider: QLPreviewProvider {
2839
let view = ContentView(content: highlightedContent)
2940

3041
// Create a QLPreviewReply with the SwiftUI view
31-
let reply = QLPreviewReply(contextSize: CGSize(width: 800, height: 600), isBitmap: false) { (context, replyHandler) in
32-
let hostingController = NSHostingController(rootView: view)
33-
hostingController.view.frame = .zero
42+
let reply = QLPreviewReply(contextSize: CGSize(width: 800, height: 600), isBitmap: true) { (context, replyHandler) in
43+
44+
// Set up the NSGraphicsContext
45+
NSGraphicsContext.saveGraphicsState()
46+
let nsContext = NSGraphicsContext(cgContext: context, flipped: false)
47+
NSGraphicsContext.current = nsContext
3448

35-
// Render the SwiftUI view into the graphics context
49+
// Render the SwiftUI view
50+
let hostingController = NSHostingController(rootView: view)
51+
hostingController.view.frame = CGRect(origin: .zero, size: CGSize(width: 800, height: 600))
52+
hostingController.view.layoutSubtreeIfNeeded()
53+
hostingController.view.draw(hostingController.view.bounds)
3654

55+
// Clean up
56+
NSGraphicsContext.restoreGraphicsState()
3757
replyHandler
3858
}
3959

@@ -46,39 +66,11 @@ struct ContentView: View {
4666
let content: AttributedString
4767

4868
var body: some View {
49-
ScrollView {
69+
ScrollView([.vertical, .horizontal]) {
5070
Text(content)
5171
.font(.system(size: 12, weight: .regular, design: .monospaced))
5272
.padding()
5373
.background(Color(NSColor.textBackgroundColor))
5474
}
5575
}
5676
}
57-
58-
struct SyntaxHighlighter {
59-
static func highlight(content: String, fileExtension: String) -> AttributedString {
60-
var attributedString = AttributedString(content)
61-
let nsString = content as NSString
62-
let wholeRange = NSRange(location: 0, length: nsString.length)
63-
64-
// Define regex patterns
65-
let patterns: [(pattern: String, color: NSColor)] = [
66-
("(\"[^\"]*\"|'[^']*')", NSColor.systemTeal), // Strings in light blue
67-
("\\b\\d+(?:\\.\\d+)?\\b", NSColor.orange), // Numbers in orange
68-
("\\b(?:True|False|true|false)\\b", NSColor.systemBlue) // Booleans in dark blue
69-
]
70-
71-
for (pattern, color) in patterns {
72-
if let regex = try? NSRegularExpression(pattern: pattern, options: []) {
73-
let matches = regex.matches(in: content, options: [], range: wholeRange)
74-
for match in matches {
75-
if let range = Range(match.range, in: attributedString) {
76-
attributedString[range].foregroundColor = Color(color)
77-
}
78-
}
79-
}
80-
}
81-
82-
return attributedString
83-
}
84-
}
Lines changed: 17 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
// PreviewViewController.swift
22

33
import Cocoa
4-
import QuickLook
5-
import WebKit
64
import QuickLookUI
5+
import SwiftUI
76

87
class PreviewViewController: NSViewController, QLPreviewingController {
9-
var webView: WKWebView!
8+
var contentView: NSHostingView<ContentView>?
109

1110
override func loadView() {
1211
self.view = NSView()
1312
}
1413

1514
override func viewDidLoad() {
1615
super.viewDidLoad()
17-
18-
// Initialize the WKWebView
19-
webView = WKWebView(frame: self.view.bounds)
20-
webView.autoresizingMask = [.width, .height]
21-
self.view.addSubview(webView)
22-
print("zzz zzzzzzzzzzz")
2316
}
2417

2518
func preparePreviewOfFile(at fileURL: URL, completionHandler handler: @escaping (Error?) -> Void) {
@@ -41,115 +34,25 @@ class PreviewViewController: NSViewController, QLPreviewingController {
4134
return
4235
}
4336

44-
// Escape and highlight the content
45-
let highlightedHTML = self.generateHTML(for: content)
37+
// Apply syntax highlighting
38+
let highlightedContent = SyntaxHighlighter.highlight(content: content, fileExtension: fileExtension)
39+
40+
// Create the SwiftUI view
41+
let swiftUIView = ContentView(content: highlightedContent)
4642

47-
// Load the HTML into the WKWebView on the main thread
4843
DispatchQueue.main.async {
49-
self.webView.loadHTMLString(highlightedHTML, baseURL: nil)
44+
// Remove existing content view if any
45+
self.contentView?.removeFromSuperview()
46+
47+
// Embed the SwiftUI view into NSHostingView
48+
let hostingView = NSHostingView(rootView: swiftUIView)
49+
hostingView.frame = self.view.bounds
50+
hostingView.autoresizingMask = [.width, .height]
51+
self.view.addSubview(hostingView)
52+
self.contentView = hostingView
53+
5054
handler(nil)
5155
}
5256
}
5357
}
5458
}
55-
56-
extension PreviewViewController {
57-
func generateHTML(for content: String) -> String {
58-
// Escape HTML special characters
59-
var escapedContent = content
60-
.replacingOccurrences(of: "&", with: "&amp;")
61-
.replacingOccurrences(of: "<", with: "&lt;")
62-
.replacingOccurrences(of: ">", with: "&gt;")
63-
.replacingOccurrences(of: "\"", with: "&quot;")
64-
.replacingOccurrences(of: "'", with: "&#39;")
65-
66-
// Apply syntax highlighting
67-
escapedContent = applySyntaxHighlighting(to: escapedContent)
68-
69-
// HTML Template
70-
let htmlTemplate = """
71-
<!DOCTYPE html>
72-
<html>
73-
<head>
74-
<meta charset="utf-8">
75-
<style>
76-
body {
77-
font-family: Menlo, monospace;
78-
font-size: 12px;
79-
background-color: #f5f5f5;
80-
color: #333;
81-
padding: 10px;
82-
word-wrap: break-word;
83-
}
84-
pre {
85-
white-space: pre-wrap;
86-
}
87-
.string { color: lightblue; }
88-
.number { color: orange; }
89-
.boolean { color: darkblue; }
90-
</style>
91-
</head>
92-
<body>
93-
<pre><code>\(escapedContent)</code></pre>
94-
</body>
95-
</html>
96-
"""
97-
return htmlTemplate
98-
}
99-
100-
func applySyntaxHighlighting(to content: String) -> String {
101-
let patterns: [(pattern: String, cssClass: String)] = [
102-
("(\"[^\"]*\"|'[^']*')", "string"),
103-
("\\b\\d+(?:\\.\\d+)?\\b", "number"),
104-
("\\b(?:True|False|true|false)\\b", "boolean")
105-
]
106-
107-
// Combine patterns into one regex with capture groups
108-
let combinedPattern = patterns.map { "(\($0.pattern))" }.joined(separator: "|")
109-
110-
guard let regex = try? NSRegularExpression(pattern: combinedPattern, options: []) else {
111-
return content
112-
}
113-
114-
let nsContent = content as NSString
115-
let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: nsContent.length))
116-
117-
var lastIndex = 0
118-
var result = ""
119-
120-
for match in matches {
121-
let matchRange = match.range
122-
123-
// Append text before the match
124-
if matchRange.location > lastIndex {
125-
let range = NSRange(location: lastIndex, length: matchRange.location - lastIndex)
126-
let text = nsContent.substring(with: range)
127-
result += text
128-
}
129-
130-
// Determine which group matched
131-
var matchedCssClass = ""
132-
for (index, (_, cssClass)) in patterns.enumerated() {
133-
if match.range(at: index + 1).location != NSNotFound {
134-
matchedCssClass = cssClass
135-
break
136-
}
137-
}
138-
139-
// Append the matched text with span
140-
let matchedText = nsContent.substring(with: matchRange)
141-
result += "<span class=\"\(matchedCssClass)\">\(matchedText)</span>"
142-
143-
lastIndex = matchRange.location + matchRange.length
144-
}
145-
146-
// Append remaining text
147-
if lastIndex < nsContent.length {
148-
let range = NSRange(location: lastIndex, length: nsContent.length - lastIndex)
149-
let text = nsContent.substring(with: range)
150-
result += text
151-
}
152-
153-
return result
154-
}
155-
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import SwiftUI
2+
3+
struct SyntaxHighlighter {
4+
static func highlight(content: String, fileExtension: String) -> AttributedString {
5+
var attributedString = AttributedString()
6+
let lines = content.components(separatedBy: .newlines)
7+
let newline = AttributedString("\n")
8+
9+
for line in lines {
10+
var lineAttributedString = AttributedString(line)
11+
applySyntaxHighlighting(to: &lineAttributedString)
12+
attributedString += lineAttributedString + newline
13+
}
14+
15+
return attributedString
16+
}
17+
18+
static func applySyntaxHighlighting(to attributedString: inout AttributedString) {
19+
let nsString = String(attributedString.characters) as NSString
20+
let wholeRange = NSRange(location: 0, length: nsString.length)
21+
22+
// syntax rules
23+
let patterns = SyntaxRules.shared.rules
24+
25+
for (pattern, color) in patterns {
26+
if let regex = try? NSRegularExpression(pattern: pattern, options: []) {
27+
let matches = regex.matches(in: nsString as String, options: [], range: wholeRange)
28+
for match in matches {
29+
if let range = Range(match.range, in: attributedString) {
30+
attributedString[range].foregroundColor = Color(color)
31+
}
32+
}
33+
}
34+
}
35+
}
36+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SyntaxRules.swift
2+
3+
import AppKit
4+
5+
class SyntaxRules {
6+
static let shared = SyntaxRules()
7+
8+
let rules: [(pattern: String, color: NSColor)]
9+
10+
private init() {
11+
rules = [
12+
// Strings
13+
("(\"[^\"]*\"|'[^']*')", NSColor.systemTeal),
14+
// Numbers
15+
("\\b\\d+(?:\\.\\d+)?\\b", NSColor.orange),
16+
// Python
17+
("\\b(def|class|if|else|elif|return|import|from|as|for|while|try|except|with|lambda|pass|break|continue|yield|assert|async|await)\\b", NSColor.systemPurple),
18+
// Booleans
19+
("\\b(?:True|False|true|false)\\b", NSColor.systemBlue),
20+
// &
21+
]
22+
}
23+
}

0 commit comments

Comments
 (0)