Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ public class TextViewController: NSViewController {
/// The type of highlight to use when highlighting bracket pairs. Leave as `nil` to disable highlighting.
public var bracketPairEmphasis: BracketPairEmphasis? { configuration.appearance.bracketPairEmphasis }

/// The optional vertical separator between the gutter and editor content.
public var gutterSeparator: GutterSeparator? { configuration.appearance.gutterSeparator }

/// The column at which to show the reformatting guide
public var reformatAtColumn: Int { configuration.behavior.reformatAtColumn }

Expand Down
46 changes: 46 additions & 0 deletions Sources/CodeEditSourceEditor/Gutter/GutterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ public protocol GutterViewDelegate: AnyObject {
func gutterViewWidthDidUpdate()
}

/// Configures the vertical separator between the gutter and editor content.
public struct GutterSeparator: Equatable {
/// The separator's stroke color.
public var color: NSColor
/// The separator's width in points.
public var lineWidth: CGFloat
/// Alternating painted and unpainted lengths. An empty array draws a solid line.
public var dashPattern: [CGFloat]

/// Creates a gutter separator.
/// - Parameters:
/// - color: The separator's stroke color.
/// - lineWidth: The separator's width in points.
/// - dashPattern: Alternating painted and unpainted lengths. Leave empty for a solid line.
public init(color: NSColor = .separatorColor, lineWidth: CGFloat = 1, dashPattern: [CGFloat] = []) {
self.color = color
self.lineWidth = lineWidth
self.dashPattern = dashPattern
}
}

/// 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.
Expand Down Expand Up @@ -73,6 +94,14 @@ public class GutterView: NSView {
@Invalidating(.display)
var selectedLineColor: NSColor = NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)

var separator: GutterSeparator? {
didSet {
if oldValue != separator {
needsDisplay = true
}
}
}

/// Toggle the visibility of the line fold decoration.
@Invalidating(.display)
public var showFoldingRibbon: Bool = true {
Expand Down Expand Up @@ -145,6 +174,7 @@ public class GutterView: NSView {
controller: controller,
delegate: delegate
)
separator = configuration.appearance.gutterSeparator
}

public init(
Expand Down Expand Up @@ -327,6 +357,7 @@ public class GutterView: NSView {
drawBackground(context, dirtyRect: dirtyRect)
drawSelectedLines(context)
drawLineNumbers(context, dirtyRect: dirtyRect)
drawSeparator(context)
context.restoreGState()
}

Expand All @@ -336,3 +367,18 @@ public class GutterView: NSView {
textView = nil
}
}

private extension GutterView {
func drawSeparator(_ context: CGContext) {
guard let separator, separator.lineWidth > 0 else { return }
context.saveGState()
context.setStrokeColor(separator.color.cgColor)
context.setLineWidth(separator.lineWidth)
context.setLineDash(phase: 0, lengths: separator.dashPattern)
let xPosition = bounds.maxX - separator.lineWidth / 2
context.move(to: CGPoint(x: xPosition, y: bounds.minY))
context.addLine(to: CGPoint(x: xPosition, y: bounds.maxY))
context.strokePath()
context.restoreGState()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ extension SourceEditorConfiguration {
/// See ``BracketPairEmphasis`` for more information. Defaults to `.flash`.
public var bracketPairEmphasis: BracketPairEmphasis? = .flash

/// The optional vertical separator between the gutter and editor content.
public var gutterSeparator: GutterSeparator?

/// Create a new appearance configuration object.
/// - Parameters:
/// - theme: The theme for syntax highlighting.
Expand All @@ -53,6 +56,7 @@ extension SourceEditorConfiguration {
/// - tabWidth: The visual tab width in number of spaces.
/// - bracketPairEmphasis: The type of highlight to use to highlight bracket pairs. See
/// ``BracketPairEmphasis`` for more information. Defaults to `.flash`.
/// - gutterSeparator: The optional vertical separator between the gutter and editor content.
public init(
theme: EditorTheme,
useThemeBackground: Bool = true,
Expand All @@ -62,7 +66,8 @@ extension SourceEditorConfiguration {
wrapLines: Bool,
useSystemCursor: Bool = true,
tabWidth: Int = 4,
bracketPairEmphasis: BracketPairEmphasis? = .flash
bracketPairEmphasis: BracketPairEmphasis? = .flash,
gutterSeparator: GutterSeparator? = nil
) {
self.theme = theme
self.useThemeBackground = useThemeBackground
Expand All @@ -77,6 +82,7 @@ extension SourceEditorConfiguration {
}
self.tabWidth = tabWidth
self.bracketPairEmphasis = bracketPairEmphasis
self.gutterSeparator = gutterSeparator
}

@MainActor
Expand Down Expand Up @@ -123,6 +129,8 @@ extension SourceEditorConfiguration {
controller.emphasizeSelectionPairs()
}

controller.gutterView.separator = gutterSeparator

// Cant put these in one if sadly
if #available(macOS 14, *) {
if oldConfig?.useSystemCursor != useSystemCursor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,17 @@ final class TextViewControllerTests: XCTestCase {
XCTAssertEqual(controller.textViewInsets.right, MinimapView.maxWidth)
}

func test_gutterSeparator() {
XCTAssertNil(controller.gutterSeparator)
XCTAssertNil(controller.gutterView.separator)

let separator = GutterSeparator(color: .systemRed, lineWidth: 2, dashPattern: [1, 1])
controller.configuration.appearance.gutterSeparator = separator

XCTAssertEqual(controller.gutterSeparator, separator)
XCTAssertEqual(controller.gutterView.separator, separator)
}

// MARK: Folding Ribbon

func test_foldingRibbonToggle() {
Expand Down