Changing NSTextStorage causes the insertion point to move to the end of the line

I have a subclass of NSTextView acting as its delegate to NSTextStorage . I am trying to do 2 things:

  • Highlight text in some way
  • Rate the text and add the answer to the text box.

I do this in two different ways called by the delegate callback - (void)textStorageWillProcessEditing:(NSNotification *)notification .

I can make syntax highlighting just fine, but when it comes to adding my answer, the insertion point jumps to the end of the line , and I really don't know why. My assessment method is as follows:

 NSString *result = ..; NSRange lineRange = [[textStorage string] lineRangeForRange:[self selectedRange]]; NSString *line = [[textStorage string] substringWithRange:lineRange]; line = [self appendResult:result toLine:line]; // appends the answer [textStorage replaceCharactersInRange:lineRange withString:line]; 

Doing this will result in my result being excellent, but the problem is that, as already mentioned, the insertion point jumps to the end.

I tried:

  • The wrapper of these calls is higher in [textStorage beginEditing] and -endEditing .
  • Saving the selection range (i.e. the insertion point) before replacing the text store, so I can reset after that, but without the dice.

Am I doing it right? I'm trying to do this in the least hacky way, and I'm also not sure if this is the perfect place for my parsing / highlighting. The docs make me believe it, but maybe it's wrong.

+7
source share
3 answers

I know this question has long been answered, but I had exactly the same problem. In my subclass of NSTextStorage I did the following:

 - (void)processEditing { //Process self.editedRange and apply styles first [super processEditing]; } 

However, the right thing:

 - (void)processEditing { [super processEditing]; //Process self.editedRange and apply styles after calling superclass method } 
+4
source

Reason for entry point to move

Surprisingly, I never found an actual explanation of why this sentence works (or does not work).

In this case, the reason for insertion point: .editedCharacters ( NSTextStorageEditedCharacters in ObjC) affects the position of the insertion point from NSLayoutManager.processEditing(from:editedMask:...) .

If only .editedAttributes / NSTextStorageEditedAttributes , the insertion point will not be affected. This is what you want to achieve when you highlight: change only attributes.

Why backlighting affects the insertion point

The problem with highlighting is that NSTextStorage collects all edited calls during one processing run and combines ranges starting from a user-editable change (for example, insert at input), then forming a union of this and all ranges reported by addAttributes(_:range:) . This results in a single call to NSLayoutManager.processEditing(from:editedMask:...) - with editedMask as [.editedCharacters, .editedAttributes] .

So, you want to send .editedAttributes for the selected ranges, but in the end you will form a union with .editedCharacters . This union moves the waaaaaaaaay entry point beyond where it should go.

Reordering in processEditing to call super first works because the layout manager will be notified of the finished editing. But this approach will still break for some edge cases, which leads to invalid layouts or scroll offsets when you enter very large paragraphs.

This is true for connecting to NSTextStorageDelegate , by the way.

Interact in callbacks after the layout has really finished calling backlight instead of processEditing

The only solution that will work reliably based on the reasons inherent in the Cocoa infrastructure is to perform the selection from textDidChange(_:) exclusively, that is, after the completion of the layout processing. Subscribing to NSTextDidChangeNotification works just as well.

Downside: you need to initiate the allocation of passes for programmatic changes on the main line, since they will not call the textDidChange(_:) callback.


If you want to know more about the source of the problem, I will provide more of my research, various approaches, and solution details in a much longer blog for reference. This post is still a self-sufficient solution in itself: http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/

+1
source

It's simple! I ended this problem in two parts. I still highlight the syntax as a result of the textStorage delegate textStorage , but now I am doing my evaluation and adding to another place.

I ended up redefining both -insertText: and -deleteBackwards: (I could do the same for -deleteForwards: . Both overrides are as follows:

 - (void)insertText:(id)insertString { [super insertText:insertString]; NSRange selectedRange = [self selectedRange]; [self doEvaluationAndAppendResult]; [self setSelectedRange:selectedRange]; } 

In the end, I had to reset the input point. I would still like to understand why this is necessary, but at least it seems less than a hack.

0
source

All Articles