// 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; use std::io::Write; 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 graph pub enum Edge { Present { target: T, direct: bool }, Missing, } impl Edge { 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 { fn add_node( &mut self, id: &K, edges: &[Edge], node_symbol: &str, text: &str, ) -> io::Result<()>; fn default_node_symbol(&self) -> &str; fn elided_node_symbol(&self) -> &str; fn width(&self, id: &K, edges: &[Edge]) -> usize; } pub struct SaplingGraphLog<'writer, R> { renderer: R, writer: &'writer mut dyn Write, default_node_symbol: String, elided_node_symbol: String, } impl From<&Edge> for Ancestor { fn from(e: &Edge) -> 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 for SaplingGraphLog<'writer, R> where K: Clone + Eq + Hash, R: Renderer, { fn add_node( &mut self, id: &K, edges: &[Edge], 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 elided_node_symbol(&self) -> &str { &self.elided_node_symbol } fn width(&self, id: &K, edges: &[Edge]) -> 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( renderer: R, formatter: &'writer mut dyn Write, default_node_symbol: &str, elided_node_symbol: &str, ) -> Box + 'writer> where K: Clone + Eq + Hash + 'writer, R: Renderer + 'writer, { Box::new(SaplingGraphLog { renderer, writer: formatter, default_node_symbol: default_node_symbol.to_owned(), elided_node_symbol: elided_node_symbol.to_owned(), }) } } pub fn get_graphlog<'a, K: Clone + Eq + Hash + 'a>( settings: &UserSettings, formatter: &'a mut dyn Write, ) -> Box + 'a> { let builder = GraphRowRenderer::new().output().with_min_row_height(0); match settings.graph_style().as_str() { "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", "."), // "curved" _ => SaplingGraphLog::create(builder.build_box_drawing(), formatter, "◉", "◌"), } }