Skip to content

Commit aaef5f2

Browse files
committed
Merge branch 'feat/multiple-highlighters' of https://github.com/thecoolwinter/CodeEditSourceEditor into feat/multiple-highlighters
2 parents 053a7d4 + 0b00fb9 commit aaef5f2

File tree

4 files changed

+55
-7
lines changed

4 files changed

+55
-7
lines changed

.swiftlint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ disabled_rules:
55
- todo
66
- trailing_comma
77
- nesting
8+
- optional_data_string_conversion
89

910
type_name:
1011
excluded:

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Documents/CodeEditSourceEditorExampleDocument.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct CodeEditSourceEditorExampleDocument: FileDocument {
2929
}
3030

3131
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
32-
let data = text.data(using: .utf8)!
32+
let data = Data(text.utf8)
3333
return .init(regularFileWithContents: data)
3434
}
3535
}

Sources/CodeEditSourceEditor/Controller/TextViewController.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ public class TextViewController: NSViewController {
9393
didSet {
9494
textView.layoutManager.wrapLines = wrapLines
9595
scrollView.hasHorizontalScroller = !wrapLines
96+
textView.edgeInsets = HorizontalEdgeInsets(
97+
left: textView.edgeInsets.left,
98+
right: textViewTrailingInset // Refresh this value, see docs
99+
)
96100
}
97101
}
98102

@@ -194,6 +198,11 @@ public class TextViewController: NSViewController {
194198
return max(inset, .zero)
195199
}
196200

201+
/// The trailing inset for the editor. Grows when line wrapping is disabled.
202+
package var textViewTrailingInset: CGFloat {
203+
wrapLines ? 1 : 48
204+
}
205+
197206
// MARK: Init
198207

199208
init(
@@ -315,6 +324,6 @@ public class TextViewController: NSViewController {
315324
extension TextViewController: GutterViewDelegate {
316325
public func gutterViewWidthDidUpdate(newWidth: CGFloat) {
317326
gutterView?.frame.size.width = newWidth
318-
textView?.edgeInsets = HorizontalEdgeInsets(left: newWidth, right: 0)
327+
textView?.edgeInsets = HorizontalEdgeInsets(left: newWidth, right: textViewTrailingInset)
319328
}
320329
}

Sources/CodeEditSourceEditor/Gutter/GutterView.swift

+43-5
Original file line numberDiff line numberDiff line change
@@ -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+
///
1634
public 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

Comments
 (0)