1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
/// A detached modifier with its marker, nestling level, task status (if set),
/// and content.
struct DetachedModifier {
let marker: Character
let level: Int
let status: TaskStatus?
let statusIndex: String.Index?
let content: Substring
/// Finds and parses a detached modifier if present. The accepting markers
/// is used to reduce the markers being parsed.
static func parse(_ line: Substring, accepting markers: ASCIIByteSet) -> DetachedModifier? {
let utf8 = line.utf8
let end = utf8.endIndex
var i = utf8.startIndex
while i < end, ASCIIHelper.isWhitespace(utf8[i]) { i = utf8.index(after: i) }
guard i < end else { return nil }
let markerByte = utf8[i]
guard markers.contains(markerByte) else { return nil }
var level = 0
var run = i
while run < end, utf8[run] == markerByte {
level += 1
run = utf8.index(after: run)
}
guard run < end, ASCIIHelper.isWhitespace(utf8[run]) else { return nil }
var rest = run
while rest < end, ASCIIHelper.isWhitespace(utf8[rest]) { rest = utf8.index(after: rest) }
var status: TaskStatus?
var statusIndex: String.Index?
var contentStart = rest
if rest < end, utf8[rest] == UInt8(ascii: "(") {
let markerPosition = utf8.index(after: rest)
if markerPosition < end {
let closePosition = utf8.index(after: markerPosition)
let statusByte = utf8[markerPosition]
if closePosition < end, utf8[closePosition] == UInt8(ascii: ")"),
let parsed = TaskStatus(markerByte: statusByte) {
status = parsed
statusIndex = markerPosition
contentStart = utf8.index(after: closePosition)
}
}
}
return DetachedModifier(
marker: Character(Unicode.Scalar(markerByte)),
level: level,
status: status,
statusIndex: statusIndex,
content: TextHelper.whitespaceTrimmed(line[contentStart...])
)
}
}
|