ramify/
layout.rs

1mod ops;
2#[cfg(test)]
3mod tests;
4
5use std::{io, iter::repeat, ops::Range};
6
7use crate::{
8    Config, Ramify, TryRamify,
9    writer::{
10        DiagramWriter, DoubledLines, RoundedCorners, RoundedCornersWide, SharpCorners,
11        SharpCornersWide, WriteBranch,
12    },
13};
14
15/// A generator which incrementally writes the branch diagram to a writer.
16///
17/// Once you have a [`Ramify`] impementation, initialize this struct with the [`init`](Self::init) method. After initializing, the branch
18/// diagram can be incrementally written to a [writer](io::Write) using the
19/// [`write_next_vertex`](Self::write_next_vertex) method.
20///
21/// The documentation here is mostly relevant for using the [`Generator`]. The layout algorithm
22/// is documented in the [`writer` module](crate::writer#layout-algorithm-documentation).
23///
24/// ## Compile-time and dynamic configuration
25/// This struct can be configured by passing an appropriate [`Config`] struct. The configuration
26/// contains compile-time and runtime configuration. The compile-time configuration is included in
27/// the state parameter (for example, a [`RoundedCorners`] parameter), which describes the appearance of the
28/// branch diagram. The runtime configuration concerns features relevant to the layout algorithm.
29///
30/// It is possible to modify configuration while writing the diagram (that is, in between calls to
31/// [`write_next_vertex`](Self::write_next_vertex)) by using the [`config_mut`](Self::config_mut)
32/// method. Any such modifications of the configuration are guaranteed to not
33/// corrupt the branch diagram.
34///
35/// ## Runtime and memory complexity
36///
37/// The branch diagram generator holds the minimal possible state required to generate the diagram.
38/// This state is essentially list of column indices corresponding to the *active vertices*: the vertices not yet drawn to the diagram, but
39/// for which a parent has already been drawn to the diagram.
40/// More precisely, the memory usage is `(4 + size_of<V>) * num_active_vertices` plus a constant
41/// if you do not write annotations.
42///
43/// Writing a branch diagram row only requires making a single pass over the list of vertices.
44/// Therefore the runtime to write a single branch diagram row is `O(num_active_vertices)`,
45/// assuming the various methods in [`Ramify`] take constant time.
46///
47/// If an annotation is written, the entire annotation is loaded into a scratch buffer. The scratch
48/// buffer is re-used between calls to [`write_next_vertex`](Self::write_next_vertex).
49#[derive(Debug)]
50pub struct Generator<V, R, B = RoundedCorners> {
51    columns: Vec<(V, usize)>,
52    min_index: Option<usize>, // None iff columns.is_empty()
53    ramifier: R,
54    config: Config<B>,
55    annotation_buf: String,
56}
57
58impl<V, R, B: WriteBranch> Generator<V, R, B> {
59    /// Get a new branch diagram generator starting at a given vertex of type `V` using the provided
60    /// configuration.
61    pub fn init(root: V, ramifier: R, config: Config<B>) -> Self {
62        Self {
63            columns: vec![(root, 0)],
64            min_index: Some(0),
65            ramifier,
66            config,
67            annotation_buf: String::new(),
68        }
69    }
70
71    /// Get a new branch diagram generator starting at a given vertex of type `V` using the default
72    /// configuration.
73    ///
74    /// Calling this method requires type annotations. Also see the convenience methods:
75    ///
76    /// - [`with_rounded_corners`](Self::with_rounded_corners)
77    /// - [`with_rounded_corners_wide`](Self::with_rounded_corners_wide)
78    /// - [`with_sharp_corners`](Self::with_sharp_corners)
79    /// - [`with_sharp_corners_wide`](Self::with_sharp_corners_wide)
80    /// - [`with_doubled_lines`](Self::with_doubled_lines)
81    pub fn with_default_config(root: V, ramifier: R) -> Self {
82        Self::init(root, ramifier, Config::new())
83    }
84
85    /// Get a mutable reference to the configuration in order to change configuration parameters
86    /// while generating the branch diagram.
87    pub fn config_mut(&mut self) -> &mut Config<B> {
88        &mut self.config
89    }
90}
91
92/// An error which might occur when calling [`Generator::try_write_next_vertex`].
93#[derive(Debug)]
94pub enum WriteVertexError {
95    /// An IO error was raised by the writer.
96    IO(io::Error),
97    /// The [`TryRamify`] implementation failed to determine the children for the active vertex.
98    TryChildrenFailed,
99}
100
101impl From<io::Error> for WriteVertexError {
102    fn from(err: io::Error) -> Self {
103        Self::IO(err)
104    }
105}
106
107impl<V, R, B: WriteBranch> Generator<V, R, B> {
108    /// Write a row containing a vertex along with its annotation to the provided writer.
109    ///
110    /// This method returns `Ok(true)` if there are vertices remaining, and otherwise `Ok(false)`.
111    ///
112    /// # Output rows
113    ///
114    /// A single call to this method will first write the row containing the vertex. Then, it will
115    /// write a number of non-marker rows in order to accommodate additional lines of annotation
116    /// and to set the generator state so that the subsequent call can immediately write
117    /// a vertex.
118    ///
119    ///
120    /// # Buffered writes
121    ///
122    /// The implementation tries to minimize the number of [`write`](io::Write::write) made by this method,
123    /// but the number of calls is still large. It is recommended that the provided writer is
124    /// buffered, for example using an [`io::BufWriter`] or an [`io::LineWriter`]. Many writers
125    /// provided by the standard library are already buffered.
126    pub fn write_next_vertex<W: io::Write>(&mut self, writer: W) -> io::Result<bool>
127    where
128        R: Ramify<V>,
129    {
130        self.try_write_next_vertex(writer).map_err(|e| match e {
131            WriteVertexError::IO(error) => error,
132            // the implementation of TryRamify if `R` is `Ramify` always succeeds
133            WriteVertexError::TryChildrenFailed => unreachable!(),
134        })
135    }
136
137    /// Attempt to write the next vertex, failing to do so if the call to [`TryRamify::try_children`]
138    /// results in an error.
139    ///
140    /// If the call fails and the replacement vertex is different, this could still result in some
141    /// rows written in order to prepare the new index to be written immediately if the next call
142    /// succeeds. If the original vertex is returned on error it is guaranteed that no writes will be made.
143    pub fn try_write_next_vertex<W: io::Write>(
144        &mut self,
145        writer: W,
146    ) -> Result<bool, WriteVertexError>
147    where
148        R: TryRamify<V>,
149    {
150        let mut writer = DiagramWriter::<W, B>::new(writer);
151        let Some(min_idx) = self.min_index else {
152            return Ok(false);
153        };
154
155        // perform the substitution first since we will use information
156        // about the next minimal element in order to make predictive writes
157        let (marker_char, col, l, r) = {
158            #[cfg(test)]
159            self.debug_cols_header("Replacing min index");
160            let original_col_count = self.columns.len();
161
162            // use the 'sentinel' pattern
163            let (marker_char, col) = if min_idx + 1 == self.columns.len() {
164                // the minimal index is at the end
165
166                // remove the last element
167                let (vtx, col) = self.columns.pop().unwrap();
168
169                // determine the data associated with the element
170                let (marker_char, maybe_children) =
171                    Self::get_vtx_data(&mut self.ramifier, &mut self.annotation_buf, vtx);
172
173                // FIXME: annoying workaround to deal with borrow checker
174                let children = if maybe_children.is_err() {
175                    let replacement = unsafe { maybe_children.unwrap_err_unchecked() };
176                    // put the column back, but with the replacement element
177                    self.columns.push((replacement, col));
178
179                    return Err(self.handle_no_children(&mut writer));
180                } else {
181                    unsafe { maybe_children.unwrap_unchecked() }
182                };
183
184                // append the new elements
185                self.columns.extend(children.into_iter().zip(repeat(col)));
186
187                (marker_char, col)
188            } else {
189                // temporarily swap the minimal element with the last element
190                let (vtx, col) = self.columns.swap_remove(min_idx);
191
192                // determine the data associated with the element
193                let (marker_char, maybe_children) =
194                    Self::get_vtx_data(&mut self.ramifier, &mut self.annotation_buf, vtx);
195
196                // FIXME: annoying workaround to deal with borrow checker
197                let children = if maybe_children.is_err() {
198                    let replacement = unsafe { maybe_children.unwrap_err_unchecked() };
199                    // put the column back with the replacement element
200                    let last_idx = self.columns.len();
201                    self.columns.push((replacement, col));
202                    self.columns.swap(last_idx, min_idx);
203
204                    return Err(self.handle_no_children(&mut writer));
205                } else {
206                    unsafe { maybe_children.unwrap_unchecked() }
207                };
208
209                // splice onto the swapped last element, inserting the new children
210                let last = {
211                    let mut iter = self
212                        .columns
213                        .splice(min_idx..min_idx + 1, children.into_iter().zip(repeat(col)));
214                    iter.next().unwrap()
215                };
216                // put the last element back
217                self.columns.push(last);
218
219                (marker_char, col)
220            };
221
222            // update the min index
223            self.min_index = self
224                .columns
225                .iter()
226                .enumerate()
227                .min_by_key(|(_, (e, _))| self.ramifier.get_key(e))
228                .map(|(a, _)| a);
229
230            // compute the number of new elements added by checking how much the length changed.
231            let child_count = self.columns.len() + 1 - original_col_count;
232
233            #[cfg(test)]
234            self.debug_cols();
235
236            (marker_char, col, min_idx, min_idx + child_count)
237        };
238
239        // either get the next minimal index, or write the final line and annotation and return
240        let Some(next_min_idx) = self.min_index else {
241            let (_, offset) = ops::marker(&mut writer, marker_char, 0, col)?;
242            let diagram_width = self.compute_diagram_width(offset);
243            let annotation_alignment = (B::GUTTER_WIDTH + 1) * diagram_width - B::GUTTER_WIDTH;
244
245            let mut lines = self.annotation_buf.lines();
246
247            if let Some(line) = lines.next() {
248                writer.write_annotation_line(
249                    line,
250                    annotation_alignment,
251                    self.config.annotation_margin,
252                )?;
253                for line in lines {
254                    writer.write_annotation_line(
255                        line,
256                        annotation_alignment,
257                        self.config.annotation_margin,
258                    )?;
259                }
260            } else {
261                writer.write_newline()?;
262            }
263
264            return Ok(false);
265        };
266
267        let diagram_width = self.compute_diagram_width_no_base(next_min_idx);
268
269        let delay_fork = self.config.row_padding > 0;
270
271        if next_min_idx < l {
272            // the next minimal index lands before the marker
273
274            let mut offset = if delay_fork {
275                ops::fork_align::<_, _, _, false>(
276                    &mut writer,
277                    &mut self.columns[..l],
278                    next_min_idx,
279                    ..col,
280                )?
281            } else {
282                ops::fork_align::<_, _, _, true>(
283                    &mut writer,
284                    &mut self.columns[..l],
285                    next_min_idx,
286                    ..col,
287                )?
288            };
289
290            let (actual, next_offset) = ops::marker(&mut writer, marker_char, offset, col)?;
291            offset = next_offset;
292            if r < self.columns.len() {
293                writer.queue_blank(offset.min(self.columns[r].1) - actual);
294                ops::align(&mut writer, &mut self.columns[r..], offset..diagram_width)?;
295            }
296        } else if next_min_idx < r {
297            // the next minimal index is a child of the marker
298
299            // first, we use `align` on the preceding columns to make as much space as
300            // possible. we can use the unbounded version since `align` by default compacts and this
301            // may result in better codegen
302            let mut offset = ops::align(&mut writer, &mut self.columns[..l], ..)?;
303            let (actual, next_offset) = ops::mark_and_prepare(
304                &mut writer,
305                &self.columns,
306                marker_char,
307                offset,
308                next_min_idx,
309            )?;
310            offset = next_offset;
311            if r < self.columns.len() {
312                writer.queue_blank(offset.min(self.columns[r].1) - actual);
313                ops::align(&mut writer, &mut self.columns[r..], offset..diagram_width)?;
314            }
315        } else {
316            // the next minimal index follows the marker
317
318            let mut offset = ops::align(&mut writer, &mut self.columns[..l], ..)?;
319            let (actual, next_offset) = ops::marker(&mut writer, marker_char, offset, col)?;
320            offset = next_offset;
321            if r < self.columns.len() {
322                writer.queue_blank(offset.min(self.columns[r].1) - actual);
323                if delay_fork {
324                    ops::fork_align::<_, _, _, false>(
325                        &mut writer,
326                        &mut self.columns[r..],
327                        next_min_idx - r,
328                        offset..diagram_width,
329                    )?;
330                } else {
331                    ops::fork_align::<_, _, _, true>(
332                        &mut writer,
333                        &mut self.columns[r..],
334                        next_min_idx - r,
335                        offset..diagram_width,
336                    )?;
337                }
338            }
339        };
340
341        let annotation_alignment =
342            ((B::GUTTER_WIDTH + 1) * diagram_width - B::GUTTER_WIDTH).max(writer.line_char_count());
343
344        let mut lines = self.annotation_buf.lines();
345
346        // write the first annotation line or a newline
347        if let Some(line) = lines.next() {
348            writer.write_annotation_line(
349                line,
350                annotation_alignment,
351                self.config.annotation_margin,
352            )?;
353        } else {
354            writer.write_newline()?;
355        }
356
357        #[cfg(test)]
358        self.debug_cols_header(format!("Wrote marker line with marker {marker_char}"));
359
360        // we prepare space for the next annotation, but don't fork until necessary
361        if let Some(mut prev_line) = lines.next() {
362            for line in lines {
363                ops::fork_align::<_, _, _, false>(
364                    &mut writer,
365                    &mut self.columns,
366                    next_min_idx,
367                    ..diagram_width,
368                )?;
369                writer.write_annotation_line(
370                    prev_line,
371                    annotation_alignment,
372                    self.config.annotation_margin,
373                )?;
374                #[cfg(test)]
375                self.debug_cols_header("Wrote annotation line");
376
377                prev_line = line;
378            }
379
380            ops::fork_align::<_, _, _, true>(
381                &mut writer,
382                &mut self.columns,
383                next_min_idx,
384                ..diagram_width,
385            )?;
386            writer.write_annotation_line(
387                prev_line,
388                annotation_alignment,
389                self.config.annotation_margin,
390            )?;
391            #[cfg(test)]
392            self.debug_cols_header("Wrote final annotation line");
393        }
394
395        // write some padding lines, and also prepare for the next row simultaneously
396        for _ in 0..self.config.row_padding {
397            self.try_make_singleton(next_min_idx, &mut writer, diagram_width)?;
398        }
399
400        // finally, prepare for the next row by repeatedly calling
401        // `fork_align` until the index is a singleton, writing enough rows
402        // to get the desired padding
403        self.make_singleton(next_min_idx, &mut writer, diagram_width)?;
404
405        Ok(true)
406    }
407
408    fn compute_min_idx(&self) -> Option<usize>
409    where
410        R: TryRamify<V>,
411    {
412        self.columns
413            .iter()
414            .enumerate()
415            .min_by_key(|(_, (e, _))| self.ramifier.get_key(e))
416            .map(|(a, _)| a)
417    }
418
419    fn get_vtx_data(
420        ramifier: &mut R,
421        buf: &mut String,
422        vtx: V,
423    ) -> (char, Result<impl IntoIterator<Item = V>, V>)
424    where
425        R: TryRamify<V>,
426    {
427        let marker_char = ramifier.marker(&vtx);
428        buf.clear();
429        ramifier
430            .annotation(&vtx, buf)
431            .expect("Writing to a `String` should not fail.");
432        (marker_char, ramifier.try_children(vtx))
433    }
434
435    /// Write a row which tries to prepare for the next vertex.
436    fn try_make_singleton<W: io::Write>(
437        &mut self,
438        idx: usize,
439        writer: &mut DiagramWriter<W, B>,
440        diagram_width: usize,
441    ) -> io::Result<()> {
442        ops::fork_align::<_, _, _, true>(writer, &mut self.columns, idx, ..diagram_width)?;
443        writer.write_newline()?;
444        #[cfg(test)]
445        self.debug_cols_header("Wrote non-marker line");
446        Ok(())
447    }
448
449    /// Given an index, repeatedly call `ops::fork_align` until the corresponding index is a singleton
450    /// column.
451    fn make_singleton<W: io::Write>(
452        &mut self,
453        idx: usize,
454        writer: &mut DiagramWriter<W, B>,
455        diagram_width: usize,
456    ) -> io::Result<()> {
457        while !self.is_singleton(idx) {
458            self.try_make_singleton(idx, writer, diagram_width)?;
459        }
460        Ok(())
461    }
462
463    /// The index of the final `open` edge, or `None` if there are no edges.
464    ///
465    /// For example, the below diagram has maximum edge index `4`.
466    /// ```txt
467    /// 0
468    /// ├┬╮
469    /// │1│
470    /// ├╮╰─╮
471    /// ```
472    /// This is not the same as the width of the diagram row which was previously written. However,
473    /// we can use this information to compute the width of the diagram row by taking the maximum of the edge index and the
474    /// edge index prior to writing a row, multiplying by the gutter width, and then adding `1`.
475    pub fn max_edge_index(&self) -> Option<usize> {
476        self.columns.last().map(|(_, c)| *c)
477    }
478
479    /// The number of active vertices.
480    ///
481    /// Note that multiple vertices may use the same edge. In particular, this number is
482    /// distinct from the number of outgoing edges.
483    ///
484    /// Also note that there might be internal whitespace. In particular, this number is distinct
485    /// from the actual width (in characters) of the diagram, even after taking into account the
486    /// gutter width.
487    pub fn girth(&self) -> usize {
488        self.columns.len()
489    }
490
491    /// Whether or not there are any active vertices.
492    pub fn is_empty(&self) -> bool {
493        self.columns.is_empty()
494    }
495
496    /// Returns whether a provided index is a singleton, i.e. the corresponding edge is not shared
497    /// by any other vertices.
498    fn is_singleton(&self, idx: usize) -> bool {
499        let Range { start: l, end: r } = ops::column_range(&self.columns, idx);
500        l + 1 == r
501    }
502
503    fn handle_no_children<W: io::Write>(
504        &mut self,
505        writer: &mut DiagramWriter<W, B>,
506    ) -> WriteVertexError
507    where
508        R: TryRamify<V>,
509    {
510        // recompute the min index
511        let new_min_idx = self.compute_min_idx().unwrap();
512
513        self.min_index = Some(new_min_idx);
514
515        // prepare to write the vertex next iteration
516        let diagram_width = self.compute_diagram_width_no_base(new_min_idx);
517        if let Err(e) = self.make_singleton(new_min_idx, writer, diagram_width) {
518            return e.into();
519        }
520        WriteVertexError::TryChildrenFailed
521    }
522
523    /// Returns the amount of diagram width from the base diagram width (the amount of space
524    /// required for all of the rows before writing the next vertex) and taking into account the
525    /// configuration.
526    fn compute_diagram_width(&self, base_diagram_width: usize) -> usize {
527        let slack: usize = self.config.width_slack.into();
528        (base_diagram_width + slack).max(self.config.min_diagram_width)
529    }
530
531    fn compute_diagram_width_no_base(&self, min_idx: usize) -> usize {
532        self.compute_diagram_width(ops::required_width(&self.columns, min_idx))
533    }
534
535    #[cfg(test)]
536    fn debug_cols(&self) {
537        self.debug_cols_impl(None::<std::convert::Infallible>);
538    }
539
540    #[cfg(test)]
541    fn debug_cols_header<D: std::fmt::Display>(&self, header: D) {
542        self.debug_cols_impl(Some(header));
543    }
544
545    #[cfg(test)]
546    fn debug_cols_impl<D: std::fmt::Display>(&self, header: Option<D>) {
547        if let Some(min_idx) = self.min_index {
548            if let Some(s) = header {
549                println!("{s}:");
550            }
551            print!(" ->");
552            for (i, (_, col)) in self.columns.iter().enumerate() {
553                if i == min_idx {
554                    print!(" *{col}");
555                } else {
556                    print!("  {col}");
557                }
558            }
559            println!();
560        } else {
561            println!("Tree is empty");
562        }
563    }
564}
565
566impl<V, R> Generator<V, R, RoundedCorners> {
567    /// Initialize using default configuration with the *rounded corners* style.
568    ///
569    /// See the documentation for [`RoundedCorners`] for an example.
570    pub fn with_rounded_corners(root: V, ramifier: R) -> Self {
571        Self::init(root, ramifier, Config::with_rounded_corners())
572    }
573}
574
575impl<V, R> Generator<V, R, RoundedCornersWide> {
576    /// Initialize using default configuration with the *rounded corners* style, and extra internal
577    /// whitespace.
578    ///
579    /// See the documentation for [`RoundedCornersWide`] for an example.
580    pub fn with_rounded_corners_wide(root: V, ramifier: R) -> Self {
581        Self::init(root, ramifier, Config::with_rounded_corners_wide())
582    }
583}
584
585impl<V, R> Generator<V, R, SharpCorners> {
586    /// Initialize using default configuration with the *sharp corners* style.
587    ///
588    /// See the documentation for [`SharpCorners`] for an example.
589    pub fn with_sharp_corners(root: V, ramifier: R) -> Self {
590        Self::init(root, ramifier, Config::with_sharp_corners())
591    }
592}
593
594impl<V, R> Generator<V, R, SharpCornersWide> {
595    /// Initialize using default configuration with the *sharp corners* style, and extra internal
596    /// whitespace.
597    ///
598    /// See the documentation for [`SharpCornersWide`] for an example.
599    pub fn with_sharp_corners_wide(root: V, ramifier: R) -> Self {
600        Self::init(root, ramifier, Config::with_sharp_corners_wide())
601    }
602}
603
604impl<V, R> Generator<V, R, DoubledLines> {
605    /// Initialize using default configuration with the *doubled lines* style.
606    ///
607    /// See the documentation for [`DoubledLines`] for an example.
608    pub fn with_doubled_lines(root: V, ramifier: R) -> Self {
609        Self::init(root, ramifier, Config::with_doubled_lines())
610    }
611}