macOS NSTextStorage -> NSLayoutManager lazy attribute applying

Dear All,

There is a property `fixesAttributesLazily` of NSTextStorage class, which is read-only. In the documentation is written that ' ..The system’s concrete subclass overrides this property and sets it to

YES
...' which I really don't now how to interpret.

I'm using a NSTextView object as a simple text editor with simple syntax highlightng. I'm changinf the attributes (only the foreground color) of recognized tokens in the NSTextStorageDelegate:


func textStorage(textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int)


During this I can see the the value of `fixesAttributesLazily` property of the NSTextStorage is FALSE. Is there a way to enable background attribute change and if so - how?


I.

What is the high-level goal you're trying to achieve? What do you mean by "background attribute change" and why do you want to enable it?


It looks to me like fixesAttributesLazily is not about "background" fixing of attributes, but lazy fixing. That is, the fixing is deferred until something queries the attributes, at which point it's done on demand but still synchronously.


So, edited(_:range:changeInLength:) and endEditing() will call processEditing(). processEditing() will call invalidateAttributesInRange(). If fixesAttributesLazily is false, that will call fixAttributesInRange() (inherited from NSMutableAttributedString) and the attributes will be immediately fixed.


However, a subclass can arrange to be safe for lazy fixing of attributes by making sure all of its attribute-accessing methods (overrides of methods inherited from NSMutableAttributedString and NSAttributedString) call ensureAttributesAreFixedInRange() before actually accessing them. If it does this, it can also override fixesAttributesLazily to return true. Once it has, invalidateAttributesInRange() will simply record internally the ranges that have been invalidated rather than immediately fixing them. The later calls to ensureAttributesAreFixedInRange() from the attribute accessors will consult those recorded ranges to see if they need to call fixAttributesInRange(). If so, they will call it and clear that range from the list of ranges needing fixing.


So, fixesAttributesLazily is just a way for a subclass to advertise to the rest of framework whether or not it has opted in to this system by implementing its attribute accessors in the required way. From the outside, the behavior should be largely the same either way, with possible performance differences. Since you're just modifying one attribute once between draws, it doesn't seem like it would matter much for your case.

Thank you very much for the answer. Let me explain in details what I'm trying to achieve:

1. I'm finishng an application which requires syntax highlight. It's written in Swift. I'm using flex to find tokens and iterrating through them inside the NSTextStorageDelegate method:


optional func textStorage(_ textStorage: NSTextStorage,
        didProcessEditing editedMask: NSTextStorageEditActions,
                    range editedRange: NSRange,
           changeInLength delta: Int)


2. Whenever I find a token I'm changing the foreground color using setAttribute(_:_:_:). As you can imagine this is the bottle neck, especially with big text files.

3. The NSLayoutManager is NOT noncontiguous. I tried turning noncontiguous mode to TRUE, but whenever I change even a single attribute, the entire layout management starts to behave not correcty. For example if I have 5-6 new lines and press backspace - the carret goes 3-4 lines above it's current position and stays there for a while, no matter what keys are pressed ... and on insert the scroll bars are jumping .. i.e. the entire text view. If you know please advise where is safe to change attributes when having noncontiguous mode enabled (I tried during the other delegate method - willProcessEditing ... nothing changed).

4. I subclasses NSTextSorage. It's pretty much exactly the way Apple advised in their documentation. Here is the class:


class myTextStorage: NSTextStorage {

  private var storage: NSTextStorage

  override var string: String { return storage.string }

//  override var fixesAttributesLazily: Bool { get { return true } }

  override init() {
       storage = NSMutableAttributedString()
       super.init()
  }

  required init?(coder aDecoder: NSCoder) {
       fatalError("init(coder:) has not been implemented")
  }

  required init?(pasteboardPropertyList propertyList: AnyObject, ofType type: String) {
       fatalError("init(pasteboardPropertyList:ofType:) has not been implemented")
  }

  override func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] {
       return storage.attributesAtIndex(location, effectiveRange: range)
  }

  override func replaceCharactersInRange(range: NSRange, withString str: String) {
       storage.replaceCharactersInRange(range, withString: str)
       edited(.EditedCharacters, range: range, changeInLength: str.utf16.count - range.length)
  }

  override func setAttributes(attrs: [String : AnyObject]?, range: NSRange) {
       storage.setAttributes(attrs, range: range)
       edited(.EditedAttributes, range: range, changeInLength: 0)
  }
}


Unfortunately it works with small texts. In the moment I pase 4 MBytes of text - everything crashes .... and I mean everything, including the macOS. And this is happeneing before ever going into didProcessEditing method.


I know that I need to highlight smaller portions of text, which can be handeled very quickly. What I'm looking for is advise how to lazily set the attributes. What you mentioned may be the right way. May I ask you to explain in more details what you mean?


If you want you can reach me via ivailon at gmail dot com for specifics regarding the app.

macOS NSTextStorage -> NSLayoutManager lazy attribute applying
 
 
Q