aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2025-12-14 20:21:24 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2025-12-15 00:10:39 +0100
commit0d5e6636405dbe332c33c0fb82c7ccccb20f96cb (patch)
treea9cda2b711bbfc9277d66310440dd965b16682fc /Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift
parenta5ae7aedc840a38505b3181f4e5ba92e90d94fa1 (diff)
Use wmap-parser-swift and tree-sitter-wmap for parsing and highlighting.
Diffstat (limited to 'Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift')
-rw-r--r--Map/Presentation/Base Components/MapTextEditor/MapTextEditor.swift89
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 = {}
) {