diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift index 15c4f839a..f0b828399 100644 --- a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift +++ b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift @@ -23,10 +23,7 @@ extension TextViewController { delegate: self ) gutterView.updateWidthIfNeeded() - scrollView.addFloatingSubview( - gutterView, - for: .horizontal - ) + scrollView.addFloatingSubview(gutterView, for: .horizontal) minimapView = MinimapView(textView: textView, theme: theme) scrollView.addFloatingSubview(minimapView, for: .vertical) @@ -89,7 +86,7 @@ extension TextViewController { minimapView.bottomAnchor.constraint(equalTo: scrollView.contentView.bottomAnchor), minimapXConstraint, maxWidthConstraint, - relativeWidthConstraint, + relativeWidthConstraint ]) } @@ -124,7 +121,11 @@ extension TextViewController { queue: .main ) { [weak self] _ in self?.gutterView.frame.size.height = (self?.textView.frame.height ?? 0) + 10 + self?.gutterView.frame.origin.y = (self?.textView.frame.origin.y ?? 0.0) + - (self?.scrollView.contentInsets.top ?? 0) + self?.gutterView.needsDisplay = true + self?.scrollView.needsLayout = true } NotificationCenter.default.addObserver( @@ -146,7 +147,7 @@ extension TextViewController { // Reset content insets and gutter position when appearance changes self.styleScrollView() - self.gutterView.frame.origin.y = -self.scrollView.contentInsets.top + self.gutterView.frame.origin.y = self.textView.frame.origin.y - self.scrollView.contentInsets.top } } .store(in: &cancellables) diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift index bcf0a6869..2cc2f13b5 100644 --- a/Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift +++ b/Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift @@ -102,7 +102,7 @@ extension TextViewController { findViewController?.topPadding = contentInsets?.top - gutterView.frame.origin.y = -scrollView.contentInsets.top + gutterView.frame.origin.y = textView.frame.origin.y - scrollView.contentInsets.top // Update scrollview tiling scrollView.reflectScrolledClipView(scrollView.contentView) diff --git a/Sources/CodeEditSourceEditor/Gutter/GutterView.swift b/Sources/CodeEditSourceEditor/Gutter/GutterView.swift index 8832eb337..0d9cf5b04 100644 --- a/Sources/CodeEditSourceEditor/Gutter/GutterView.swift +++ b/Sources/CodeEditSourceEditor/Gutter/GutterView.swift @@ -45,7 +45,11 @@ public class GutterView: NSView { var textColor: NSColor = .secondaryLabelColor @Invalidating(.display) - var font: NSFont = .systemFont(ofSize: 13) + var font: NSFont = .systemFont(ofSize: 13) { + didSet { + updateFontLineHeight() + } + } @Invalidating(.display) var edgeInsets: EdgeInsets = EdgeInsets(leading: 20, trailing: 12) @@ -74,6 +78,19 @@ public class GutterView: NSView { /// The maximum number of digits found for a line number. private var maxLineLength: Int = 0 + private var fontLineHeight = 1.0 + + private func updateFontLineHeight() { + let string = NSAttributedString(string: "0", attributes: [.font: font]) + let typesetter = CTTypesetterCreateWithAttributedString(string) + let ctLine = CTTypesetterCreateLine(typesetter, CFRangeMake(0, 1)) + var ascent: CGFloat = 0 + var descent: CGFloat = 0 + var leading: CGFloat = 0 + CTLineGetTypographicBounds(ctLine, &ascent, &descent, &leading) + fontLineHeight = (ascent + descent + leading) + } + override public var isFlipped: Bool { true } @@ -181,7 +198,7 @@ public class GutterView: NSView { y: line.yPos, width: width, height: line.height - ) + ).pixelAligned ) } @@ -217,12 +234,16 @@ public class GutterView: NSView { let fragment: LineFragment? = linePosition.data.lineFragments.first?.data var ascent: CGFloat = 0 let lineNumberWidth = CTLineGetTypographicBounds(ctLine, &ascent, nil, nil) + let fontHeightDifference = ((fragment?.height ?? 0) - fontLineHeight) / 4 - let yPos = linePosition.yPos + ascent + (fragment?.heightDifference ?? 0)/2 + let yPos = linePosition.yPos + ascent + (fragment?.heightDifference ?? 0)/2 + fontHeightDifference // Leading padding + (width - linewidth) let xPos = edgeInsets.leading + (maxWidth - lineNumberWidth) - context.textPosition = CGPoint(x: xPos, y: yPos).pixelAligned + ContextSetHiddenSmoothingStyle(context, 16) + + context.textPosition = CGPoint(x: xPos, y: yPos) + CTLineDraw(ctLine, context) } context.restoreGState()