]> git.r.bdr.sh - rbdr/map/blobdiff - Map/Views/MapTextEditor.swift
Fix performance and undo
[rbdr/map] / Map / Views / MapTextEditor.swift
index 7251c59460d8b4c6fe306c688cc5d70796edc033..4fbff82f673b329f9b22e1a9b2130e0c6a46dbc4 100644 (file)
@@ -4,9 +4,19 @@ import SwiftUI
 class MapTextEditorController: NSViewController {
 
   @Binding var text: String
+  var colorScheme: ColorScheme
 
-  init(text: Binding<String>) {
+  private let vertexRegex = MapParsingPatterns.vertex
+  private let edgeRegex = MapParsingPatterns.edge
+  private let blockerRegex = MapParsingPatterns.blocker
+  private let opportunityRegex = MapParsingPatterns.opportunity
+  private let stageRegex = MapParsingPatterns.stage
+
+  private let debouncer: Debouncer = Debouncer(seconds: 0.2)
+
+  init(text: Binding<String>, colorScheme: ColorScheme) {
     self._text = text
+    self.colorScheme = colorScheme
     super.init(nibName: nil, bundle: nil)
   }
 
@@ -20,7 +30,9 @@ class MapTextEditorController: NSViewController {
 
     scrollView.translatesAutoresizingMaskIntoConstraints = false
 
+    textView.allowsUndo = true
     textView.delegate = self
+    textView.textStorage?.delegate = self
     textView.string = self.text
     textView.isEditable = true
     textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
@@ -30,6 +42,10 @@ class MapTextEditorController: NSViewController {
   override func viewDidAppear() {
     self.view.window?.makeFirstResponder(self.view)
   }
+
+  func updateColorScheme(_ colorScheme: ColorScheme) {
+    self.colorScheme = colorScheme
+  }
 }
 
 extension MapTextEditorController: NSTextViewDelegate {
@@ -53,19 +69,80 @@ extension MapTextEditorController: NSTextViewDelegate {
   }
 }
 
+extension MapTextEditorController: NSTextStorageDelegate {
+  override func textStorageDidProcessEditing(_ obj: Notification) {
+    if let textStorage = obj.object as? NSTextStorage {
+      debouncer.debounce {
+        DispatchQueue.main.async {
+          self.colorizeText(textStorage: textStorage)
+        }
+      }
+    }
+  }
+
+  private func colorizeText(textStorage: NSTextStorage) {
+    let range = NSMakeRange(0, textStorage.length)
+    var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range)
+    let colors = MapColor.colorForScheme(colorScheme)
+
+    for match in matches {
+      textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 3))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 4))
+    }
+
+    matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
+
+    for match in matches {
+      textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1))
+      let arrowRange = match.range(at: 2)
+      textStorage.addAttributes(
+        [.foregroundColor: colors.syntax.symbol],
+        range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 3))
+    }
+
+    matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
+
+    for match in matches {
+      textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.symbol], range: match.range(at: 3))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 4))
+    }
+
+    matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
+
+    for match in matches {
+      textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2))
+    }
+
+    matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
+
+    for match in matches {
+      textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
+      textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2))
+    }
+  }
+}
+
 struct MapTextEditor: NSViewControllerRepresentable {
 
   @Binding var text: String
+  let colorScheme: ColorScheme
 
   func makeNSViewController(
     context: NSViewControllerRepresentableContext<MapTextEditor>
   ) -> MapTextEditorController {
-    return MapTextEditorController(text: $text)
+    return MapTextEditorController(text: $text, colorScheme: colorScheme)
   }
 
   func updateNSViewController(
     _ nsViewController: MapTextEditorController,
     context: NSViewControllerRepresentableContext<MapTextEditor>
   ) {
+    nsViewController.updateColorScheme(context.environment.colorScheme)
   }
 }