@@ -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