1031 lines · 31.8 KB · Rust
RawDownload
// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::hash::Hash;use std::io::Write;use std::{cmp, io}; use itertools::Itertools;use jj_lib::settings::UserSettings;use renderdag::{Ancestor, GraphRowRenderer, Renderer}; #[derive(Debug, Clone, PartialEq, Eq)]// An edge to another node in the graphpub enum Edge<T> {    Present { target: T, direct: bool },    Missing,} impl<T> Edge<T> {    pub fn missing() -> Self {        Edge::Missing    }     pub fn direct(id: T) -> Self {        Edge::Present {            target: id,            direct: true,        }    }     pub fn indirect(id: T) -> Self {        Edge::Present {            target: id,            direct: false,        }    }} pub trait GraphLog<K: Clone + Eq + Hash> {    fn add_node(        &mut self,        id: &K,        edges: &[Edge<K>],        node_symbol: &str,        text: &str,    ) -> io::Result<()>;     fn default_node_symbol(&self) -> &str;     fn width(&self, id: &K, edges: &[Edge<K>]) -> usize;} pub struct SaplingGraphLog<'writer, R> {    renderer: R,    writer: &'writer mut dyn Write,    default_node_symbol: String,} impl<K: Clone> From<&Edge<K>> for Ancestor<K> {    fn from(e: &Edge<K>) -> Self {        match e {            Edge::Present {                target,                direct: true,            } => Ancestor::Parent(target.clone()),            Edge::Present { target, .. } => Ancestor::Ancestor(target.clone()),            Edge::Missing => Ancestor::Anonymous,        }    }} impl<'writer, K, R> GraphLog<K> for SaplingGraphLog<'writer, R>where    K: Clone + Eq + Hash,    R: Renderer<K, Output = String>,{    fn add_node(        &mut self,        id: &K,        edges: &[Edge<K>],        node_symbol: &str,        text: &str,    ) -> io::Result<()> {        let row = self.renderer.next_row(            id.clone(),            edges.iter().map_into().collect(),            node_symbol.into(),            text.into(),        );         write!(self.writer, "{row}")    }     fn default_node_symbol(&self) -> &str {        &self.default_node_symbol    }     fn width(&self, id: &K, edges: &[Edge<K>]) -> usize {        let parents = edges.iter().map_into().collect();        let w: u64 = self.renderer.width(Some(id), Some(&parents));        w.try_into().unwrap()    }} impl<'writer, R> SaplingGraphLog<'writer, R> {    pub fn create<K>(        renderer: R,        formatter: &'writer mut dyn Write,        default_node_symbol: &str,    ) -> Box<dyn GraphLog<K> + 'writer>    where        K: Clone + Eq + Hash + 'writer,        R: Renderer<K, Output = String> + 'writer,    {        Box::new(SaplingGraphLog {            renderer,            writer: formatter,            default_node_symbol: default_node_symbol.to_owned(),        })    }} pub fn get_graphlog<'a, K: Clone + Eq + Hash + 'a>(    settings: &UserSettings,    formatter: &'a mut dyn Write,) -> Box<dyn GraphLog<K> + 'a> {    let builder = GraphRowRenderer::new().output().with_min_row_height(0);     match settings.graph_style().as_str() {        "curved" => SaplingGraphLog::create(builder.build_box_drawing(), formatter, "◉"),        "square" => SaplingGraphLog::create(            builder.build_box_drawing().with_square_glyphs(),            formatter,            "◉",        ),        "ascii" => SaplingGraphLog::create(builder.build_ascii(), formatter, "o"),        "ascii-large" => SaplingGraphLog::create(builder.build_ascii_large(), formatter, "o"),        _ => Box::new(AsciiGraphDrawer::new(formatter)),    }} pub struct AsciiGraphDrawer<'writer, K> {    writer: &'writer mut dyn Write,    edges: Vec<Edge<K>>,    pending_text: Vec<String>,} impl<'writer, K> AsciiGraphDrawer<'writer, K>where    K: Clone + Eq + Hash,{    pub fn new(writer: &'writer mut dyn Write) -> Self {        Self {            writer,            edges: Default::default(),            pending_text: Default::default(),        }    }} impl<'writer, K: Clone + Eq + Hash> GraphLog<K> for AsciiGraphDrawer<'writer, K> {    fn add_node(        &mut self,        id: &K,        edges: &[Edge<K>],        node_symbol: &str,        text: &str,    ) -> io::Result<()> {        assert!(self.pending_text.is_empty());        for line in text.split(|x| x == '\n') {            self.pending_text.push(line.into());        }        if self.pending_text.last() == Some(&"".into()) {            self.pending_text.pop().unwrap();        }        self.pending_text.reverse();         // Check if an existing edge should be terminated by the new node. If there        // is, draw the new node in the same column. Otherwise, insert it at the right.        let edge_index = if let Some(edge_index) = self.index_by_target(id) {            // This edge terminates in the node we're adding             // If we're inserting a merge somewhere that's not the very right, the edges            // right of it will move further right, so we need to prepare by inserting rows            // of '\'.            if edges.len() > 2 && edge_index < self.edges.len() - 1 {                for i in 2..edges.len() {                    for edge in self.edges.iter().take(edge_index + 1) {                        AsciiGraphDrawer::straight_edge(&mut self.writer, edge)?;                    }                    for _ in 0..i - 2 {                        write!(self.writer, "  ")?;                    }                    for _ in edge_index + 1..self.edges.len() {                        write!(self.writer, " \\")?;                    }                    writeln!(self.writer)?;                }            }             self.edges.remove(edge_index);            edge_index        } else {            self.edges.len()        };         // Draw the edges to the left of the new node        for edge in self.edges.iter().take(edge_index) {            AsciiGraphDrawer::straight_edge(&mut self.writer, edge)?;        }        // Draw the new node        write!(self.writer, "{node_symbol}")?;        // If it's a merge of many nodes, draw a vertical line to the right        for _ in 3..edges.len() {            write!(self.writer, "--")?;        }        if edges.len() > 2 {            write!(self.writer, "-.")?;        }        write!(self.writer, " ")?;        // Draw the edges to the right of the new node        for edge in self.edges.iter().skip(edge_index) {            AsciiGraphDrawer::straight_edge(&mut self.writer, edge)?;        }        if edges.len() > 1 {            write!(self.writer, "  ")?;        }         self.maybe_write_pending_text()?;         // Update the data model.        for (i, edge) in edges.iter().enumerate() {            self.edges.insert(edge_index + i, edge.clone());        }         // If it's a merge commit, insert a row of '\'.        if edges.len() >= 2 {            for edge in self.edges.iter().take(edge_index) {                AsciiGraphDrawer::straight_edge(&mut self.writer, edge)?;            }            AsciiGraphDrawer::straight_edge_no_space(&mut self.writer, &self.edges[edge_index])?;            for _ in edge_index + 1..self.edges.len() {                write!(self.writer, "\\ ")?;            }            write!(self.writer, " ")?;            self.maybe_write_pending_text()?;        }         let pad_to_index = self.edges.len() + usize::from(edges.is_empty());        // Close any edges to missing nodes.        for (i, edge) in edges.iter().enumerate().rev() {            if *edge == Edge::Missing {                self.close_missing_edge(edge_index + i, pad_to_index)?;            }        }        // If this was the last node in a chain, add a "/" for any edges to the right of        // it.        if edges.is_empty() && edge_index < self.edges.len() {            self.close_edge(edge_index, pad_to_index)?;        }         // Merge new edges that share the same target.        let mut source_index = 1;        while source_index < self.edges.len() {            if let Edge::Present { target, .. } = &self.edges[source_index] {                if let Some(target_index) = self.index_by_target(target) {                    // There already is an edge leading to the same target node. Mark that we                    // want to merge the higher index into the lower index.                    if source_index > target_index {                        self.merge_edges(source_index, target_index, pad_to_index)?;                        // Don't increment source_index.                        continue;                    }                }            }            source_index += 1;        }         // Emit any remaining lines of text.        while !self.pending_text.is_empty() {            for edge in &self.edges {                AsciiGraphDrawer::straight_edge(&mut self.writer, edge)?;            }            for _ in self.edges.len()..pad_to_index {                write!(self.writer, "  ")?;            }            self.maybe_write_pending_text()?;        }         Ok(())    }     fn default_node_symbol(&self) -> &str {        "o"    }     fn width(&self, id: &K, edges: &[Edge<K>]) -> usize {        let orig = self.edges.len() - usize::from(self.index_by_target(id).is_some());        let added = cmp::max(edges.len(), 1);        2 * (orig + added)    }} impl<'writer, K: Clone + Eq + Hash> AsciiGraphDrawer<'writer, K> {    fn index_by_target(&self, id: &K) -> Option<usize> {        for (i, edge) in self.edges.iter().enumerate() {            match edge {                Edge::Present { target, .. } if target == id => return Some(i),                _ => {}            }        }        None    }     /// Not an instance method so the caller doesn't need mutable access to the    /// whole struct.    fn straight_edge(writer: &mut dyn Write, edge: &Edge<K>) -> io::Result<()> {        AsciiGraphDrawer::straight_edge_no_space(writer, edge)?;        write!(writer, " ")    }     /// Not an instance method so the caller doesn't need mutable access to the    /// whole struct.    fn straight_edge_no_space(writer: &mut dyn Write, edge: &Edge<K>) -> io::Result<()> {        match edge {            Edge::Present { direct: true, .. } => {                write!(writer, "|")?;            }            Edge::Present { direct: false, .. } => {                write!(writer, ":")?;            }            Edge::Missing => {                write!(writer, "|")?;            }        }        Ok(())    }     fn merge_edges(&mut self, source: usize, target: usize, pad_to_index: usize) -> io::Result<()> {        assert!(target < source);        self.edges.remove(source);        for i in 0..target {            AsciiGraphDrawer::straight_edge(&mut self.writer, &self.edges[i])?;        }        if source == target + 1 {            // If we're merging exactly one step to the left, draw a '/' to join the lines.            AsciiGraphDrawer::straight_edge_no_space(&mut self.writer, &self.edges[target])?;            for _ in source..self.edges.len() + 1 {                write!(self.writer, "/ ")?;            }            write!(self.writer, " ")?;            for _ in self.edges.len() + 1..pad_to_index {                write!(self.writer, "  ")?;            }        } else {            // If we're merging more than one step to the left, we need two rows:            // | |_|_|/            // |/| | |            AsciiGraphDrawer::straight_edge(&mut self.writer, &self.edges[target])?;            for i in target + 1..source - 1 {                AsciiGraphDrawer::straight_edge_no_space(&mut self.writer, &self.edges[i])?;                write!(self.writer, "_")?;            }            AsciiGraphDrawer::straight_edge_no_space(&mut self.writer, &self.edges[source - 1])?;            for _ in source..self.edges.len() + 1 {                write!(self.writer, "/ ")?;            }            write!(self.writer, " ")?;            for _ in self.edges.len() + 1..pad_to_index {                write!(self.writer, "  ")?;            }            self.maybe_write_pending_text()?;             for i in 0..target {                AsciiGraphDrawer::straight_edge(&mut self.writer, &self.edges[i])?;            }            AsciiGraphDrawer::straight_edge_no_space(&mut self.writer, &self.edges[target])?;            write!(self.writer, "/")?;            for i in target + 1..self.edges.len() {                AsciiGraphDrawer::straight_edge(&mut self.writer, &self.edges[i])?;            }            for _ in self.edges.len()..pad_to_index {                write!(self.writer, "  ")?;            }        }        self.maybe_write_pending_text()?;         Ok(())    }     fn close_missing_edge(&mut self, source: usize, pad_to_index: usize) -> io::Result<()> {        self.edges.remove(source);        for i in 0..source {            AsciiGraphDrawer::straight_edge(&mut self.writer, &self.edges[i])?;        }        write!(self.writer, "~")?;        for _ in source..self.edges.len() {            write!(self.writer, "/ ")?;        }        write!(self.writer, " ")?;        for _ in self.edges.len() + 1..pad_to_index {            write!(self.writer, "  ")?;        }        self.maybe_write_pending_text()    }     fn close_edge(&mut self, source: usize, pad_to_index: usize) -> io::Result<()> {        for i in 0..source {            AsciiGraphDrawer::straight_edge(&mut self.writer, &self.edges[i])?;        }        write!(self.writer, " ")?;        for _ in source..self.edges.len() {            write!(self.writer, "/ ")?;        }        write!(self.writer, " ")?;        for _ in self.edges.len() + 1..pad_to_index {            write!(self.writer, "  ")?;        }        self.maybe_write_pending_text()    }     fn maybe_write_pending_text(&mut self) -> io::Result<()> {        if let Some(text) = self.pending_text.pop() {            write!(self.writer, "{text}")?;        }        writeln!(self.writer)    }} #[cfg(test)]mod tests {    use super::*;     /// Adapter to insert width calculation for each `.add_node()`    struct AsciiGraphDrawerWithWidths<'writer, K> {        inner: AsciiGraphDrawer<'writer, K>,    }     impl<'writer, K: Clone + Eq + Hash> AsciiGraphDrawerWithWidths<'writer, K> {        fn new(writer: &'writer mut dyn Write) -> Self {            AsciiGraphDrawerWithWidths {                inner: AsciiGraphDrawer::new(writer),            }        }         fn add_node(            &mut self,            id: &K,            edges: &[Edge<K>],            node_symbol: &str,            text: &str,        ) -> io::Result<()> {            let width = self.inner.width(id, edges);            let text = format!("{text} [width={width}]");            self.inner.add_node(id, edges, node_symbol, &text)        }    }     #[test]    fn single_node() -> io::Result<()> {        let mut buffer = vec![];        let mut graph = AsciiGraphDrawerWithWidths::new(&mut buffer);        graph.add_node(&1, &[], "@", "node 1")?;         insta::assert_snapshot!(String::from_utf8_lossy(&buffer), @r###"        @ node 1 [width=2]        "###);         Ok(())    }     #[test]    fn long_description() -> io::Result<()> {        let mut buffer = vec![];        let mut graph = AsciiGraphDrawer::new(&mut buffer);        graph.add_node(&2, &[Edge::direct(1)], "@", "many\nlines\nof\ntext\n")?;        graph.add_node(&1, &[], "o", "single line")?;         insta::assert_snapshot!(String::from_utf8_lossy(&buffer), @r###"        @ many        | lines        | of        | text        o single line        "###);         Ok(())    } 

Lines 1–500 of 1031.