1use 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}