@@ -13,6 +13,24 @@ public protocol GutterViewDelegate: AnyObject {
13
13
func gutterViewWidthDidUpdate( newWidth: CGFloat )
14
14
}
15
15
16
+ /// The gutter view displays line numbers that match the text view's line indexes.
17
+ /// This view is used as a scroll view's ruler view. It sits on top of the text view so text scrolls underneath the
18
+ /// gutter if line wrapping is disabled.
19
+ ///
20
+ /// If the gutter needs more space (when the number of digits in the numbers increases eg. adding a line after line 99),
21
+ /// it will notify it's delegate via the ``GutterViewDelegate/gutterViewWidthDidUpdate(newWidth:)`` method. In
22
+ /// `CodeEditSourceEditor`, this notifies the ``TextViewController``, which in turn updates the textview's edge insets
23
+ /// to adjust for the new leading inset.
24
+ ///
25
+ /// This view also listens for selection updates, and draws a selected background on selected lines to keep the illusion
26
+ /// that the gutter's line numbers are inline with the line itself.
27
+ ///
28
+ /// The gutter view has insets of it's own that are relative to the widest line index. By default, these insets are 20px
29
+ /// leading, and 12px trailing. However, this view also has a ``GutterView/backgroundEdgeInsets`` property, that pads
30
+ /// the rect that has a background drawn. This allows the text to be scrolled under the gutter view for 8px before being
31
+ /// overlapped by the gutter. It should help the textview keep the cursor visible if the user types while the cursor is
32
+ /// off the leading edge of the editor.
33
+ ///
16
34
public class GutterView : NSView {
17
35
struct EdgeInsets : Equatable , Hashable {
18
36
let leading : CGFloat
@@ -32,6 +50,9 @@ public class GutterView: NSView {
32
50
@Invalidating ( . display)
33
51
var edgeInsets : EdgeInsets = EdgeInsets ( leading: 20 , trailing: 12 )
34
52
53
+ @Invalidating ( . display)
54
+ var backgroundEdgeInsets : EdgeInsets = EdgeInsets ( leading: 0 , trailing: 8 )
55
+
35
56
@Invalidating ( . display)
36
57
var backgroundColor : NSColor ? = NSColor . controlBackgroundColor
37
58
@@ -44,6 +65,7 @@ public class GutterView: NSView {
44
65
@Invalidating ( . display)
45
66
var selectedLineColor : NSColor = NSColor . selectedTextBackgroundColor. withSystemEffect ( . disabled)
46
67
68
+ /// The required width of the entire gutter, including padding.
47
69
private( set) public var gutterWidth : CGFloat = 0
48
70
49
71
private weak var textView : TextView ?
@@ -118,6 +140,17 @@ public class GutterView: NSView {
118
140
}
119
141
}
120
142
143
+ private func drawBackground( _ context: CGContext ) {
144
+ guard let backgroundColor else { return }
145
+ let xPos = backgroundEdgeInsets. leading
146
+ let width = gutterWidth - backgroundEdgeInsets. trailing
147
+
148
+ context. saveGState ( )
149
+ context. setFillColor ( backgroundColor. cgColor)
150
+ context. fill ( CGRect ( x: xPos, y: 0 , width: width, height: frame. height) )
151
+ context. restoreGState ( )
152
+ }
153
+
121
154
private func drawSelectedLines( _ context: CGContext ) {
122
155
guard let textView = textView,
123
156
let selectionManager = textView. selectionManager,
@@ -126,10 +159,14 @@ public class GutterView: NSView {
126
159
return
127
160
}
128
161
context. saveGState ( )
162
+
129
163
var highlightedLines : Set < UUID > = [ ]
130
164
context. setFillColor ( selectedLineColor. cgColor)
131
- for selection in selectionManager. textSelections
132
- where selection. range. isEmpty {
165
+
166
+ let xPos = backgroundEdgeInsets. leading
167
+ let width = gutterWidth - backgroundEdgeInsets. trailing
168
+
169
+ for selection in selectionManager. textSelections where selection. range. isEmpty {
133
170
guard let line = textView. layoutManager. textLineForOffset ( selection. range. location) ,
134
171
visibleRange. intersection ( line. range) != nil || selection. range. location == textView. length,
135
172
!highlightedLines. contains ( line. data. id) else {
@@ -138,13 +175,14 @@ public class GutterView: NSView {
138
175
highlightedLines. insert ( line. data. id)
139
176
context. fill (
140
177
CGRect (
141
- x: 0.0 ,
178
+ x: xPos ,
142
179
y: line. yPos,
143
- width: maxWidth + edgeInsets . horizontal ,
180
+ width: width ,
144
181
height: line. height
145
182
)
146
183
)
147
184
}
185
+
148
186
context. restoreGState ( )
149
187
}
150
188
@@ -204,8 +242,8 @@ public class GutterView: NSView {
204
242
CATransaction . begin ( )
205
243
superview? . clipsToBounds = false
206
244
superview? . layer? . masksToBounds = false
207
- layer? . backgroundColor = backgroundColor? . cgColor
208
245
updateWidthIfNeeded ( )
246
+ drawBackground ( context)
209
247
drawSelectedLines ( context)
210
248
drawLineNumbers ( context)
211
249
CATransaction . commit ( )
0 commit comments