1#![cfg_attr(allow_unknown_lints, allow(unknown_lints))]
5#![cfg_attr(allow_unknown_lints, allow(renamed_and_removed_lints))]
6#![deny(
8 future_incompatible,
9 nonstandard_style,
10 rust_2018_compatibility,
11 rust_2018_idioms,
12 rust_2021_compatibility,
13 unused,
14 warnings
15)]
16#![deny(
18 absolute_paths_not_starting_with_crate,
19 deprecated_in_future,
20 elided_lifetimes_in_paths,
21 explicit_outlives_requirements,
22 ffi_unwind_calls,
23 keyword_idents,
24 let_underscore_drop,
25 macro_use_extern_crate,
26 meta_variable_misuse,
27 missing_abi,
28 missing_copy_implementations,
29 missing_debug_implementations,
30 missing_docs,
31 non_ascii_idents,
32 noop_method_call,
33 pointer_structural_match,
34 rust_2021_incompatible_closure_captures,
35 rust_2021_incompatible_or_patterns,
36 rust_2021_prefixes_incompatible_syntax,
37 rust_2021_prelude_collisions,
38 single_use_lifetimes,
39 trivial_casts,
40 trivial_numeric_casts,
41 unreachable_pub,
42 unsafe_code,
43 unsafe_op_in_unsafe_fn,
44 unused_crate_dependencies,
45 unused_extern_crates,
46 unused_import_braces,
47 unused_lifetimes,
48 unused_macro_rules,
49 unused_qualifications,
50 unused_results,
51 unused_tuple_struct_fields,
52 variant_size_differences
53)]
54#![deny(clippy::all, clippy::cargo, clippy::pedantic, clippy::restriction)]
56#![cfg_attr(include_nightly_lints, deny(clippy::nursery))]
57#![allow(
58 clippy::arithmetic_side_effects,
59 clippy::arithmetic_side_effects,
60 clippy::blanket_clippy_restriction_lints,
61 clippy::bool_to_int_with_if,
62 clippy::default_numeric_fallback,
63 clippy::else_if_without_else,
64 clippy::expect_used,
65 clippy::float_arithmetic,
66 clippy::implicit_return,
67 clippy::indexing_slicing,
68 clippy::map_err_ignore,
69 clippy::missing_docs_in_private_items,
70 clippy::missing_trait_methods,
71 clippy::mod_module_files,
72 clippy::module_name_repetitions,
73 clippy::new_without_default,
74 clippy::non_ascii_literal,
75 clippy::option_if_let_else,
76 clippy::pub_use,
77 clippy::question_mark_used,
78 clippy::redundant_pub_crate,
79 clippy::ref_patterns,
80 clippy::std_instead_of_alloc,
81 clippy::std_instead_of_core,
82 clippy::tabs_in_doc_comments,
83 clippy::tests_outside_test_module,
84 clippy::too_many_lines,
85 clippy::unwrap_used
86)]
87#![deny(
88 rustdoc::bare_urls,
89 rustdoc::broken_intra_doc_links,
90 rustdoc::invalid_codeblock_attributes,
91 rustdoc::invalid_html_tags,
92 rustdoc::missing_crate_level_docs,
93 rustdoc::private_doc_tests,
94 rustdoc::private_intra_doc_links
95)]
96#![cfg_attr(
98 test,
99 allow(
100 let_underscore_drop,
101 clippy::cognitive_complexity,
102 clippy::let_underscore_must_use,
103 clippy::let_underscore_untyped,
104 clippy::needless_pass_by_value,
105 clippy::panic,
106 clippy::shadow_reuse,
107 clippy::shadow_unrelated,
108 clippy::undocumented_unsafe_blocks,
109 clippy::unimplemented,
110 clippy::unreachable
111 )
112)]
113#![cfg_attr(
115 include_nightly_lints,
116 allow(
117 clippy::arc_with_non_send_sync,
118 clippy::min_ident_chars,
119 clippy::needless_raw_strings,
120 clippy::pub_with_shorthand,
121 clippy::redundant_closure_call,
122 clippy::single_call_fn
123 )
124)]
125#![allow(clippy::as_conversions, clippy::integer_division)]
127
128mod line_segment;
139mod render_context;
140mod render_slice;
141mod scroll_position;
142#[cfg(all(feature = "testutils", not(tarpaulin_include)))]
143pub mod testutil;
144mod thread;
145mod view_data;
146mod view_data_updater;
147mod view_line;
148
149#[cfg(test)]
150mod tests;
151
152use anyhow::{Error, Result};
153use display::{Display, DisplayColor, Tui};
154
155pub use self::{
156 line_segment::LineSegment,
157 render_context::RenderContext,
158 thread::{State, Thread, MAIN_THREAD_NAME, REFRESH_THREAD_NAME},
159 view_data::ViewData,
160 view_data_updater::ViewDataUpdater,
161 view_line::ViewLine,
162};
163use self::{render_slice::RenderSlice, thread::ViewAction};
164
165const TITLE: &str = "Git Interactive Rebase Tool";
166const TITLE_SHORT: &str = "Git Rebase";
167const TITLE_HELP_INDICATOR_LABEL: &str = "Help: ";
168const SCROLLBAR_INDICATOR_CHARACTER: &str = "\u{2588}"; #[derive(Debug)]
172pub struct View<C: Tui> {
173 character_vertical_spacing: String,
174 display: Display<C>,
175 help_indicator_key: String,
176 last_render_version: u32,
177}
178
179impl<C: Tui> View<C> {
180 #[inline]
182 pub fn new(display: Display<C>, character_vertical_spacing: &str, help_indicator_key: &str) -> Self {
183 Self {
184 character_vertical_spacing: String::from(character_vertical_spacing),
185 display,
186 help_indicator_key: String::from(help_indicator_key),
187 last_render_version: u32::MAX,
188 }
189 }
190
191 #[inline]
196 pub(crate) fn start(&mut self) -> Result<()> {
197 self.display.start().map_err(Error::from)
198 }
199
200 #[inline]
205 pub(crate) fn end(&mut self) -> Result<()> {
206 self.display.end().map_err(Error::from)
207 }
208
209 #[inline]
214 pub fn render(&mut self, render_slice: &RenderSlice) -> Result<()> {
215 let current_render_version = render_slice.get_version();
216 if self.last_render_version == current_render_version {
217 return Ok(());
218 }
219 self.last_render_version = current_render_version;
220 let view_size = self.display.get_window_size();
221 let window_height = view_size.height();
222
223 self.display.clear()?;
224
225 self.display.ensure_at_line_start()?;
226 if render_slice.show_title() {
227 self.display.ensure_at_line_start()?;
228 self.draw_title(render_slice.show_help())?;
229 self.display.next_line()?;
230 }
231
232 let lines = render_slice.get_lines();
233 let leading_line_count = render_slice.get_leading_lines_count();
234 let trailing_line_count = render_slice.get_trailing_lines_count();
235 let lines_count = lines.len() - leading_line_count - trailing_line_count;
236 let show_scroll_bar = render_slice.should_show_scroll_bar();
237 let scroll_indicator_index = render_slice.get_scroll_index();
238 let view_height = window_height - leading_line_count - trailing_line_count;
239
240 let leading_lines_iter = lines.iter().take(leading_line_count);
241 let lines_iter = lines.iter().skip(leading_line_count).take(lines_count);
242 let trailing_lines_iter = lines.iter().skip(leading_line_count + lines_count);
243
244 for line in leading_lines_iter {
245 self.display.ensure_at_line_start()?;
246 self.draw_view_line(line)?;
247 self.display.next_line()?;
248 }
249
250 for (index, line) in lines_iter.enumerate() {
251 self.display.ensure_at_line_start()?;
252 self.draw_view_line(line)?;
253 if show_scroll_bar {
254 self.display.move_from_end_of_line(1)?;
255 self.display.color(DisplayColor::Normal, true)?;
256 self.display.draw_str(
257 if scroll_indicator_index == index {
258 SCROLLBAR_INDICATOR_CHARACTER
259 }
260 else {
261 " "
262 },
263 )?;
264 }
265 self.display.color(DisplayColor::Normal, false)?;
266 self.display.set_style(false, false, false)?;
267 self.display.next_line()?;
268 }
269
270 if view_height > lines_count {
271 self.display.color(DisplayColor::Normal, false)?;
272 self.display.set_style(false, false, false)?;
273 let draw_height = view_height - lines_count - if render_slice.show_title() { 1 } else { 0 };
274 self.display.ensure_at_line_start()?;
275 for _x in 0..draw_height {
276 self.display.draw_str(self.character_vertical_spacing.as_str())?;
277 self.display.next_line()?;
278 }
279 }
280
281 for line in trailing_lines_iter {
282 self.display.ensure_at_line_start()?;
283 self.draw_view_line(line)?;
284 self.display.next_line()?;
285 }
286 self.display.refresh()?;
287 Ok(())
288 }
289
290 fn draw_view_line(&mut self, line: &ViewLine) -> Result<()> {
291 for segment in line.get_segments() {
292 self.display.color(segment.get_color(), line.get_selected())?;
293 self.display
294 .set_style(segment.is_dimmed(), segment.is_underlined(), segment.is_reversed())?;
295 self.display.draw_str(segment.get_content())?;
296 }
297
298 self.display.color(DisplayColor::Normal, false)?;
300 self.display.set_style(false, false, false)?;
301 Ok(())
302 }
303
304 fn draw_title(&mut self, show_help: bool) -> Result<()> {
305 self.display.color(DisplayColor::Normal, false)?;
306 self.display.set_style(false, true, false)?;
307 let window_width = self.display.get_window_size().width();
308
309 let title_help_indicator_total_length = TITLE_HELP_INDICATOR_LABEL.len() + self.help_indicator_key.len();
310
311 if window_width >= TITLE.len() {
312 self.display.draw_str(TITLE)?;
313 if window_width > TITLE.len() + title_help_indicator_total_length {
315 if (window_width - TITLE.len() - title_help_indicator_total_length) > 0 {
316 let padding = " ".repeat(window_width - TITLE.len() - title_help_indicator_total_length);
317 self.display.draw_str(padding.as_str())?;
318 }
319 if show_help {
320 self.display
321 .draw_str(format!("{TITLE_HELP_INDICATOR_LABEL}{}", self.help_indicator_key).as_str())?;
322 }
323 else {
324 let padding = " ".repeat(title_help_indicator_total_length);
325 self.display.draw_str(padding.as_str())?;
326 }
327 }
328 else if (window_width - TITLE.len()) > 0 {
329 let padding = " ".repeat(window_width - TITLE.len());
330 self.display.draw_str(padding.as_str())?;
331 }
332 }
333 else {
334 self.display.draw_str(TITLE_SHORT)?;
335 if (window_width - TITLE_SHORT.len()) > 0 {
336 let padding = " ".repeat(window_width - TITLE_SHORT.len());
337 self.display.draw_str(padding.as_str())?;
338 }
339 }
340
341 self.display.color(DisplayColor::Normal, false)?;
343 self.display.set_style(false, false, false)?;
344 Ok(())
345 }
346}