diff options
author | Tim Oram <dev@mitmaro.ca> | 2024-02-09 13:53:36 -0330 |
---|---|---|
committer | Tim Oram <dev@mitmaro.ca> | 2024-02-15 20:27:06 -0330 |
commit | caf3d1169f70c2fff557b740f3ca577c0ec23fff (patch) | |
tree | d4af0add03383a0f020e6430ba4994ce12e6a407 /src/display | |
parent | bae935621588026c521849e986dd0f9a4feb37e6 (diff) |
Move display crate into core
Diffstat (limited to 'src/display')
-rw-r--r-- | src/display/Cargo.toml | 27 | ||||
-rw-r--r-- | src/display/Makefile.toml | 2 | ||||
-rw-r--r-- | src/display/README.md | 3 | ||||
-rw-r--r-- | src/display/build.rs | 11 | ||||
-rw-r--r-- | src/display/src/color_mode.rs | 86 | ||||
-rw-r--r-- | src/display/src/crossterm.rs | 165 | ||||
-rw-r--r-- | src/display/src/display_color.rs | 43 | ||||
-rw-r--r-- | src/display/src/error.rs | 45 | ||||
-rw-r--r-- | src/display/src/lib.rs | 751 | ||||
-rw-r--r-- | src/display/src/size.rs | 29 | ||||
-rw-r--r-- | src/display/src/testutil.rs | 22 | ||||
-rw-r--r-- | src/display/src/testutil/mockable_tui.rs | 150 | ||||
-rw-r--r-- | src/display/src/testutil/mockcrossterm.rs | 199 | ||||
-rw-r--r-- | src/display/src/testutil/state.rs | 11 | ||||
-rw-r--r-- | src/display/src/tui.rs | 109 | ||||
-rw-r--r-- | src/display/src/utils.rs | 514 |
16 files changed, 0 insertions, 2167 deletions
diff --git a/src/display/Cargo.toml b/src/display/Cargo.toml deleted file mode 100644 index 4de4d2b..0000000 --- a/src/display/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "girt-display" -version = "2.3.0" -authors = ["Tim Oram <dev@mitmaro.ca>"] -license = "GPL-3.0-or-later" -description = "Display modules for git-interactive-rebase-tool" -homepage = "https://gitrebasetool.mitmaro.ca/" -repository = "https://github.com/MitMaro/git-interactive-rebase-tool" -edition = "2021" -keywords = ["git", "cli"] -categories = ["command-line-interface", "command-line-utilities"] -readme = "README.md" - -[lib] -name = "display" - -[dependencies] -crossterm = "0.26.1" -thiserror = "1.0.44" -girt-config = {version = "2.3.0", path = "../config"} - -[dev-dependencies] -rstest = "0.18.1" -serial_test = "2.0.0" - -[build-dependencies] -rustc_version = "0.4.0" diff --git a/src/display/Makefile.toml b/src/display/Makefile.toml deleted file mode 100644 index c19ab5c..0000000 --- a/src/display/Makefile.toml +++ /dev/null @@ -1,2 +0,0 @@ -extend = "../../Makefile.toml" - diff --git a/src/display/README.md b/src/display/README.md deleted file mode 100644 index 40b4f2d..0000000 --- a/src/display/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# GIRT Display Crate - -This crate is part of the larger [Git Interactive Rebase Tool](../../README.md). The version is pinned to the root project, and this crate does not follow SemVer. diff --git a/src/display/build.rs b/src/display/build.rs deleted file mode 100644 index 4cc52ff..0000000 --- a/src/display/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -use rustc_version::{version_meta, Channel}; - -fn main() { - // allow unknown lints in nightly builds - if let Ok(meta) = version_meta() { - if meta.channel == Channel::Nightly { - println!("cargo:rustc-cfg=allow_unknown_lints"); - println!("cargo:rustc-cfg=include_nightly_lints"); - } - } -} diff --git a/src/display/src/color_mode.rs b/src/display/src/color_mode.rs deleted file mode 100644 index 1476521..0000000 --- a/src/display/src/color_mode.rs +++ /dev/null @@ -1,86 +0,0 @@ -/// Represents the color mode of a terminal interface. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[allow(clippy::exhaustive_enums)] -pub enum ColorMode { - /// Supports 2 colors. - TwoTone, - /// Supports 8 colors. - ThreeBit, - /// Supports 16 colors. - FourBit, - /// Supports 256 colors. - EightBit, - /// Supports 24 bits of color. - TrueColor, -} - -impl ColorMode { - /// Supports 4 bit or more of color. - #[inline] - #[must_use] - pub fn has_minimum_four_bit_color(self) -> bool { - self == Self::FourBit || self == Self::EightBit || self == Self::TrueColor - } - - /// Has true color support. - #[inline] - #[must_use] - pub fn has_true_color(self) -> bool { - self == Self::TrueColor - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn color_mode_has_minimum_four_bit_color_two_tone() { - assert!(!ColorMode::TwoTone.has_minimum_four_bit_color()); - } - - #[test] - fn color_mode_has_minimum_four_bit_color_three_bit() { - assert!(!ColorMode::ThreeBit.has_minimum_four_bit_color()); - } - - #[test] - fn color_mode_has_minimum_four_bit_color_four_bit() { - assert!(ColorMode::FourBit.has_minimum_four_bit_color()); - } - - #[test] - fn color_mode_has_minimum_four_bit_color_eight_bit() { - assert!(ColorMode::EightBit.has_minimum_four_bit_color()); - } - - #[test] - fn color_mode_has_minimum_four_bit_color_true_color() { - assert!(ColorMode::TrueColor.has_minimum_four_bit_color()); - } - - #[test] - fn color_mode_has_true_color_two_tone() { - assert!(!ColorMode::TwoTone.has_true_color()); - } - - #[test] - fn color_mode_has_true_color_three_bit() { - assert!(!ColorMode::ThreeBit.has_true_color()); - } - - #[test] - fn color_mode_has_true_color_four_bit() { - assert!(!ColorMode::FourBit.has_true_color()); - } - - #[test] - fn color_mode_has_true_color_eight_bit() { - assert!(!ColorMode::EightBit.has_true_color()); - } - - #[test] - fn color_mode_has_true_color_true_color() { - assert!(ColorMode::TrueColor.has_true_color()); - } -} diff --git a/src/display/src/crossterm.rs b/src/display/src/crossterm.rs deleted file mode 100644 index e96a4e3..0000000 --- a/src/display/src/crossterm.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::io::{stdout, BufWriter, Stdout, Write}; - -use crossterm::{ - cursor::{Hide, MoveTo, MoveToColumn, MoveToNextLine, Show}, - event::{ - DisableMouseCapture, - EnableMouseCapture, - KeyboardEnhancementFlags, - PopKeyboardEnhancementFlags, - PushKeyboardEnhancementFlags, - }, - style::{available_color_count, Attribute, Colors, Print, ResetColor, SetAttribute, SetColors}, - terminal::{ - disable_raw_mode, - enable_raw_mode, - size, - Clear, - ClearType, - DisableLineWrap, - EnableLineWrap, - EnterAlternateScreen, - LeaveAlternateScreen, - }, - Command, - QueueableCommand, -}; - -use crate::{color_mode::ColorMode, error::DisplayError, size::Size, tui::Tui, utils::detect_color_mode}; - -/// A thin wrapper over the [Crossterm library](https://github.com/crossterm-rs/crossterm). -#[derive(Debug)] -pub struct CrossTerm { - color_mode: ColorMode, - window: BufWriter<Stdout>, -} - -impl Tui for CrossTerm { - #[inline] - fn get_color_mode(&self) -> ColorMode { - self.color_mode - } - - #[inline] - fn reset(&mut self) -> Result<(), DisplayError> { - self.queue_command(ResetColor)?; - self.queue_command(SetAttribute(Attribute::Reset))?; - self.queue_command(Clear(ClearType::All))?; - self.queue_command(MoveTo(0, 0)) - } - - #[inline] - fn flush(&mut self) -> Result<(), DisplayError> { - self.window.flush().map_err(DisplayError::Unexpected) - } - - #[inline] - fn print(&mut self, s: &str) -> Result<(), DisplayError> { - self.queue_command(Print(s)) - } - - #[inline] - fn set_color(&mut self, colors: Colors) -> Result<(), DisplayError> { - self.queue_command(SetColors(colors)) - } - - #[inline] - fn set_dim(&mut self, dim: bool) -> Result<(), DisplayError> { - self.queue_command(SetAttribute( - if dim { - Attribute::Dim - } - else { - Attribute::NormalIntensity - }, - )) - } - - #[inline] - fn set_underline(&mut self, underline: bool) -> Result<(), DisplayError> { - self.queue_command(SetAttribute( - if underline { - Attribute::Underlined - } - else { - Attribute::NoUnderline - }, - )) - } - - #[inline] - fn set_reverse(&mut self, reverse: bool) -> Result<(), DisplayError> { - self.queue_command(SetAttribute( - if reverse { - Attribute::Reverse - } - else { - Attribute::NoReverse - }, - )) - } - - #[inline] - fn get_size(&self) -> Size { - size().map_or_else( - |_| Size::new(0, 0), - |(width, height)| Size::new(usize::from(width), usize::from(height)), - ) - } - - #[inline] - fn move_to_column(&mut self, x: u16) -> Result<(), DisplayError> { - self.queue_command(MoveToColumn(x)) - } - - #[inline] - fn move_next_line(&mut self) -> Result<(), DisplayError> { - self.queue_command(MoveToNextLine(1)) - } - - #[inline] - fn start(&mut self) -> Result<(), DisplayError> { - self.queue_command(EnterAlternateScreen)?; - self.queue_command(DisableLineWrap)?; - self.queue_command(Hide)?; - self.queue_command(EnableMouseCapture)?; - // this will fail on terminals without support, so ignore any errors - let _command_result = self.queue_command(PushKeyboardEnhancementFlags( - KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES - | KeyboardEnhancementFlags::REPORT_EVENT_TYPES - | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS - | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES, - )); - enable_raw_mode().map_err(DisplayError::Unexpected)?; - self.flush() - } - - #[inline] - fn end(&mut self) -> Result<(), DisplayError> { - // this will fail on terminals without support, so ignore any errors - let _command_result = self.queue_command(PopKeyboardEnhancementFlags); - self.queue_command(DisableMouseCapture)?; - self.queue_command(Show)?; - self.queue_command(EnableLineWrap)?; - self.queue_command(LeaveAlternateScreen)?; - disable_raw_mode().map_err(DisplayError::Unexpected)?; - self.flush() - } -} - -impl CrossTerm { - /// Create a new instance. - #[inline] - #[must_use] - pub fn new() -> Self { - Self { - window: BufWriter::new(stdout()), - color_mode: detect_color_mode(available_color_count()), - } - } - - fn queue_command(&mut self, command: impl Command) -> Result<(), DisplayError> { - let _result = self.window.queue(command).map_err(DisplayError::Unexpected)?; - Ok(()) - } -} diff --git a/src/display/src/display_color.rs b/src/display/src/display_color.rs deleted file mode 100644 index 1207f6b..0000000 --- a/src/display/src/display_color.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// An abstraction of colors to display. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[allow(clippy::exhaustive_enums)] -pub enum DisplayColor { - /// The color for the break action. - ActionBreak, - /// The color for the drop action. - ActionDrop, - /// The color for the edit action. - ActionEdit, - /// The color for the exec action. - ActionExec, - /// The color for the fixup action. - ActionFixup, - /// The color for the pick action. - ActionPick, - /// The color for the reword action. - ActionReword, - /// The color for the squash action. - ActionSquash, - /// The color for the label action. - ActionLabel, - /// The color for the reset action. - ActionReset, - /// The color for the merge action. - ActionMerge, - /// The color for the merge action. - ActionUpdateRef, - /// The color for added lines in a diff. - DiffAddColor, - /// The color for changed lines in a diff. - DiffChangeColor, - /// The color for removed lines in a diff. - DiffRemoveColor, - /// The color for context lines in a diff. - DiffContextColor, - /// The color for whitespace characters in a diff. - DiffWhitespaceColor, - /// The color for indicator text. - IndicatorColor, - /// The color for the standard text. - Normal, -} diff --git a/src/display/src/error.rs b/src/display/src/error.rs deleted file mode 100644 index 7f8e952..0000000 --- a/src/display/src/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::io; - -use thiserror::Error; - -/// A display error. -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum DisplayError { - /// An unexpected error occurred. - #[error("Unexpected error")] - Unexpected(io::Error), -} - -impl PartialEq for DisplayError { - #[inline] - #[allow(clippy::pattern_type_mismatch)] - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Unexpected(self_io_error), Self::Unexpected(other_io_error)) => { - self_io_error.kind() == other_io_error.kind() - }, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn partial_eq_io_error_same() { - assert_eq!( - DisplayError::Unexpected(io::Error::from(io::ErrorKind::Other)), - DisplayError::Unexpected(io::Error::from(io::ErrorKind::Other)) - ); - } - - #[test] - fn partial_eq_io_error_different() { - assert_ne!( - DisplayError::Unexpected(io::Error::from(io::ErrorKind::Other)), - DisplayError::Unexpected(io::Error::from(io::ErrorKind::NotFound)) - ); - } -} diff --git a/src/display/src/lib.rs b/src/display/src/lib.rs deleted file mode 100644 index a74de2e..0000000 --- a/src/display/src/lib.rs +++ /dev/null @@ -1,751 +0,0 @@ -// LINT-REPLACE-START -// This section is autogenerated, do not modify directly -// nightly sometimes removes/renames lints -#![cfg_attr(allow_unknown_lints, allow(unknown_lints))] -#![cfg_attr(allow_unknown_lints, allow(renamed_and_removed_lints))] -// enable all rustc's built-in lints -#![deny( - future_incompatible, - nonstandard_style, - rust_2018_compatibility, - rust_2018_idioms, - rust_2021_compatibility, - unused, - warnings -)] -// rustc's additional allowed by default lints -#![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 -)] -// enable all of Clippy's lints -#![deny(clippy::all, clippy::cargo, clippy::pedantic, clippy::restriction)] -#![allow( - clippy::absolute_paths, - 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::min_ident_chars, - clippy::missing_docs_in_private_items, - clippy::missing_trait_methods, - clippy::module_name_repetitions, - clippy::needless_raw_string_hashes, - clippy::needless_raw_strings, - clippy::new_without_default, - clippy::non_ascii_literal, - clippy::option_if_let_else, - clippy::pattern_type_mismatch, - clippy::pub_use, - clippy::pub_with_shorthand, - clippy::question_mark_used, - clippy::redundant_closure_call, - clippy::redundant_pub_crate, - clippy::ref_patterns, - clippy::self_named_module_files, - clippy::single_call_fn, - 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 -)] -// allow some things in tests -#![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 - ) -)] -// allowable upcoming nightly lints -#![cfg_attr(include_nightly_lints, allow(clippy::absolute_paths, clippy::arc_with_non_send_sync))] -// LINT-REPLACE-END - -//! Git Interactive Rebase Tool - Display Module -//! -//! # Description -//! This module is used to handle working with the terminal display. -//! -//! ``` -//! use config::Theme; -//! use display::{CrossTerm, Display, DisplayColor}; -//! let theme = Theme::new(); -//! let tui = CrossTerm::new(); -//! let mut display = Display::new(tui, &theme); -//! -//! display.start(); -//! display.clear(); -//! display.draw_str("Hello world!"); -//! display.color(DisplayColor::IndicatorColor, false); -//! display.set_style(false, true, false); -//! display.draw_str("Hello colorful, underlined world!"); -//! display.refresh(); -//! display.end(); -//! ``` -//! -//! ## Test Utilities -//! To facilitate testing the usages of this crate, a set of testing utilities are provided. Since -//! these utilities are not tested, and often are optimized for developer experience than -//! performance should only be used in test code. - -mod color_mode; -#[cfg(not(tarpaulin_include))] -mod crossterm; -mod display_color; -mod error; -mod size; -#[cfg(not(tarpaulin_include))] -pub mod testutil; -mod tui; -mod utils; - -use ::crossterm::style::{Color, Colors}; -use config::Theme; - -use self::utils::register_selectable_color_pairs; -pub use self::{ - color_mode::ColorMode, - crossterm::CrossTerm, - display_color::DisplayColor, - error::DisplayError, - size::Size, - tui::Tui, -}; - -/// A high level interface to the terminal display. -#[derive(Debug)] -pub struct Display<T: Tui> { - action_break: (Colors, Colors), - action_drop: (Colors, Colors), - action_edit: (Colors, Colors), - action_exec: (Colors, Colors), - action_fixup: (Colors, Colors), - action_label: (Colors, Colors), - action_merge: (Colors, Colors), - action_pick: (Colors, Colors), - action_reset: (Colors, Colors), - action_reword: (Colors, Colors), - action_squash: (Colors, Colors), - action_update_ref: (Colors, Colors), - tui: T, - diff_add: (Colors, Colors), - diff_change: (Colors, Colors), - diff_context: (Colors, Colors), - diff_remove: (Colors, Colors), - diff_whitespace: (Colors, Colors), - indicator: (Colors, Colors), - normal: (Colors, Colors), -} - -impl<T: Tui> Display<T> { - /// Create a new display instance. - #[inline] - pub fn new(tui: T, theme: &Theme) -> Self { - let color_mode = tui.get_color_mode(); - let normal = register_selectable_color_pairs( - color_mode, - theme.color_foreground, - theme.color_background, - theme.color_selected_background, - ); - let indicator = register_selectable_color_pairs( - color_mode, - theme.color_indicator, - theme.color_background, - theme.color_selected_background, - ); - let action_break = register_selectable_color_pairs( - color_mode, - theme.color_action_break, - theme.color_background, - theme.color_selected_background, - ); - let action_drop = register_selectable_color_pairs( - color_mode, - theme.color_action_drop, - theme.color_background, - theme.color_selected_background, - ); - let action_edit = register_selectable_color_pairs( - color_mode, - theme.color_action_edit, - theme.color_background, - theme.color_selected_background, - ); - let action_exec = register_selectable_color_pairs( - color_mode, - theme.color_action_exec, - theme.color_background, - theme.color_selected_background, - ); - let action_fixup = register_selectable_color_pairs( - color_mode, - theme.color_action_fixup, - theme.color_background, - theme.color_selected_background, - ); - let action_pick = register_selectable_color_pairs( - color_mode, - theme.color_action_pick, - theme.color_background, - theme.color_selected_background, - ); - let action_reword = register_selectable_color_pairs( - color_mode, - theme.color_action_reword, - theme.color_background, - theme.color_selected_background, - ); - let action_squash = register_selectable_color_pairs( - color_mode, - theme.color_action_squash, - theme.color_background, - theme.color_selected_background, - ); - let action_label = register_selectable_color_pairs( - color_mode, - theme.color_action_label, - theme.color_background, - theme.color_selected_background, - ); - let action_reset = register_selectable_color_pairs( - color_mode, - theme.color_action_reset, - theme.color_background, - theme.color_selected_background, - ); - let action_merge = register_selectable_color_pairs( - color_mode, - theme.color_action_merge, - theme.color_background, - theme.color_selected_background, - ); - let action_update_ref = register_selectable_color_pairs( - color_mode, - theme.color_action_update_ref, - theme.color_background, - theme.color_selected_background, - ); - let diff_add = register_selectable_color_pairs( - color_mode, - theme.color_diff_add, - theme.color_background, - theme.color_selected_background, - ); - let diff_change = register_selectable_color_pairs( - color_mode, - theme.color_diff_change, - theme.color_background, - theme.color_selected_background, - ); - let diff_remove = register_selectable_color_pairs( - color_mode, - theme.color_diff_remove, - theme.color_background, - theme.color_selected_background, - ); - let diff_context = register_selectable_color_pairs( - color_mode, - theme.color_diff_context, - theme.color_background, - theme.color_selected_background, - ); - let diff_whitespace = register_selectable_color_pairs( - color_mode, - theme.color_diff_whitespace, - theme.color_background, - theme.color_selected_background, - ); - - Self { - action_break, - action_drop, - action_edit, - action_exec, - action_fixup, - action_label, - action_merge, - action_pick, - action_reset, - action_reword, - action_squash, - action_update_ref, - tui, - diff_add, - diff_change, - diff_context, - diff_remove, - diff_whitespace, - indicator, - normal, - } - } - - /// Draws a string of text to the terminal interface. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn draw_str(&mut self, s: &str) -> Result<(), DisplayError> { - self.tui.print(s) - } - - /// Clear the terminal interface and reset any style and color attributes. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn clear(&mut self) -> Result<(), DisplayError> { - self.color(DisplayColor::Normal, false)?; - self.set_style(false, false, false)?; - self.tui.reset() - } - - /// Force a refresh of the terminal interface. This normally should be called after after all - /// text has been drawn to the terminal interface. This is considered a slow operation, so - /// should be called only as needed. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn refresh(&mut self) -> Result<(), DisplayError> { - self.tui.flush() - } - - /// Set the color of text drawn to the terminal interface. This will only change text drawn to - /// the terminal after this function call. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn color(&mut self, color: DisplayColor, selected: bool) -> Result<(), DisplayError> { - self.tui.set_color( - if selected { - match color { - DisplayColor::ActionBreak => self.action_break.1, - DisplayColor::ActionDrop => self.action_drop.1, - DisplayColor::ActionEdit => self.action_edit.1, - DisplayColor::ActionExec => self.action_exec.1, - DisplayColor::ActionFixup => self.action_fixup.1, - DisplayColor::ActionPick => self.action_pick.1, - DisplayColor::ActionReword => self.action_reword.1, - DisplayColor::ActionSquash => self.action_squash.1, - DisplayColor::ActionLabel => self.action_label.1, - DisplayColor::ActionReset => self.action_reset.1, - DisplayColor::ActionMerge => self.action_merge.1, - DisplayColor::ActionUpdateRef => self.action_update_ref.1, - DisplayColor::Normal => self.normal.1, - DisplayColor::IndicatorColor => self.indicator.1, - DisplayColor::DiffAddColor => self.diff_add.1, - DisplayColor::DiffRemoveColor => self.diff_remove.1, - DisplayColor::DiffChangeColor => self.diff_change.1, - DisplayColor::DiffContextColor => self.diff_context.1, - DisplayColor::DiffWhitespaceColor => self.diff_whitespace.1, - } - } - else { - match color { - DisplayColor::ActionBreak => self.action_break.0, - DisplayColor::ActionDrop => self.action_drop.0, - DisplayColor::ActionEdit => self.action_edit.0, - DisplayColor::ActionExec => self.action_exec.0, - DisplayColor::ActionFixup => self.action_fixup.0, - DisplayColor::ActionPick => self.action_pick.0, - DisplayColor::ActionReword => self.action_reword.0, - DisplayColor::ActionSquash => self.action_squash.0, - DisplayColor::ActionLabel => self.action_label.0, - DisplayColor::ActionReset => self.action_reset.0, - DisplayColor::ActionMerge => self.action_merge.0, - DisplayColor::ActionUpdateRef => self.action_update_ref.0, - DisplayColor::Normal => self.normal.0, - DisplayColor::IndicatorColor => self.indicator.0, - DisplayColor::DiffAddColor => self.diff_add.0, - DisplayColor::DiffRemoveColor => self.diff_remove.0, - DisplayColor::DiffChangeColor => self.diff_change.0, - DisplayColor::DiffContextColor => self.diff_context.0, - DisplayColor::DiffWhitespaceColor => self.diff_whitespace.0, - } - }, - ) - } - - /// Set the style attributes of text drawn to the terminal interface. This will only change text - /// drawn to the terminal after this function call. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn set_style(&mut self, dim: bool, underline: bool, reverse: bool) -> Result<(), DisplayError> { - self.set_dim(dim)?; - self.set_underline(underline)?; - self.set_reverse(reverse) - } - - /// Get the width and height of the terminal interface. This can be a slow operation, so should - /// not be called unless absolutely needed. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn get_window_size(&self) -> Size { - self.tui.get_size() - } - - /// Reset the cursor position to the start of the line. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn ensure_at_line_start(&mut self) -> Result<(), DisplayError> { - self.tui.move_to_column(0) - } - - /// Move the cursor position `right` characters from the end of the line. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn move_from_end_of_line(&mut self, right: u16) -> Result<(), DisplayError> { - let width = self.get_window_size().width().try_into().unwrap_or(u16::MAX); - self.tui.move_to_column(width - right) - } - - /// Move the cursor to the next line. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn next_line(&mut self) -> Result<(), DisplayError> { - self.tui.move_next_line() - } - - /// Start the terminal interface interactions. This should be called before any terminal - /// interactions are performed. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn start(&mut self) -> Result<(), DisplayError> { - self.tui.start()?; - self.tui.flush() - } - - /// End the terminal interface interactions. This should be called after all terminal - /// interactions are complete. This resets the terminal interface to the default state, and - /// should be called on program exit. - /// - /// # Errors - /// Will error if the underlying terminal interface is in an error state. - #[inline] - pub fn end(&mut self) -> Result<(), DisplayError> { - self.tui.end()?; - self.tui.flush() - } - - fn set_dim(&mut self, on: bool) -> Result<(), DisplayError> { - self.tui.set_dim(on) - } - - fn set_underline(&mut self, on: bool) -> Result<(), DisplayError> { - self.tui.set_underline(on) - } - - fn set_reverse(&mut self, on: bool) -> Result<(), DisplayError> { - self.tui.set_reverse(on) - } -} - -#[cfg(test)] -mod tests { - use ::crossterm::style::Color as CrosstermColor; - use rstest::rstest; - - use super::*; - use crate::testutil::{CrossTerm, State}; - - #[test] - fn draw_str() { - let mut display = Display::new(CrossTerm::new(), &Theme::new()); - display.draw_str("Test String").unwrap(); - assert_eq!(display.tui.get_output(), &["Test String"]); - } - - #[test] - fn clear() { - let mut display = Display::new(CrossTerm::new(), &Theme::new()); - display.draw_str("Test String").unwrap(); - display.set_dim(true).unwrap(); - display.set_reverse(true).unwrap(); - display.set_underline(true).unwrap(); - display.clear().unwrap(); - assert!(display.tui.get_output().is_empty()); - assert!(!display.tui.is_dimmed()); - assert!(!display.tui.is_reverse()); - assert!(!display.tui.is_underline()); - } - - #[test] - fn refresh() { - let mut display = Display::new(CrossTerm::new(), &Theme::new()); - display.refresh().unwrap(); - assert!(!display.tui.is_dirty()); - } - - #[rstest] - #[case::action_break(DisplayColor::ActionBreak, false, CrosstermColor::White, CrosstermColor::Reset)] - #[case::action_break_selected( - DisplayColor::ActionBreak, - true, - CrosstermColor::White, - CrosstermColor::AnsiValue(237) |