Skip to main content

ephemeris_documents/json/cst/
object.rs

1//! Objects of the JSON syntax tree and their member edits.
2
3use std::fmt::{self, Write as _};
4use std::mem;
5
6use super::key::Key;
7use super::tail::{Comma, Position, Tail};
8use super::trivia::{Indent, Trivia};
9use super::{Kind, Value};
10use crate::annotation::Annotated;
11use crate::entries::Entries;
12use crate::json::Dialect;
13
14/// An object with its members in source order.
15///
16/// Member names are unique.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub(crate) struct Object {
19    members: Vec<Member>,
20    /// Trivia before the closing brace.
21    inner: Trivia,
22    /// The indent of the member that holds this object. The first member
23    /// inserted into an object with no line break of its own is placed
24    /// relative to it.
25    base: String,
26    /// One level of indentation for members inserted into this object.
27    unit: Indent,
28}
29
30/// A member name that an object already has.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub(in crate::json) struct DuplicateKey {
33    pub(in crate::json) name: String,
34}
35
36/// One `key: value` pair and the trivia around it. The leading trivia is the
37/// value's.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub(crate) struct Member {
40    key: Key,
41    /// Between the key and the colon.
42    after_key: Trivia,
43    /// Between the colon and the value.
44    after_colon: Trivia,
45    value: Value,
46    tail: Tail,
47}
48
49impl Member {
50    pub(in crate::json) const fn parsed(
51        key: Key,
52        after_key: Trivia,
53        after_colon: Trivia,
54        value: Value,
55        tail: Tail,
56    ) -> Self {
57        Self {
58            key,
59            after_key,
60            after_colon,
61            value,
62            tail,
63        }
64    }
65
66    /// A member with a canonical key and colon.
67    fn canonical(name: &str, value: Value, tail: Tail) -> Self {
68        Self::parsed(
69            Key::canonical(name),
70            Trivia::default(),
71            Trivia::new(" ".to_owned()),
72            value,
73            tail,
74        )
75    }
76
77    fn build(name: &str, node: &Annotated, dialect: Dialect, depth: usize) -> Self {
78        let leading = Trivia::above(node.comment.as_ref(), depth, dialect);
79        Self::canonical(
80            name,
81            Value::build(node, dialect, depth, leading),
82            Tail::canonical(),
83        )
84    }
85
86    fn trivia_mut(&mut self, visit: &mut dyn FnMut(&mut Trivia)) {
87        self.value.trivia_mut(visit);
88        visit(&mut self.after_key);
89        visit(&mut self.after_colon);
90        self.tail.trivia_mut(visit);
91    }
92}
93
94/// Where the next member of an object goes.
95enum Layout {
96    /// On a line of its own, after `head`, at `indent`.
97    Lines { head: String, indent: String },
98    /// On the line of the previous member.
99    Inline,
100}
101
102impl Layout {
103    /// The leading trivia of a new member holding `value`, which is moved
104    /// from the indent `from` to its place.
105    fn leading(self, value: &mut Value, from: &str) -> Trivia {
106        match self {
107            Self::Lines { head, indent } => {
108                Object::reindent_incoming(value, from, &indent);
109                let mut leading = mem::take(&mut value.leading);
110                if leading.has_newline() {
111                    leading.reindent_from(from, &indent);
112                } else {
113                    leading = Trivia::new(["\n", &indent, leading.as_str()].concat());
114                }
115                Trivia::new([head.as_str(), leading.as_str()].concat())
116            }
117            Self::Inline => {
118                Object::reindent_incoming(value, from, "");
119                Trivia::new(" ".to_owned())
120            }
121        }
122    }
123}
124
125impl Object {
126    pub(in crate::json) fn new() -> Self {
127        Self {
128            members: Vec::new(),
129            inner: Trivia::new(String::new()),
130            base: String::new(),
131            unit: Indent::default(),
132        }
133    }
134
135    /// The canonical object for `entries`, at `depth`.
136    pub(super) fn build(entries: &Entries, dialect: Dialect, depth: usize) -> Self {
137        let child = depth.saturating_add(1);
138        Self {
139            members: entries
140                .into_iter()
141                .map(|(name, value)| Member::build(name, value, dialect, child))
142                .collect(),
143            inner: if entries.is_empty() {
144                Trivia::default()
145            } else {
146                Trivia::closing(depth)
147            },
148            base: String::new(),
149            unit: Indent::default(),
150        }
151    }
152
153    /// This object with the trivia before its closing brace.
154    pub(in crate::json) fn closed_by(mut self, inner: Trivia) -> Self {
155        self.inner = inner;
156        self
157    }
158
159    /// The indent unit of the first multi-line member, if there is one.
160    pub(super) fn indent_unit(&self) -> Option<Indent> {
161        self.detect_unit("")
162    }
163
164    /// The same for an object that starts a line at indent `base`.
165    fn detect_unit(&self, base: &str) -> Option<Indent> {
166        for member in &self.members {
167            let leading = &member.value.leading;
168            if !leading.has_newline() {
169                continue;
170            }
171            let own = leading.indent();
172            if let Some(unit) = own.strip_prefix(base).and_then(Indent::from_blanks) {
173                return Some(unit);
174            }
175            if let Kind::Object(child) = &member.value.kind
176                && let Some(unit) = child.detect_unit(own)
177            {
178                return Some(unit);
179            }
180        }
181        None
182    }
183
184    /// Set the indent unit for members inserted into this object.
185    pub(super) fn set_unit(&mut self, unit: &Indent) {
186        self.unit.clone_from(unit);
187    }
188
189    /// Append a parsed member.
190    ///
191    /// # Errors
192    /// Returns the name when it is already present.
193    pub(in crate::json) fn push(&mut self, member: Member) -> Result<(), DuplicateKey> {
194        if self.position(member.key.name()).is_some() {
195            return Err(DuplicateKey {
196                name: member.key.name().to_owned(),
197            });
198        }
199        self.members.push(member);
200        Ok(())
201    }
202
203    fn position(&self, name: &str) -> Option<usize> {
204        self.members
205            .iter()
206            .position(|member| member.key.name() == name)
207    }
208
209    /// The member names in source order.
210    pub(in crate::json) fn keys(&self) -> impl Iterator<Item = &str> {
211        self.members.iter().map(|member| member.key.name())
212    }
213
214    pub(in crate::json) fn get(&self, name: &str) -> Option<&Value> {
215        self.members
216            .iter()
217            .find(|member| member.key.name() == name)
218            .map(|member| &member.value)
219    }
220
221    /// Enter the value of `name` for editing. An object value takes this
222    /// object's indent unit and the indent it is placed at, so that members
223    /// inserted into it line up.
224    pub(in crate::json) fn descend(&mut self, name: &str) -> Option<&mut Value> {
225        let index = self.position(name)?;
226        let member = self.members.get_mut(index)?;
227        if let Kind::Object(object) = &mut member.value.kind {
228            object.unit.clone_from(&self.unit);
229            let base = if member.value.leading.has_newline() {
230                member.value.leading.indent()
231            } else {
232                &self.base
233            };
234            object.base.clear();
235            object.base.push_str(base);
236        }
237        Some(&mut member.value)
238    }
239
240    /// Replace the value of `name`, or append a member.
241    ///
242    /// An existing member keeps its own leading trivia, so the user's spacing
243    /// and comments stay, unless `value` carries a comment of its own. Then
244    /// the leading trivia of `value` replaces it. A new member takes the
245    /// leading trivia of `value`, moved to the indent of its siblings, and
246    /// a key spelled the canonical way for the dialect it is printed in.
247    /// `value` is laid out like a canonical member value: its lines are
248    /// indented relative to the indent of its leading trivia.
249    ///
250    /// An empty object stays as it is until a member is inserted into it.
251    /// Then it takes a line break and the indent of the member that holds it,
252    /// so that the new member and its closing brace line up.
253    pub(in crate::json) fn insert(&mut self, name: &str, mut value: Value) {
254        value.reindent_unit(&self.unit);
255        let from = value.leading.indent().to_owned();
256        let commented = !value.leading.is_blank();
257        match self.position(name) {
258            Some(index) => self.replace_member(index, value, &from),
259            None => self.append_member(name, value, &from, commented),
260        }
261    }
262
263    /// Give the member at `index` the body of `value`. `from` is the indent
264    /// of the leading trivia of `value`.
265    fn replace_member(&mut self, index: usize, mut value: Value, from: &str) {
266        if !value.leading.is_blank() {
267            self.expand_before(index);
268        }
269        let Some(member) = self.members.get_mut(index) else {
270            return;
271        };
272        let to = member.value.leading.indent().to_owned();
273        Self::reindent_incoming(&mut value, from, &to);
274        if !value.leading.is_blank() && member.value.leading.has_newline() {
275            value.leading.reindent_from(from, &to);
276            member.value.leading = value.leading;
277        }
278        member.value.kind = value.kind;
279    }
280
281    /// Add a member for `value` after the last one. `from` is the indent of
282    /// the leading trivia of `value`, which has a comment when `commented`.
283    fn append_member(&mut self, name: &str, mut value: Value, from: &str, commented: bool) {
284        let kept = self
285            .members
286            .last()
287            .map_or(Comma::Present, |last| last.tail.comma);
288        let mut layout = self.plan_layout();
289        if self.members.is_empty() {
290            self.expand_empty();
291        } else if commented && matches!(layout, Layout::Inline) {
292            layout = Layout::Lines {
293                head: String::new(),
294                indent: self.expand(),
295            };
296        }
297        let inline = matches!(layout, Layout::Inline);
298        let leading = layout.leading(&mut value, from);
299        let trailing = match self.members.last_mut() {
300            Some(last) if inline && last.tail.trailing.is_blank() => {
301                mem::take(&mut last.tail.trailing)
302            }
303            _ => Trivia::default(),
304        };
305        let tail = Tail::new(Trivia::default(), kept, trailing);
306        self.members.push(Member::canonical(
307            name,
308            Value::new(leading, value.kind),
309            tail,
310        ));
311    }
312
313    /// Put the member at `index` on a line of its own. A one-line object is
314    /// expanded to one member per line.
315    fn expand_before(&mut self, index: usize) {
316        let indent = match self.plan_layout() {
317            Layout::Lines { indent, head: _ } => indent,
318            Layout::Inline => self.expand(),
319        };
320        if let Some(member) = self.members.get_mut(index)
321            && !member.value.leading.has_newline()
322        {
323            let text = ["\n", &indent, member.value.leading.as_str().trim_start()].concat();
324            member.value.leading = Trivia::new(text);
325        }
326    }
327
328    /// Move every member to its own line and the closing brace to a line of
329    /// its own. Returns the indent of the members.
330    fn expand(&mut self) -> String {
331        let indent = [self.base.as_str(), self.unit.as_str()].concat();
332        for member in &mut self.members {
333            let text = ["\n", &indent, member.value.leading.as_str().trim_start()].concat();
334            member.value.leading = Trivia::new(text);
335            if member.tail.trailing.is_blank() {
336                member.tail.trailing = Trivia::default();
337            }
338        }
339        let inner = ["\n", &self.base, self.inner.as_str().trim_start()].concat();
340        self.inner = Trivia::new(inner);
341        indent
342    }
343
344    /// Fit the body of an incoming value to its new place.
345    fn reindent_incoming(value: &mut Value, from: &str, to: &str) {
346        value.kind.reindent_from(from, to);
347        if let Kind::Object(object) = &mut value.kind
348            && object.members.is_empty()
349        {
350            to.clone_into(&mut object.base);
351        }
352    }
353
354    /// Where a new member goes.
355    fn plan_layout(&self) -> Layout {
356        if self.members.is_empty() {
357            let kept = self.inner.as_str().trim_end_matches([' ', '\t']);
358            return Layout::Lines {
359                head: kept.strip_suffix('\n').unwrap_or(kept).to_owned(),
360                indent: [self.empty_base().as_str(), self.unit.as_str()].concat(),
361            };
362        }
363        self.members
364            .iter()
365            .rev()
366            .find(|member| member.value.leading.has_newline())
367            .map_or(Layout::Inline, |member| Layout::Lines {
368                head: String::new(),
369                indent: member.value.leading.indent().to_owned(),
370            })
371    }
372
373    /// The indent of the line that holds the closing brace of an empty
374    /// object, once it has one.
375    fn empty_base(&self) -> String {
376        if self.inner.has_newline() {
377            self.inner.indent().to_owned()
378        } else {
379            self.base.clone()
380        }
381    }
382
383    /// Move the closing brace of an empty object to a line of its own.
384    fn expand_empty(&mut self) {
385        self.inner = Trivia::new(["\n", &self.empty_base()].concat());
386    }
387
388    /// Remove the member `name` with its leading trivia. A removed last member
389    /// hands its comma style to the new last member. An object left empty
390    /// prints as `{}` unless a comment is left inside it.
391    pub(in crate::json) fn remove(&mut self, name: &str) {
392        let Some(index) = self.position(name) else {
393            return;
394        };
395        let removed = self.members.remove(index);
396        if index == self.members.len()
397            && let Some(last) = self.members.last_mut()
398        {
399            last.tail.comma = removed.tail.comma;
400        }
401        if self.members.is_empty() && self.inner.is_blank() {
402            self.inner = Trivia::default();
403        }
404    }
405
406    pub(super) fn trivia_mut(&mut self, visit: &mut dyn FnMut(&mut Trivia)) {
407        for member in &mut self.members {
408            member.trivia_mut(visit);
409        }
410        visit(&mut self.inner);
411    }
412
413    pub(super) fn write(&self, f: &mut fmt::Formatter<'_>, dialect: Dialect) -> fmt::Result {
414        f.write_char('{')?;
415        for (position, member) in Position::each(&self.members) {
416            write!(
417                f,
418                "{}{}{}:{}",
419                member.value.leading,
420                member.key.spelling(dialect),
421                member.after_key,
422                member.after_colon
423            )?;
424            member.value.kind.write(f, dialect)?;
425            member.tail.write(f, dialect, position)?;
426        }
427        write!(f, "{}}}", self.inner)
428    }
429}