]> git.r.bdr.sh - rbdr/map/blob - Map/Views/MapTextEditor.swift
Add sidebar menu
[rbdr/map] / Map / Views / MapTextEditor.swift
1 import Cocoa
2 import SwiftUI
3
4 class MapTextEditorController: NSViewController {
5
6 @Binding var text: String
7 var colorScheme: ColorScheme
8
9 private let vertexRegex = MapParsingPatterns.vertex
10 private let edgeRegex = MapParsingPatterns.edge
11 private let blockerRegex = MapParsingPatterns.blocker
12 private let opportunityRegex = MapParsingPatterns.opportunity
13 private let stageRegex = MapParsingPatterns.stage
14
15 private let debouncer: Debouncer = Debouncer(seconds: 0.2)
16
17 init(text: Binding<String>, colorScheme: ColorScheme) {
18 self._text = text
19 self.colorScheme = colorScheme
20 super.init(nibName: nil, bundle: nil)
21 }
22
23 required init?(coder: NSCoder) {
24 fatalError("init(coder:) has not been implemented")
25 }
26
27 override func loadView() {
28 let scrollView = NSTextView.scrollableTextView()
29 let textView = scrollView.documentView as! NSTextView
30
31 scrollView.translatesAutoresizingMaskIntoConstraints = false
32
33 textView.delegate = self
34 textView.textStorage?.delegate = self
35 textView.string = self.text
36 textView.isEditable = true
37 textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
38 self.view = scrollView
39 }
40
41 override func viewDidAppear() {
42 self.view.window?.makeFirstResponder(self.view)
43 }
44
45 func updateColorScheme(_ colorScheme: ColorScheme) {
46 self.colorScheme = colorScheme
47 }
48 }
49
50 extension MapTextEditorController: NSTextViewDelegate {
51
52 func textDidChange(_ obj: Notification) {
53 if let textField = obj.object as? NSTextView {
54 self.text = textField.string
55 }
56 }
57
58 func textView(_ view: NSTextView, shouldChangeTextIn: NSRange, replacementString: String?) -> Bool
59 {
60 let range = Range(shouldChangeTextIn, in: view.string)
61 let target = view.string[range!]
62
63 if target == "--" {
64 return false
65 }
66
67 return true
68 }
69 }
70
71 extension MapTextEditorController: NSTextStorageDelegate {
72 override func textStorageDidProcessEditing(_ obj: Notification) {
73 if let textStorage = obj.object as? NSTextStorage {
74 debouncer.debounce {
75 DispatchQueue.main.async {
76 self.colorizeText(textStorage: textStorage)
77 }
78 }
79 }
80 }
81
82 private func colorizeText(textStorage: NSTextStorage) {
83 let range = NSMakeRange(0, textStorage.length)
84 var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range)
85 let colors = MapColor.colorForScheme(colorScheme)
86
87 for match in matches {
88 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1))
89 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2))
90 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 3))
91 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 4))
92 }
93
94 matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
95
96 for match in matches {
97 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1))
98 let arrowRange = match.range(at: 2)
99 textStorage.addAttributes(
100 [.foregroundColor: colors.syntax.symbol],
101 range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
102 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 3))
103 }
104
105 matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
106
107 for match in matches {
108 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
109 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2))
110 textStorage.addAttributes([.foregroundColor: colors.syntax.symbol], range: match.range(at: 3))
111 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 4))
112 }
113
114 matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
115
116 for match in matches {
117 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
118 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2))
119 }
120
121 matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
122
123 for match in matches {
124 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
125 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2))
126 }
127 }
128 }
129
130 struct MapTextEditor: NSViewControllerRepresentable {
131
132 @Binding var text: String
133 let colorScheme: ColorScheme
134
135 func makeNSViewController(
136 context: NSViewControllerRepresentableContext<MapTextEditor>
137 ) -> MapTextEditorController {
138 return MapTextEditorController(text: $text, colorScheme: colorScheme)
139 }
140
141 func updateNSViewController(
142 _ nsViewController: MapTextEditorController,
143 context: NSViewControllerRepresentableContext<MapTextEditor>
144 ) {
145 nsViewController.updateColorScheme(context.environment.colorScheme)
146 }
147 }