// 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 jj_lib::config::ConfigGetError; use jj_lib::graph::GraphEdge; use jj_lib::graph::GraphEdgeType; use jj_lib::settings::UserSettings; use renderdag::Ancestor; use renderdag::GraphRowRenderer; use renderdag::Renderer; pub trait GraphLog { fn add_node( &mut self, id: &K, edges: &[GraphEdge], node_symbol: &str, text: &str, ) -> io::Result<()>; fn width(&self, id: &K, edges: &[GraphEdge]) -> usize; } pub struct SaplingGraphLog<'writer, R> { renderer: R, writer: &'writer mut dyn Write, } fn convert_graph_edge_into_ancestor(e: &GraphEdge) -> Ancestor { match e.edge_type { GraphEdgeType::Direct => Ancestor::Parent(e.target.clone()), GraphEdgeType::Indirect => Ancestor::Ancestor(e.target.clone()), GraphEdgeType::Missing => Ancestor::Anonymous, } } impl GraphLog for SaplingGraphLog<'_, R> where K: Clone + Eq + Hash, R: Renderer, { fn add_node( &mut self, id: &K, edges: &[GraphEdge], node_symbol: &str, text: &str, ) -> io::Result<()> { let row = self.renderer.next_row( id.clone(), edges.iter().map(convert_graph_edge_into_ancestor).collect(), node_symbol.into(), text.into(), ); write!(self.writer, "{row}") } fn width(&self, id: &K, edges: &[GraphEdge]) -> usize { let parents = edges.iter().map(convert_graph_edge_into_ancestor).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, ) -> Box + 'writer> where K: Clone + Eq + Hash + 'writer, R: Renderer + 'writer, { Box::new(SaplingGraphLog { renderer, writer: formatter, }) } } #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] pub enum GraphStyle { Ascii, AsciiLarge, Curved, Square, } impl GraphStyle { pub fn from_settings(settings: &UserSettings) -> Result { settings.get("ui.graph.style") } pub fn is_ascii(self) -> bool { match self { GraphStyle::Ascii | GraphStyle::AsciiLarge => true, GraphStyle::Curved | GraphStyle::Square => false, } } } pub fn get_graphlog<'a, K: Clone + Eq + Hash + 'a>( style: GraphStyle, formatter: &'a mut dyn Write, ) -> Box + 'a> { let builder = GraphRowRenderer::new().output().with_min_row_height(0); match style { GraphStyle::Ascii => SaplingGraphLog::create(builder.build_ascii(), formatter), GraphStyle::AsciiLarge => SaplingGraphLog::create(builder.build_ascii_large(), formatter), GraphStyle::Curved => SaplingGraphLog::create(builder.build_box_drawing(), formatter), GraphStyle::Square => { SaplingGraphLog::create(builder.build_box_drawing().with_square_glyphs(), formatter) } } }