just playing with tangled
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 pub fn is_ascii(self) -> bool {
112 match self {
113 GraphStyle::Ascii | GraphStyle::AsciiLarge => true,
114 GraphStyle::Curved | GraphStyle::Square => false,
115 }
116 }
117}
118
119pub fn get_graphlog<'a, K: Clone + Eq + Hash + 'a>(
120 style: GraphStyle,
121 formatter: &'a mut dyn Write,
122) -> Box<dyn GraphLog<K> + 'a> {
123 let builder = GraphRowRenderer::new().output().with_min_row_height(0);
124 match style {
125 GraphStyle::Ascii => SaplingGraphLog::create(builder.build_ascii(), formatter),
126 GraphStyle::AsciiLarge => SaplingGraphLog::create(builder.build_ascii_large(), formatter),
127 GraphStyle::Curved => SaplingGraphLog::create(builder.build_box_drawing(), formatter),
128 GraphStyle::Square => {
129 SaplingGraphLog::create(builder.build_box_drawing().with_square_glyphs(), formatter)
130 }
131 }
132}