From ac36988a3d473b7bb09aa1516070ae885f915dad Mon Sep 17 00:00:00 2001 From: albertvinizhanau Date: Sat, 14 Oct 2023 21:12:52 -0500 Subject: [PATCH] Make CEUndoManager public --- .../CodeEditTextView/CodeEditTextView.swift | 9 +++-- .../Controller/CEUndoManager.swift | 33 +++++++++++-------- .../STTextViewController+Lifecycle.swift | 2 +- .../Controller/STTextViewController.swift | 6 ++-- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Sources/CodeEditTextView/CodeEditTextView.swift b/Sources/CodeEditTextView/CodeEditTextView.swift index a0ac4390e..891cc6749 100644 --- a/Sources/CodeEditTextView/CodeEditTextView.swift +++ b/Sources/CodeEditTextView/CodeEditTextView.swift @@ -35,6 +35,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable { /// character's width between characters, etc. Defaults to `1.0` /// - bracketPairHighlight: The type of highlight to use to highlight bracket pairs. /// See `BracketPairHighlight` for more information. Defaults to `nil` + /// - undoManager: The undo manager for the text view. Defaults to `nil`, which will create a new CEUndoManager public init( _ text: Binding, language: CodeLanguage, @@ -51,7 +52,8 @@ public struct CodeEditTextView: NSViewControllerRepresentable { contentInsets: NSEdgeInsets? = nil, isEditable: Bool = true, letterSpacing: Double = 1.0, - bracketPairHighlight: BracketPairHighlight? = nil + bracketPairHighlight: BracketPairHighlight? = nil, + undoManager: CEUndoManager? = nil ) { self._text = text self.language = language @@ -69,6 +71,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable { self.isEditable = isEditable self.letterSpacing = letterSpacing self.bracketPairHighlight = bracketPairHighlight + self.undoManager = undoManager ?? CEUndoManager() } @Binding private var text: String @@ -87,6 +90,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable { private var isEditable: Bool private var letterSpacing: Double private var bracketPairHighlight: BracketPairHighlight? + private var undoManager: CEUndoManager public typealias NSViewControllerType = STTextViewController @@ -107,7 +111,8 @@ public struct CodeEditTextView: NSViewControllerRepresentable { contentInsets: contentInsets, isEditable: isEditable, letterSpacing: letterSpacing, - bracketPairHighlight: bracketPairHighlight + bracketPairHighlight: bracketPairHighlight, + undoManager: undoManager ) return controller } diff --git a/Sources/CodeEditTextView/Controller/CEUndoManager.swift b/Sources/CodeEditTextView/Controller/CEUndoManager.swift index 1f1ad4b56..823537d39 100644 --- a/Sources/CodeEditTextView/Controller/CEUndoManager.swift +++ b/Sources/CodeEditTextView/Controller/CEUndoManager.swift @@ -16,30 +16,30 @@ import TextStory /// - Grouping pasted text /// /// If needed, the automatic undo grouping can be overridden using the `beginGrouping()` and `endGrouping()` methods. -class CEUndoManager { +public class CEUndoManager { /// An `UndoManager` subclass that forwards relevant actions to a `CEUndoManager`. /// Allows for objects like `STTextView` to use the `UndoManager` API /// while CETV manages the undo/redo actions. - class DelegatedUndoManager: UndoManager { + public class DelegatedUndoManager: UndoManager { weak var parent: CEUndoManager? - override var canUndo: Bool { parent?.canUndo ?? false } - override var canRedo: Bool { parent?.canRedo ?? false } + public override var canUndo: Bool { parent?.canUndo ?? false } + public override var canRedo: Bool { parent?.canRedo ?? false } - func registerMutation(_ mutation: TextMutation) { + public func registerMutation(_ mutation: TextMutation) { parent?.registerMutation(mutation) removeAllActions() } - override func undo() { + public override func undo() { parent?.undo() } - override func redo() { + public override func redo() { parent?.redo() } - override func registerUndo(withTarget target: Any, selector: Selector, object anObject: Any?) { + public override func registerUndo(withTarget target: Any, selector: Selector, object anObject: Any?) { // no-op, but just in case to save resources: removeAllActions() } @@ -71,23 +71,25 @@ class CEUndoManager { /// A stack of operations that can be redone. private var redoStack: [UndoGroup] = [] - private unowned let textView: STTextView + internal weak var textView: STTextView? private(set) var isGrouping: Bool = false - public init(textView: STTextView) { - self.textView = textView + public init() { self.manager = DelegatedUndoManager() manager.parent = self } /// Performs an undo operation if there is one available. public func undo() { - guard let item = undoStack.popLast() else { + guard let item = undoStack.popLast(), + let textView else { return } isUndoing = true for mutation in item.mutations.reversed() { + NotificationCenter.default.post(name: .NSUndoManagerWillUndoChange, object: self.manager) textView.applyMutationNoUndo(mutation.inverse) + NotificationCenter.default.post(name: .NSUndoManagerDidUndoChange, object: self.manager) } redoStack.append(item) isUndoing = false @@ -95,12 +97,15 @@ class CEUndoManager { /// Performs a redo operation if there is one available. public func redo() { - guard let item = redoStack.popLast() else { + guard let item = redoStack.popLast(), + let textView else { return } isRedoing = true for mutation in item.mutations { + NotificationCenter.default.post(name: .NSUndoManagerWillRedoChange, object: self.manager) textView.applyMutationNoUndo(mutation.mutation) + NotificationCenter.default.post(name: .NSUndoManagerDidRedoChange, object: self.manager) } undoStack.append(item) isRedoing = false @@ -117,6 +122,8 @@ class CEUndoManager { /// Calling this method while the manager is in an undo/redo operation will result in a no-op. /// - Parameter mutation: The mutation to register for undo/redo public func registerMutation(_ mutation: TextMutation) { + guard let textView else { return } + if (mutation.range.length == 0 && mutation.string.isEmpty) || isUndoing || isRedoing { return } let newMutation = UndoGroup.Mutation(mutation: mutation, inverse: textView.inverseMutation(for: mutation)) if !undoStack.isEmpty, let lastMutation = undoStack.last?.mutations.last { diff --git a/Sources/CodeEditTextView/Controller/STTextViewController+Lifecycle.swift b/Sources/CodeEditTextView/Controller/STTextViewController+Lifecycle.swift index 962722b4d..54f42f054 100644 --- a/Sources/CodeEditTextView/Controller/STTextViewController+Lifecycle.swift +++ b/Sources/CodeEditTextView/Controller/STTextViewController+Lifecycle.swift @@ -57,7 +57,7 @@ extension STTextViewController { return event } - textViewUndoManager = CEUndoManager(textView: textView) + textViewUndoManager.textView = textView reloadUI() setUpHighlighter() setHighlightProvider(self.highlightProvider) diff --git a/Sources/CodeEditTextView/Controller/STTextViewController.swift b/Sources/CodeEditTextView/Controller/STTextViewController.swift index 5d9c4350e..92185c52f 100644 --- a/Sources/CodeEditTextView/Controller/STTextViewController.swift +++ b/Sources/CodeEditTextView/Controller/STTextViewController.swift @@ -27,7 +27,7 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt /// for every new selection. internal var lastTextSelections: [NSTextRange] = [] - internal var textViewUndoManager: CEUndoManager! + internal var textViewUndoManager: CEUndoManager /// Binding for the `textView`s string public var text: Binding @@ -133,7 +133,8 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt contentInsets: NSEdgeInsets? = nil, isEditable: Bool, letterSpacing: Double, - bracketPairHighlight: BracketPairHighlight? = nil + bracketPairHighlight: BracketPairHighlight? = nil, + undoManager: CEUndoManager ) { self.text = text self.language = language @@ -150,6 +151,7 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt self.contentInsets = contentInsets self.isEditable = isEditable self.bracketPairHighlight = bracketPairHighlight + self.textViewUndoManager = undoManager super.init(nibName: nil, bundle: nil) }