#![cfg_attr(allow_unknown_lints, allow(unknown_lints))]
#![cfg_attr(allow_unknown_lints, allow(renamed_and_removed_lints))]
#![deny(
future_incompatible,
nonstandard_style,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
unused,
warnings
)]
#![deny(
absolute_paths_not_starting_with_crate,
deprecated_in_future,
elided_lifetimes_in_paths,
explicit_outlives_requirements,
ffi_unwind_calls,
keyword_idents,
let_underscore_drop,
macro_use_extern_crate,
meta_variable_misuse,
missing_abi,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
non_ascii_idents,
noop_method_call,
pointer_structural_match,
rust_2021_incompatible_closure_captures,
rust_2021_incompatible_or_patterns,
rust_2021_prefixes_incompatible_syntax,
rust_2021_prelude_collisions,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_code,
unsafe_op_in_unsafe_fn,
unused_crate_dependencies,
unused_extern_crates,
unused_import_braces,
unused_lifetimes,
unused_macro_rules,
unused_qualifications,
unused_results,
unused_tuple_struct_fields,
variant_size_differences
)]
#![deny(clippy::all, clippy::cargo, clippy::pedantic, clippy::restriction)]
#![cfg_attr(include_nightly_lints, deny(clippy::nursery))]
#![allow(
clippy::arithmetic_side_effects,
clippy::arithmetic_side_effects,
clippy::blanket_clippy_restriction_lints,
clippy::bool_to_int_with_if,
clippy::default_numeric_fallback,
clippy::else_if_without_else,
clippy::expect_used,
clippy::float_arithmetic,
clippy::implicit_return,
clippy::indexing_slicing,
clippy::map_err_ignore,
clippy::missing_docs_in_private_items,
clippy::missing_trait_methods,
clippy::mod_module_files,
clippy::module_name_repetitions,
clippy::new_without_default,
clippy::non_ascii_literal,
clippy::option_if_let_else,
clippy::pub_use,
clippy::question_mark_used,
clippy::redundant_pub_crate,
clippy::ref_patterns,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
clippy::tabs_in_doc_comments,
clippy::tests_outside_test_module,
clippy::too_many_lines,
clippy::unwrap_used
)]
#![deny(
rustdoc::bare_urls,
rustdoc::broken_intra_doc_links,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_html_tags,
rustdoc::missing_crate_level_docs,
rustdoc::private_doc_tests,
rustdoc::private_intra_doc_links
)]
#![cfg_attr(
test,
allow(
let_underscore_drop,
clippy::cognitive_complexity,
clippy::let_underscore_must_use,
clippy::let_underscore_untyped,
clippy::needless_pass_by_value,
clippy::panic,
clippy::shadow_reuse,
clippy::shadow_unrelated,
clippy::undocumented_unsafe_blocks,
clippy::unimplemented,
clippy::unreachable
)
)]
#![cfg_attr(
include_nightly_lints,
allow(
clippy::arc_with_non_send_sync,
clippy::min_ident_chars,
clippy::needless_raw_strings,
clippy::pub_with_shorthand,
clippy::redundant_closure_call,
clippy::single_call_fn
)
)]
#![allow(clippy::as_conversions, clippy::integer_division)]
mod line_segment;
mod render_context;
mod render_slice;
mod scroll_position;
#[cfg(all(feature = "testutils", not(tarpaulin_include)))]
pub mod testutil;
mod thread;
mod view_data;
mod view_data_updater;
mod view_line;
#[cfg(test)]
mod tests;
use anyhow::{Error, Result};
use display::{Display, DisplayColor, Tui};
pub use self::{
line_segment::LineSegment,
render_context::RenderContext,
thread::{State, Thread, MAIN_THREAD_NAME, REFRESH_THREAD_NAME},
view_data::ViewData,
view_data_updater::ViewDataUpdater,
view_line::ViewLine,
};
use self::{render_slice::RenderSlice, thread::ViewAction};
const TITLE: &str = "Git Interactive Rebase Tool";
const TITLE_SHORT: &str = "Git Rebase";
const TITLE_HELP_INDICATOR_LABEL: &str = "Help: ";
const SCROLLBAR_INDICATOR_CHARACTER: &str = "\u{2588}"; #[derive(Debug)]
pub struct View<C: Tui> {
character_vertical_spacing: String,
display: Display<C>,
help_indicator_key: String,
last_render_version: u32,
}
impl<C: Tui> View<C> {
#[inline]
pub fn new(display: Display<C>, character_vertical_spacing: &str, help_indicator_key: &str) -> Self {
Self {
character_vertical_spacing: String::from(character_vertical_spacing),
display,
help_indicator_key: String::from(help_indicator_key),
last_render_version: u32::MAX,
}
}
#[inline]
pub(crate) fn start(&mut self) -> Result<()> {
self.display.start().map_err(Error::from)
}
#[inline]
pub(crate) fn end(&mut self) -> Result<()> {
self.display.end().map_err(Error::from)
}
#[inline]
pub fn render(&mut self, render_slice: &RenderSlice) -> Result<()> {
let current_render_version = render_slice.get_version();
if self.last_render_version == current_render_version {
return Ok(());
}
self.last_render_version = current_render_version;
let view_size = self.display.get_window_size();
let window_height = view_size.height();
self.display.clear()?;
self.display.ensure_at_line_start()?;
if render_slice.show_title() {
self.display.ensure_at_line_start()?;
self.draw_title(render_slice.show_help())?;
self.display.next_line()?;
}
let lines = render_slice.get_lines();
let leading_line_count = render_slice.get_leading_lines_count();
let trailing_line_count = render_slice.get_trailing_lines_count();
let lines_count = lines.len() - leading_line_count - trailing_line_count;
let show_scroll_bar = render_slice.should_show_scroll_bar();
let scroll_indicator_index = render_slice.get_scroll_index();
let view_height = window_height - leading_line_count - trailing_line_count;
let leading_lines_iter = lines.iter().take(leading_line_count);
let lines_iter = lines.iter().skip(leading_line_count).take(lines_count);
let trailing_lines_iter = lines.iter().skip(leading_line_count + lines_count);
for line in leading_lines_iter {
self.display.ensure_at_line_start()?;
self.draw_view_line(line)?;
self.display.next_line()?;
}
for (index, line) in lines_iter.enumerate() {
self.display.ensure_at_line_start()?;
self.draw_view_line(line)?;
if show_scroll_bar {
self.display.move_from_end_of_line(1)?;
self.display.color(DisplayColor::Normal, true)?;
self.display.draw_str(
if scroll_indicator_index == index {
SCROLLBAR_INDICATOR_CHARACTER
}
else {
" "
},
)?;
}
self.display.color(DisplayColor::Normal, false)?;
self.display.set_style(false, false, false)?;
self.display.next_line()?;
}
if view_height > lines_count {
self.display.color(DisplayColor::Normal, false)?;
self.display.set_style(false, false, false)?;
let draw_height = view_height - lines_count - if render_slice.show_title() { 1 } else { 0 };
self.display.ensure_at_line_start()?;
for _x in 0..draw_height {
self.display.draw_str(self.character_vertical_spacing.as_str())?;
self.display.next_line()?;
}
}
for line in trailing_lines_iter {
self.display.ensure_at_line_start()?;
self.draw_view_line(line)?;
self.display.next_line()?;
}
self.display.refresh()?;
Ok(())
}
fn draw_view_line(&mut self, line: &ViewLine) -> Result<()> {
for segment in line.get_segments() {
self.display.color(segment.get_color(), line.get_selected())?;
self.display
.set_style(segment.is_dimmed(), segment.is_underlined(), segment.is_reversed())?;
self.display.draw_str(segment.get_content())?;
}
self.display.color(DisplayColor::Normal, false)?;
self.display.set_style(false, false, false)?;
Ok(())
}
fn draw_title(&mut self, show_help: bool) -> Result<()> {
self.display.color(DisplayColor::Normal, false)?;
self.display.set_style(false, true, false)?;
let window_width = self.display.get_window_size().width();
let title_help_indicator_total_length = TITLE_HELP_INDICATOR_LABEL.len() + self.help_indicator_key.len();
if window_width >= TITLE.len() {
self.display.draw_str(TITLE)?;
if window_width > TITLE.len() + title_help_indicator_total_length {
if (window_width - TITLE.len() - title_help_indicator_total_length) > 0 {
let padding = " ".repeat(window_width - TITLE.len() - title_help_indicator_total_length);
self.display.draw_str(padding.as_str())?;
}
if show_help {
self.display
.draw_str(format!("{TITLE_HELP_INDICATOR_LABEL}{}", self.help_indicator_key).as_str())?;
}
else {
let padding = " ".repeat(title_help_indicator_total_length);
self.display.draw_str(padding.as_str())?;
}
}
else if (window_width - TITLE.len()) > 0 {
let padding = " ".repeat(window_width - TITLE.len());
self.display.draw_str(padding.as_str())?;
}
}
else {
self.display.draw_str(TITLE_SHORT)?;
if (window_width - TITLE_SHORT.len()) > 0 {
let padding = " ".repeat(window_width - TITLE_SHORT.len());
self.display.draw_str(padding.as_str())?;
}
}
self.display.color(DisplayColor::Normal, false)?;
self.display.set_style(false, false, false)?;
Ok(())
}
}