4 class MapTextEditorController: NSViewController {
6 @Binding var text: String
7 var colorScheme: ColorScheme
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
15 private let debouncer: Debouncer = Debouncer(seconds: 0.2)
17 init(text: Binding<String>, colorScheme: ColorScheme) {
19 self.colorScheme = colorScheme
20 super.init(nibName: nil, bundle: nil)
23 required init?(coder: NSCoder) {
24 fatalError("init(coder:) has not been implemented")
27 override func loadView() {
28 let scrollView = NSTextView.scrollableTextView()
29 let textView = scrollView.documentView as! NSTextView
31 scrollView.translatesAutoresizingMaskIntoConstraints = false
33 textView.allowsUndo = true
34 textView.delegate = self
35 textView.textStorage?.delegate = self
36 textView.string = self.text
37 textView.isEditable = true
38 textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
39 self.view = scrollView
42 override func viewDidAppear() {
43 self.view.window?.makeFirstResponder(self.view)
46 func updateColorScheme(_ colorScheme: ColorScheme) {
47 self.colorScheme = colorScheme
51 extension MapTextEditorController: NSTextViewDelegate {
53 func textDidChange(_ obj: Notification) {
54 if let textField = obj.object as? NSTextView {
55 self.text = textField.string
59 func textView(_ view: NSTextView, shouldChangeTextIn: NSRange, replacementString: String?) -> Bool
61 let range = Range(shouldChangeTextIn, in: view.string)
62 let target = view.string[range!]
72 extension MapTextEditorController: NSTextStorageDelegate {
73 override func textStorageDidProcessEditing(_ obj: Notification) {
74 if let textStorage = obj.object as? NSTextStorage {
76 DispatchQueue.main.async {
77 self.colorizeText(textStorage: textStorage)
83 private func colorizeText(textStorage: NSTextStorage) {
84 let range = NSMakeRange(0, textStorage.length)
85 var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range)
86 let colors = MapColor.colorForScheme(colorScheme)
88 for match in matches {
89 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1))
90 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2))
91 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 3))
92 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 4))
95 matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
97 for match in matches {
98 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1))
99 let arrowRange = match.range(at: 2)
100 textStorage.addAttributes(
101 [.foregroundColor: colors.syntax.symbol],
102 range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
103 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 3))
106 matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
108 for match in matches {
109 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
110 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2))
111 textStorage.addAttributes([.foregroundColor: colors.syntax.symbol], range: match.range(at: 3))
112 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 4))
115 matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
117 for match in matches {
118 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
119 textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2))
122 matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
124 for match in matches {
125 textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1))
126 textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2))
131 struct MapTextEditor: NSViewControllerRepresentable {
133 @Binding var text: String
134 let colorScheme: ColorScheme
136 func makeNSViewController(
137 context: NSViewControllerRepresentableContext<MapTextEditor>
138 ) -> MapTextEditorController {
139 return MapTextEditorController(text: $text, colorScheme: colorScheme)
142 func updateNSViewController(
143 _ nsViewController: MapTextEditorController,
144 context: NSViewControllerRepresentableContext<MapTextEditor>
146 nsViewController.updateColorScheme(context.environment.colorScheme)