diff options
Diffstat (limited to 'Map/Presentation/Base Components/MapTextEditor')
| -rw-r--r-- | Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift | 89 |
1 files changed, 64 insertions, 25 deletions
diff --git a/Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift index a1b357d..99fa684 100644 --- a/Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift +++ b/Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift @@ -14,17 +14,17 @@ // along with this program. If not, see https://map.tranquil.systems. import Cocoa import SwiftUI +import WmapParser +import SwiftTreeSitter +import SwiftTreeSitterLayer +import TreeSitterWmap + class MapTextEditorController: NSViewController { @Binding var text: String - var parsedMap: Wmap.ParsedMap { - didSet { - syntaxHighlighter.updateSyntaxElements(from: parsedMap) - // Don't re-highlight entire document here - let the text change handler do it - } - } + var parsedMap: WmapParser.Map? var highlightRanges: [Range<String.Index>] { didSet { @@ -41,17 +41,18 @@ class MapTextEditorController: NSViewController { let onChange: () -> Void - private let syntaxHighlighter = Wmap.SyntaxHighlighter() private let changeDebouncer: Debouncer = Debouncer(seconds: 1) private var trackingArea: NSTrackingArea? private var hoverView: NSView? + private var rootLayer: LanguageLayer? + var hoverTimer: Timer? var isHoveringValidationText = false var isHoveringHoverView = false init( text: Binding<String>, - parsedMap: Wmap.ParsedMap = Wmap.ParsedMap(entities: [], vertexLabels: Set()), + parsedMap: WmapParser.Map? = nil, highlightRanges: [Range<String.Index>], selectedRange: Int, onChange: @escaping () -> Void @@ -62,9 +63,16 @@ class MapTextEditorController: NSViewController { self.highlightRanges = highlightRanges self.selectedRange = selectedRange super.init(nibName: nil, bundle: nil) + + if let wmapConfiguration = try? LanguageConfiguration(tree_sitter_wmap(), name: "wmap") { + let languageConfiguration = LanguageLayer.Configuration(languageProvider: { name in + switch name { + case "wmap": return wmapConfiguration + default: return nil + } + }) + self.rootLayer = try? LanguageLayer(languageConfig: wmapConfiguration, configuration: languageConfiguration) - Task { - syntaxHighlighter.updateSyntaxElements(from: parsedMap) } } @@ -75,7 +83,7 @@ class MapTextEditorController: NSViewController { override func loadView() { let scrollView = NSTextView.scrollableTextView() let textView = scrollView.documentView as! NSTextView - + scrollView.translatesAutoresizingMaskIntoConstraints = false textView.backgroundColor = .Theme.UI.background @@ -87,7 +95,7 @@ class MapTextEditorController: NSViewController { textView.font = NSFont.Theme.Editor.regular textView.textColor = NSColor.Theme.Syntax.text textView.textContainerInset = NSSize( - width: Dimensions.Spacing.coziest, height: Dimensions.Spacing.coziest) + width: Dimensions.Spacing.regular, height: Dimensions.Spacing.regular) // Configure text wrapping based on user preference let softWrapLines = UserDefaults.standard.bool(forKey: "softWrapLines") @@ -114,6 +122,7 @@ class MapTextEditorController: NSViewController { textView.isVerticallyResizable = true setupTrackingArea(for: textView) + rootLayer?.replaceContent(with: text) self.view = scrollView } @@ -241,10 +250,10 @@ class MapTextEditorController: NSViewController { var effectiveRange = NSRange() let attributes = textStorage.attributes(at: characterIndex, effectiveRange: &effectiveRange) - if let missinVertex = attributes[.init(rawValue: "MissingComponent")] as? String { + if let missingVertex = attributes[.init(rawValue: "MissingComponent")] as? String { isHoveringValidationText = true hoverTimer?.invalidate() - showHoverView(for: effectiveRange, with: missinVertex, characterIndex: characterIndex) + showHoverView(for: effectiveRange, with: missingVertex, characterIndex: characterIndex) } else { isHoveringValidationText = false scheduleHideHoverView() @@ -346,17 +355,39 @@ class MapTextEditorController: NSViewController { } } - private func getSyntaxElementAt(characterIndex: Int) -> Wmap.SyntaxElement? { - let textStorage = currentTextView?.textStorage - guard let textStorage = textStorage else { return nil } + private func applyTreeSitterHighlighting(textStorage: NSTextStorage, range: NSRange) { + guard let highlights = try? rootLayer?.highlights(in: range, provider: text.predicateTextProvider) + else { return } + + for highlight in highlights { + let color = NSColor.Theme.Syntax.colorForSyntax(highlight.name) + var attributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: color + ] - for element in syntaxHighlighter.cachedSyntaxElements { - let nsRange = NSRange(element.range.stringRange, in: textStorage.string) - if nsRange.contains(characterIndex) { - return element + // Check for missing components + if UserDefaults.standard.bool(forKey: "useSmartEditor"), + let parsedMap, + highlight.name == "variable.component_label" { + + // Extract the component label text from the range + let componentText = (textStorage.string as NSString).substring(with: highlight.range).trimmingCharacters(in: .whitespacesAndNewlines) + + // Check if this component exists in the parsed map + let componentExists = parsedMap.components.contains(where: { + $0.label.lowercased() == componentText.lowercased() + }) + + if !componentExists && UserDefaults.standard.bool(forKey: "highlightMissingComponents") { + attributes[.underlineStyle] = + NSUnderlineStyle.thick.rawValue | NSUnderlineStyle.patternDot.rawValue + attributes[.underlineColor] = NSColor.Theme.Syntax.error + attributes[.init("MissingComponent")] = componentText + } } + + textStorage.addAttributes(attributes, range: highlight.range) } - return nil } } @@ -400,6 +431,13 @@ extension MapTextEditorController: NSTextStorageDelegate { } let editedRange = textStorage.editedRange + let content = textStorage.string + + // Update tree-sitter layer with new content + Task { @MainActor in + rootLayer?.replaceContent(with: content) + } + if editedRange.location != NSNotFound { DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in guard let self = self, @@ -432,21 +470,22 @@ extension MapTextEditorController: NSTextStorageDelegate { // Set default text color for the range textStorage.addAttribute(.foregroundColor, value: NSColor.Theme.Syntax.text, range: range) - syntaxHighlighter.applySyntaxHighlighting(textStorage: textStorage, range: range) + // Apply tree-sitter syntax highlighting + applyTreeSitterHighlighting(textStorage: textStorage, range: range) } } struct MapTextEditor: NSViewControllerRepresentable { @Binding var text: String - var parsedMap: Wmap.ParsedMap + var parsedMap: WmapParser.Map? var highlightRanges: [Range<String.Index>] var selectedRange: Int var onChange: () -> Void = {} init( text: Binding<String>, - parsedMap: Wmap.ParsedMap = Wmap.ParsedMap(entities: [], vertexLabels: Set()), + parsedMap: WmapParser.Map? = nil, highlightRanges: [Range<String.Index>] = [], selectedRange: Int = 0, onChange: @escaping () -> Void = {} ) { |