aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation/Base Components/MapTextEditor.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Map/Presentation/Base Components/MapTextEditor.swift')
-rw-r--r--Map/Presentation/Base Components/MapTextEditor.swift284
1 files changed, 0 insertions, 284 deletions
diff --git a/Map/Presentation/Base Components/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor.swift
deleted file mode 100644
index 4295918..0000000
--- a/Map/Presentation/Base Components/MapTextEditor.swift
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright (C) 2024 Rubén Beltrán del Río
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see https://map.tranquil.systems.
-import Cocoa
-import SwiftUI
-
-class MapTextEditorController: NSViewController {
-
- @Binding var text: String
-
- var parsedMap: [Wmap.Entity] = [] {
- didSet {
- syntaxHighlighter.updateSyntaxElements(from: parsedMap)
- // Don't re-highlight entire document here - let the text change handler do it
- }
- }
-
- var highlightRanges: [Range<String.Index>] {
- didSet {
- updateHighlights()
- }
- }
-
- var selectedRange: Int {
- didSet {
- updateHighlights()
- focusOnResult()
- }
- }
-
- let onChange: () -> Void
-
- private let syntaxHighlighter = Wmap.SyntaxHighlighter()
- private let changeDebouncer: Debouncer = Debouncer(seconds: 1)
-
- init(
- text: Binding<String>,
- parsedMap: [Wmap.Entity] = [],
- highlightRanges: [Range<String.Index>],
- selectedRange: Int,
- onChange: @escaping () -> Void
- ) {
- self._text = text
- self.parsedMap = parsedMap
- self.onChange = onChange
- self.highlightRanges = highlightRanges
- self.selectedRange = selectedRange
- super.init(nibName: nil, bundle: nil)
-
- Task {
- syntaxHighlighter.updateSyntaxElements(from: parsedMap)
- }
- }
-
- 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.backgroundColor = .Theme.UI.background
- textView.allowsUndo = true
- textView.delegate = self
- textView.textStorage?.delegate = self
- textView.string = self.text
- textView.isEditable = true
- textView.font = NSFont.Theme.Editor.regular
- textView.textContainerInset = NSSize(width: 0, height: Dimensions.Spacing.coziest)
-
- // Configure text wrapping based on user preference
- let softWrapLines = UserDefaults.standard.bool(forKey: "softWrapLines")
- if !softWrapLines {
- // Disable word wrapping to prevent flashing on soft-wrapped lines
- textView.textContainer?.widthTracksTextView = false
- textView.textContainer?.containerSize = NSSize(
- width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
- } else {
- textView.textContainer?.widthTracksTextView = true
- }
-
- textView.isHorizontallyResizable = true
- textView.isVerticallyResizable = true
-
- self.view = scrollView
- }
-
- override func viewDidAppear() {
- self.view.window?.makeFirstResponder(self.view)
- updateHighlights()
- // Initial colorization of the entire document
- if let textView = currentTextView, let textStorage = textView.textStorage {
- colorizeText(textStorage: textStorage)
- }
- }
-
- var currentTextView: NSTextView? {
- return (view as? NSScrollView)?.documentView as? NSTextView
- }
-
- private func updateHighlights() {
- if let textView = currentTextView {
- if let textStorage = textView.textStorage {
- textStorage.removeAttribute(
- .backgroundColor, range: NSRange(location: 0, length: textStorage.length))
-
- for (index, range) in highlightRanges.enumerated() {
- let nsRange = NSRange(range, in: textStorage.string)
-
- let color =
- index == selectedRange
- ? NSColor.Theme.Syntax.highlightMatch : NSColor.Theme.Syntax.match
- textStorage.addAttribute(.backgroundColor, value: color, range: nsRange)
- }
-
- textView.needsDisplay = true
-
- }
- }
- }
-
- private func focusOnResult() {
- if let textView = currentTextView {
- if let textStorage = textView.textStorage {
- if selectedRange < highlightRanges.count {
- let range = highlightRanges[selectedRange]
- let nsRange = NSRange(range, in: textStorage.string)
- textView.scrollRangeToVisible(nsRange)
- }
- }
- }
- }
-}
-
-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 {
- // Only process if this is a text content change, not an attribute change
- guard textStorage.editedMask.contains(.editedCharacters) else {
- return
- }
-
- let editedRange = textStorage.editedRange
- if editedRange.location != NSNotFound {
- // Use a small delay to batch rapid changes and avoid flashing
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
- guard let self = self,
- let currentTextView = self.currentTextView,
- let currentTextStorage = currentTextView.textStorage
- else { return }
-
- self.colorizeEditedText(textStorage: currentTextStorage, editedRange: editedRange)
- }
- }
- }
- }
-
- private func colorizeEditedText(textStorage: NSTextStorage, editedRange: NSRange) {
- // Expand range to include the entire line(s) that were edited
- let string = textStorage.string as NSString
- let lineRange = string.lineRange(for: editedRange)
-
- // Clear existing syntax highlighting attributes in the range, but preserve background colors
- textStorage.removeAttribute(.foregroundColor, range: lineRange)
-
- // Apply syntax highlighting
- syntaxHighlighter.applySyntaxHighlighting(textStorage: textStorage, range: lineRange)
- }
-
- private func colorizeText(textStorage: NSTextStorage) {
- let range = NSMakeRange(0, textStorage.length)
-
- // Clear all existing attributes
- textStorage.removeAttribute(.foregroundColor, range: range)
-
- syntaxHighlighter.applySyntaxHighlighting(textStorage: textStorage, range: range)
- }
-}
-
-struct MapTextEditor: NSViewControllerRepresentable {
-
- @Binding var text: String
- var parsedMap: [Wmap.Entity] = []
- var highlightRanges: [Range<String.Index>]
- var selectedRange: Int
- var onChange: () -> Void = {}
-
- init(
- text: Binding<String>, parsedMap: [Wmap.Entity] = [],
- highlightRanges: [Range<String.Index>] = [], selectedRange: Int = 0,
- onChange: @escaping () -> Void = {}
- ) {
- self._text = text
- self.parsedMap = parsedMap
- self.highlightRanges = highlightRanges
- self.selectedRange = selectedRange
- self.onChange = onChange
- }
-
- func makeNSViewController(
- context: NSViewControllerRepresentableContext<MapTextEditor>
- ) -> MapTextEditorController {
- return MapTextEditorController(
- text: $text, parsedMap: parsedMap, highlightRanges: highlightRanges,
- selectedRange: selectedRange,
- onChange: onChange)
- }
-
- func updateNSViewController(
- _ nsViewController: MapTextEditorController,
- context: NSViewControllerRepresentableContext<MapTextEditor>
- ) {
- // Only update if something actually changed to prevent excessive updates
- var hasChanges = false
-
- // Always update parsed map since the content may have changed even if count is same
- nsViewController.parsedMap = parsedMap
-
- if nsViewController.highlightRanges != highlightRanges {
- nsViewController.highlightRanges = highlightRanges
- hasChanges = true
- }
-
- if nsViewController.selectedRange != selectedRange {
- nsViewController.selectedRange = selectedRange
- hasChanges = true
- }
-
- // Update text view content if text has changed
- if let textView = nsViewController.currentTextView {
- if textView.string != text {
- textView.string = text
- hasChanges = true
- }
- }
-
- // Only log if we made changes (for debugging)
- if hasChanges {
- print("MapTextEditor: Updated view controller")
- }
- }
-}