Skip to content

Commit 5856fac

Browse files
committed
Merge branch 'feature/auto-indent-1'
2 parents 7ec0204 + 4d6ea14 commit 5856fac

File tree

4 files changed

+400
-94
lines changed

4 files changed

+400
-94
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,39 @@ struct MyEditor: View {
9999
Note: The `CodeEditor` doesn't do automatic theme changes if the appearance
100100
changes.
101101

102+
103+
### Smart Indent and Open/Close Pairing
104+
105+
Inspired by [NTYSmartTextView](https://github.com/naoty/NTYSmartTextView),
106+
`CodeEditor` now also supports (on macOS):
107+
- smarter indents (preserving the indent of the previous line)
108+
- soft indents (insert a configurable amount of spaces if the user presses tabs)
109+
- auto character pairing, e.g. when entering `{`, the matching `}` will be auto-added
110+
111+
To enable smart indents, add the `smartIndent` flag, e.g.:
112+
```swift
113+
CodeEditor(source: $source, language: language,
114+
flags: [ .selectable, .editable, .smartIndent ])
115+
```
116+
It is enabled for editors by default.
117+
118+
To configure soft indents, use the `indentStyle` parameter, e.g.
119+
```swift
120+
CodeEditor(source: $source, language: language,
121+
indentStyle: .softTab(width: 2))
122+
```
123+
It defaults to tabs, as per system settings.
124+
125+
Auto character pairing is automatic based on the language. E.g. there is a set of
126+
defaults for C like languages (e.g. Swift), Python or XML. The defaults can be overridden
127+
using the respective static variable in `CodeEditor`,
128+
or the desired pairing can be set explicitly:
129+
```swift
130+
CodeEditor(source: $source, language: language,
131+
autoPairs: [ "{": "}", "<": ">", "'": "'" ])
132+
```
133+
134+
102135
### Font Sizing
103136

104137
On macOS the editor supports sizing of the font (using Cmd +/Cmd - and the

Sources/CodeEditor/CodeEditor.swift

Lines changed: 149 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,37 @@ import Highlightr
5959
* Note: The `CodeEditor` doesn't do automatic theme changes if the appearance
6060
* changes.
6161
*
62+
* ### Smart Indent and Open/Close Pairing
63+
*
64+
* Inspired by [NTYSmartTextView](https://github.com/naoty/NTYSmartTextView),
65+
* `CodeEditor` now also supports (on macOS):
66+
* - smarter indents (preserving the indent of the previous line)
67+
* - soft indents (insert a configurable amount of spaces if the user presses tabs)
68+
* - auto character pairing, e.g. when entering `{`, the matching `}` will be auto-added
69+
*
70+
* To enable smart indents, add the `smartIndent` flag, e.g.:
71+
*
72+
* CodeEditor(source: $source, language: language,
73+
* flags: [ .selectable, .editable, .smartIndent ])
74+
*
75+
* It is enabled for editors by default.
76+
*
77+
* To configure soft indents, use the `indentStyle` parameter, e.g.
78+
*
79+
* CodeEditor(source: $source, language: language,
80+
* indentStyle: .softTab(width: 2))
81+
*
82+
* It defaults to tabs, as per system settings.
83+
*
84+
* Auto character pairing is automatic based on the language. E.g. there is a set of
85+
* defaults for C like languages (e.g. Swift), Python or XML. The defaults can be overridden
86+
* using the respective static variable in `CodeEditor`,
87+
* or the desired pairing can be set explicitly:
88+
*
89+
* CodeEditor(source: $source, language: language,
90+
* autoPairs: [ "{": "}", "<": ">", "'": "'" ])
91+
*
92+
*
6293
* ### Font Sizing
6394
*
6495
* On macOS the editor supports sizing of the font (using Cmd +/Cmd - and the
@@ -114,78 +145,146 @@ public struct CodeEditor: View {
114145

115146
/// Whether the displayed content should be selectable by the user.
116147
public static let selectable = Flags(rawValue: 1 << 1)
148+
149+
/// If the user starts a newline, the editor automagically adds the same
150+
/// whitespace as on the previous line.
151+
public static let smartIndent = Flags(rawValue: 1 << 2)
152+
153+
public static let defaultViewerFlags : Flags = [ .selectable ]
154+
public static let defaultEditorFlags : Flags =
155+
[ .selectable, .editable, .smartIndent ]
156+
}
157+
158+
@frozen public enum IndentStyle: Equatable {
159+
case system
160+
case softTab(width: Int)
117161
}
118162

163+
/**
164+
* Default auto pairing mappings for languages.
165+
*/
166+
public static var defaultAutoPairs : [ Language : [ String : String ] ] = [
167+
.c: cStyleAutoPairs, .cpp: cStyleAutoPairs, .objectivec: cStyleAutoPairs,
168+
.swift: cStyleAutoPairs,
169+
.java: cStyleAutoPairs, .javascript: cStyleAutoPairs,
170+
.xml: xmlStyleAutoPairs,
171+
.python: [ "(": ")", "[": "]", "\"": "\"", "'": "'", "`": "`" ]
172+
]
173+
public static var cStyleAutoPairs = [
174+
"(": ")", "[": "]", "{": "}", "\"": "\"", "'": "'", "`": "`"
175+
]
176+
public static var xmlStyleAutoPairs = [ "<": ">", "\"": "\"", "'": "'" ]
177+
178+
119179
/**
120180
* Configures a CodeEditor View with the given parameters.
121181
*
122182
* - Parameters:
123-
* - source: A binding to a String that holds the source code to be edited
124-
* (or displayed).
125-
* - language: Optionally set a language (e.g. `.swift`), otherwise
126-
* Highlight.js will attempt to detect the language.
127-
* - theme: The name of the theme to use, defaults to "pojoaque".
128-
* - fontSize: On macOS this Binding can be used to persist the size of
129-
* the font in use. At runtime this is combined with the
130-
* theme to produce the full font information. (optional)
131-
* - flags: Configure whether the text is editable and/or selectable
132-
* (defaults to both).
183+
* - source: A binding to a String that holds the source code to be
184+
* edited (or displayed).
185+
* - language: Optionally set a language (e.g. `.swift`), otherwise
186+
* Highlight.js will attempt to detect the language.
187+
* - theme: The name of the theme to use, defaults to "pojoaque".
188+
* - fontSize: On macOS this Binding can be used to persist the size of
189+
* the font in use. At runtime this is combined with the
190+
* theme to produce the full font information. (optional)
191+
* - flags: Configure whether the text is editable and/or selectable
192+
* (defaults to both).
193+
* - indentStyle: Optionally insert a configurable amount of spaces if the
194+
* user hits "tab".
195+
* - autoPairs: A mapping of open/close characters, where the close
196+
* characters are automatically injected when the user enters
197+
* the opening character. For example: `[ "{": "}" ]` would
198+
* automatically insert the closing "}" if the user enters
199+
* "{". If no value is given, the default mapping for the
200+
* language is used.
201+
* - inset: The editor can be inset in the scroll view. Defaults to
202+
* 8/8.
133203
*/
134-
public init(source : Binding<String>,
135-
language : Language? = nil,
136-
theme : ThemeName = .default,
137-
fontSize : Binding<CGFloat>? = nil,
138-
flags : Flags = [ .selectable, .editable ])
204+
public init(source : Binding<String>,
205+
language : Language? = nil,
206+
theme : ThemeName = .default,
207+
fontSize : Binding<CGFloat>? = nil,
208+
flags : Flags = .defaultEditorFlags,
209+
indentStyle : IndentStyle = .system,
210+
autoPairs : [ String : String ]? = nil,
211+
inset : CGSize? = nil)
139212
{
140-
self.source = source
141-
self.fontSize = fontSize
142-
self.language = language
143-
self.themeName = theme
144-
self.flags = flags
213+
self.source = source
214+
self.fontSize = fontSize
215+
self.language = language
216+
self.themeName = theme
217+
self.flags = flags
218+
self.indentStyle = indentStyle
219+
self.inset = inset ?? CGSize(width: 8, height: 8)
220+
self.autoPairs = autoPairs
221+
?? language.flatMap({ CodeEditor.defaultAutoPairs[$0] })
222+
?? [:]
145223
}
146224

147225
/**
148226
* Configures a read-only CodeEditor View with the given parameters.
149227
*
150228
* - Parameters:
151-
* - source: A String that holds the source code to be displayed.
152-
* - language: Optionally set a language (e.g. `.swift`), otherwise
153-
* Highlight.js will attempt to detect the language.
154-
* - theme: The name of the theme to use, defaults to "pojoaque".
155-
* - fontSize: On macOS this Binding can be used to persist the size of
156-
* the font in use. At runtime this is combined with the
157-
* theme to produce the full font information. (optional)
158-
* - flags: Configure whether the text is selectable
159-
* (defaults to both).
229+
* - source: A String that holds the source code to be displayed.
230+
* - language: Optionally set a language (e.g. `.swift`), otherwise
231+
* Highlight.js will attempt to detect the language.
232+
* - theme: The name of the theme to use, defaults to "pojoaque".
233+
* - fontSize: On macOS this Binding can be used to persist the size of
234+
* the font in use. At runtime this is combined with the
235+
* theme to produce the full font information. (optional)
236+
* - flags: Configure whether the text is selectable
237+
* (defaults to both).
238+
* - indentStyle: Optionally insert a configurable amount of spaces if the
239+
* user hits "tab".
240+
* - autoPairs: A mapping of open/close characters, where the close
241+
* characters are automatically injected when the user enters
242+
* the opening character. For example: `[ "{": "}" ]` would
243+
* automatically insert the closing "}" if the user enters
244+
* "{". If no value is given, the default mapping for the
245+
* language is used.
246+
* - inset: The editor can be inset in the scroll view. Defaults to
247+
* 8/8.
160248
*/
161249
@inlinable
162-
public init(source : String,
163-
language : Language? = nil,
164-
theme : ThemeName = .default,
165-
fontSize : Binding<CGFloat>? = nil,
166-
flags : Flags = [ .selectable ])
250+
public init(source : String,
251+
language : Language? = nil,
252+
theme : ThemeName = .default,
253+
fontSize : Binding<CGFloat>? = nil,
254+
flags : Flags = .defaultViewerFlags,
255+
indentStyle : IndentStyle = .system,
256+
autoPairs : [ String : String ]? = nil,
257+
inset : CGSize? = nil)
167258
{
168259
assert(!flags.contains(.editable), "Editing requires a Binding")
169-
self.init(source : .constant(source),
170-
language : language,
171-
theme : theme,
172-
fontSize : fontSize,
173-
flags : flags.subtracting(.editable))
260+
self.init(source : .constant(source),
261+
language : language,
262+
theme : theme,
263+
fontSize : fontSize,
264+
flags : flags.subtracting(.editable),
265+
indentStyle : indentStyle,
266+
autoPairs : autoPairs,
267+
inset : inset)
174268
}
175269

176-
private var source : Binding<String>
177-
private var fontSize : Binding<CGFloat>?
178-
private let language : Language?
179-
private let themeName : ThemeName
180-
private let flags : Flags
181-
private let inset = CGSize(width: 8, height: 8)
270+
private var source : Binding<String>
271+
private var fontSize : Binding<CGFloat>?
272+
private let language : Language?
273+
private let themeName : ThemeName
274+
private let flags : Flags
275+
private let indentStyle : IndentStyle
276+
private let autoPairs : [ String : String ]
277+
private let inset : CGSize
182278

183279
public var body: some View {
184-
UXCodeTextViewRepresentable(source : source,
185-
language : language,
186-
theme : themeName,
187-
fontSize : fontSize,
188-
flags : flags)
280+
UXCodeTextViewRepresentable(source : source,
281+
language : language,
282+
theme : themeName,
283+
fontSize : fontSize,
284+
flags : flags,
285+
indentStyle : indentStyle,
286+
autoPairs : autoPairs,
287+
inset : inset)
189288
}
190289
}
191290

0 commit comments

Comments
 (0)