5
5
// Created by Lukas Pistrol on 24.05.22.
6
6
//
7
7
8
+ import AppKit
8
9
import SwiftUI
9
10
import CodeEditTextView
10
11
import CodeEditLanguages
11
12
13
+ /// A SwiftUI View that provides source editing functionality.
12
14
public struct CodeEditSourceEditor : NSViewControllerRepresentable {
15
+ package enum TextAPI {
16
+ case binding( Binding < String > )
17
+ case storage( NSTextStorage )
18
+ }
13
19
14
20
/// Initializes a Text Editor
15
21
/// - Parameters:
@@ -22,7 +28,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
22
28
/// - lineHeight: The line height multiplier (e.g. `1.2`)
23
29
/// - wrapLines: Whether lines wrap to the width of the editor
24
30
/// - 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)`
26
32
/// - useThemeBackground: Determines whether the editor uses the theme's background color, or a transparent
27
33
/// background color
28
34
/// - highlightProvider: A class you provide to perform syntax highlighting. Leave this as `nil` to use the
@@ -37,6 +43,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
37
43
/// - bracketPairHighlight: The type of highlight to use to highlight bracket pairs.
38
44
/// See `BracketPairHighlight` for more information. Defaults to `nil`
39
45
/// - 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.
40
47
public init (
41
48
_ text: Binding < String > ,
42
49
language: CodeLanguage ,
@@ -58,7 +65,76 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
58
65
undoManager: CEUndoManager ? = nil ,
59
66
coordinators: [ any TextViewCoordinator ] = [ ]
60
67
) {
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)
62
138
self . language = language
63
139
self . theme = theme
64
140
self . useThemeBackground = useThemeBackground
@@ -68,7 +144,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
68
144
self . lineHeight = lineHeight
69
145
self . wrapLines = wrapLines
70
146
self . editorOverscroll = editorOverscroll
71
- self . _cursorPositions = cursorPositions
147
+ self . cursorPositions = cursorPositions
72
148
self . highlightProvider = highlightProvider
73
149
self . contentInsets = contentInsets
74
150
self . isEditable = isEditable
@@ -79,7 +155,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
79
155
self . coordinators = coordinators
80
156
}
81
157
82
- @ Binding private var text : String
158
+ package var text : TextAPI
83
159
private var language : CodeLanguage
84
160
private var theme : EditorTheme
85
161
private var font : NSFont
@@ -88,7 +164,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
88
164
private var lineHeight : Double
89
165
private var wrapLines : Bool
90
166
private var editorOverscroll : CGFloat
91
- @ Binding private var cursorPositions : [ CursorPosition ]
167
+ package var cursorPositions : Binding < [ CursorPosition ] >
92
168
private var useThemeBackground : Bool
93
169
private var highlightProvider : HighlightProviding ?
94
170
private var contentInsets : NSEdgeInsets ?
@@ -97,21 +173,21 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
97
173
private var letterSpacing : Double
98
174
private var bracketPairHighlight : BracketPairHighlight ?
99
175
private var undoManager : CEUndoManager ?
100
- private var coordinators : [ any TextViewCoordinator ]
176
+ package var coordinators : [ any TextViewCoordinator ]
101
177
102
178
public typealias NSViewControllerType = TextViewController
103
179
104
180
public func makeNSViewController( context: Context ) -> TextViewController {
105
181
let controller = TextViewController (
106
- string: text ,
182
+ string: " " ,
107
183
language: language,
108
184
font: font,
109
185
theme: theme,
110
186
tabWidth: tabWidth,
111
187
indentOption: indentOption,
112
188
lineHeight: lineHeight,
113
189
wrapLines: wrapLines,
114
- cursorPositions: cursorPositions,
190
+ cursorPositions: cursorPositions. wrappedValue ,
115
191
editorOverscroll: editorOverscroll,
116
192
useThemeBackground: useThemeBackground,
117
193
highlightProvider: highlightProvider,
@@ -122,11 +198,17 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
122
198
bracketPairHighlight: bracketPairHighlight,
123
199
undoManager: undoManager
124
200
)
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
+ }
125
207
if controller. textView == nil {
126
208
controller. loadView ( )
127
209
}
128
210
if !cursorPositions. isEmpty {
129
- controller. setCursorPositions ( cursorPositions)
211
+ controller. setCursorPositions ( cursorPositions. wrappedValue )
130
212
}
131
213
132
214
context. coordinator. controller = controller
@@ -144,7 +226,7 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
144
226
if !context. coordinator. isUpdateFromTextView {
145
227
// Prevent infinite loop of update notifications
146
228
context. coordinator. isUpdatingFromRepresentable = true
147
- controller. setCursorPositions ( cursorPositions)
229
+ controller. setCursorPositions ( cursorPositions. wrappedValue )
148
230
context. coordinator. isUpdatingFromRepresentable = false
149
231
} else {
150
232
context. coordinator. isUpdateFromTextView = false
@@ -216,67 +298,6 @@ public struct CodeEditSourceEditor: NSViewControllerRepresentable {
216
298
controller. letterSpacing == letterSpacing &&
217
299
controller. bracketPairHighlight == bracketPairHighlight
218
300
}
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
- }
280
301
}
281
302
282
303
// swiftlint:disable:next line_length
0 commit comments