I've written an NSTextView subclass that does frequent programmatic modification of the text within itself (kinda like an IDE's code formatting - auto-insertion of close braces, for example).
My initial implementation of this used NSTextView's insertText:
. This actually appeared to work completely fine. But then while reading the NSTextView documentation (which I do for fun sometimes), I noticed in the Discussion section for insertText:
This method is the entry point for inserting text typed by the user and is generally not suitable for other purposes. Programmatic modification of the text is best done by operating on the text storage directly.
Oh, my bad, I thought. So I dutifully went around changing all my insertText calls to calls to the underlying NSTextStorage (replaceCharactersInRange:withString:
, mostly). That appeared to work OK, until I noticed that it completely screws up Undo (of course, because Undo is handled by NSTextView, not NSTextStorage).
So before I haul off and put a buncha undo code in my text storage, I wonder if maybe I've been Punk'd, and really insertText:
isn't so bad?
Right, so my question is this: is NSTextView's insertText:
call really "not suitable" for programmatic modification of the text of an NSTextView, and if so, why?
insertText:
is a method of NSResponder
-- in general these would be thought of as methods that respond to user events. To a certain degree they imply a "user action." When the docs tell you to edit the NSTextStorage
directly if you want to change things programmatically, the word "programmatically" is being used to distinguish user intent from application operation. If you want your changes to be undoable as if they were user actions, then insertText:
seems like it would be OK to use. That said, most of the time, if the modification was not initiated by a user action, then the user won't consider it to be an undoable action, and making it a unit of undoable action would lead to confusion.
For example, say I pasted a word, "foo", into your text view, and your application then colored that word red (for whatever reason). If I then select undo, I expect my action to be the thing that's undone, not the coloring. The coloring isn't part of my user intent. If I then have to hit Cmd-Z again to actually undo my action, I'm left thinking, "WTF?"
NSUndoManager
has support for grouping events via beginUndoGrouping
and endUndoGrouping
. This can allow the unit of user intent (the paste operation) to be grouped with the application coloring into a single "unit" of undo. In the simplest case, what you might want to try here is to set groupsByEvent
on the NSUndoManager
to YES and then find a way to trigger your application's action to occur in the same pass of the runLoop as the user action. This will cause NSUndoManager
to group them automatically.
Beyond that, say if your programmatic modifications need to come in asynchronously or something, you'll need to manage these groupings somehow yourself, but that's likely going to be non-trivial to implement.
I don't know if you saw my similar question from a couple years ago, but I can tell you that @ipmcc is correct and that trying to manually manage the undo stack while making programmatic changes to the NSTextStorage
is extremely non-trivial. I spent weeks on it and failed.
But your question and @ipmcc's answer make me think that what I was trying to do (and it sounds like pretty much the exact same thing that you are trying to do) may actually be more in the realm of responding to user intent than what the docs mean by programmatic change. So maybe your original solution of using insertText:
is the right way to do it. It's been so long since I abandoned my project that I can't remember for sure if I ever tried that or not, but I don't think I did because I was trying to build my editor using just delegate methods, without subclassing NSTextView
.
In my case, as an example, if the user selects some text and hits either the open or closed bracket key, instead of the default behavior of replacing the selected text with the bracket, what I want to do is wrap the selected text in brackets. And if the user then hits cmd-Z, I want the brackets to disappear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With