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}