@@ -13,6 +13,24 @@ public protocol GutterViewDelegate: AnyObject {
1313 func gutterViewWidthDidUpdate( newWidth: CGFloat )
1414}
1515
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+ ///
1634public class GutterView : NSView {
1735 struct EdgeInsets : Equatable , Hashable {
1836 let leading : CGFloat
@@ -32,6 +50,9 @@ public class GutterView: NSView {
3250 @Invalidating ( . display)
3351 var edgeInsets : EdgeInsets = EdgeInsets ( leading: 20 , trailing: 12 )
3452
53+ @Invalidating ( . display)
54+ var backgroundEdgeInsets : EdgeInsets = EdgeInsets ( leading: 0 , trailing: 8 )
55+
3556 @Invalidating ( . display)
3657 var backgroundColor : NSColor ? = NSColor . controlBackgroundColor
3758
@@ -44,6 +65,7 @@ public class GutterView: NSView {
4465 @Invalidating ( . display)
4566 var selectedLineColor : NSColor = NSColor . selectedTextBackgroundColor. withSystemEffect ( . disabled)
4667
68+ /// The required width of the entire gutter, including padding.
4769 private( set) public var gutterWidth : CGFloat = 0
4870
4971 private weak var textView : TextView ?
@@ -118,6 +140,17 @@ public class GutterView: NSView {
118140 }
119141 }
120142
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+
121154 private func drawSelectedLines( _ context: CGContext ) {
122155 guard let textView = textView,
123156 let selectionManager = textView. selectionManager,
@@ -126,10 +159,14 @@ public class GutterView: NSView {
126159 return
127160 }
128161 context. saveGState ( )
162+
129163 var highlightedLines : Set < UUID > = [ ]
130164 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 {
133170 guard let line = textView. layoutManager. textLineForOffset ( selection. range. location) ,
134171 visibleRange. intersection ( line. range) != nil || selection. range. location == textView. length,
135172 !highlightedLines. contains ( line. data. id) else {
@@ -138,13 +175,14 @@ public class GutterView: NSView {
138175 highlightedLines. insert ( line. data. id)
139176 context. fill (
140177 CGRect (
141- x: 0.0 ,
178+ x: xPos ,
142179 y: line. yPos,
143- width: maxWidth + edgeInsets . horizontal ,
180+ width: width ,
144181 height: line. height
145182 )
146183 )
147184 }
185+
148186 context. restoreGState ( )
149187 }
150188
@@ -204,8 +242,8 @@ public class GutterView: NSView {
204242 CATransaction . begin ( )
205243 superview? . clipsToBounds = false
206244 superview? . layer? . masksToBounds = false
207- layer? . backgroundColor = backgroundColor? . cgColor
208245 updateWidthIfNeeded ( )
246+ drawBackground ( context)
209247 drawSelectedLines ( context)
210248 drawLineNumbers ( context)
211249 CATransaction . commit ( )
0 commit comments