Skip to content

Commit 6cf5e5f

Browse files
committed
Move to Under Scroller, Performance Improvements
1 parent 95e0b91 commit 6cf5e5f

16 files changed

+175
-131
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
// A fast, efficient, text view for code.
1818
.package(
1919
// url: "https://github.com/CodeEditApp/CodeEditTextView.git",
20-
// from: "0.8.2"
20+
// from: "0.9.1"
2121
path: "../CodeEditTextView"
2222
),
2323
// tree-sitter languages

Sources/CodeEditSourceEditor/Controller/EditorContainerView.swift

Lines changed: 0 additions & 56 deletions
This file was deleted.

Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ extension TextViewController {
2929
)
3030

3131
minimapView = MinimapView(textView: textView, theme: theme)
32+
scrollView.addFloatingSubview(minimapView, for: .vertical)
3233

33-
editorContainer = EditorContainerView(scrollView: scrollView, minimapView: minimapView)
34-
35-
let findViewController = FindViewController(target: self, childView: editorContainer)
34+
let findViewController = FindViewController(target: self, childView: scrollView)
3635
addChild(findViewController)
3736
self.findViewController = findViewController
3837
self.view.addSubview(findViewController.view)
@@ -51,17 +50,11 @@ extension TextViewController {
5150
setUpHighlighter()
5251
setUpTextFormation()
5352

54-
NSLayoutConstraint.activate([
55-
findViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
56-
findViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
57-
findViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
58-
findViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
59-
])
60-
6153
if !cursorPositions.isEmpty {
6254
setCursorPositions(cursorPositions)
6355
}
6456

57+
setUpConstraints()
6558
setUpListeners()
6659

6760
textView.updateFrameIfNeeded()
@@ -72,15 +65,45 @@ extension TextViewController {
7265
setUpKeyBindings(eventMonitor: &self.localEvenMonitor)
7366
}
7467

68+
func setUpConstraints() {
69+
guard let findViewController else { return }
70+
71+
let maxWidthConstraint = minimapView.widthAnchor.constraint(lessThanOrEqualToConstant: 150)
72+
let relativeWidthConstraint = minimapView.widthAnchor.constraint(
73+
equalTo: view.widthAnchor,
74+
multiplier: 0.17
75+
)
76+
relativeWidthConstraint.priority = .defaultLow
77+
let minimapXConstraint = minimapView.trailingAnchor.constraint(
78+
equalTo: scrollView.contentView.safeAreaLayoutGuide.trailingAnchor
79+
)
80+
self.minimapXConstraint = minimapXConstraint
81+
82+
NSLayoutConstraint.activate([
83+
findViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
84+
findViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
85+
findViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
86+
findViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
87+
88+
minimapView.topAnchor.constraint(equalTo: scrollView.contentView.safeAreaLayoutGuide.topAnchor),
89+
minimapView.bottomAnchor.constraint(equalTo: scrollView.contentView.safeAreaLayoutGuide.bottomAnchor),
90+
minimapXConstraint,
91+
maxWidthConstraint,
92+
relativeWidthConstraint,
93+
])
94+
}
95+
7596
func setUpListeners() {
7697
// Layout on scroll change
7798
NotificationCenter.default.addObserver(
7899
forName: NSView.boundsDidChangeNotification,
79100
object: scrollView.contentView,
80101
queue: .main
81-
) { [weak self] _ in
102+
) { [weak self] notification in
103+
guard let clipView = notification.object as? NSClipView else { return }
82104
self?.textView.updatedViewport(self?.scrollView.documentVisibleRect ?? .zero)
83105
self?.gutterView.needsDisplay = true
106+
self?.minimapXConstraint?.constant = clipView.bounds.origin.x
84107
}
85108

86109
// Layout on frame change

Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ extension TextViewController {
6767
scrollView.hasVerticalScroller = true
6868
scrollView.hasHorizontalScroller = !wrapLines
6969

70+
scrollView.scrollerStyle = .overlay
71+
7072
updateContentInsets()
7173
}
7274

Sources/CodeEditSourceEditor/Controller/TextViewController.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,18 @@ public class TextViewController: NSViewController {
2222

2323
weak var findViewController: FindViewController?
2424

25-
// Container view for the editor contents (scrolling textview, gutter, and minimap)
26-
// Is a child of the find container, so editor contents all move below the find panel when open.
27-
var editorContainer: EditorContainerView!
2825
var scrollView: NSScrollView!
2926
var textView: TextView!
3027
var gutterView: GutterView!
3128
var minimapView: MinimapView!
3229

33-
internal var _undoManager: CEUndoManager!
34-
internal var systemAppearance: NSAppearance.Name?
30+
var minimapXConstraint: NSLayoutConstraint?
3531

36-
package var localEvenMonitor: Any?
37-
package var isPostingCursorNotification: Bool = false
32+
var _undoManager: CEUndoManager!
33+
var systemAppearance: NSAppearance.Name?
34+
35+
var localEvenMonitor: Any?
36+
var isPostingCursorNotification: Bool = false
3837

3938
/// The string contents.
4039
public var string: String {
@@ -100,6 +99,7 @@ public class TextViewController: NSViewController {
10099
public var wrapLines: Bool {
101100
didSet {
102101
textView.layoutManager.wrapLines = wrapLines
102+
minimapView.layoutManager?.wrapLines = wrapLines
103103
scrollView.hasHorizontalScroller = !wrapLines
104104
textView.textInsets = textViewInsets
105105
}
@@ -127,7 +127,7 @@ public class TextViewController: NSViewController {
127127
/// Optional insets to offset the text view and find panel in the scroll view by.
128128
public var contentInsets: NSEdgeInsets? {
129129
didSet {
130-
styleScrollView()
130+
updateContentInsets()
131131
findViewController?.topPadding = contentInsets?.top
132132
}
133133
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// NSScrollView+percentScrolled.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 4/15/25.
6+
//
7+
8+
import AppKit
9+
10+
extension NSScrollView {
11+
var documentMaxOriginY: CGFloat {
12+
let totalHeight = (documentView?.frame.height ?? 0.0) + contentInsets.top
13+
return totalHeight - (documentVisibleRect.height - contentInsets.top)
14+
}
15+
16+
var percentScrolled: CGFloat {
17+
let currentYPos = documentVisibleRect.origin.y + contentInsets.top
18+
return currentYPos / documentMaxOriginY
19+
}
20+
}

Sources/CodeEditSourceEditor/Minimap/MinimapView+DocumentVisibleView.swift

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,15 @@
77

88
import AppKit
99

10-
extension NSScrollView {
11-
var percentScrolled: CGFloat {
12-
get {
13-
let currentYPos = documentVisibleRect.origin.y + contentInsets.top
14-
let totalHeight = (documentView?.frame.height ?? 0.0) + contentInsets.top
15-
let goalYPos = totalHeight - (documentVisibleRect.height - contentInsets.top)
16-
17-
return currentYPos / goalYPos
18-
}
19-
set {
20-
let totalHeight = (documentView?.frame.height ?? 0.0) + contentInsets.top
21-
contentView.scroll(
22-
to: NSPoint(
23-
x: contentView.frame.origin.x,
24-
y: (newValue * (totalHeight - (documentVisibleRect.height - contentInsets.top))) - contentInsets.top
25-
)
26-
)
27-
reflectScrolledClipView(contentView)
28-
}
29-
}
30-
}
31-
3210
extension MinimapView {
3311
func updateDocumentVisibleViewPosition() {
3412
guard let textView = textView, let editorScrollView = textView.enclosingScrollView, let layoutManager else {
3513
return
3614
}
3715

38-
let minimapHeight = contentView.frame.height
39-
let editorHeight = textView.frame.height
40-
let editorToMinimapHeightRatio = minimapHeight / editorHeight
41-
42-
let containerHeight = editorScrollView.visibleRect.height - editorScrollView.contentInsets.vertical
4316
let availableHeight = min(minimapHeight, containerHeight)
4417
let scrollPercentage = editorScrollView.percentScrolled
18+
guard scrollPercentage.isFinite else { return }
4519

4620
// Update Visible Pane, should scroll down slowly as the user scrolls the document, following the scroller.
4721
// Visible pane's height = scrollview visible height * (minimap line height / editor line height)
@@ -57,9 +31,23 @@ extension MinimapView {
5731

5832
// Minimap scroll offset slowly scrolls down with the visible pane.
5933
if minimapHeight > containerHeight {
60-
scrollView.percentScrolled = scrollPercentage
34+
setScrollViewPosition(scrollPercentage: scrollPercentage)
6135
}
6236

6337
layoutManager.layoutLines()
6438
}
39+
40+
private func setScrollViewPosition(scrollPercentage: CGFloat) {
41+
let totalHeight = contentView.frame.height + scrollView.contentInsets.top
42+
let topInset = scrollView.contentInsets.top
43+
scrollView.contentView.scroll(
44+
to: NSPoint(
45+
x: scrollView.contentView.frame.origin.x,
46+
y: (
47+
scrollPercentage * (totalHeight - (scrollView.documentVisibleRect.height - topInset))
48+
) - topInset
49+
)
50+
)
51+
scrollView.reflectScrolledClipView(scrollView.contentView)
52+
}
6553
}

0 commit comments

Comments
 (0)