// 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; use renderdag::GraphRowRenderer; use renderdag::Renderer; #[derive(Debug, Clone, PartialEq, Eq)] // An edge to another node in the graph pub enum Edge { Direct(T), Indirect(T), Missing, } pub trait GraphLog { fn add_node( &mut self, id: &K, edges: &[Edge], node_symbol: &str, text: &str, ) -> io::Result<()>; fn width(&self, id: &K, edges: &[Edge]) -> usize; } pub struct SaplingGraphLog<'writer, R> { renderer: R, writer: &'writer mut dyn Write, } impl From<&Edge> for Ancestor { fn from(e: &Edge) -> Self { match e { Edge::Direct(target) => Ancestor::Parent(target.clone()), Edge::Indirect(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 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, ) -> 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) -> Self { settings .config() .get("ui.graph.style") .unwrap_or(GraphStyle::Curved) } pub fn is_ascii(self) -> bool { match self { GraphStyle::Ascii | GraphStyle::AsciiLarge => true, GraphStyle::Curved | GraphStyle::Square => false, } } } pub fn node_template_for_key( settings: &UserSettings, key: &str, fallback: &str, ascii_fallback: &str, ) -> String { let symbol = settings.config().get_string(key); if GraphStyle::from_settings(settings).is_ascii() { symbol.unwrap_or_else(|_| ascii_fallback.to_owned()) } else { symbol.unwrap_or_else(|_| fallback.to_owned()) } } 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) } } }