Skip to content

Commit 1740482

Browse files
Add NSTextStorage Initializer (#248)
1 parent d121483 commit 1740482

File tree

9 files changed

+305
-82
lines changed

9 files changed

+305
-82
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// CodeEditSourceEditor+Coordinator.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 5/20/24.
6+
//
7+
8+
import Foundation
9+
import CodeEditTextView
10+
11+
extension CodeEditSourceEditor {
12+
@MainActor
13+
public class Coordinator: NSObject {
14+
var parent: CodeEditSourceEditor
15+
weak var controller: TextViewController?
16+
var isUpdatingFromRepresentable: Bool = false
17+
var isUpdateFromTextView: Bool = false
18+
19+
init(parent: CodeEditSourceEditor) {
20+
self.parent = parent
21+
super.init()
22+
23+
NotificationCenter.default.addObserver(
24+
self,
25+
selector: #selector(textViewDidChangeText(_:)),
26+
name: TextView.textDidChangeNotification,
27+
object: nil
28+
)
29+
30+
NotificationCenter.default.addObserver(
31+
self,
32+
selector: #selector(textControllerCursorsDidUpdate(_:)),
33+
name: TextViewController.cursorPositionUpdatedNotification,
34+
object: nil
35+
)
36+
}
37+
38+
@objc func textViewDidChangeText(_ notification: Notification) {
39+
guard let textView = notification.object as? TextView,
40+
let controller,
41+
controller.textView === textView else {
42+
return
43+
}
44+
if case .binding(let binding) = parent.text {
45+
binding.wrappedValue = textView.string
46+
}
47+
parent.coordinators.forEach {
48+
$0.textViewDidChangeText(controller: controller)
49+
}
50+
}
51+
52+
@objc func textControllerCursorsDidUpdate(_ notification: Notification) {
53+
guard !isUpdatingFromRepresentable else { return }
54+
self.isUpdateFromTextView = true
55+
self.parent.cursorPositions.wrappedValue = self.controller?.cursorPositions ?? []
56+
if self.controller != nil {
57+
self.parent.coordinators.forEach {
58+
$0.textViewDidChangeSelection(
59+
controller: self.controller!,
60+
newPositions: self.controller!.cursorPositions
61+
)
62+
}
63+
}
64+
}
65+
66+
deinit {
67+
parent.coordinators.forEach {
68+
$0.destroy()
69+
}
70+
parent.coordinators.removeAll()
71+
NotificationCenter.default.removeObserver(self)
72+
}
73+
}
74+
}

Sources/CodeEditSourceEditor/CodeEditSourceEditor.swift renamed to Sources/CodeEditSourceEditor/CodeEditSourceEditor/CodeEditSourceEditor.swift

Lines changed: 92 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
// Created by Lukas Pistrol on 24.05.22.
66
//
77

8+
import AppKit
89
import SwiftUI
910
import CodeEditTextView
1011
import CodeEditLanguages
1112

13+
/// A SwiftUI View that provides source editing functionality.
1214
public struct CodeEditSourceEditor: NSViewControllerRepresentable {
15+
package enum TextAPI {
16+
case binding(Binding<String>)
17+
case storage(NSTextStorage)
18+
}
1319

1420
/// Initializes a Text Editor
1521
/// - Parameters:
@@ -22,7 +28,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
2228
/// - lineHeight: The line height multiplier (e.g. `1.2`)
2329
/// - wrapLines: Whether lines wrap to the width of the editor
2430
/// - editorOverscroll: The distance to overscroll the editor by.
25-
/// - cursorPosition: The cursor's position in the editor, measured in `(lineNum, columnNum)`
31+
/// - cursorPositions: The cursor's position in the editor, measured in `(lineNum, columnNum)`
2632
/// - useThemeBackground: Determines whether the editor uses the theme's background color, or a transparent
2733
/// background color
2834
/// - highlightProvider: A class you provide to perform syntax highlighting. Leave this as `nil` to use the
@@ -37,6 +43,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
3743
/// - bracketPairHighlight: The type of highlight to use to highlight bracket pairs.
3844
/// See `BracketPairHighlight` for more information. Defaults to `nil`
3945
/// - undoManager: The undo manager for the text view. Defaults to `nil`, which will create a new CEUndoManager
46+
/// - coordinators: Any text coordinators for the view to use. See ``TextViewCoordinator`` for more information.
4047
public init(
4148
_ text: Binding<String>,
4249
language: CodeLanguage,
@@ -58,7 +65,76 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
5865
undoManager: CEUndoManager? = nil,
5966
coordinators: [any TextViewCoordinator] = []
6067
) {
61-
self._text = text
68+
self.text = .binding(text)
69+
self.language = language
70+
self.theme = theme
71+
self.useThemeBackground = useThemeBackground
72+
self.font = font
73+
self.tabWidth = tabWidth
74+
self.indentOption = indentOption
75+
self.lineHeight = lineHeight
76+
self.wrapLines = wrapLines
77+
self.editorOverscroll = editorOverscroll
78+
self.cursorPositions = cursorPositions
79+
self.highlightProvider = highlightProvider
80+
self.contentInsets = contentInsets
81+
self.isEditable = isEditable
82+
self.isSelectable = isSelectable
83+
self.letterSpacing = letterSpacing
84+
self.bracketPairHighlight = bracketPairHighlight
85+
self.undoManager = undoManager
86+
self.coordinators = coordinators
87+
}
88+
89+
/// Initializes a Text Editor
90+
/// - Parameters:
91+
/// - text: The text content
92+
/// - language: The language for syntax highlighting
93+
/// - theme: The theme for syntax highlighting
94+
/// - font: The default font
95+
/// - tabWidth: The visual tab width in number of spaces
96+
/// - indentOption: The behavior to use when the tab key is pressed. Defaults to 4 spaces.
97+
/// - lineHeight: The line height multiplier (e.g. `1.2`)
98+
/// - wrapLines: Whether lines wrap to the width of the editor
99+
/// - editorOverscroll: The distance to overscroll the editor by.
100+
/// - cursorPositions: The cursor's position in the editor, measured in `(lineNum, columnNum)`
101+
/// - useThemeBackground: Determines whether the editor uses the theme's background color, or a transparent
102+
/// background color
103+
/// - highlightProvider: A class you provide to perform syntax highlighting. Leave this as `nil` to use the
104+
/// built-in `TreeSitterClient` highlighter.
105+
/// - contentInsets: Insets to use to offset the content in the enclosing scroll view. Leave as `nil` to let the
106+
/// scroll view automatically adjust content insets.
107+
/// - isEditable: A Boolean value that controls whether the text view allows the user to edit text.
108+
/// - isSelectable: A Boolean value that controls whether the text view allows the user to select text. If this
109+
/// value is true, and `isEditable` is false, the editor is selectable but not editable.
110+
/// - letterSpacing: The amount of space to use between letters, as a percent. Eg: `1.0` = no space, `1.5` = 1/2 a
111+
/// character's width between characters, etc. Defaults to `1.0`
112+
/// - bracketPairHighlight: The type of highlight to use to highlight bracket pairs.
113+
/// See `BracketPairHighlight` for more information. Defaults to `nil`
114+
/// - undoManager: The undo manager for the text view. Defaults to `nil`, which will create a new CEUndoManager
115+
/// - coordinators: Any text coordinators for the view to use. See ``TextViewCoordinator`` for more information.
116+
public init(
117+
_ text: NSTextStorage,
118+
language: CodeLanguage,
119+
theme: EditorTheme,
120+
font: NSFont,
121+
tabWidth: Int,
122+
indentOption: IndentOption = .spaces(count: 4),
123+
lineHeight: Double,
124+
wrapLines: Bool,
125+
editorOverscroll: CGFloat = 0,
126+
cursorPositions: Binding<[CursorPosition]>,
127+
useThemeBackground: Bool = true,
128+
highlightProvider: HighlightProviding? = nil,
129+
contentInsets: NSEdgeInsets? = nil,
130+
isEditable: Bool = true,
131+
isSelectable: Bool = true,
132+
letterSpacing: Double = 1.0,
133+
bracketPairHighlight: BracketPairHighlight? = nil,
134+
undoManager: CEUndoManager? = nil,
135+
coordinators: [any TextViewCoordinator] = []
136+
) {
137+
self.text = .storage(text)
62138
self.language = language
63139
self.theme = theme
64140
self.useThemeBackground = useThemeBackground
@@ -68,7 +144,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
68144
self.lineHeight = lineHeight
69145
self.wrapLines = wrapLines
70146
self.editorOverscroll = editorOverscroll
71-
self._cursorPositions = cursorPositions
147+
self.cursorPositions = cursorPositions
72148
self.highlightProvider = highlightProvider
73149
self.contentInsets = contentInsets
74150
self.isEditable = isEditable
@@ -79,7 +155,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
79155
self.coordinators = coordinators
80156
}
81157

82-
@Binding private var text: String
158+
package var text: TextAPI
83159
private var language: CodeLanguage
84160
private var theme: EditorTheme
85161
private var font: NSFont
@@ -88,7 +164,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
88164
private var lineHeight: Double
89165
private var wrapLines: Bool
90166
private var editorOverscroll: CGFloat
91-
@Binding private var cursorPositions: [CursorPosition]
167+
package var cursorPositions: Binding<[CursorPosition]>
92168
private var useThemeBackground: Bool
93169
private var highlightProvider: HighlightProviding?
94170
private var contentInsets: NSEdgeInsets?
@@ -97,21 +173,21 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
97173
private var letterSpacing: Double
98174
private var bracketPairHighlight: BracketPairHighlight?
99175
private var undoManager: CEUndoManager?
100-
private var coordinators: [any TextViewCoordinator]
176+
package var coordinators: [any TextViewCoordinator]
101177

102178
public typealias NSViewControllerType = TextViewController
103179

104180
public func makeNSViewController(context: Context) -> TextViewController {
105181
let controller = TextViewController(
106-
string: text,
182+
string: "",
107183
language: language,
108184
font: font,
109185
theme: theme,
110186
tabWidth: tabWidth,
111187
indentOption: indentOption,
112188
lineHeight: lineHeight,
113189
wrapLines: wrapLines,
114-
cursorPositions: cursorPositions,
190+
cursorPositions: cursorPositions.wrappedValue,
115191
editorOverscroll: editorOverscroll,
116192
useThemeBackground: useThemeBackground,
117193
highlightProvider: highlightProvider,
@@ -122,11 +198,17 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
122198
bracketPairHighlight: bracketPairHighlight,
123199
undoManager: undoManager
124200
)
201+
switch text {
202+
case .binding(let binding):
203+
controller.textView.setText(binding.wrappedValue)
204+
case .storage(let textStorage):
205+
controller.textView.setTextStorage(textStorage)
206+
}
125207
if controller.textView == nil {
126208
controller.loadView()
127209
}
128210
if !cursorPositions.isEmpty {
129-
controller.setCursorPositions(cursorPositions)
211+
controller.setCursorPositions(cursorPositions.wrappedValue)
130212
}
131213

132214
context.coordinator.controller = controller
@@ -144,7 +226,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
144226
if !context.coordinator.isUpdateFromTextView {
145227
// Prevent infinite loop of update notifications
146228
context.coordinator.isUpdatingFromRepresentable = true
147-
controller.setCursorPositions(cursorPositions)
229+
controller.setCursorPositions(cursorPositions.wrappedValue)
148230
context.coordinator.isUpdatingFromRepresentable = false
149231
} else {
150232
context.coordinator.isUpdateFromTextView = false
@@ -216,67 +298,6 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
216298
controller.letterSpacing == letterSpacing &&
217299
controller.bracketPairHighlight == bracketPairHighlight
218300
}
219-
220-
@MainActor
221-
public class Coordinator: NSObject {
222-
var parent: CodeEditSourceEditor
223-
weak var controller: TextViewController?
224-
var isUpdatingFromRepresentable: Bool = false
225-
var isUpdateFromTextView: Bool = false
226-
227-
init(parent: CodeEditSourceEditor) {
228-
self.parent = parent
229-
super.init()
230-
231-
NotificationCenter.default.addObserver(
232-
self,
233-
selector: #selector(textViewDidChangeText(_:)),
234-
name: TextView.textDidChangeNotification,
235-
object: nil
236-
)
237-
238-
NotificationCenter.default.addObserver(
239-
self,
240-
selector: #selector(textControllerCursorsDidUpdate(_:)),
241-
name: TextViewController.cursorPositionUpdatedNotification,
242-
object: nil
243-
)
244-
}
245-
246-
@objc func textViewDidChangeText(_ notification: Notification) {
247-
guard let textView = notification.object as? TextView,
248-
let controller,
249-
controller.textView === textView else {
250-
return
251-
}
252-
parent.text = textView.string
253-
parent.coordinators.forEach {
254-
$0.textViewDidChangeText(controller: controller)
255-
}
256-
}
257-
258-
@objc func textControllerCursorsDidUpdate(_ notification: Notification) {
259-
guard !isUpdatingFromRepresentable else { return }
260-
self.isUpdateFromTextView = true
261-
self.parent._cursorPositions.wrappedValue = self.controller?.cursorPositions ?? []
262-
if self.controller != nil {
263-
self.parent.coordinators.forEach {
264-
$0.textViewDidChangeSelection(
265-
controller: self.controller!,
266-
newPositions: self.controller!.cursorPositions
267-
)
268-
}
269-
}
270-
}
271-
272-
deinit {
273-
parent.coordinators.forEach {
274-
$0.destroy()
275-
}
276-
parent.coordinators.removeAll()
277-
NotificationCenter.default.removeObserver(self)
278-
}
279-
}
280301
}
281302

282303
// swiftlint:disable:next line_length

Sources/CodeEditSourceEditor/Documentation.docc/Documentation.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,50 @@ A code editor with syntax highlighting powered by tree-sitter.
44

55
## Overview
66

7-
![logo](codeedittextview-logo)
7+
![logo](codeeditsourceeditor-logo)
88

99
An Xcode-inspired code editor view written in Swift powered by tree-sitter for [CodeEdit](https://github.com/CodeEditApp/CodeEdit). Features include syntax highlighting (based on the provided theme), code completion, find and replace, text diff, validation, current line highlighting, minimap, inline messages (warnings and errors), bracket matching, and more.
1010

11-
This package includes both `AppKit` and `SwiftUI` components. It also relies on the `CodeEditLanguages` and `Theme` module.
12-
1311
![banner](preview)
1412

15-
## Syntax Highlighting
13+
This package includes both `AppKit` and `SwiftUI` components. It also relies on the [`CodeEditLanguages`](https://github.com/CodeEditApp/CodeEditLanguages) for optional syntax highlighting using tree-sitter.
14+
15+
> **CodeEditSourceEditor is currently in development and it is not ready for production use.** <br> Please check back later for updates on this project. Contributors are welcome as we build out the features mentioned above!
1616
17-
``CodeEditSourceEditor`` uses `tree-sitter` for syntax highlighting. A list of already supported languages can be found [here](https://github.com/CodeEditApp/CodeEditSourceEditor/issues/15).
17+
## Currently Supported Languages
1818

19-
New languages need to be added to the [CodeEditLanguages](https://github.com/CodeEditApp/CodeEditLanguages) repo.
19+
See this issue [CodeEditLanguages#10](https://github.com/CodeEditApp/CodeEditLanguages/issues/10) on `CodeEditLanguages` for more information on supported languages.
2020

2121
## Dependencies
2222

23-
Special thanks to both [Marcin Krzyzanowski](https://twitter.com/krzyzanowskim) & [Matt Massicotte](https://twitter.com/mattie) for the great work they've done!
23+
Special thanks to [Matt Massicotte](https://twitter.com/mattie) for the great work he's done!
2424

2525
| Package | Source | Author |
26-
| - | - | - |
27-
| `STTextView` | [GitHub](https://github.com/krzyzanowskim/STTextView) | [Marcin Krzyzanowski](https://twitter.com/krzyzanowskim) |
26+
| :- | :- | :- |
2827
| `SwiftTreeSitter` | [GitHub](https://github.com/ChimeHQ/SwiftTreeSitter) | [Matt Massicotte](https://twitter.com/mattie) |
2928

29+
## License
30+
31+
Licensed under the [MIT license](https://github.com/CodeEditApp/CodeEdit/blob/main/LICENSE.md).
32+
3033
## Topics
3134

3235
### Text View
3336

3437
- ``CodeEditSourceEditor/CodeEditSourceEditor``
35-
- ``CodeEditSourceEditor/TextViewController``
38+
- ``TextViewController``
39+
- ``GutterView``
3640

37-
### Theme
41+
### Themes
3842

3943
- ``EditorTheme``
44+
45+
### Text Coordinators
46+
47+
- <doc:TextViewCoordinators>
48+
- ``TextViewCoordinator``
49+
- ``CombineCoordinator``
50+
51+
### Cursors
52+
53+
- ``CursorPosition``
Loading

0 commit comments

Comments
 (0)