From fdb4633d3e9158e457d57e820df6e1efb4df39c2 Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Sun, 7 May 2023 15:22:54 +0200 Subject: Update to support notes + new style --- .../Base Components/MapTextEditor.swift | 154 +++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Map/Presentation/Base Components/MapTextEditor.swift (limited to 'Map/Presentation/Base Components/MapTextEditor.swift') diff --git a/Map/Presentation/Base Components/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor.swift new file mode 100644 index 0000000..f3838a6 --- /dev/null +++ b/Map/Presentation/Base Components/MapTextEditor.swift @@ -0,0 +1,154 @@ +import Cocoa +import SwiftUI + +class MapTextEditorController: NSViewController { + + @Binding var text: String + let onChange: () -> Void + + private let vertexRegex = MapParsingPatterns.vertex + private let edgeRegex = MapParsingPatterns.edge + private let blockerRegex = MapParsingPatterns.blocker + private let opportunityRegex = MapParsingPatterns.opportunity + private let noteRegex = MapParsingPatterns.note + private let stageRegex = MapParsingPatterns.stage + + private let changeDebouncer: Debouncer = Debouncer(seconds: 1) + + init(text: Binding, onChange: @escaping () -> Void) { + self._text = text + self.onChange = onChange + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + let scrollView = NSTextView.scrollableTextView() + let textView = scrollView.documentView as! NSTextView + + 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) + self.view = scrollView + } + + override func viewDidAppear() { + self.view.window?.makeFirstResponder(self.view) + } +} + +extension MapTextEditorController: NSTextViewDelegate { + + func textDidChange(_ obj: Notification) { + if let textField = obj.object as? NSTextView { + self.text = textField.string + + + changeDebouncer.debounce { + DispatchQueue.main.async { + self.onChange() + } + } + } + } + + func textView(_ view: NSTextView, shouldChangeTextIn: NSRange, replacementString: String?) -> Bool + { + let range = Range(shouldChangeTextIn, in: view.string) + let target = view.string[range!] + + if target == "--" { + return false + } + + return true + } +} + +extension MapTextEditorController: NSTextStorageDelegate { + + override func textStorageDidProcessEditing(_ obj: Notification) { + if let textStorage = obj.object as? NSTextStorage { + 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) + + for match in matches { + textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 4)) + } + + matches = edgeRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1)) + let arrowRange = match.range(at: 2) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.symbol], + range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3)) + } + + matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 4)) + } + + matches = blockerRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) + } + + matches = noteRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3)) + } + + matches = stageRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + } + } +} + +struct MapTextEditor: NSViewControllerRepresentable { + + @Binding var text: String + var onChange: () -> Void = {} + + func makeNSViewController( + context: NSViewControllerRepresentableContext + ) -> MapTextEditorController { + return MapTextEditorController(text: $text, onChange: onChange) + } + + func updateNSViewController( + _ nsViewController: MapTextEditorController, + context: NSViewControllerRepresentableContext + ) {} +} -- cgit