@@ -124,21 +124,36 @@ public enum RenderBlockContent: Equatable {
124
124
public var code : [ String ]
125
125
/// Additional metadata for this code block.
126
126
public var metadata : RenderContentMetadata ?
127
+ /// Annotations for code blocks
128
+ public var options : CodeBlockOptions ?
129
+
130
+ /// Make a new `CodeListing` with the given data.
131
+ public init ( syntax: String ? , code: [ String ] , metadata: RenderContentMetadata ? , options: CodeBlockOptions ? ) {
132
+ self . syntax = syntax
133
+ self . code = code
134
+ self . metadata = metadata
135
+ self . options = options
136
+ }
137
+ }
138
+
139
+ public struct CodeBlockOptions : Equatable {
140
+ public var language : String ?
127
141
public var copyToClipboard : Bool = true
128
- public var wrap : Int = 100
142
+ public var wrap : Int = 0
129
143
public var highlight : [ Int ] = [ Int] ( )
130
144
public var showLineNumbers : Bool = false
131
145
public var strikeout : [ Int ] = [ Int] ( )
132
146
133
147
public enum OptionName : String , CaseIterable {
148
+ case _nonFrozenEnum_useDefaultCase
134
149
case nocopy
135
150
case wrap
136
151
case highlight
137
152
case showLineNumbers
138
153
case strikeout
139
154
case unknown
140
155
141
- init ? < S : StringProtocol > ( caseInsensitive raw: S ) {
156
+ init ? ( caseInsensitive raw: some StringProtocol ) {
142
157
self . init ( rawValue: raw. lowercased ( ) )
143
158
}
144
159
}
@@ -147,17 +162,141 @@ public enum RenderBlockContent: Equatable {
147
162
Set ( OptionName . allCases. map ( \. rawValue) )
148
163
}
149
164
150
- /// Make a new `CodeListing` with the given data.
151
- public init ( syntax: String ? , code: [ String ] , metadata: RenderContentMetadata ? , copyToClipboard: Bool , wrap: Int , highlight: [ Int ] , strikeout: [ Int ] , showLineNumbers: Bool ) {
152
- self . syntax = syntax
153
- self . code = code
154
- self . metadata = metadata
165
+ // empty initializer with default values
166
+ public init ( ) {
167
+ self . language = " "
168
+ self . copyToClipboard = true
169
+ self . wrap = 0
170
+ self . highlight = [ Int] ( )
171
+ self . showLineNumbers = false
172
+ self . strikeout = [ Int] ( )
173
+ }
174
+
175
+ public init ( parsingLanguageString language: String ? ) {
176
+ let ( lang, tokens) = Self . tokenizeLanguageString ( language)
177
+
178
+ self . language = lang
179
+ self . copyToClipboard = !tokens. contains { $0. name == . nocopy }
180
+ self . showLineNumbers = tokens. contains { $0. name == . showLineNumbers }
181
+
182
+ if let wrapString = tokens. first ( where: { $0. name == . wrap } ) ? . value,
183
+ let wrapValue = Int ( wrapString) {
184
+ self . wrap = wrapValue
185
+ } else {
186
+ self . wrap = 0
187
+ }
188
+
189
+ if let highlightString = tokens. first ( where: { $0. name == . highlight } ) ? . value,
190
+ let highlightValue = Self . parseCodeBlockOptionsArray ( highlightString) {
191
+ self . highlight = highlightValue
192
+ } else {
193
+ self . highlight = [ ]
194
+ }
195
+
196
+ if let strikeoutString = tokens. first ( where: { $0. name == . strikeout } ) ? . value,
197
+ let strikeoutValue = Self . parseCodeBlockOptionsArray ( strikeoutString) {
198
+ self . strikeout = strikeoutValue
199
+ } else {
200
+ self . strikeout = [ ]
201
+ }
202
+ }
203
+
204
+ public init ( copyToClipboard: Bool , wrap: Int , highlight: [ Int ] , strikeout: [ Int ] , showLineNumbers: Bool ) {
155
205
self . copyToClipboard = copyToClipboard
156
206
self . wrap = wrap
157
207
self . highlight = highlight
158
208
self . showLineNumbers = showLineNumbers
159
209
self . strikeout = strikeout
160
210
}
211
+
212
+ /// A function that parses array values on code block options from the language line string
213
+ static internal func parseCodeBlockOptionsArray( _ value: String ? ) -> [ Int ] ? {
214
+ guard var s = value? . trimmingCharacters ( in: . whitespaces) , !s. isEmpty else { return [ ] }
215
+
216
+ if s. hasPrefix ( " [ " ) && s. hasSuffix ( " ] " ) {
217
+ s. removeFirst ( )
218
+ s. removeLast ( )
219
+ }
220
+
221
+ return s. split ( separator: " , " ) . compactMap { Int ( $0. trimmingCharacters ( in: . whitespaces) ) }
222
+ }
223
+
224
+ /// A function that parses the language line options on code blocks, returning the language and tokens, an array of OptionName and option values
225
+ static internal func tokenizeLanguageString( _ input: String ? ) -> ( lang: String ? , tokens: [ ( name: OptionName , value: String ? ) ] ) {
226
+ guard let input else { return ( lang: nil , tokens: [ ] ) }
227
+
228
+ let parts = parseLanguageString ( input)
229
+ var tokens : [ ( OptionName , String ? ) ] = [ ]
230
+ var lang : String ? = nil
231
+
232
+ for (index, part) in parts. enumerated ( ) {
233
+ if let eq = part. firstIndex ( of: " = " ) {
234
+ let key = part [ ..< eq] . trimmingCharacters ( in: . whitespaces) . lowercased ( )
235
+ let value = part [ part. index ( after: eq) ... ] . trimmingCharacters ( in: . whitespaces)
236
+ if key == " wrap " {
237
+ tokens. append ( ( . wrap, value) )
238
+ } else if key == " highlight " {
239
+ tokens. append ( ( . highlight, value) )
240
+ } else if key == " strikeout " {
241
+ tokens. append ( ( . strikeout, value) )
242
+ } else {
243
+ tokens. append ( ( . unknown, key) )
244
+ }
245
+ } else {
246
+ let key = part. trimmingCharacters ( in: . whitespaces) . lowercased ( )
247
+ if key == " nocopy " {
248
+ tokens. append ( ( . nocopy, nil as String ? ) )
249
+ } else if key == " showlinenumbers " {
250
+ tokens. append ( ( . showLineNumbers, nil as String ? ) )
251
+ } else if key == " wrap " {
252
+ tokens. append ( ( . wrap, nil as String ? ) )
253
+ } else if key == " highlight " {
254
+ tokens. append ( ( . highlight, nil as String ? ) )
255
+ } else if key == " strikeout " {
256
+ tokens. append ( ( . strikeout, nil as String ? ) )
257
+ } else if index == 0 && !key. contains ( " [ " ) && !key. contains ( " ] " ) {
258
+ lang = key
259
+ } else {
260
+ tokens. append ( ( . unknown, key) )
261
+ }
262
+ }
263
+ }
264
+ return ( lang, tokens)
265
+ }
266
+
267
+ // helper function for tokenizeLanguageString to parse the language line
268
+ static func parseLanguageString( _ input: String ? ) -> [ Substring ] {
269
+
270
+ guard let input else { return [ ] }
271
+ var parts : [ Substring ] = [ ]
272
+ var start = input. startIndex
273
+ var i = input. startIndex
274
+
275
+ var bracketDepth = 0
276
+
277
+ while i < input. endIndex {
278
+ let c = input [ i]
279
+
280
+ if c == " [ " { bracketDepth += 1 }
281
+ else if c == " ] " { bracketDepth = max ( 0 , bracketDepth - 1 ) }
282
+ else if c == " , " && bracketDepth == 0 {
283
+ let seq = input [ start..< i]
284
+ if !seq. isEmpty {
285
+ parts. append ( seq)
286
+ }
287
+ input. formIndex ( after: & i)
288
+ start = i
289
+ continue
290
+ }
291
+ input. formIndex ( after: & i)
292
+ }
293
+ let tail = input [ start..< input. endIndex]
294
+ if !tail. isEmpty {
295
+ parts. append ( tail)
296
+ }
297
+
298
+ return parts
299
+ }
161
300
}
162
301
163
302
/// A heading with the given level.
@@ -747,15 +886,23 @@ extension RenderBlockContent: Codable {
747
886
self = try . aside( . init( style: style, content: container. decode ( [ RenderBlockContent ] . self, forKey: . content) ) )
748
887
case . codeListing:
749
888
let copy = FeatureFlags . current. isExperimentalCodeBlockEnabled
889
+ let options : CodeBlockOptions ?
890
+ if !Set( container. allKeys) . isDisjoint ( with: [ . copyToClipboard, . wrap, . highlight, . strikeout, . showLineNumbers] ) {
891
+ options = try CodeBlockOptions (
892
+ copyToClipboard: container. decodeIfPresent ( Bool . self, forKey: . copyToClipboard) ?? copy,
893
+ wrap: container. decodeIfPresent ( Int . self, forKey: . wrap) ?? 0 ,
894
+ highlight: container. decodeIfPresent ( [ Int ] . self, forKey: . highlight) ?? [ ] ,
895
+ strikeout: container. decodeIfPresent ( [ Int ] . self, forKey: . strikeout) ?? [ ] ,
896
+ showLineNumbers: container. decodeIfPresent ( Bool . self, forKey: . showLineNumbers) ?? false
897
+ )
898
+ } else {
899
+ options = nil
900
+ }
750
901
self = try . codeListing( . init(
751
902
syntax: container. decodeIfPresent ( String . self, forKey: . syntax) ,
752
903
code: container. decode ( [ String ] . self, forKey: . code) ,
753
904
metadata: container. decodeIfPresent ( RenderContentMetadata . self, forKey: . metadata) ,
754
- copyToClipboard: container. decodeIfPresent ( Bool . self, forKey: . copyToClipboard) ?? copy,
755
- wrap: container. decodeIfPresent ( Int . self, forKey: . wrap) ?? 0 ,
756
- highlight: container. decodeIfPresent ( [ Int ] . self, forKey: . highlight) ?? [ Int] ( ) ,
757
- strikeout: container. decodeIfPresent ( [ Int ] . self, forKey: . strikeout) ?? [ Int] ( ) ,
758
- showLineNumbers: container. decodeIfPresent ( Bool . self, forKey: . showLineNumbers) ?? false
905
+ options: options
759
906
) )
760
907
case . heading:
761
908
self = try . heading( . init( level: container. decode ( Int . self, forKey: . level) , text: container. decode ( String . self, forKey: . text) , anchor: container. decodeIfPresent ( String . self, forKey: . anchor) ) )
@@ -859,11 +1006,11 @@ extension RenderBlockContent: Codable {
859
1006
try container. encode ( l. syntax, forKey: . syntax)
860
1007
try container. encode ( l. code, forKey: . code)
861
1008
try container. encodeIfPresent ( l. metadata, forKey: . metadata)
862
- try container. encode ( l . copyToClipboard, forKey: . copyToClipboard)
863
- try container. encode ( l . wrap, forKey: . wrap)
864
- try container. encode ( l . highlight, forKey: . highlight)
865
- try container. encode ( l . strikeout, forKey: . strikeout)
866
- try container. encode ( l . showLineNumbers, forKey: . showLineNumbers)
1009
+ try container. encodeIfPresent ( l . options ? . copyToClipboard, forKey: . copyToClipboard)
1010
+ try container. encodeIfPresent ( l . options ? . wrap, forKey: . wrap)
1011
+ try container. encodeIfPresent ( l . options ? . highlight, forKey: . highlight)
1012
+ try container. encodeIfPresent ( l . options ? . strikeout, forKey: . strikeout)
1013
+ try container. encodeIfPresent ( l . options ? . showLineNumbers, forKey: . showLineNumbers)
867
1014
case . heading( let h) :
868
1015
try container. encode ( h. level, forKey: . level)
869
1016
try container. encode ( h. text, forKey: . text)
0 commit comments