Skip to content

Commit

Permalink
fix(linenumbers): Adjust Gutter Background, Add Trailing Padding (#272)
Browse files Browse the repository at this point in the history
- Updates the gutter to consider an inset for where to draw it's
background.
- Adds some padding to the trailing edge when line wrap is disabled.
This is not an opened issue, but while I was adjusting padding might as
well.

### Related Issues

* closes #271
  • Loading branch information
thecoolwinter authored Nov 9, 2024
1 parent b7e3175 commit b26a606
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 7 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ disabled_rules:
- todo
- trailing_comma
- nesting
- optional_data_string_conversion

type_name:
excluded:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct CodeEditSourceEditorExampleDocument: FileDocument {
}

func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
let data = Data(text.utf8)
return .init(regularFileWithContents: data)
}
}
11 changes: 10 additions & 1 deletion Sources/CodeEditSourceEditor/Controller/TextViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ public class TextViewController: NSViewController {
didSet {
textView.layoutManager.wrapLines = wrapLines
scrollView.hasHorizontalScroller = !wrapLines
textView.edgeInsets = HorizontalEdgeInsets(
left: textView.edgeInsets.left,
right: textViewTrailingInset // Refresh this value, see docs
)
}
}

Expand Down Expand Up @@ -194,6 +198,11 @@ public class TextViewController: NSViewController {
return max(inset, .zero)
}

/// The trailing inset for the editor. Grows when line wrapping is disabled.
package var textViewTrailingInset: CGFloat {
wrapLines ? 1 : 48
}

// MARK: Init

init(
Expand Down Expand Up @@ -315,6 +324,6 @@ public class TextViewController: NSViewController {
extension TextViewController: GutterViewDelegate {
public func gutterViewWidthDidUpdate(newWidth: CGFloat) {
gutterView?.frame.size.width = newWidth
textView?.edgeInsets = HorizontalEdgeInsets(left: newWidth, right: 0)
textView?.edgeInsets = HorizontalEdgeInsets(left: newWidth, right: textViewTrailingInset)
}
}
48 changes: 43 additions & 5 deletions Sources/CodeEditSourceEditor/Gutter/GutterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ public protocol GutterViewDelegate: AnyObject {
func gutterViewWidthDidUpdate(newWidth: CGFloat)
}

/// The gutter view displays line numbers that match the text view's line indexes.
/// This view is used as a scroll view's ruler view. It sits on top of the text view so text scrolls underneath the
/// gutter if line wrapping is disabled.
///
/// If the gutter needs more space (when the number of digits in the numbers increases eg. adding a line after line 99),
/// it will notify it's delegate via the ``GutterViewDelegate/gutterViewWidthDidUpdate(newWidth:)`` method. In
/// `CodeEditSourceEditor`, this notifies the ``TextViewController``, which in turn updates the textview's edge insets
/// to adjust for the new leading inset.
///
/// This view also listens for selection updates, and draws a selected background on selected lines to keep the illusion
/// that the gutter's line numbers are inline with the line itself.
///
/// The gutter view has insets of it's own that are relative to the widest line index. By default, these insets are 20px
/// leading, and 12px trailing. However, this view also has a ``GutterView/backgroundEdgeInsets`` property, that pads
/// the rect that has a background drawn. This allows the text to be scrolled under the gutter view for 8px before being
/// overlapped by the gutter. It should help the textview keep the cursor visible if the user types while the cursor is
/// off the leading edge of the editor.
///
public class GutterView: NSView {
struct EdgeInsets: Equatable, Hashable {
let leading: CGFloat
Expand All @@ -32,6 +50,9 @@ public class GutterView: NSView {
@Invalidating(.display)
var edgeInsets: EdgeInsets = EdgeInsets(leading: 20, trailing: 12)

@Invalidating(.display)
var backgroundEdgeInsets: EdgeInsets = EdgeInsets(leading: 0, trailing: 8)

@Invalidating(.display)
var backgroundColor: NSColor? = NSColor.controlBackgroundColor

Expand All @@ -44,6 +65,7 @@ public class GutterView: NSView {
@Invalidating(.display)
var selectedLineColor: NSColor = NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)

/// The required width of the entire gutter, including padding.
private(set) public var gutterWidth: CGFloat = 0

private weak var textView: TextView?
Expand Down Expand Up @@ -118,6 +140,17 @@ public class GutterView: NSView {
}
}

private func drawBackground(_ context: CGContext) {
guard let backgroundColor else { return }
let xPos = backgroundEdgeInsets.leading
let width = gutterWidth - backgroundEdgeInsets.trailing

context.saveGState()
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(x: xPos, y: 0, width: width, height: frame.height))
context.restoreGState()
}

private func drawSelectedLines(_ context: CGContext) {
guard let textView = textView,
let selectionManager = textView.selectionManager,
Expand All @@ -126,10 +159,14 @@ public class GutterView: NSView {
return
}
context.saveGState()

var highlightedLines: Set<UUID> = []
context.setFillColor(selectedLineColor.cgColor)
for selection in selectionManager.textSelections
where selection.range.isEmpty {

let xPos = backgroundEdgeInsets.leading
let width = gutterWidth - backgroundEdgeInsets.trailing

for selection in selectionManager.textSelections where selection.range.isEmpty {
guard let line = textView.layoutManager.textLineForOffset(selection.range.location),
visibleRange.intersection(line.range) != nil || selection.range.location == textView.length,
!highlightedLines.contains(line.data.id) else {
Expand All @@ -138,13 +175,14 @@ public class GutterView: NSView {
highlightedLines.insert(line.data.id)
context.fill(
CGRect(
x: 0.0,
x: xPos,
y: line.yPos,
width: maxWidth + edgeInsets.horizontal,
width: width,
height: line.height
)
)
}

context.restoreGState()
}

Expand Down Expand Up @@ -204,8 +242,8 @@ public class GutterView: NSView {
CATransaction.begin()
superview?.clipsToBounds = false
superview?.layer?.masksToBounds = false
layer?.backgroundColor = backgroundColor?.cgColor
updateWidthIfNeeded()
drawBackground(context)
drawSelectedLines(context)
drawLineNumbers(context)
CATransaction.commit()
Expand Down

0 comments on commit b26a606

Please sign in to comment.