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)]
125mod color_mode;
155#[cfg(not(tarpaulin_include))]
156mod crossterm;
157mod display_color;
158mod error;
159mod size;
160#[cfg(not(tarpaulin_include))]
161pub mod testutil;
162mod tui;
163mod utils;
164
165use ::crossterm::style::{Color, Colors};
166use config::Theme;
167
168use self::utils::register_selectable_color_pairs;
169pub use self::{
170 color_mode::ColorMode,
171 crossterm::CrossTerm,
172 display_color::DisplayColor,
173 error::DisplayError,
174 size::Size,
175 tui::Tui,
176};
177
178#[derive(Debug)]
180pub struct Display<T: Tui> {
181 action_break: (Colors, Colors),
182 action_drop: (Colors, Colors),
183 action_edit: (Colors, Colors),
184 action_exec: (Colors, Colors),
185 action_fixup: (Colors, Colors),
186 action_label: (Colors, Colors),
187 action_merge: (Colors, Colors),
188 action_pick: (Colors, Colors),
189 action_reset: (Colors, Colors),
190 action_reword: (Colors, Colors),
191 action_squash: (Colors, Colors),
192 action_update_ref: (Colors, Colors),
193 tui: T,
194 diff_add: (Colors, Colors),
195 diff_change: (Colors, Colors),
196 diff_context: (Colors, Colors),
197 diff_remove: (Colors, Colors),
198 diff_whitespace: (Colors, Colors),
199 indicator: (Colors, Colors),
200 normal: (Colors, Colors),
201}
202
203impl<T: Tui> Display<T> {
204 #[inline]
206 pub fn new(tui: T, theme: &Theme) -> Self {
207 let color_mode = tui.get_color_mode();
208 let normal = register_selectable_color_pairs(
209 color_mode,
210 theme.color_foreground,
211 theme.color_background,
212 theme.color_selected_background,
213 );
214 let indicator = register_selectable_color_pairs(
215 color_mode,
216 theme.color_indicator,
217 theme.color_background,
218 theme.color_selected_background,
219 );
220 let action_break = register_selectable_color_pairs(
221 color_mode,
222 theme.color_action_break,
223 theme.color_background,
224 theme.color_selected_background,
225 );
226 let action_drop = register_selectable_color_pairs(
227 color_mode,
228 theme.color_action_drop,
229 theme.color_background,
230 theme.color_selected_background,
231 );
232 let action_edit = register_selectable_color_pairs(
233 color_mode,
234 theme.color_action_edit,
235 theme.color_background,
236 theme.color_selected_background,
237 );
238 let action_exec = register_selectable_color_pairs(
239 color_mode,
240 theme.color_action_exec,
241 theme.color_background,
242 theme.color_selected_background,
243 );
244 let action_fixup = register_selectable_color_pairs(
245 color_mode,
246 theme.color_action_fixup,
247 theme.color_background,
248 theme.color_selected_background,
249 );
250 let action_pick = register_selectable_color_pairs(
251 color_mode,
252 theme.color_action_pick,
253 theme.color_background,
254 theme.color_selected_background,
255 );
256 let action_reword = register_selectable_color_pairs(
257 color_mode,
258 theme.color_action_reword,
259 theme.color_background,
260 theme.color_selected_background,
261 );
262 let action_squash = register_selectable_color_pairs(
263 color_mode,
264 theme.color_action_squash,
265 theme.color_background,
266 theme.color_selected_background,
267 );
268 let action_label = register_selectable_color_pairs(
269 color_mode,
270 theme.color_action_label,
271 theme.color_background,
272 theme.color_selected_background,
273 );
274 let action_reset = register_selectable_color_pairs(
275 color_mode,
276 theme.color_action_reset,
277 theme.color_background,
278 theme.color_selected_background,
279 );
280 let action_merge = register_selectable_color_pairs(
281 color_mode,
282 theme.color_action_merge,
283 theme.color_background,
284 theme.color_selected_background,
285 );
286 let action_update_ref = register_selectable_color_pairs(
287 color_mode,
288 theme.color_action_update_ref,
289 theme.color_background,
290 theme.color_selected_background,
291 );
292 let diff_add = register_selectable_color_pairs(
293 color_mode,
294 theme.color_diff_add,
295 theme.color_background,
296 theme.color_selected_background,
297 );
298 let diff_change = register_selectable_color_pairs(
299 color_mode,
300 theme.color_diff_change,
301 theme.color_background,
302 theme.color_selected_background,
303 );
304 let diff_remove = register_selectable_color_pairs(
305 color_mode,
306 theme.color_diff_remove,
307 theme.color_background,
308 theme.color_selected_background,
309 );
310 let diff_context = register_selectable_color_pairs(
311 color_mode,
312 theme.color_diff_context,
313 theme.color_background,
314 theme.color_selected_background,
315 );
316 let diff_whitespace = register_selectable_color_pairs(
317 color_mode,
318 theme.color_diff_whitespace,
319 theme.color_background,
320 theme.color_selected_background,
321 );
322
323 Self {
324 action_break,
325 action_drop,
326 action_edit,
327 action_exec,
328 action_fixup,
329 action_label,
330 action_merge,
331 action_pick,
332 action_reset,
333 action_reword,
334 action_squash,
335 action_update_ref,
336 tui,
337 diff_add,
338 diff_change,
339 diff_context,
340 diff_remove,
341 diff_whitespace,
342 indicator,
343 normal,
344 }
345 }
346
347 #[inline]
352 pub fn draw_str(&mut self, s: &str) -> Result<(), DisplayError> {
353 self.tui.print(s)
354 }
355
356 #[inline]
361 pub fn clear(&mut self) -> Result<(), DisplayError> {
362 self.color(DisplayColor::Normal, false)?;
363 self.set_style(false, false, false)?;
364 self.tui.reset()
365 }
366
367 #[inline]
374 pub fn refresh(&mut self) -> Result<(), DisplayError> {
375 self.tui.flush()
376 }
377
378 #[inline]
384 pub fn color(&mut self, color: DisplayColor, selected: bool) -> Result<(), DisplayError> {
385 self.tui.set_color(
386 if selected {
387 match color {
388 DisplayColor::ActionBreak => self.action_break.1,
389 DisplayColor::ActionDrop => self.action_drop.1,
390 DisplayColor::ActionEdit => self.action_edit.1,
391 DisplayColor::ActionExec => self.action_exec.1,
392 DisplayColor::ActionFixup => self.action_fixup.1,
393 DisplayColor::ActionPick => self.action_pick.1,
394 DisplayColor::ActionReword => self.action_reword.1,
395 DisplayColor::ActionSquash => self.action_squash.1,
396 DisplayColor::ActionLabel => self.action_label.1,
397 DisplayColor::ActionReset => self.action_reset.1,
398 DisplayColor::ActionMerge => self.action_merge.1,
399 DisplayColor::ActionUpdateRef => self.action_update_ref.1,
400 DisplayColor::Normal => self.normal.1,
401 DisplayColor::IndicatorColor => self.indicator.1,
402 DisplayColor::DiffAddColor => self.diff_add.1,
403 DisplayColor::DiffRemoveColor => self.diff_remove.1,
404 DisplayColor::DiffChangeColor => self.diff_change.1,
405 DisplayColor::DiffContextColor => self.diff_context.1,
406 DisplayColor::DiffWhitespaceColor => self.diff_whitespace.1,
407 }
408 }
409 else {
410 match color {
411 DisplayColor::ActionBreak => self.action_break.0,
412 DisplayColor::ActionDrop => self.action_drop.0,
413 DisplayColor::ActionEdit => self.action_edit.0,
414 DisplayColor::ActionExec => self.action_exec.0,
415 DisplayColor::ActionFixup => self.action_fixup.0,
416 DisplayColor::ActionPick => self.action_pick.0,
417 DisplayColor::ActionReword => self.action_reword.0,
418 DisplayColor::ActionSquash => self.action_squash.0,
419 DisplayColor::ActionLabel => self.action_label.0,
420 DisplayColor::ActionReset => self.action_reset.0,
421 DisplayColor::ActionMerge => self.action_merge.0,
422 DisplayColor::ActionUpdateRef => self.action_update_ref.0,
423 DisplayColor::Normal => self.normal.0,
424 DisplayColor::IndicatorColor => self.indicator.0,
425 DisplayColor::DiffAddColor => self.diff_add.0,
426 DisplayColor::DiffRemoveColor => self.diff_remove.0,
427 DisplayColor::DiffChangeColor => self.diff_change.0,
428 DisplayColor::DiffContextColor => self.diff_context.0,
429 DisplayColor::DiffWhitespaceColor => self.diff_whitespace.0,
430 }
431 },
432 )
433 }
434
435 #[inline]
441 pub fn set_style(&mut self, dim: bool, underline: bool, reverse: bool) -> Result<(), DisplayError> {
442 self.set_dim(dim)?;
443 self.set_underline(underline)?;
444 self.set_reverse(reverse)
445 }
446
447 #[inline]
453 pub fn get_window_size(&self) -> Size {
454 self.tui.get_size()
455 }
456
457 #[inline]
462 pub fn ensure_at_line_start(&mut self) -> Result<(), DisplayError> {
463 self.tui.move_to_column(0)
464 }
465
466 #[inline]
471 pub fn move_from_end_of_line(&mut self, right: u16) -> Result<(), DisplayError> {
472 let width = self.get_window_size().width().try_into().unwrap_or(u16::MAX);
473 self.tui.move_to_column(width - right)
474 }
475
476 #[inline]
481 pub fn next_line(&mut self) -> Result<(), DisplayError> {
482 self.tui.move_next_line()
483 }
484
485 #[inline]
491 pub fn start(&mut self) -> Result<(), DisplayError> {
492 self.tui.start()?;
493 self.tui.flush()
494 }
495
496 #[inline]
503 pub fn end(&mut self) -> Result<(), DisplayError> {
504 self.tui.end()?;
505 self.tui.flush()
506 }
507
508 fn set_dim(&mut self, on: bool) -> Result<(), DisplayError> {
509 self.tui.set_dim(on)
510 }
511
512 fn set_underline(&mut self, on: bool) -> Result<(), DisplayError> {
513 self.tui.set_underline(on)
514 }
515
516 fn set_reverse(&mut self, on: bool) -> Result<(), DisplayError> {
517 self.tui.set_reverse(on)
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use ::crossterm::style::Color as CrosstermColor;
524 use rstest::rstest;
525
526 use super::{testutil::CrossTerm, *};
527 use crate::testutil::State;
528
529 #[test]
530 fn draw_str() {
531 let mut display = Display::new(CrossTerm::new(), &Theme::new());
532 display.draw_str("Test String").unwrap();
533 assert_eq!(display.tui.get_output(), &["Test String"]);
534 }
535
536 #[test]
537 fn clear() {
538 let mut display = Display::new(CrossTerm::new(), &Theme::new());
539 display.draw_str("Test String").unwrap();
540 display.set_dim(true).unwrap();
541 display.set_reverse(true).unwrap();
542 display.set_underline(true).unwrap();
543 display.clear().unwrap();
544 assert!(display.tui.get_output().is_empty());
545 assert!(!display.tui.is_dimmed());
546 assert!(!display.tui.is_reverse());
547 assert!(!display.tui.is_underline());
548 }
549
550 #[test]
551 fn refresh() {
552 let mut display = Display::new(CrossTerm::new(), &Theme::new());
553 display.refresh().unwrap();
554 assert!(!display.tui.is_dirty());
555 }
556
557 #[rstest]
558 #[case::action_break(DisplayColor::ActionBreak, false, CrosstermColor::White, CrosstermColor::Reset)]
559 #[case::action_break_selected(
560 DisplayColor::ActionBreak,
561 true,
562 CrosstermColor::White,
563 CrosstermColor::AnsiValue(237)
564 )]
565 #[case::action_drop(DisplayColor::ActionDrop, false, CrosstermColor::Red, CrosstermColor::Reset)]
566 #[case::action_drop_selected(DisplayColor::ActionDrop, true, CrosstermColor::Red, CrosstermColor::AnsiValue(237))]
567 #[case::action_edit(DisplayColor::ActionEdit, false, CrosstermColor::Blue, CrosstermColor::Reset)]
568 #[case::action_edit_selected(DisplayColor::ActionEdit, true, CrosstermColor::Blue, CrosstermColor::AnsiValue(237))]
569 #[case::action_exec(DisplayColor::ActionExec, false, CrosstermColor::White, CrosstermColor::Reset)]
570 #[case::action_exec_selected(
571 DisplayColor::ActionExec,
572 true,
573 CrosstermColor::White,
574 CrosstermColor::AnsiValue(237)
575 )]
576 #[case::action_fixup(DisplayColor::ActionFixup, false, CrosstermColor::Magenta, CrosstermColor::Reset)]
577 #[case::action_fixup_selected(
578 DisplayColor::ActionFixup,
579 true,
580 CrosstermColor::Magenta,
581 CrosstermColor::AnsiValue(237)
582 )]
583 #[case::action_pick(DisplayColor::ActionPick, false, CrosstermColor::Green, CrosstermColor::Reset)]
584 #[case::action_pick_selected(
585 DisplayColor::ActionPick,
586 true,
587 CrosstermColor::Green,
588 CrosstermColor::AnsiValue(237)
589 )]
590 #[case::action_reword(DisplayColor::ActionReword, false, CrosstermColor::Yellow, CrosstermColor::Reset)]
591 #[case::action_reword_selected(
592 DisplayColor::ActionReword,
593 true,
594 CrosstermColor::Yellow,
595 CrosstermColor::AnsiValue(237)
596 )]
597 #[case::action_squash(DisplayColor::ActionSquash, false, CrosstermColor::Cyan, CrosstermColor::Reset)]
598 #[case::action_squash_selected(
599 DisplayColor::ActionSquash,
600 true,
601 CrosstermColor::Cyan,
602 CrosstermColor::AnsiValue(237)
603 )]
604 #[case::action_label(DisplayColor::ActionLabel, false, CrosstermColor::DarkYellow, CrosstermColor::Reset)]
605 #[case::action_label_selected(
606 DisplayColor::ActionLabel,
607 true,
608 CrosstermColor::DarkYellow,
609 CrosstermColor::AnsiValue(237)
610 )]
611 #[case::action_reset(DisplayColor::ActionReset, false, CrosstermColor::DarkYellow, CrosstermColor::Reset)]
612 #[case::action_reset_selected(
613 DisplayColor::ActionReset,
614 true,
615 CrosstermColor::DarkYellow,
616 CrosstermColor::AnsiValue(237)
617 )]
618 #[case::action_merge(DisplayColor::ActionMerge, false, CrosstermColor::DarkYellow, CrosstermColor::Reset)]
619 #[case::action_merge_selected(
620 DisplayColor::ActionMerge,
621 true,
622 CrosstermColor::DarkYellow,
623 CrosstermColor::AnsiValue(237)
624 )]
625 #[case::action_update_ref(
626 DisplayColor::ActionUpdateRef,
627 false,
628 CrosstermColor::DarkMagenta,
629 CrosstermColor::Reset
630 )]
631 #[case::action_update_ref_selected(
632 DisplayColor::ActionUpdateRef,
633 true,
634 CrosstermColor::DarkMagenta,
635 CrosstermColor::AnsiValue(237)
636 )]
637 #[case::normal(DisplayColor::Normal, false, CrosstermColor::Reset, CrosstermColor::Reset)]
638 #[case::normal_selected(DisplayColor::Normal, true, CrosstermColor::Reset, CrosstermColor::AnsiValue(237))]
639 #[case::indicator(DisplayColor::IndicatorColor, false, CrosstermColor::Cyan, CrosstermColor::Reset)]
640 #[case::indicator_selected(
641 DisplayColor::IndicatorColor,
642 true,
643 CrosstermColor::Cyan,
644 CrosstermColor::AnsiValue(237)
645 )]
646 #[case::diff_add(DisplayColor::DiffAddColor, false, CrosstermColor::Green, CrosstermColor::Reset)]
647 #[case::diff_add_selected(
648 DisplayColor::DiffAddColor,
649 true,
650 CrosstermColor::Green,
651 CrosstermColor::AnsiValue(237)
652 )]
653 #[case::diff_remove(DisplayColor::DiffRemoveColor, false, CrosstermColor::Red, CrosstermColor::Reset)]
654 #[case::diff_remove_selected(
655 DisplayColor::DiffRemoveColor,
656 true,
657 CrosstermColor::Red,
658 CrosstermColor::AnsiValue(237)
659 )]
660 #[case::diff_change(DisplayColor::DiffChangeColor, false, CrosstermColor::Yellow, CrosstermColor::Reset)]
661 #[case::diff_change_selected(
662 DisplayColor::DiffChangeColor,
663 true,
664 CrosstermColor::Yellow,
665 CrosstermColor::AnsiValue(237)
666 )]
667 #[case::diff_context(DisplayColor::DiffContextColor, false, CrosstermColor::White, CrosstermColor::Reset)]
668 #[case::diff_context_selected(
669 DisplayColor::DiffContextColor,
670 true,
671 CrosstermColor::White,
672 CrosstermColor::AnsiValue(237)
673 )]
674 #[case::diff_whitespace(
675 DisplayColor::DiffWhitespaceColor,
676 false,
677 CrosstermColor::DarkGrey,
678 CrosstermColor::Reset
679 )]
680 #[case::diff_whitespace_selected(
681 DisplayColor::DiffWhitespaceColor,
682 true,
683 CrosstermColor::DarkGrey,
684 CrosstermColor::AnsiValue(237)
685 )]
686 fn color(
687 #[case] display_color: DisplayColor,
688 #[case] selected: bool,
689 #[case] expected_foreground: CrosstermColor,
690 #[case] expected_background: CrosstermColor,
691 ) {
692 let mut display = Display::new(CrossTerm::new(), &Theme::new());
693 display.color(display_color, selected).unwrap();
694 assert!(
695 display
696 .tui
697 .is_colors_enabled(Colors::new(expected_foreground, expected_background))
698 );
699 }
700
701 #[rstest]
702 #[case::all_off(false, false, false)]
703 #[case::reverse(false, false, true)]
704 #[case::underline(false, true, false)]
705 #[case::underline_reverse(false, true, true)]
706 #[case::dim(true, false, false)]
707 #[case::dim_reverse(true, false, true)]
708 #[case::dim_underline(true, true, false)]
709 #[case::all_on(true, true, true)]
710 fn style(#[case] dim: bool, #[case] underline: bool, #[case] reverse: bool) {
711 let mut display = Display::new(CrossTerm::new(), &Theme::new());
712 display.set_style(dim, underline, reverse).unwrap();
713 assert_eq!(display.tui.is_dimmed(), dim);
714 assert_eq!(display.tui.is_underline(), underline);
715 assert_eq!(display.tui.is_reverse(), reverse);
716 }
717
718 #[test]
719 fn get_window_size() {
720 let mut display = Display::new(CrossTerm::new(), &Theme::new());
721 display.tui.set_size(Size::new(12, 10));
722 assert_eq!(display.get_window_size(), Size::new(12, 10));
723 }
724
725 #[test]
726 fn ensure_at_line_start() {
727 let mut display = Display::new(CrossTerm::new(), &Theme::new());
728 display.ensure_at_line_start().unwrap();
729 assert_eq!(display.tui.get_position(), (0, 0));
730 }
731
732 #[test]
733 fn move_from_end_of_line() {
734 let mut display = Display::new(CrossTerm::new(), &Theme::new());
735 display.tui.set_size(Size::new(20, 10));
736 display.move_from_end_of_line(5).unwrap();
737 assert_eq!(display.tui.get_position(), (15, 0));
739 }
740
741 #[test]
742 fn start() {
743 let mut display = Display::new(CrossTerm::new(), &Theme::new());
744 display.start().unwrap();
745 assert_eq!(display.tui.get_state(), State::Normal);
746 }
747
748 #[test]
749 fn end() {
750 let mut display = Display::new(CrossTerm::new(), &Theme::new());
751 display.end().unwrap();
752 assert_eq!(display.tui.get_state(), State::Ended);
753 }
754}