Skip to main content

arity_parser/syntax/
ptr.rs

1//! A cursor-free, serializable handle to a CST node.
2//!
3//! rowan ships [`SyntaxNodePtr`](rowan::ast::SyntaxNodePtr) /
4//! [`AstPtr`](rowan::ast::AstPtr) for the same-revision case (re-exported from
5//! [`crate::ast`]), but their fields are private — they cannot be constructed
6//! from a mapped `(kind, range)` or deserialized. [`NodePtr`] is arity's
7//! canonical handle: it owns its construction (so a range mapped across an edit
8//! can be re-resolved) and derives `serde` (so it can ride an LSP
9//! `CallHierarchyItem.data` field).
10//!
11//! Like rowan's pointers, a `NodePtr` resolves only against a tree built from
12//! the *same text* it was taken against. To survive an edit, first map its range
13//! through that edit
14//! ([`map_range_through_edit`](crate::parser::reparse::map_range_through_edit)),
15//! then resolve against the new tree — the path the `arity` crate's
16//! `incremental::Analysis::resolve_ptr` takes.
17
18use rowan::{NodeOrToken, TextRange, TextSize};
19
20use crate::syntax::{SyntaxKind, SyntaxNode};
21
22/// A location-based handle to a syntax node: its [`SyntaxKind`] plus its text
23/// range. Cheap (`Copy`), serializable, and resolvable to a live [`SyntaxNode`]
24/// via [`NodePtr::try_to_node`].
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
26pub struct NodePtr {
27    kind: SyntaxKind,
28    start: u32,
29    len: u32,
30}
31
32impl NodePtr {
33    /// Capture a handle to `node`.
34    pub fn from_node(node: &SyntaxNode) -> Self {
35        Self::new(node.kind(), node.text_range())
36    }
37
38    /// The kind of node this points to.
39    pub fn kind(&self) -> SyntaxKind {
40        self.kind
41    }
42
43    /// The text range this points to.
44    pub fn text_range(&self) -> TextRange {
45        TextRange::at(TextSize::new(self.start), TextSize::new(self.len))
46    }
47
48    /// Return a copy pointing at `range`, keeping the kind. Used after mapping
49    /// the range across an edit.
50    pub fn with_range(self, range: TextRange) -> Self {
51        Self::new(self.kind, range)
52    }
53
54    /// Resolve this handle to a node in `root`, which must be a *root* built from
55    /// the same text the handle was taken against (or a range already mapped onto
56    /// that text). Returns `None` if `root` is not a root, the range falls
57    /// outside it, or no node of the stored kind spans exactly the stored range.
58    pub fn try_to_node(&self, root: &SyntaxNode) -> Option<SyntaxNode> {
59        if root.parent().is_some() {
60            return None;
61        }
62        let range = self.text_range();
63        if !root.text_range().contains_range(range) {
64            return None;
65        }
66        let start = match root.covering_element(range) {
67            NodeOrToken::Node(node) => node,
68            NodeOrToken::Token(token) => token.parent()?,
69        };
70        // `covering_element` returns the deepest element spanning `range`; the
71        // node we want is it or an ancestor with the exact range and kind.
72        start
73            .ancestors()
74            .find(|node| node.text_range() == range && node.kind() == self.kind)
75    }
76
77    fn new(kind: SyntaxKind, range: TextRange) -> Self {
78        Self {
79            kind,
80            start: range.start().into(),
81            len: range.len().into(),
82        }
83    }
84}