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}