From 7b2deea78cd282cd912f32ce3edb5131733b1555 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:31:45 -0500 Subject: [PATCH 1/5] Fix Ambiguous Gutter Position --- .../Controller/TextViewController+LoadView.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift index 15c4f839a..fc48a637f 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) @@ -90,6 +87,8 @@ extension TextViewController { minimapXConstraint, maxWidthConstraint, relativeWidthConstraint, + + gutterView.topAnchor.constraint(equalTo: textView.topAnchor) ]) } @@ -123,7 +122,11 @@ extension TextViewController { object: textView, queue: .main ) { [weak self] _ in - self?.gutterView.frame.size.height = (self?.textView.frame.height ?? 0) + 10 + guard let scrollView = self?.scrollView else { return } + self?.gutterView.frame.size.height = max( + (self?.textView.frame.height ?? 0) + 10, + (self?.scrollView.documentVisibleRect.height ?? 0.0) + (self?.scrollView.contentInsets.vertical ?? 0.0) + ) self?.gutterView.needsDisplay = true } From 7a2130065c6d91ce5b3b87ba76dca4733b1f0085 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:39:48 -0500 Subject: [PATCH 2/5] Remove Unnecessary Code --- .../Controller/TextViewController+LoadView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift index fc48a637f..18b4d98b2 100644 --- a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift +++ b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift @@ -122,7 +122,6 @@ extension TextViewController { object: textView, queue: .main ) { [weak self] _ in - guard let scrollView = self?.scrollView else { return } self?.gutterView.frame.size.height = max( (self?.textView.frame.height ?? 0) + 10, (self?.scrollView.documentVisibleRect.height ?? 0.0) + (self?.scrollView.contentInsets.vertical ?? 0.0) From 08eae14cd8c418bb97e16e0cded63ea80e2814e9 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:03:43 -0500 Subject: [PATCH 3/5] Don't Constrain to TextView, Constrain to Leading And Top ClipView --- .../Controller/TextViewController+LoadView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift index 18b4d98b2..b89e5bf38 100644 --- a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift +++ b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift @@ -88,7 +88,8 @@ extension TextViewController { maxWidthConstraint, relativeWidthConstraint, - gutterView.topAnchor.constraint(equalTo: textView.topAnchor) + gutterView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor), + gutterView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor) ]) } From ed0e401bfa5c8d14f50ac1b6d0f3c61c757de52a Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:35:45 -0500 Subject: [PATCH 4/5] Adjust Gutter Drawing - Correct, Fix Y Position Again --- .../TextViewController+LoadView.swift | 15 ++++------ .../TextViewController+StyleViews.swift | 2 +- .../Gutter/GutterView.swift | 29 ++++++++++++++++--- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift index b89e5bf38..1ec152c8e 100644 --- a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift +++ b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift @@ -86,10 +86,7 @@ extension TextViewController { minimapView.bottomAnchor.constraint(equalTo: scrollView.contentView.bottomAnchor), minimapXConstraint, maxWidthConstraint, - relativeWidthConstraint, - - gutterView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor), - gutterView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor) + relativeWidthConstraint ]) } @@ -123,11 +120,11 @@ extension TextViewController { object: textView, queue: .main ) { [weak self] _ in - self?.gutterView.frame.size.height = max( - (self?.textView.frame.height ?? 0) + 10, - (self?.scrollView.documentVisibleRect.height ?? 0.0) + (self?.scrollView.contentInsets.vertical ?? 0.0) - ) + 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( @@ -149,7 +146,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() From c32a2ca26a1de6155773856e875aaa1b742932ba Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 23 Apr 2025 09:14:05 -0500 Subject: [PATCH 5/5] Lint --- .../Controller/TextViewController+LoadView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift index 1ec152c8e..f0b828399 100644 --- a/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift +++ b/Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift @@ -121,7 +121,8 @@ 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.frame.origin.y = (self?.textView.frame.origin.y ?? 0.0) + - (self?.scrollView.contentInsets.top ?? 0) self?.gutterView.needsDisplay = true self?.scrollView.needsLayout = true