openquiver_graph/trivia.rs
1//! Comments, and what they are anchored to.
2
3use std::collections::BTreeMap;
4
5use crate::{Edge, NodeName};
6
7/// Where a comment is anchored.
8///
9/// One enum rather than a bucket per position. Adding a construct to the
10/// grammar then means adding a variant here — a compile error in every `match`
11/// that has to handle it — instead of quietly creating another place for
12/// comments to fall through, which is exactly how three positions were lost
13/// when configuration blocks and `layout` were added.
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
15pub enum Anchor {
16 /// Between the schema header and the `graph` block.
17 Header,
18 Node(NodeName),
19 /// One entry inside a node's configuration block.
20 Config(NodeName, String),
21 /// Just before a configuration block's closing brace.
22 ConfigTrailing(NodeName),
23 Edge(Edge),
24 /// Just before the graph block's closing brace.
25 GraphTrailing,
26 /// Between the graph block and `layout`.
27 LayoutLeading,
28 LayoutEntry(NodeName),
29 /// Just before the layout block's closing brace.
30 LayoutTrailing,
31 /// After everything else in the file.
32 FileTrailing,
33}
34
35impl Anchor {
36 /// The node this anchor hangs off, if any. Used to move comments when a
37 /// node is renamed or removed.
38 pub(crate) fn node(&self) -> Option<&NodeName> {
39 match self {
40 Anchor::Node(n)
41 | Anchor::Config(n, _)
42 | Anchor::ConfigTrailing(n)
43 | Anchor::LayoutEntry(n) => Some(n),
44 Anchor::Edge(_)
45 | Anchor::Header
46 | Anchor::GraphTrailing
47 | Anchor::LayoutLeading
48 | Anchor::LayoutTrailing
49 | Anchor::FileTrailing => None,
50 }
51 }
52
53 pub(crate) fn with_node(&self, name: NodeName) -> Self {
54 match self {
55 Anchor::Node(_) => Anchor::Node(name),
56 Anchor::Config(_, pin) => Anchor::Config(name, pin.clone()),
57 Anchor::ConfigTrailing(_) => Anchor::ConfigTrailing(name),
58 Anchor::LayoutEntry(_) => Anchor::LayoutEntry(name),
59 other => other.clone(),
60 }
61 }
62}
63
64/// Ordinary `//` comments, keyed by what they describe.
65///
66/// Keying by identity rather than by position is what lets the editor mutate a
67/// graph and save it without losing anybody's notes: an untouched node keeps
68/// its comments wherever canonical ordering moves it.
69///
70/// This lives beside the IR rather than in the syntax crate for one reason: the
71/// keys are IR identities, and renaming a node has to update the table in the
72/// same operation that updates the graph. A table one layer above the code
73/// performing the rename cannot be kept in step with it.
74///
75/// `///` comments are deliberately absent — those are semantic and live on
76/// [`Node::doc`].
77#[derive(Debug, Clone, Default, PartialEq, Eq)]
78pub struct Trivia(pub(crate) BTreeMap<Anchor, Vec<String>>);
79
80impl Trivia {
81 pub fn get(&self, anchor: &Anchor) -> &[String] {
82 self.0.get(anchor).map_or(&[], Vec::as_slice)
83 }
84
85 /// Appends rather than replaces. Two identical wires drawn on two branches
86 /// dedupe into one edge, but discarding one side's explanation of it is the
87 /// wrong half to throw away.
88 pub fn add(&mut self, anchor: Anchor, comments: Vec<String>) {
89 if comments.is_empty() {
90 return;
91 }
92 self.0.entry(anchor).or_default().extend(comments);
93 }
94
95 pub fn is_empty(&self) -> bool {
96 self.0.is_empty()
97 }
98}