jj_cli/
graphlog.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::hash::Hash;
16use std::io;
17use std::io::Write;
18
19use jj_lib::config::ConfigGetError;
20use jj_lib::graph::GraphEdge;
21use jj_lib::graph::GraphEdgeType;
22use jj_lib::settings::UserSettings;
23use renderdag::Ancestor;
24use renderdag::GraphRowRenderer;
25use renderdag::Renderer;
26
27pub trait GraphLog<K: Clone + Eq + Hash> {
28    fn add_node(
29        &mut self,
30        id: &K,
31        edges: &[GraphEdge<K>],
32        node_symbol: &str,
33        text: &str,
34    ) -> io::Result<()>;
35
36    fn width(&self, id: &K, edges: &[GraphEdge<K>]) -> usize;
37}
38
39pub struct SaplingGraphLog<'writer, R> {
40    renderer: R,
41    writer: &'writer mut dyn Write,
42}
43
44fn convert_graph_edge_into_ancestor<K: Clone>(e: &GraphEdge<K>) -> Ancestor<K> {
45    match e.edge_type {
46        GraphEdgeType::Direct => Ancestor::Parent(e.target.clone()),
47        GraphEdgeType::Indirect => Ancestor::Ancestor(e.target.clone()),
48        GraphEdgeType::Missing => Ancestor::Anonymous,
49    }
50}
51
52impl<K, R> GraphLog<K> for SaplingGraphLog<'_, R>
53where
54    K: Clone + Eq + Hash,
55    R: Renderer<K, Output = String>,
56{
57    fn add_node(
58        &mut self,
59        id: &K,
60        edges: &[GraphEdge<K>],
61        node_symbol: &str,
62        text: &str,
63    ) -> io::Result<()> {
64        let row = self.renderer.next_row(
65            id.clone(),
66            edges.iter().map(convert_graph_edge_into_ancestor).collect(),
67            node_symbol.into(),
68            text.into(),
69        );
70
71        write!(self.writer, "{row}")
72    }
73
74    fn width(&self, id: &K, edges: &[GraphEdge<K>]) -> usize {
75        let parents = edges.iter().map(convert_graph_edge_into_ancestor).collect();
76        let w: u64 = self.renderer.width(Some(id), Some(&parents));
77        w.try_into().unwrap()
78    }
79}
80
81impl<'writer, R> SaplingGraphLog<'writer, R> {
82    pub fn create<K>(
83        renderer: R,
84        formatter: &'writer mut dyn Write,
85    ) -> Box<dyn GraphLog<K> + 'writer>
86    where
87        K: Clone + Eq + Hash + 'writer,
88        R: Renderer<K, Output = String> + 'writer,
89    {
90        Box::new(SaplingGraphLog {
91            renderer,
92            writer: formatter,
93        })
94    }
95}
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)]
98#[serde(rename_all(deserialize = "kebab-case"))]
99pub enum GraphStyle {
100    Ascii,
101    AsciiLarge,
102    Curved,
103    Square,
104}
105
106impl GraphStyle {
107    pub fn from_settings(settings: &UserSettings) -> Result<Self, ConfigGetError> {
108        settings.get("ui.graph.style")
109    }
110}
111
112pub fn get_graphlog<'a, K: Clone + Eq + Hash + 'a>(
113    style: GraphStyle,
114    formatter: &'a mut dyn Write,
115) -> Box<dyn GraphLog<K> + 'a> {
116    let builder = GraphRowRenderer::new().output().with_min_row_height(0);
117    match style {
118        GraphStyle::Ascii => SaplingGraphLog::create(builder.build_ascii(), formatter),
119        GraphStyle::AsciiLarge => SaplingGraphLog::create(builder.build_ascii_large(), formatter),
120        GraphStyle::Curved => SaplingGraphLog::create(builder.build_box_drawing(), formatter),
121        GraphStyle::Square => {
122            SaplingGraphLog::create(builder.build_box_drawing().with_square_glyphs(), formatter)
123        }
124    }
125}