git_igitt/widgets/
commit_view.rs1use crate::app::DiffType;
2use crate::util::ctrl_chars::CtrlChars;
3use crate::widgets::list::{ListItem, StatefulList};
4use git2::Oid;
5use tui::buffer::Buffer;
6use tui::layout::Rect;
7use tui::style::Style;
8use tui::widgets::{Block, StatefulWidget, Widget};
9
10#[derive(Default)]
11pub struct CommitViewState {
12 pub content: Option<CommitViewInfo>,
13}
14
15pub struct DiffItem {
16 pub(crate) file: String,
17 pub(crate) diff_type: DiffType,
18}
19
20impl ListItem for DiffItem {
21 fn is_selectable(&self) -> bool {
22 true
23 }
24}
25
26pub struct CommitViewInfo {
27 pub text: Vec<String>,
28 pub diffs: StatefulList<DiffItem>,
29 pub oid: Oid,
30 pub compare_oid: Oid,
31 pub scroll: u16,
32}
33impl CommitViewInfo {
34 pub fn new(
35 text: Vec<String>,
36 diffs: StatefulList<DiffItem>,
37 oid: Oid,
38 compare_oid: Oid,
39 ) -> Self {
40 Self {
41 text,
42 diffs,
43 oid,
44 compare_oid,
45 scroll: 0,
46 }
47 }
48}
49
50#[derive(Default)]
51pub struct CommitView<'a> {
52 block: Option<Block<'a>>,
53 highlight_symbol: Option<&'a str>,
54 style: Style,
55}
56
57impl<'a> CommitView<'a> {
58 pub fn block(mut self, block: Block<'a>) -> CommitView<'a> {
59 self.block = Some(block);
60 self
61 }
62
63 pub fn style(mut self, style: Style) -> CommitView<'a> {
64 self.style = style;
65 self
66 }
67
68 pub fn highlight_symbol(mut self, highlight_symbol: &'a str) -> CommitView<'a> {
69 self.highlight_symbol = Some(highlight_symbol);
70 self
71 }
72}
73
74impl<'a> StatefulWidget for CommitView<'a> {
75 type State = CommitViewState;
76
77 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
78 buf.set_style(area, self.style);
79 let list_area = match self.block.take() {
80 Some(b) => {
81 let inner_area = b.inner(area);
82 b.render(area, buf);
83 inner_area
84 }
85 None => area,
86 };
87
88 if list_area.width < 1 || list_area.height < 1 {
89 return;
90 }
91
92 let (x_start, y0) = (list_area.left(), list_area.top());
93 let list_bottom = list_area.top() + list_area.height;
94
95 let max_element_width = list_area.width;
96 if let Some(commit_info) = &state.content {
97 let scroll = commit_info.scroll;
98 let y_start = y0 as i32 - scroll as i32;
99
100 let wrapping =
101 textwrap::Options::new(list_area.width as usize).subsequent_indent(" ");
102 let ellipsis = &format!(
103 " ...{}",
104 " ".repeat(max_element_width.saturating_sub(7) as usize)
105 )[..max_element_width as usize];
106
107 let mut y = y_start;
108 for (line_idx, text_line) in commit_info.text.iter().enumerate() {
109 if text_line.is_empty() {
110 y += 1;
111 if y >= list_bottom as i32 {
112 buf.set_string(x_start, (y - 1) as u16, ellipsis, self.style);
113 break;
114 }
115 } else {
116 let wrapped = if line_idx > 1 {
117 #[allow(clippy::needless_borrow)]
118 textwrap::fill(text_line, &wrapping)
119 } else {
120 text_line.clone()
121 };
122
123 for line in wrapped.lines() {
124 let mut x = x_start;
125 let mut remaining_width = max_element_width;
126
127 let line_span = CtrlChars::parse(line).into_text();
128 if y >= y0 as i32 {
129 for txt in line_span {
130 for line in txt.lines {
131 if remaining_width == 0 {
132 break;
133 }
134 let pos = buf.set_spans(x, y as u16, &line, remaining_width);
135 let w = pos.0.saturating_sub(x);
136 x = pos.0;
137 y = pos.1 as i32;
138 remaining_width = remaining_width.saturating_sub(w);
139 }
140 }
141 }
142 y += 1;
143 if y >= list_bottom as i32 {
144 break;
145 }
146 }
147 if y >= list_bottom as i32 {
148 buf.set_string(x_start, (y - 1) as u16, ellipsis, self.style);
149 break;
150 }
151 }
152 }
153 }
154 }
155}
156
157impl<'a> Widget for CommitView<'a> {
158 fn render(self, area: Rect, buf: &mut Buffer) {
159 let mut state = CommitViewState::default();
160 StatefulWidget::render(self, area, buf, &mut state);
161 }
162}