-
-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This pull request changes the behaviour of the undo functionality and fixes some small issues in the current implementation: * If you edited at the end of the current text (e.g. by typing at the end of the text) and used undo, then the undo didn't work properly and only the cursor moved, but the text was not removed again. This happened because the undo range was calculated before the text was added, therefore the undo range was always too small. * If you pasted text that was longer than the end of the document and used undo, then the text ended up in a wrong state and parts of the text were repeated. * An undo operation could happen at the wrong place in the text. Unlike before, a newline will now break the typing coalescing which is the same behaviour as in other text editors (e.g. Xcode or TextEdit). Also, an undo operation will now restore the text selection that existed when the operation that is undone was done, again mimicking the behaviour of other editors such as Xcode. Additionally, this pull request provides some handy helpers to create keyboard events that can be used in tests. --------- Co-authored-by: Lukas Stührk <[email protected]>
- Loading branch information
1 parent
60faeec
commit b53f822
Showing
6 changed files
with
386 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,59 @@ | ||
// Created by Marcin Krzyzanowski | ||
// https://github.com/krzyzanowskim/STTextView/blob/main/LICENSE.md | ||
|
||
import Foundation | ||
import AppKit | ||
import STTextKitPlus | ||
|
||
final class CoalescingUndoManager: UndoManager { | ||
|
||
private(set) var coalescing: (value: TypingTextUndo?, undoAction: ((TypingTextUndo) -> Void)?)? | ||
private var lastRange: NSTextRange? | ||
|
||
private var coalescingIsUndoing: Bool = false | ||
private var coalescingIsRedoing: Bool = false | ||
|
||
var isCoalescing: Bool { | ||
coalescing != nil | ||
} | ||
|
||
func breakCoalescing() { | ||
guard isUndoRegistrationEnabled else { | ||
return | ||
} | ||
|
||
// register undo and break coalescing | ||
if !isUndoing, !isRedoing, let undoAction = coalescing?.undoAction, let value = coalescing?.value { | ||
// Disable implicit grouping to avoid group coalescing and non-coalescing undo | ||
groupsByEvent = false | ||
beginUndoGrouping() | ||
registerUndo(withTarget: self) { _ in | ||
undoAction(value) | ||
} | ||
endUndoGrouping() | ||
groupsByEvent = true | ||
} | ||
|
||
coalescing = nil | ||
} | ||
private var isCoalescing: Bool = false | ||
|
||
override init() { | ||
super.init() | ||
self.runLoopModes = [.default, .common, .eventTracking, .modalPanel] | ||
} | ||
|
||
func coalesce(_ value: TypingTextUndo) { | ||
guard isUndoRegistrationEnabled else { | ||
return | ||
} | ||
|
||
assert(isCoalescing, "Coalescing not started. Call startCoalescing(withTarget:_) first") | ||
|
||
coalescing = (value: value, undoAction: coalescing?.undoAction) | ||
return | ||
} | ||
|
||
func startCoalescing<Target>(_ value: TypingTextUndo, withTarget target: Target, _ undoAction: @escaping (Target, TypingTextUndo) -> Void) where Target: AnyObject { | ||
guard isUndoRegistrationEnabled else { return } | ||
coalescing = (value: value, undoAction: { undoAction(target, $0) }) | ||
} | ||
|
||
override var canRedo: Bool { | ||
super.canRedo | ||
} | ||
|
||
override var canUndo: Bool { | ||
super.canUndo || isCoalescing | ||
} | ||
|
||
override var isUndoing: Bool { | ||
super.isUndoing || coalescingIsUndoing | ||
} | ||
|
||
override var isRedoing: Bool { | ||
super.isRedoing || coalescingIsRedoing | ||
self.groupsByEvent = false | ||
} | ||
|
||
override func undo() { | ||
if let undoAction = coalescing?.undoAction, let value = coalescing?.value { | ||
coalescingIsUndoing = true | ||
undoAction(value) | ||
breakCoalescing() | ||
coalescingIsUndoing = false | ||
// FIXME: call undo to register redo | ||
// When the Undo system performs an undo action, | ||
// it expects me to register the redo actions using the same code as for undo. | ||
// That makes the coalescing flow tricky to make right right now | ||
} else { | ||
super.undo() | ||
if isCoalescing { | ||
endCoalescing() | ||
} | ||
super.undo() | ||
} | ||
|
||
override func redo() { | ||
if isCoalescing { | ||
endCoalescing() | ||
} | ||
super.redo() | ||
} | ||
|
||
override var undoMenuItemTitle: String { | ||
if canUndo { | ||
return super.undoMenuItemTitle | ||
} else { | ||
return NSLocalizedString("Undo", comment: "Undo") | ||
func checkCoalescing(range: NSTextRange) { | ||
defer { | ||
lastRange = range | ||
} | ||
guard isCoalescing, let lastRange else { | ||
startCoalescing() | ||
return | ||
} | ||
if !lastRange.intersects(range) && lastRange.endLocation != range.location { | ||
endCoalescing() | ||
startCoalescing() | ||
} | ||
} | ||
|
||
override var redoMenuItemTitle: String { | ||
if canRedo { | ||
return super.redoMenuItemTitle | ||
} else { | ||
return NSLocalizedString("Redo", comment: "Redo") | ||
} | ||
func startCoalescing() { | ||
guard !isCoalescing else { return } | ||
isCoalescing = true | ||
beginUndoGrouping() | ||
} | ||
|
||
func endCoalescing() { | ||
guard isCoalescing else { return } | ||
isCoalescing = false | ||
lastRange = nil | ||
endUndoGrouping() | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.