aboutsummaryrefslogtreecommitdiff
path: root/Sources/NorgKit/Models/DetachedModifier.swift
blob: 1f28c3659fa977bc0f8fcc3c584ac30e97b130df (plain)
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...])
    )
  }
}