diff --git a/Package.swift b/Package.swift index 5c1ae4da6..5b7c530ae 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/krzyzanowskim/STTextView.git", - exact: "0.4.0" + from: "0.4.1" ), .package( url: "https://github.com/CodeEditApp/CodeEditLanguages.git", diff --git a/Sources/CodeEditTextView/CodeEditTextView.swift b/Sources/CodeEditTextView/CodeEditTextView.swift index de434ed20..2ff103072 100644 --- a/Sources/CodeEditTextView/CodeEditTextView.swift +++ b/Sources/CodeEditTextView/CodeEditTextView.swift @@ -25,6 +25,8 @@ public struct CodeEditTextView: NSViewControllerRepresentable { /// - editorOverscroll: The percentage for overscroll, between 0-1 (default: `0.0`) /// - highlightProvider: A class you provide to perform syntax highlighting. Leave this as `nil` to use the /// built-in `TreeSitterClient` highlighter. + /// - contentInsets: Insets to use to offset the content in the enclosing scroll view. Leave as `nil` to let the + /// scroll view automatically adjust content insets. public init( _ text: Binding, language: CodeLanguage, @@ -36,7 +38,8 @@ public struct CodeEditTextView: NSViewControllerRepresentable { editorOverscroll: Binding = .constant(0.0), cursorPosition: Published<(Int, Int)>.Publisher? = nil, useThemeBackground: Bool = true, - highlightProvider: HighlightProviding? = nil + highlightProvider: HighlightProviding? = nil, + contentInsets: NSEdgeInsets? = nil ) { self._text = text self.language = language @@ -49,6 +52,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable { self._editorOverscroll = editorOverscroll self.cursorPosition = cursorPosition self.highlightProvider = highlightProvider + self.contentInsets = contentInsets } @Binding private var text: String @@ -62,6 +66,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable { private var cursorPosition: Published<(Int, Int)>.Publisher? private var useThemeBackground: Bool private var highlightProvider: HighlightProviding? + private var contentInsets: NSEdgeInsets? public typealias NSViewControllerType = STTextViewController @@ -76,7 +81,8 @@ public struct CodeEditTextView: NSViewControllerRepresentable { cursorPosition: cursorPosition, editorOverscroll: editorOverscroll, useThemeBackground: useThemeBackground, - highlightProvider: highlightProvider + highlightProvider: highlightProvider, + contentInsets: contentInsets ) controller.lineHeightMultiple = lineHeight return controller @@ -89,6 +95,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable { controller.useThemeBackground = useThemeBackground controller.lineHeightMultiple = lineHeight controller.editorOverscroll = editorOverscroll + controller.contentInsets = contentInsets // Updating the language and theme needlessly can cause highlights to be re-calculated. if controller.language.id != language.id { diff --git a/Sources/CodeEditTextView/STTextViewController.swift b/Sources/CodeEditTextView/STTextViewController.swift index 7e87347e9..92a149952 100644 --- a/Sources/CodeEditTextView/STTextViewController.swift +++ b/Sources/CodeEditTextView/STTextViewController.swift @@ -55,6 +55,9 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt /// Filters used when applying edits.. internal var textFilters: [TextFormation.Filter] = [] + /// Optional insets to offset the text view in the scroll view by. + public var contentInsets: NSEdgeInsets? + // MARK: - Highlighting internal var highlighter: Highlighter? @@ -77,7 +80,8 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt cursorPosition: Published<(Int, Int)>.Publisher? = nil, editorOverscroll: Double, useThemeBackground: Bool, - highlightProvider: HighlightProviding? = nil + highlightProvider: HighlightProviding? = nil, + contentInsets: NSEdgeInsets? = nil ) { self.text = text self.language = language @@ -89,6 +93,7 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt self.editorOverscroll = editorOverscroll self.useThemeBackground = useThemeBackground self.highlightProvider = highlightProvider + self.contentInsets = contentInsets super.init(nibName: nil, bundle: nil) } @@ -98,6 +103,7 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt // MARK: VC Lifecycle + // swiftlint:disable function_body_length public override func loadView() { textView = STTextView() @@ -106,6 +112,10 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt scrollView.hasVerticalScroller = true scrollView.documentView = textView scrollView.drawsBackground = useThemeBackground + scrollView.automaticallyAdjustsContentInsets = contentInsets == nil + if let contentInsets = contentInsets { + scrollView.contentInsets = contentInsets + } rulerView = STLineNumberRulerView(textView: textView, scrollView: scrollView) rulerView.backgroundColor = useThemeBackground ? theme.background : .clear @@ -218,9 +228,14 @@ public class STTextViewController: NSViewController, STTextViewDelegate, ThemeAt rulerView?.separatorColor = theme.invisibles rulerView?.baselineOffset = baselineOffset - (view as? NSScrollView)?.drawsBackground = useThemeBackground - (view as? NSScrollView)?.backgroundColor = useThemeBackground ? theme.background : .clear - (view as? NSScrollView)?.contentView.contentInsets.bottom = bottomContentInsets + if let scrollView = view as? NSScrollView { + scrollView.drawsBackground = useThemeBackground + scrollView.backgroundColor = useThemeBackground ? theme.background : .clear + if let contentInsets = contentInsets { + scrollView.contentInsets = contentInsets + } + scrollView.contentInsets.bottom = bottomContentInsets + (contentInsets?.bottom ?? 0) + } setStandardAttributes() } diff --git a/Tests/CodeEditTextViewTests/STTextViewControllerTests.swift b/Tests/CodeEditTextViewTests/STTextViewControllerTests.swift index b866d270c..b170b46f1 100644 --- a/Tests/CodeEditTextViewTests/STTextViewControllerTests.swift +++ b/Tests/CodeEditTextViewTests/STTextViewControllerTests.swift @@ -70,6 +70,10 @@ final class STTextViewControllerTests: XCTestCase { width: 100, height: 100) + controller.editorOverscroll = 0 + controller.contentInsets = nil + controller.reloadUI() + // editorOverscroll: 0 XCTAssertEqual(scrollView.contentView.contentInsets.bottom, 0) @@ -86,6 +90,48 @@ final class STTextViewControllerTests: XCTestCase { XCTAssertEqual(scrollView.contentView.contentInsets.bottom, 87.0) } + func test_editorInsets() throws { + let scrollView = try XCTUnwrap(controller.view as? NSScrollView) + scrollView.frame = .init(x: .zero, + y: .zero, + width: 100, + height: 100) + + func assertInsetsEqual(_ lhs: NSEdgeInsets, _ rhs: NSEdgeInsets) throws { + XCTAssertEqual(lhs.top, rhs.top) + XCTAssertEqual(lhs.right, rhs.right) + XCTAssertEqual(lhs.bottom, rhs.bottom) + XCTAssertEqual(lhs.left, rhs.left) + } + + controller.editorOverscroll = 0 + controller.contentInsets = nil + controller.reloadUI() + + // contentInsets: 0 + try assertInsetsEqual(scrollView.contentInsets, NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)) + + // contentInsets: 16 + controller.contentInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + controller.reloadUI() + + try assertInsetsEqual(scrollView.contentInsets, NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)) + + // contentInsets: different + controller.contentInsets = NSEdgeInsets(top: 32.5, left: 12.3, bottom: 20, right: 1) + controller.reloadUI() + + try assertInsetsEqual(scrollView.contentInsets, NSEdgeInsets(top: 32.5, left: 12.3, bottom: 20, right: 1)) + + // contentInsets: 16 + // editorOverscroll: 0.5 + controller.contentInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + controller.editorOverscroll = 0.5 + controller.reloadUI() + + try assertInsetsEqual(scrollView.contentInsets, NSEdgeInsets(top: 16, left: 16, bottom: 16 + 50, right: 16)) + } + func test_editorOverScroll_ZeroCondition() throws { let scrollView = try XCTUnwrap(controller.view as? NSScrollView) scrollView.frame = .zero