diff options
| author | Ruben Beltran del Rio <jj@r.bdr.sh> | 2025-12-14 20:21:24 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <jj@r.bdr.sh> | 2025-12-15 00:10:39 +0100 |
| commit | 0d5e6636405dbe332c33c0fb82c7ccccb20f96cb (patch) | |
| tree | a9cda2b711bbfc9277d66310440dd965b16682fc /Map/Presentation/Base Components | |
| parent | a5ae7aedc840a38505b3181f4e5ba92e90d94fa1 (diff) | |
Use wmap-parser-swift and tree-sitter-wmap for parsing and highlighting.
Diffstat (limited to 'Map/Presentation/Base Components')
13 files changed, 78 insertions, 43 deletions
diff --git a/Map/Presentation/Base Components/EvolutionPicker/EvolutionPicker.swift b/Map/Presentation/Base Components/EvolutionPicker/EvolutionPicker.swift index da67054..83d5009 100644 --- a/Map/Presentation/Base Components/EvolutionPicker/EvolutionPicker.swift +++ b/Map/Presentation/Base Components/EvolutionPicker/EvolutionPicker.swift @@ -18,7 +18,6 @@ struct EvolutionPicker: View { @State var isShowingMenu = false @State var isHovered = false - @FocusState var isFocused: Bool @Binding var selectedEvolution: StageType diff --git a/Map/Presentation/Base Components/EvolutionPicker/EvolutionPickerMenuHeader.swift b/Map/Presentation/Base Components/EvolutionPicker/EvolutionPickerMenuHeader.swift index b3eeb97..095c74c 100644 --- a/Map/Presentation/Base Components/EvolutionPicker/EvolutionPickerMenuHeader.swift +++ b/Map/Presentation/Base Components/EvolutionPicker/EvolutionPickerMenuHeader.swift @@ -8,6 +8,7 @@ struct EvolutionPickerMenuHeader: View { HStack(alignment: .center, spacing: Dimensions.Spacing.regular) { Text(key) .font(.Theme.Caption.emphasized) + .lineSpacing(Dimensions.LineHeight.caption - Dimensions.FontSize.caption) .padding(.vertical, Dimensions.Spacing.cozy) .textCase(.uppercase) .padding(.leading, Dimensions.Spacing.regular) diff --git a/Map/Presentation/Base Components/EvolutionPicker/GlassEvolutionPicker.swift b/Map/Presentation/Base Components/EvolutionPicker/GlassEvolutionPicker.swift index 3b6f199..72a572a 100644 --- a/Map/Presentation/Base Components/EvolutionPicker/GlassEvolutionPicker.swift +++ b/Map/Presentation/Base Components/EvolutionPicker/GlassEvolutionPicker.swift @@ -18,7 +18,6 @@ import SwiftUI struct GlassEvolutionPicker: View { @State var isShowingMenu = false - @FocusState var isFocused: Bool @Binding var selectedEvolution: StageType diff --git a/Map/Presentation/Base Components/MapRender/MapAxes.swift b/Map/Presentation/Base Components/MapRender/MapAxes.swift index 9d0f43f..7c2b74b 100644 --- a/Map/Presentation/Base Components/MapRender/MapAxes.swift +++ b/Map/Presentation/Base Components/MapRender/MapAxes.swift @@ -19,7 +19,7 @@ struct MapAxes: View { let mapSize: CGSize let lineWidth: CGFloat let evolution: Stage - let stages: [CGFloat] + let stages: [Double] let stageHeight = CGFloat(100.0) let padding = CGFloat(5.0) diff --git a/Map/Presentation/Base Components/MapRender/MapBackground.swift b/Map/Presentation/Base Components/MapRender/MapBackground.swift index b33e791..710d549 100644 --- a/Map/Presentation/Base Components/MapRender/MapBackground.swift +++ b/Map/Presentation/Base Components/MapRender/MapBackground.swift @@ -14,14 +14,13 @@ // along with this program. If not, see https://map.tranquil.systems. import Patterns import SwiftUI +import WmapParser struct MapBackground: View { let mapSize: CGSize - let lineWidth: CGFloat - let stages: [CGFloat] - let opacity = 0.1 - + let stages: [Double] + var body: some View { ZStack(alignment: .topLeading) { PatternView( @@ -57,6 +56,6 @@ struct MapBackground: View { #Preview { MapBackground( - mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(0.5), - stages: [25.0, 50.0, 75.0]) + mapSize: CGSize(width: 200.0, height: 200.0), + stages: []) } diff --git a/Map/Presentation/Base Components/MapRender/MapEdges.swift b/Map/Presentation/Base Components/MapRender/MapEdges.swift index 3c6ef82..5819069 100644 --- a/Map/Presentation/Base Components/MapRender/MapEdges.swift +++ b/Map/Presentation/Base Components/MapRender/MapEdges.swift @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see https://map.tranquil.systems. import SwiftUI +import WmapParser struct MapEdges: View { diff --git a/Map/Presentation/Base Components/MapRender/MapGroup.swift b/Map/Presentation/Base Components/MapRender/MapGroup.swift index a5877a8..ca5e954 100644 --- a/Map/Presentation/Base Components/MapRender/MapGroup.swift +++ b/Map/Presentation/Base Components/MapRender/MapGroup.swift @@ -22,7 +22,6 @@ struct MapGroup: View { let group: [Vertex] let color: Color - let cornerSize = CGSize(width: 2.0, height: 2.0) var strokeSize: CGFloat { 1.75 * vertexSize.width } var hull: [CGPoint] { @@ -92,6 +91,6 @@ struct MapGroup: View { Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), - Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .xMark), ], color: .red) } diff --git a/Map/Presentation/Base Components/MapRender/MapGroups.swift b/Map/Presentation/Base Components/MapRender/MapGroups.swift index 1983dcd..149ea65 100644 --- a/Map/Presentation/Base Components/MapRender/MapGroups.swift +++ b/Map/Presentation/Base Components/MapRender/MapGroups.swift @@ -40,7 +40,7 @@ struct MapGroups: View { Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), - Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .xMark), ] ]) } diff --git a/Map/Presentation/Base Components/MapRender/MapMask.swift b/Map/Presentation/Base Components/MapRender/MapMask.swift index b477c0a..c4ba055 100644 --- a/Map/Presentation/Base Components/MapRender/MapMask.swift +++ b/Map/Presentation/Base Components/MapRender/MapMask.swift @@ -65,7 +65,7 @@ struct MapMask: View { Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), - Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .xMark), ], labelPositions: [:]) } diff --git a/Map/Presentation/Base Components/MapRender/MapStages.swift b/Map/Presentation/Base Components/MapRender/MapStages.swift index 7d7e51e..bcd95a2 100644 --- a/Map/Presentation/Base Components/MapRender/MapStages.swift +++ b/Map/Presentation/Base Components/MapRender/MapStages.swift @@ -19,8 +19,7 @@ struct MapStages: View { let mapSize: CGSize let lineWidth: CGFloat - let stages: [CGFloat] - let opacity = 0.1 + let stages: [Double] var body: some View { ZStack(alignment: .topLeading) { diff --git a/Map/Presentation/Base Components/MapRender/MapVertices.swift b/Map/Presentation/Base Components/MapRender/MapVertices.swift index 621c2f9..a3558fa 100644 --- a/Map/Presentation/Base Components/MapRender/MapVertices.swift +++ b/Map/Presentation/Base Components/MapRender/MapVertices.swift @@ -132,7 +132,7 @@ struct MapVertices: View { to: CGPoint(x: w(vertex.position.x), y: h(vertex.position.y) + vertexSize.height)) path.closeSubpath() } - case .x: + case .xMark: return Path { path in path.move(to: CGPoint(x: w(vertex.position.x), y: h(vertex.position.y))) path.addLine( @@ -156,7 +156,7 @@ struct MapVertices: View { Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), - Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .xMark), ], labelPositions: [:]) } 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 = {} ) { diff --git a/Map/Presentation/Base Components/SearchBar.swift b/Map/Presentation/Base Components/SearchBar.swift index cf19ddd..706b66f 100644 --- a/Map/Presentation/Base Components/SearchBar.swift +++ b/Map/Presentation/Base Components/SearchBar.swift @@ -20,7 +20,6 @@ struct SearchBar: View { @FocusState var isSearchFocused: Bool var onNext: () -> Void = {} var onPrevious: () -> Void = {} - var onSubmit: () -> Void = {} var onDismiss: () -> Void = {} var body: some View { |