Skip to content

Commit

Permalink
Remove NSLayoutManager (CodeEditApp#112)
Browse files Browse the repository at this point in the history
# Description

This PR removes all references to `NSLayoutManager` and fixes syntax
highlighting after the regression in CodeEditApp#105.

# Related Issues

N/A

# Checklist

- [x] I read and understood the [contributing
guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md)
as well as the [code of
conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] I documented my code
- [x] Review requested
  • Loading branch information
matthijseikelenboom authored Jan 12, 2023
2 parents cc6d9d5 + 974aa05 commit e3c93a2
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 15 deletions.
11 changes: 9 additions & 2 deletions Sources/CodeEditTextView/CodeEditTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,19 @@ public struct CodeEditTextView: NSViewControllerRepresentable {

public func updateNSViewController(_ controller: NSViewControllerType, context: Context) {
controller.font = font
controller.language = language
controller.theme = theme
controller.tabWidth = tabWidth
controller.wrapLines = wrapLines
controller.lineHeightMultiple = lineHeight
controller.editorOverscroll = editorOverscroll

// Updating the language and theme needlessly can cause highlights to be re-calculated.
if controller.language.id != language.id {
controller.language = language
}
if controller.theme != theme {
controller.theme = theme
}

controller.reloadUI()
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,17 @@ public extension NSTextRange {

self.init(location: start, end: end)
}

/// Creates an `NSRange` using document information from the given provider.
/// - Parameter provider: The `NSTextElementProvider` to use to convert this range into an `NSRange`
/// - Returns: An `NSRange` if possible
func nsRange(using provider: NSTextElementProvider) -> NSRange? {
guard let location = provider.offset?(from: provider.documentRange.location, to: location) else {
return nil
}
guard let length = provider.offset?(from: self.location, to: endLocation) else {
return nil
}
return NSRange(location: location, length: length)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,32 @@

import Foundation
import STTextView
import AppKit

extension STTextView {
func textRange(for rect: CGRect) -> NSRange {
let length = self.textContentStorage.textStorage?.length ?? 0
/// A helper for calculating the visible range on the text view with some small vertical padding.
var visibleTextRange: NSRange? {
// This helper finds the visible rect of the text using the enclosing scroll view, then finds the nearest
// `NSTextElement`s to those points and uses those elements to create the returned range.

guard let layoutManager = self.textContainer.layoutManager else {
return NSRange(0..<length)
// Get visible rect
guard let bounds = enclosingScrollView?.documentVisibleRect else {
return textLayoutManager.documentRange.nsRange(using: textContentStorage)
}
let container = self.textContainer

let glyphRange = layoutManager.glyphRange(forBoundingRect: rect, in: container)
// Calculate min & max points w/ a small amount of padding vertically.
let minPoint = CGPoint(x: bounds.minX,
y: bounds.minY - 100)
let maxPoint = CGPoint(x: bounds.maxX,
y: bounds.maxY + 100)

return layoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
}
// Get text fragments for both the min and max points
guard let start = textLayoutManager.textLayoutFragment(for: minPoint)?.rangeInElement.location,
let end = textLayoutManager.textLayoutFragment(for: maxPoint)?.rangeInElement.endLocation else {
return textLayoutManager.documentRange.nsRange(using: textContentStorage)
}

var visibleTextRange: NSRange {
return textRange(for: visibleRect)
// Calculate a range and return it as an `NSRange`
return NSTextRange(location: start, end: end)?.nsRange(using: textContentStorage)
}
}
2 changes: 1 addition & 1 deletion Sources/CodeEditTextView/Highlighting/HighlightRange.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// HighlightRange.swift
//
//
//
// Created by Khan Winter on 9/14/22.
//
Expand Down
12 changes: 10 additions & 2 deletions Sources/CodeEditTextView/Highlighting/Highlighter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ class Highlighter: NSObject {

/// The set of visible indexes in tht text view
lazy private var visibleSet: IndexSet = {
return IndexSet(integersIn: Range(textView.visibleTextRange)!)
guard let range = textView.visibleTextRange else {
return IndexSet()
}
return IndexSet(integersIn: Range(range)!)
}()

// MARK: - UI
Expand Down Expand Up @@ -184,6 +187,11 @@ private extension Highlighter {
// Loop through each highlight and modify the textStorage accordingly.
textView.textContentStorage.textStorage?.beginEditing()
for highlight in highlightRanges {
// Does not work:
// textView.textLayoutManager.setRenderingAttributes(attributeProvider.attributesFor(highlight.capture),
// for: NSTextRange(highlight.range,
// provider: textView.textContentStorage)!)
// Temp solution (until Apple fixes above)
textView.textContentStorage.textStorage?.setAttributes(
attributeProvider.attributesFor(highlight.capture),
range: highlight.range
Expand Down Expand Up @@ -219,7 +227,7 @@ private extension Highlighter {
private extension Highlighter {
/// Updates the view to highlight newly visible text when the textview is scrolled or bounds change.
@objc func visibleTextChanged(_ notification: Notification) {
visibleSet = IndexSet(integersIn: Range(textView.visibleTextRange)!)
visibleSet = IndexSet(integersIn: Range(textView.visibleTextRange ?? NSRange())!)

// Any indices that are both *not* valid and in the visible text range should be invalidated
let newlyInvalidSet = visibleSet.subtracting(validSet)
Expand Down

0 comments on commit e3c93a2

Please sign in to comment.