Skip to content

Commit 61e5f5a

Browse files
Add Ribbon View, Demo Fold Provider, Ribbon Toggles (#315)
### Description > [!NOTE] > For reviewers, this is merging into the dev branch. These changes require the version of CETV in [this PR](CodeEditApp/CodeEditTextView#93). Please pull those changes locally and test using that. > [!NOTE] > I'll be making some TODOs in the tracking issue #43 for things that aren't included here. Like the overlapping folds UI issue. Adds the first version of the code folding ribbon, with a very basic folding model. This is mostly a UI change. It includes changes to the gutter, and a new view for displaying folds. The model and related demo fold provider should be considered incomplete and only for demo purposes. This also doesn't implement the hover state yet. Just a very basic outline of everything. Things to review: - New `FoldingRibbonView` - New `LineFoldingModel` - Changes in `GutterView` - Changes to `TextViewController` - Changes to `CodeEditSourceEditor` ### Related Issues * #43 ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots Light mode. ![Screenshot 2025-05-07 at 3 22 17 PM](https://github.com/user-attachments/assets/a9b7838f-6bea-4bb4-bd61-b72175c76788) Dark Mode. ![Screenshot 2025-05-08 at 10 12 45 AM](https://github.com/user-attachments/assets/fb8e3264-71ec-40aa-9d62-7d4a74c15343) Folds are transparent for scrolling text. ![Screenshot 2025-05-08 at 10 08 35 AM](https://github.com/user-attachments/assets/17a1623c-3e8e-40a5-ace3-6adbe8e13320) --------- Co-authored-by: Austin Condiff <austin.condiff@gmail.com>
1 parent 3dccebd commit 61e5f5a

File tree

49 files changed

+2177
-726
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2177
-726
lines changed

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/ContentView.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct ContentView: View {
2727
@State private var settingsIsPresented: Bool = false
2828
@State private var treeSitterClient = TreeSitterClient()
2929
@AppStorage("showMinimap") private var showMinimap: Bool = true
30+
@AppStorage("showFoldingRibbon") private var showFoldingRibbon: Bool = true
3031
@State private var indentOption: IndentOption = .spaces(count: 4)
3132
@AppStorage("reformatAtColumn") private var reformatAtColumn: Int = 80
3233
@AppStorage("showReformattingGuide") private var showReformattingGuide: Bool = false
@@ -56,7 +57,8 @@ struct ContentView: View {
5657
useSystemCursor: useSystemCursor,
5758
showMinimap: showMinimap,
5859
reformatAtColumn: reformatAtColumn,
59-
showReformattingGuide: showReformattingGuide
60+
showReformattingGuide: showReformattingGuide,
61+
showFoldingRibbon: showFoldingRibbon
6062
)
6163
.overlay(alignment: .bottom) {
6264
StatusBar(
@@ -71,7 +73,8 @@ struct ContentView: View {
7173
showMinimap: $showMinimap,
7274
indentOption: $indentOption,
7375
reformatAtColumn: $reformatAtColumn,
74-
showReformattingGuide: $showReformattingGuide
76+
showReformattingGuide: $showReformattingGuide,
77+
showFoldingRibbon: $showFoldingRibbon
7578
)
7679
}
7780
.ignoresSafeArea()

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/StatusBar.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct StatusBar: View {
2626
@Binding var indentOption: IndentOption
2727
@Binding var reformatAtColumn: Int
2828
@Binding var showReformattingGuide: Bool
29+
@Binding var showFoldingRibbon: Bool
2930

3031
var body: some View {
3132
HStack {
@@ -43,6 +44,7 @@ struct StatusBar: View {
4344
.onChange(of: reformatAtColumn) { _, newValue in
4445
reformatAtColumn = max(1, min(200, newValue))
4546
}
47+
Toggle("Show Folding Ribbon", isOn: $showFoldingRibbon)
4648
if #available(macOS 14, *) {
4749
Toggle("Use System Cursor", isOn: $useSystemCursor)
4850
} else {

Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ let package = Package(
1717
// A fast, efficient, text view for code.
1818
.package(
1919
url: "https://github.com/CodeEditApp/CodeEditTextView.git",
20-
from: "0.10.1"
20+
from: "0.11.0"
2121
),
2222
// tree-sitter languages
2323
.package(
2424
url: "https://github.com/CodeEditApp/CodeEditLanguages.git",
2525
exact: "0.1.20"
2626
),
27+
// CodeEditSymbols
28+
.package(
29+
url: "https://github.com/CodeEditApp/CodeEditSymbols.git",
30+
exact: "0.2.3"
31+
),
2732
// SwiftLint
2833
.package(
2934
url: "https://github.com/lukepistrol/SwiftLintPlugin",
@@ -43,7 +48,8 @@ let package = Package(
4348
dependencies: [
4449
"CodeEditTextView",
4550
"CodeEditLanguages",
46-
"TextFormation"
51+
"TextFormation",
52+
"CodeEditSymbols"
4753
],
4854
plugins: [
4955
.plugin(name: "SwiftLint", package: "SwiftLintPlugin")

Sources/CodeEditSourceEditor/CodeEditSourceEditor/CodeEditSourceEditor.swift

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,22 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
3939
/// the default `TreeSitterClient` highlighter.
4040
/// - contentInsets: Insets to use to offset the content in the enclosing scroll view. Leave as `nil` to let the
4141
/// scroll view automatically adjust content insets.
42-
/// - additionalTextInsets: An additional amount to inset the text of the editor by.
42+
/// - additionalTextInsets: A set of extra text insets to indent only *text* content. Does not effect
43+
/// decorations like the find panel.
4344
/// - isEditable: A Boolean value that controls whether the text view allows the user to edit text.
4445
/// - isSelectable: A Boolean value that controls whether the text view allows the user to select text. If this
4546
/// value is true, and `isEditable` is false, the editor is selectable but not editable.
4647
/// - letterSpacing: The amount of space to use between letters, as a percent. Eg: `1.0` = no space, `1.5` = 1/2 a
4748
/// character's width between characters, etc. Defaults to `1.0`
4849
/// - bracketPairEmphasis: The type of highlight to use to highlight bracket pairs.
4950
/// See `BracketPairHighlight` for more information. Defaults to `nil`
50-
/// - useSystemCursor: If true, uses the system cursor on `>=macOS 14`.
51+
/// - useSystemCursor: Use the system cursor instead of the default line cursor. Only available after macOS 14.
5152
/// - undoManager: The undo manager for the text view. Defaults to `nil`, which will create a new CEUndoManager
5253
/// - coordinators: Any text coordinators for the view to use. See ``TextViewCoordinator`` for more information.
53-
/// - showMinimap: Whether to show the minimap
54+
/// - showMinimap: Toggle the visibility of the minimap.
5455
/// - reformatAtColumn: The column to reformat at
5556
/// - showReformattingGuide: Whether to show the reformatting guide
57+
/// - showFoldingRibbon: Toggle the visibility of the line folding ribbon.
5658
public init(
5759
_ text: Binding<String>,
5860
language: CodeLanguage,
@@ -77,7 +79,8 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
7779
coordinators: [any TextViewCoordinator] = [],
7880
showMinimap: Bool,
7981
reformatAtColumn: Int,
80-
showReformattingGuide: Bool
82+
showReformattingGuide: Bool,
83+
showFoldingRibbon: Bool
8184
) {
8285
self.text = .binding(text)
8386
self.language = language
@@ -107,6 +110,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
107110
self.showMinimap = showMinimap
108111
self.reformatAtColumn = reformatAtColumn
109112
self.showReformattingGuide = showReformattingGuide
113+
self.showFoldingRibbon = showFoldingRibbon
110114
}
111115

112116
/// Initializes a Text Editor
@@ -127,18 +131,22 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
127131
/// the default `TreeSitterClient` highlighter.
128132
/// - contentInsets: Insets to use to offset the content in the enclosing scroll view. Leave as `nil` to let the
129133
/// scroll view automatically adjust content insets.
134+
/// - additionalTextInsets: A set of extra text insets to indent only *text* content. Does not effect
135+
/// decorations like the find panel.
130136
/// - isEditable: A Boolean value that controls whether the text view allows the user to edit text.
131137
/// - isSelectable: A Boolean value that controls whether the text view allows the user to select text. If this
132138
/// value is true, and `isEditable` is false, the editor is selectable but not editable.
133139
/// - letterSpacing: The amount of space to use between letters, as a percent. Eg: `1.0` = no space, `1.5` = 1/2 a
134140
/// character's width between characters, etc. Defaults to `1.0`
135141
/// - bracketPairEmphasis: The type of highlight to use to highlight bracket pairs.
136142
/// See `BracketPairEmphasis` for more information. Defaults to `nil`
143+
/// - useSystemCursor: Use the system cursor instead of the default line cursor. Only available after macOS 14.
137144
/// - undoManager: The undo manager for the text view. Defaults to `nil`, which will create a new CEUndoManager
138145
/// - coordinators: Any text coordinators for the view to use. See ``TextViewCoordinator`` for more information.
139-
/// - showMinimap: Whether to show the minimap
146+
/// - showMinimap: Toggle the visibility of the minimap.
140147
/// - reformatAtColumn: The column to reformat at
141148
/// - showReformattingGuide: Whether to show the reformatting guide
149+
/// - showFoldingRibbon: Toggle the visibility of the line folding ribbon.
142150
public init(
143151
_ text: NSTextStorage,
144152
language: CodeLanguage,
@@ -163,7 +171,8 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
163171
coordinators: [any TextViewCoordinator] = [],
164172
showMinimap: Bool,
165173
reformatAtColumn: Int,
166-
showReformattingGuide: Bool
174+
showReformattingGuide: Bool,
175+
showFoldingRibbon: Bool
167176
) {
168177
self.text = .storage(text)
169178
self.language = language
@@ -193,6 +202,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
193202
self.showMinimap = showMinimap
194203
self.reformatAtColumn = reformatAtColumn
195204
self.showReformattingGuide = showReformattingGuide
205+
self.showFoldingRibbon = showFoldingRibbon
196206
}
197207

198208
package var text: TextAPI
@@ -219,6 +229,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
219229
package var showMinimap: Bool
220230
private var reformatAtColumn: Int
221231
private var showReformattingGuide: Bool
232+
package var showFoldingRibbon: Bool
222233

223234
public typealias NSViewControllerType = TextViewController
224235

@@ -247,7 +258,8 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
247258
coordinators: coordinators,
248259
showMinimap: showMinimap,
249260
reformatAtColumn: reformatAtColumn,
250-
showReformattingGuide: showReformattingGuide
261+
showReformattingGuide: showReformattingGuide,
262+
showFoldingRibbon: showFoldingRibbon
251263
)
252264
switch text {
253265
case .binding(let binding):
@@ -336,6 +348,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
336348
controller.contentInsets = contentInsets
337349
controller.additionalTextInsets = additionalTextInsets
338350
controller.showMinimap = showMinimap
351+
controller.showFoldingRibbon = showFoldingRibbon
339352

340353
if controller.indentOption != indentOption {
341354
controller.indentOption = indentOption
@@ -397,6 +410,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
397410
controller.showMinimap == showMinimap &&
398411
controller.reformatAtColumn == reformatAtColumn &&
399412
controller.showReformattingGuide == showReformattingGuide &&
413+
controller.showFoldingRibbon == showFoldingRibbon &&
400414
areHighlightProvidersEqual(controller: controller, coordinator: coordinator)
401415
}
402416

Sources/CodeEditSourceEditor/Controller/TextViewController+Cursor.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import AppKit
1111
extension TextViewController {
1212
/// Sets new cursor positions.
1313
/// - Parameter positions: The positions to set. Lines and columns are 1-indexed.
14-
public func setCursorPositions(_ positions: [CursorPosition]) {
14+
public func setCursorPositions(_ positions: [CursorPosition], scrollToVisible: Bool = false) {
1515
if isPostingCursorNotification { return }
1616
var newSelectedRanges: [NSRange] = []
1717
for position in positions {
@@ -33,6 +33,10 @@ extension TextViewController {
3333
}
3434
}
3535
textView.selectionManager.setSelectedRanges(newSelectedRanges)
36+
37+
if scrollToVisible {
38+
textView.scrollSelectionToVisible()
39+
}
3640
}
3741

3842
/// Update the ``TextViewController/cursorPositions`` variable with new text selections from the text view.

Sources/CodeEditSourceEditor/Controller/TextViewController+FindPanelTarget.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
// Created by Khan Winter on 3/16/25.
66
//
77

8-
import Foundation
8+
import AppKit
99
import CodeEditTextView
1010

1111
extension TextViewController: FindPanelTarget {
12+
var findPanelTargetView: NSView {
13+
textView
14+
}
15+
1216
func findPanelWillShow(panelHeight: CGFloat) {
1317
updateContentInsets()
1418
}
@@ -17,6 +21,10 @@ extension TextViewController: FindPanelTarget {
1721
updateContentInsets()
1822
}
1923

24+
func findPanelModeDidChange(to mode: FindPanelMode) {
25+
updateContentInsets()
26+
}
27+
2028
var emphasisManager: EmphasisManager? {
2129
textView?.emphasisManager
2230
}

Sources/CodeEditSourceEditor/Controller/TextViewController+GutterViewDelegate.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
import Foundation
99

1010
extension TextViewController: GutterViewDelegate {
11-
public func gutterViewWidthDidUpdate(newWidth: CGFloat) {
12-
gutterView?.frame.size.width = newWidth
13-
textView?.textInsets = textViewInsets
11+
public func gutterViewWidthDidUpdate() {
12+
updateTextInsets()
1413
}
1514
}

Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift renamed to Sources/CodeEditSourceEditor/Controller/TextViewController+Lifecycle.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import CodeEditTextView
99
import AppKit
1010

1111
extension TextViewController {
12+
override public func viewWillAppear() {
13+
super.viewWillAppear()
14+
// The calculation this causes cannot be done until the view knows it's final position
15+
updateTextInsets()
16+
minimapView.layout()
17+
}
18+
1219
override public func loadView() {
1320
super.loadView()
1421

@@ -106,9 +113,7 @@ extension TextViewController {
106113
object: scrollView.contentView,
107114
queue: .main
108115
) { [weak self] notification in
109-
guard let clipView = notification.object as? NSClipView,
110-
let textView = self?.textView else { return }
111-
textView.updatedViewport(self?.scrollView.documentVisibleRect ?? .zero)
116+
guard let clipView = notification.object as? NSClipView else { return }
112117
self?.gutterView.needsDisplay = true
113118
self?.minimapXConstraint?.constant = clipView.bounds.origin.x
114119
}
@@ -120,7 +125,6 @@ extension TextViewController {
120125
object: scrollView.contentView,
121126
queue: .main
122127
) { [weak self] _ in
123-
self?.textView.updatedViewport(self?.scrollView.documentVisibleRect ?? .zero)
124128
self?.gutterView.needsDisplay = true
125129
self?.emphasisManager?.removeEmphases(for: EmphasisGroup.brackets)
126130
self?.updateTextInsets()
@@ -220,7 +224,7 @@ extension TextViewController {
220224
self.findViewController?.showFindPanel()
221225
return nil
222226
case (0, "\u{1b}"): // Escape key
223-
self.findViewController?.findPanel.dismiss()
227+
self.findViewController?.hideFindPanel()
224228
return nil
225229
case (_, _):
226230
return event

0 commit comments

Comments
 (0)