display/
lib.rs

1// LINT-REPLACE-START
2// This section is autogenerated, do not modify directly
3// nightly sometimes removes/renames lints
4#![cfg_attr(allow_unknown_lints, allow(unknown_lints))]
5#![cfg_attr(allow_unknown_lints, allow(renamed_and_removed_lints))]
6// enable all rustc's built-in lints
7#![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// rustc's additional allowed by default lints
17#![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// enable all of Clippy's lints
55#![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// allow some things in tests
97#![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// allowable upcoming nightly lints
114#![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// LINT-REPLACE-END
126
127//! Git Interactive Rebase Tool - Display Module
128//!
129//! # Description
130//! This module is used to handle working with the terminal display.
131//!
132//! ```
133//! use config::Theme;
134//! use display::{CrossTerm, Display, DisplayColor};
135//! let theme = Theme::new();
136//! let tui = CrossTerm::new();
137//! let mut display = Display::new(tui, &theme);
138//!
139//! display.start();
140//! display.clear();
141//! display.draw_str("Hello world!");
142//! display.color(DisplayColor::IndicatorColor, false);
143//! display.set_style(false, true, false);
144//! display.draw_str("Hello colorful, underlined world!");
145//! display.refresh();
146//! display.end();
147//! ```
148//!
149//! ## Test Utilities
150//! To facilitate testing the usages of this crate, a set of testing utilities are provided. Since
151//! these utilities are not tested, and often are optimized for developer experience than
152//! performance should only be used in test code.
153
154mod 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/// A high level interface to the terminal display.
179#[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	/// Create a new display instance.
205	#[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	/// Draws a string of text to the terminal interface.
348	///
349	/// # Errors
350	/// Will error if the underlying terminal interface is in an error state.
351	#[inline]
352	pub fn draw_str(&mut self, s: &str) -> Result<(), DisplayError> {
353		self.tui.print(s)
354	}
355
356	/// Clear the terminal interface and reset any style and color attributes.
357	///
358	/// # Errors
359	/// Will error if the underlying terminal interface is in an error state.
360	#[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	/// Force a refresh of the terminal interface. This normally should be called after after all
368	/// text has been drawn to the terminal interface. This is considered a slow operation, so
369	/// should be called only as needed.
370	///
371	/// # Errors
372	/// Will error if the underlying terminal interface is in an error state.
373	#[inline]
374	pub fn refresh(&mut self) -> Result<(), DisplayError> {
375		self.tui.flush()
376	}
377
378	/// Set the color of text drawn to the terminal interface. This will only change text drawn to
379	/// the terminal after this function call.
380	///
381	/// # Errors
382	/// Will error if the underlying terminal interface is in an error state.
383	#[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	/// Set the style attributes of text drawn to the terminal interface. This will only change text
436	/// drawn to the terminal after this function call.
437	///
438	/// # Errors
439	/// Will error if the underlying terminal interface is in an error state.
440	#[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	/// Get the width and height of the terminal interface. This can be a slow operation, so should
448	/// not be called unless absolutely needed.
449	///
450	/// # Errors
451	/// Will error if the underlying terminal interface is in an error state.
452	#[inline]
453	pub fn get_window_size(&self) -> Size {
454		self.tui.get_size()
455	}
456
457	/// Reset the cursor position to the start of the line.
458	///
459	/// # Errors
460	/// Will error if the underlying terminal interface is in an error state.
461	#[inline]
462	pub fn ensure_at_line_start(&mut self) -> Result<(), DisplayError> {
463		self.tui.move_to_column(0)
464	}
465
466	/// Move the cursor position `right` characters from the end of the line.
467	///
468	/// # Errors
469	/// Will error if the underlying terminal interface is in an error state.
470	#[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	/// Move the cursor to the next line.
477	///
478	/// # Errors
479	/// Will error if the underlying terminal interface is in an error state.
480	#[inline]
481	pub fn next_line(&mut self) -> Result<(), DisplayError> {
482		self.tui.move_next_line()
483	}
484
485	/// Start the terminal interface interactions. This should be called before any terminal
486	/// interactions are performed.
487	///
488	/// # Errors
489	/// Will error if the underlying terminal interface is in an error state.
490	#[inline]
491	pub fn start(&mut self) -> Result<(), DisplayError> {
492		self.tui.start()?;
493		self.tui.flush()
494	}
495
496	/// End the terminal interface interactions. This should be called after all terminal
497	/// interactions are complete. This resets the terminal interface to the default state, and
498	/// should be called on program exit.
499	///
500	/// # Errors
501	/// Will error if the underlying terminal interface is in an error state.
502	#[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		// character after the 15th character (0-indexed)
738		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}