krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/Syntax/SyntaxNode.swift

10c33661279a1fb0e8d3dd51509db9637778769e
orgstar/Sources/OrgCore/Syntax/SyntaxNode.swift history · blame · raw

99 lines · 3374 bytes

 1/// A view of a green node at an absolute offset, with a parent link. Created on demand.
 2public final class SyntaxNode: Sendable {
 3    public let green: GreenNode
 4    public let offset: Int
 5    public let parent: SyntaxNode?
 6
 7    init(green: GreenNode, offset: Int, parent: SyntaxNode?) {
 8        self.green = green
 9        self.offset = offset
10        self.parent = parent
11    }
12
13    public var kind: SyntaxKind { green.kind }
14    public var range: Range<Int> { offset..<(offset + green.length) }
15    public var text: String { green.text }
16
17    public var children: [SyntaxNode] {
18        var result: [SyntaxNode] = []
19        var at = offset
20        for child in green.children {
21            if case .node(let node) = child {
22                result.append(SyntaxNode(green: node, offset: at, parent: self))
23            }
24            at += child.length
25        }
26        return result
27    }
28
29    public var tokens: [SyntaxToken] {
30        var result: [SyntaxToken] = []
31        var at = offset
32        for child in green.children {
33            if case .token(let token) = child {
34                result.append(SyntaxToken(kind: token.kind, text: token.text, range: at..<(at + token.length)))
35            }
36            at += child.length
37        }
38        return result
39    }
40
41    /// Child nodes overlapping `range`. Only those are created, so a section with thousands of
42    /// children costs little when the range is small.
43    public func children(overlapping range: Range<Int>) -> [SyntaxNode] {
44        var result: [SyntaxNode] = []
45        var at = offset
46        for child in green.children {
47            let childRange = at..<(at + child.length)
48            if childRange.lowerBound >= range.upperBound, !range.isEmpty { break }
49            if case .node(let node) = child, childRange.overlaps(range) {
50                result.append(SyntaxNode(green: node, offset: at, parent: self))
51            }
52            at = childRange.upperBound
53        }
54        return result
55    }
56
57    /// The child node whose range contains `position`.
58    public func child(containing position: Int) -> SyntaxNode? {
59        var at = offset
60        for child in green.children {
61            let end = at + child.length
62            if position < end {
63                if case .node(let node) = child, position >= at { return SyntaxNode(green: node, offset: at, parent: self) }
64                return nil
65            }
66            at = end
67        }
68        return nil
69    }
70
71    /// The first child node of `kind`.
72    public func firstChild(_ kind: SyntaxKind) -> SyntaxNode? {
73        var at = offset
74        for child in green.children {
75            if case .node(let node) = child, node.kind == kind { return SyntaxNode(green: node, offset: at, parent: self) }
76            at += child.length
77        }
78        return nil
79    }
80
81    /// This node and every node below it, in document order.
82    public func descendants() -> [SyntaxNode] {
83        [self] + children.flatMap { $0.descendants() }
84    }
85}
86
87public struct SyntaxToken: Sendable, Equatable {
88    public let kind: SyntaxKind
89    public let text: String
90    public let range: Range<Int>
91}
92
93public struct OrgTree: Sendable {
94    public let green: GreenNode
95    public let settings: OrgSettings
96
97    public var root: SyntaxNode { SyntaxNode(green: green, offset: 0, parent: nil) }
98    public var text: String { green.text }
99}