import Cocoa import Quartz import SwiftUI import WmapParser import SwiftTreeSitter import SwiftTreeSitterLayer import TreeSitterWmap class PreviewViewController: NSViewController, QLPreviewingController { override var nibName: NSNib.Name? { return NSNib.Name("PreviewViewController") } override func loadView() { super.loadView() } func preparePreviewOfFile(at url: URL) async throws { // Read the .wmap file content let content = try String(contentsOf: url, encoding: .utf8) // Get the user's preview style preference from shared UserDefaults let sharedDefaults = UserDefaults(suiteName: "group.systems.tranquil.Map") ?? UserDefaults.standard let previewStyleString = sharedDefaults.string(forKey: "quickLookPreviewStyle") ?? QuickLookPreviewStyle.map.rawValue let previewStyle = QuickLookPreviewStyle(rawValue: previewStyleString) ?? .map await MainActor.run { // Clear any existing subviews and force cleanup view.subviews.forEach { subview in if let hostingView = subview as? NSHostingView { hostingView.rootView = AnyView(EmptyView()) } subview.removeFromSuperview() } // Force memory cleanup autoreleasepool { let previewContent: AnyView switch previewStyle { case .plainText: previewContent = AnyView(createPlainTextView(content: content)) case .highlightedText: previewContent = AnyView( createHighlightedTextView(content: content)) case .map: let parsedMap = WmapParser.parse(content) previewContent = AnyView(MapPreviewView(parsedMap: parsedMap)) } let hostingView = NSHostingView(rootView: previewContent) hostingView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingView) NSLayoutConstraint.activate([ hostingView.topAnchor.constraint(equalTo: view.topAnchor), hostingView.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingView.trailingAnchor.constraint(equalTo: view.trailingAnchor), hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) view.needsLayout = true view.layoutSubtreeIfNeeded() } // autoreleasepool } } private func createPlainTextView(content: String) -> some View { ScrollView([.horizontal, .vertical]) { Text(content) .font(.monospaced(.body)()) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .padding() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.white) } private func createHighlightedTextView(content: String) -> some View { QuickLookTextEditor(content: content) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.white) } deinit { // Cleanup when view controller is deallocated view.subviews.forEach { subview in if let hostingView = subview as? NSHostingView { hostingView.rootView = AnyView(EmptyView()) } subview.removeFromSuperview() } } } // A SwiftUI view specifically for QuickLook preview struct MapPreviewView: View { let parsedMap: WmapParser.Map @State private var selectedEvolution: StageType = .general var body: some View { GeometryReader { geometry in let mapSize = Dimensions.Map.size let padding = Dimensions.Map.padding let totalWidth = mapSize.width + 2 * padding let totalHeight = mapSize.height + 2 * padding // Calculate scale to fit the available space let scaleX = geometry.size.width / totalWidth let scaleY = geometry.size.height / totalHeight let scale = min(scaleX, scaleY, 1.0) // Don't scale up, only down ScrollView([.horizontal, .vertical]) { MapRenderView( parsedMap: parsedMap, evolution: $selectedEvolution ) .drawingGroup(opaque: false) .scaleEffect(scale) .frame( width: totalWidth * scale, height: totalHeight * scale ) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.white) } } } // A read-only text editor for QuickLook that shows syntax highlighting struct QuickLookTextEditor: NSViewRepresentable { let content: String func makeNSView(context: Context) -> NSScrollView { let scrollView = NSTextView.scrollableTextView() let textView = scrollView.documentView as! NSTextView // Configure the scroll view scrollView.hasVerticalScroller = true scrollView.hasHorizontalScroller = true scrollView.autohidesScrollers = false // Configure the text view for read-only display textView.isEditable = false textView.isSelectable = true textView.backgroundColor = NSColor.textBackgroundColor textView.font = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) textView.string = content textView.isVerticallyResizable = true textView.isHorizontallyResizable = true textView.textContainer?.widthTracksTextView = false textView.textContainer?.containerSize = NSSize( width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) // Apply syntax highlighting using tree-sitter if let textStorage = textView.textStorage { applyTreeSitterHighlighting(textStorage: textStorage, content: content) } return scrollView } func updateNSView(_ nsView: NSScrollView, context: Context) { // No updates needed for read-only view } private func applyTreeSitterHighlighting(textStorage: NSTextStorage, content: String) { // Set up tree-sitter language layer guard let wmapConfiguration = try? LanguageConfiguration(tree_sitter_wmap(), name: "wmap") else { return } let languageConfiguration = LanguageLayer.Configuration(languageProvider: { name in switch name { case "wmap": return wmapConfiguration default: return nil } }) guard let rootLayer = try? LanguageLayer( languageConfig: wmapConfiguration, configuration: languageConfiguration) else { return } // Update layer with content rootLayer.replaceContent(with: content) // Set default text color let fullRange = NSRange(location: 0, length: textStorage.length) textStorage.addAttribute(.foregroundColor, value: NSColor.textColor, range: fullRange) // Apply tree-sitter highlighting guard let highlights = try? rootLayer.highlights(in: fullRange, provider: content.predicateTextProvider) else { return } for highlight in highlights { let color = NSColor.Theme.Syntax.colorForSyntax(highlight.name) textStorage.addAttribute(.foregroundColor, value: color, range: highlight.range) } } }