Skip to content

Instantly share code, notes, and snippets.

@vgorloff
Last active September 14, 2022 03:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vgorloff/89587daac4c8c5ea1b237d0f269f75de to your computer and use it in GitHub Desktop.
Save vgorloff/89587daac4c8c5ea1b237d0f269f75de to your computer and use it in GitHub Desktop.
Deleted SO question: NSTextView: Visually indent wrapped lines (aka IDE editor) without modifying text storage attributes.

Say I have NSTextView and custom NSRulerView:

enter image description here

Now I want every wrapped line to indent say on 3 spaces (like on image below).

enter image description here

How to achieve it without modifying text storage attributes (i.e. Paragraph style)? Should I use NSLayoutManager? Thanks!

Sample code I found here: https://www.noodlesoft.com/blog/2008/10/05/displaying-line-numbers-with-nstextview/. There is a zip-file attachment at the bottom of the page.


UPDATE 1.

To indent wrapped lines the NSATSTypesetter subclassing needed instead of NSLayoutManager customisation. Here is an example of LineWrappingTypesetter:

import AppKit

class LineWrappingTypesetter: NSATSTypesetter {

   var textString: TextString?
   var lineInset: CGFloat = 0

   override func getLineFragmentRect(_ lineFragmentRectIn: NSRectPointer!,
                                     usedRect usedRectIn: NSRectPointer!,
                                     remaining remainingRectIn: NSRectPointer!,
                                     forStartingGlyphAt startingGlyphIndex: Int,
                                     proposedRect: NSRect,
                                     lineSpacing: CGFloat,
                                     paragraphSpacingBefore: CGFloat,
                                     paragraphSpacingAfter: CGFloat) {

      func defaultImplementation() {
         super.getLineFragmentRect(lineFragmentRectIn, usedRect: usedRectIn, remaining: remainingRectIn,
                                   forStartingGlyphAt: startingGlyphIndex, proposedRect: proposedRect,
                                   lineSpacing: lineSpacing, paragraphSpacingBefore: paragraphSpacingBefore,
                                   paragraphSpacingAfter: paragraphSpacingAfter)
      }

      guard let textString = textString, lineInset > 0, let layoutManager = layoutManager else {
         defaultImplementation()
         return
      }

      let characterIndex = layoutManager.characterIndexForGlyph(at: startingGlyphIndex)
      guard let cursor = textString.cursor(from: characterIndex) else {
         defaultImplementation()
         return
      }

      guard cursor.column > 1 else {
         defaultImplementation()
         return
      }

      var lineFragment = NSRect()
      var used = NSRect()
      var remaining = NSRect()

      super.getLineFragmentRect(&lineFragment, usedRect: &used, remaining: &remaining,
                                forStartingGlyphAt: startingGlyphIndex, proposedRect: proposedRect,
                                lineSpacing: lineSpacing, paragraphSpacingBefore: paragraphSpacingBefore,
                                paragraphSpacingAfter: paragraphSpacingAfter)

      lineFragment = lineFragment.insetLeftBy(lineInset)
      used = used.insetLeftBy(lineInset)
      var size = remaining.size.insetBy(dx: lineInset, dy: 0)
      if size < CGSize.zero {
         size = .zero
      }
      remaining = CGRect(origin: remaining.origin, size: size)

      if lineFragmentRectIn != nil {
         lineFragmentRectIn.pointee = lineFragment
      }
      if usedRectIn != nil {
         usedRectIn.pointee = used
      }
      if remainingRectIn != nil {
         remainingRectIn.pointee = remaining
      }
   }
}

Where: TextString class discussed in this SO question.

Idea behind: We calling base implementation of method getLineFragmentRect and adjusting line fragment rectangle if we processing glyph, which is not a first column character of the current line in the string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment