Skip to main content

openquiver_graph/
document.rs

1//! Editing a graph together with everything keyed by the identities in it.
2
3use std::collections::BTreeSet;
4
5use crate::{Anchor, Edge, Graph, Layout, Node, NodeName, PinRef, Trivia};
6
7/// Why a rename could not be performed.
8#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
9pub enum RenameError {
10    #[error("there is no node named `{0}`")]
11    NoSuchNode(String),
12    #[error("a node named `{0}` already exists")]
13    NameTaken(String),
14    #[error("`{0}` is not a name the format can write")]
15    NotAnIdentifier(String),
16}
17
18/// A graph together with everything keyed by the identities inside it.
19///
20/// The three tables have to move as one. They are keyed by the same node names
21/// and wires, so an edit that updated only some of them would leave the rest
22/// pointing at things that no longer exist — which, for the comment table,
23/// means deleting somebody's writing on the next save.
24///
25/// Source spans deliberately do **not** live here. A span describes the text a
26/// file was parsed from, and the first edit makes every one of them stale, not
27/// merely the ones naming the edited node. Keeping them out means a stale span
28/// cannot be read by accident.
29#[derive(Debug, Clone, PartialEq)]
30pub struct Document {
31    pub graph: Graph,
32    pub trivia: Trivia,
33    pub layout: Layout,
34}
35
36impl Document {
37    pub fn new(graph: Graph, trivia: Trivia, layout: Layout) -> Self {
38        Self {
39            graph,
40            trivia,
41            layout,
42        }
43    }
44
45    /// Removes a node together with every wire that touches it, its position,
46    /// and the comments belonging to all of them.
47    ///
48    /// Wires are not left dangling: a dangling wire is a state the compiler
49    /// would have to consider on every check, and a later node reusing the freed
50    /// name would silently inherit both the wires and the dead node's comments.
51    pub fn remove_node(&mut self, name: &str) -> Option<Node> {
52        let key = NodeName::new(name);
53        let node = self.graph.nodes.remove(&key)?;
54
55        // Compare against the normalized key, not the caller's argument: a
56        // decomposed name would otherwise match the map but not the wires, and
57        // leave exactly the dangling state this function exists to prevent.
58        let name = key.as_str();
59        let touching: Vec<Edge> = self
60            .graph
61            .edges
62            .iter()
63            .filter(|e| e.from().node() == name || e.to().node() == name)
64            .cloned()
65            .collect();
66        for edge in touching {
67            self.graph.edges.remove(&edge);
68            self.trivia.0.remove(&Anchor::Edge(edge));
69        }
70
71        self.trivia
72            .0
73            .retain(|anchor, _| anchor.node() != Some(&key));
74        self.layout.0.remove(&key);
75
76        Some(node)
77    }
78
79    /// Removes one wire and the comments attached to it.
80    pub fn remove_edge(&mut self, edge: &Edge) -> bool {
81        self.trivia.0.remove(&Anchor::Edge(edge.clone()));
82        self.graph.edges.remove(edge)
83    }
84
85    /// Renames a node, rewriting every wire that mentions it and carrying its
86    /// comments and its position across.
87    ///
88    /// This is the price of letting a node's name be its identity, and it is
89    /// paid here rather than by each caller so that it is paid the same way
90    /// every time.
91    pub fn rename_node(&mut self, from: &str, to: &str) -> Result<(), RenameError> {
92        let old = NodeName::new(from);
93        let new = NodeName::new(to);
94
95        if !self.graph.nodes.contains_key(&old) {
96            return Err(RenameError::NoSuchNode(from.to_owned()));
97        }
98        // A name the grammar cannot write would make the file unparseable the
99        // moment it is saved, so it is refused here rather than discovered then.
100        if !is_identifier(new.as_str()) {
101            return Err(RenameError::NotAnIdentifier(to.to_owned()));
102        }
103        // Merging two nodes into one identity is the duplicate-name failure by
104        // another route, so it is refused rather than resolved.
105        if old != new && self.graph.nodes.contains_key(&new) {
106            return Err(RenameError::NameTaken(to.to_owned()));
107        }
108        if old == new {
109            return Ok(());
110        }
111
112        let mut node = self.graph.nodes.remove(&old).expect("checked above");
113        node.name = new.clone();
114        self.graph.nodes.insert(new.clone(), node);
115
116        let rewrite = |pin: &PinRef| -> PinRef {
117            if pin.node == old {
118                PinRef::new(new.clone(), pin.pin.clone())
119            } else {
120                pin.clone()
121            }
122        };
123
124        let mut moved = BTreeSet::new();
125        for edge in std::mem::take(&mut self.graph.edges) {
126            let next = Edge::new(edge.kind, rewrite(edge.from()), rewrite(edge.to()));
127            // Two wires can collapse into one here, so comments are appended
128            // rather than replaced.
129            if let Some(comments) = self.trivia.0.remove(&Anchor::Edge(edge)) {
130                self.trivia
131                    .0
132                    .entry(Anchor::Edge(next.clone()))
133                    .or_default()
134                    .extend(comments);
135            }
136            moved.insert(next);
137        }
138        self.graph.edges = moved;
139
140        let rekeyed: Vec<(Anchor, Vec<String>)> = self
141            .trivia
142            .0
143            .extract_if(.., |anchor, _| anchor.node() == Some(&old))
144            .map(|(anchor, comments)| (anchor.with_node(new.clone()), comments))
145            .collect();
146        for (anchor, comments) in rekeyed {
147            self.trivia.0.entry(anchor).or_default().extend(comments);
148        }
149
150        if let Some(at) = self.layout.0.remove(&old) {
151            self.layout.0.insert(new, at);
152        }
153
154        Ok(())
155    }
156}
157
158/// Whether a name is one the grammar can write back out.
159///
160/// Unicode UAX #31, the rule Rust applies to its own identifiers.
161pub fn is_identifier(name: &str) -> bool {
162    let mut chars = name.chars();
163    let Some(first) = chars.next() else {
164        return false;
165    };
166    (first == '_' || unicode_ident::is_xid_start(first))
167        && chars.all(unicode_ident::is_xid_continue)
168}