2020-06-02 20:03:16 +02:00
|
|
|
|
use std::char;
|
2020-06-04 00:09:59 +02:00
|
|
|
|
use std::cmp;
|
2020-05-28 09:24:33 +02:00
|
|
|
|
use std::io;
|
2022-11-07 00:02:27 +01:00
|
|
|
|
use std::io::Write;
|
2020-06-01 09:33:40 +02:00
|
|
|
|
|
2022-11-07 00:02:27 +01:00
|
|
|
|
use termion::{self, color, cursor, event, screen::IntoAlternateScreen, style};
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2021-03-19 23:07:08 +01:00
|
|
|
|
use super::colors::UiColors;
|
2021-03-19 23:17:41 +01:00
|
|
|
|
use super::Selection;
|
2021-03-20 19:58:31 +01:00
|
|
|
|
use super::{HintAlignment, HintStyle};
|
2021-03-21 15:50:58 +01:00
|
|
|
|
use crate::{config::extended::OutputDestination, textbuf};
|
2020-05-31 22:45:36 +02:00
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
/// Describes where a line from the buffer is displayed on the screen and how
|
|
|
|
|
|
/// much vertical lines it takes.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The `pos_y` field is the actual vertical position due to wrapped lines
|
|
|
|
|
|
/// before this line. The `size` field is the number of screen lines occupied
|
|
|
|
|
|
/// by this line.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// For example, given a buffer in which
|
|
|
|
|
|
///
|
|
|
|
|
|
/// - the first line is smaller than the screen width,
|
|
|
|
|
|
/// - the second line is slightly larger,
|
|
|
|
|
|
/// - and the third line is smaller than the screen width,
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The corresponding `WrappedLine`s are
|
|
|
|
|
|
///
|
|
|
|
|
|
/// - the first `WrappedLine` has `pos_y: 0` and `size: 1`
|
|
|
|
|
|
/// - the second `WrappedLine` has `pos_y: 1` and `size: 2` (larger than screen
|
|
|
|
|
|
/// width)
|
|
|
|
|
|
/// - the third `WrappedLine` has `pos_y: 3` and `size: 1`
|
|
|
|
|
|
///
|
|
|
|
|
|
struct WrappedLine {
|
|
|
|
|
|
pos_y: usize,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-19 09:00:42 +01:00
|
|
|
|
pub struct ViewController<'a> {
|
2021-03-22 08:48:51 +01:00
|
|
|
|
model: &'a textbuf::Model<'a>,
|
2020-06-02 13:10:48 +02:00
|
|
|
|
term_width: u16,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
wrapped_lines: Vec<WrappedLine>,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
focus_index: usize,
|
2020-06-01 10:06:50 +02:00
|
|
|
|
focus_wrap_around: bool,
|
2021-03-17 07:55:24 +01:00
|
|
|
|
default_output_destination: OutputDestination,
|
2020-06-01 15:25:54 +02:00
|
|
|
|
rendering_colors: &'a UiColors,
|
2020-06-01 15:34:54 +02:00
|
|
|
|
hint_alignment: &'a HintAlignment,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_style: Option<HintStyle>,
|
2020-05-23 09:30:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-19 09:00:42 +01:00
|
|
|
|
impl<'a> ViewController<'a> {
|
2021-03-22 06:48:09 +01:00
|
|
|
|
// Initialize {{{1
|
|
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
pub fn new(
|
2021-03-22 08:48:51 +01:00
|
|
|
|
model: &'a textbuf::Model<'a>,
|
2020-06-01 10:06:50 +02:00
|
|
|
|
focus_wrap_around: bool,
|
2021-03-17 07:55:24 +01:00
|
|
|
|
default_output_destination: OutputDestination,
|
2020-06-01 15:25:54 +02:00
|
|
|
|
rendering_colors: &'a UiColors,
|
2020-06-01 15:34:54 +02:00
|
|
|
|
hint_alignment: &'a HintAlignment,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_style: Option<HintStyle>,
|
2021-03-19 09:00:42 +01:00
|
|
|
|
) -> ViewController<'a> {
|
2021-03-22 08:48:51 +01:00
|
|
|
|
let focus_index = if model.reverse {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
model.spans.len() - 1
|
2021-03-22 08:48:51 +01:00
|
|
|
|
} else {
|
|
|
|
|
|
0
|
|
|
|
|
|
};
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2020-06-02 23:23:47 +02:00
|
|
|
|
let (term_width, _) = termion::terminal_size().unwrap_or((80u16, 30u16)); // .expect("Cannot read the terminal size.");
|
2022-11-07 00:02:27 +01:00
|
|
|
|
let wrapped_lines = compute_wrapped_lines(model.lines, term_width);
|
2020-06-02 13:10:48 +02:00
|
|
|
|
|
2021-03-19 09:00:42 +01:00
|
|
|
|
ViewController {
|
2020-06-01 09:33:40 +02:00
|
|
|
|
model,
|
2020-06-02 13:10:48 +02:00
|
|
|
|
term_width,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
wrapped_lines,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
focus_index,
|
2020-06-01 10:06:50 +02:00
|
|
|
|
focus_wrap_around,
|
2021-03-17 07:55:24 +01:00
|
|
|
|
default_output_destination,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
rendering_colors,
|
2020-06-01 15:34:54 +02:00
|
|
|
|
hint_alignment,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_style,
|
|
|
|
|
|
}
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 06:48:09 +01:00
|
|
|
|
// }}}
|
|
|
|
|
|
// Coordinates {{{1
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
/// Returns the adjusted position of a given `Span` within the buffer
|
|
|
|
|
|
/// line.
|
2020-06-02 13:10:48 +02:00
|
|
|
|
///
|
2021-03-25 09:59:21 +01:00
|
|
|
|
/// This adjustment is necessary if multibyte characters occur before the
|
|
|
|
|
|
/// span (in the "prefix"). If this is the case then their compouding
|
|
|
|
|
|
/// takes less space on screen when printed: for instance ´ + e = é.
|
|
|
|
|
|
/// Consequently the span position has to be adjusted to the left.
|
2021-03-22 06:48:09 +01:00
|
|
|
|
///
|
2021-03-25 09:59:21 +01:00
|
|
|
|
/// This computation must happen before mapping the span position to the
|
|
|
|
|
|
/// wrapped screen space.
|
|
|
|
|
|
fn adjusted_span_position(&self, span: &textbuf::Span<'a>) -> (usize, usize) {
|
|
|
|
|
|
let pos_x = {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
let line = &self.model.lines[span.y as usize];
|
|
|
|
|
|
let prefix = &line[0..span.x as usize];
|
2021-03-22 06:48:09 +01:00
|
|
|
|
let adjust = prefix.len() - prefix.chars().count();
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(span.x as usize) - adjust
|
2021-03-22 06:48:09 +01:00
|
|
|
|
};
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let pos_y = span.y as usize;
|
2021-03-22 06:48:09 +01:00
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(pos_x, pos_y)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Convert the `Span` text into the coordinates of the wrapped lines.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Compute the new x position of the text as the remainder of the line width
|
|
|
|
|
|
/// (e.g. the Span could start at position 120 in a 80-width terminal, the new
|
|
|
|
|
|
/// position being 40).
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Compute the new y position of the text as the initial y position plus any
|
|
|
|
|
|
/// additional offset due to previous split lines. This is obtained thanks to
|
|
|
|
|
|
/// the `wrapped_lines` field.
|
|
|
|
|
|
fn map_coords_to_wrapped_space(&self, pos_x: usize, pos_y: usize) -> (usize, usize) {
|
|
|
|
|
|
let line_width = self.term_width as usize;
|
|
|
|
|
|
|
|
|
|
|
|
let new_pos_x = pos_x % line_width;
|
2022-11-07 10:45:06 +01:00
|
|
|
|
let new_pos_y = self.wrapped_lines[pos_y].pos_y + pos_x / line_width;
|
2021-03-25 09:59:21 +01:00
|
|
|
|
|
|
|
|
|
|
(new_pos_x, new_pos_y)
|
2021-03-22 06:48:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
// Focus management {{{1
|
|
|
|
|
|
|
2020-06-01 11:57:23 +02:00
|
|
|
|
/// Move focus onto the previous hint, returning both the index of the
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// previously focused Span, and the index of the newly focused one.
|
2020-06-01 11:57:23 +02:00
|
|
|
|
fn prev_focus_index(&mut self) -> (usize, usize) {
|
|
|
|
|
|
let old_index = self.focus_index;
|
2020-06-01 10:06:50 +02:00
|
|
|
|
if self.focus_wrap_around {
|
|
|
|
|
|
if self.focus_index == 0 {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
self.focus_index = self.model.spans.len() - 1;
|
2020-06-01 10:06:50 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
self.focus_index -= 1;
|
|
|
|
|
|
}
|
2021-03-13 18:51:31 +01:00
|
|
|
|
} else if self.focus_index > 0 {
|
|
|
|
|
|
self.focus_index -= 1;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
2020-06-01 11:57:23 +02:00
|
|
|
|
let new_index = self.focus_index;
|
|
|
|
|
|
(old_index, new_index)
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-01 11:57:23 +02:00
|
|
|
|
/// Move focus onto the next hint, returning both the index of the
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// previously focused Span, and the index of the newly focused one.
|
2020-06-01 11:57:23 +02:00
|
|
|
|
fn next_focus_index(&mut self) -> (usize, usize) {
|
|
|
|
|
|
let old_index = self.focus_index;
|
2020-06-01 10:06:50 +02:00
|
|
|
|
if self.focus_wrap_around {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
if self.focus_index == self.model.spans.len() - 1 {
|
2020-06-01 10:06:50 +02:00
|
|
|
|
self.focus_index = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
self.focus_index += 1;
|
|
|
|
|
|
}
|
2021-03-22 23:56:35 +01:00
|
|
|
|
} else if self.focus_index < self.model.spans.len() - 1 {
|
2021-03-13 18:51:31 +01:00
|
|
|
|
self.focus_index += 1;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
2020-06-01 11:57:23 +02:00
|
|
|
|
let new_index = self.focus_index;
|
|
|
|
|
|
(old_index, new_index)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 06:48:09 +01:00
|
|
|
|
// }}}
|
|
|
|
|
|
// Rendering {{{1
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2020-06-01 09:33:40 +02:00
|
|
|
|
/// Render entire model lines on provided writer.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// This renders the basic content on which spans and hints can be rendered.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// # Notes
|
|
|
|
|
|
/// - All trailing whitespaces are trimmed, empty lines are skipped.
|
|
|
|
|
|
/// - This writes directly on the writer, avoiding extra allocation.
|
2020-06-02 13:10:48 +02:00
|
|
|
|
fn render_base_text(
|
|
|
|
|
|
stdout: &mut dyn io::Write,
|
2021-03-13 18:51:31 +01:00
|
|
|
|
lines: &[&str],
|
2021-03-25 09:59:21 +01:00
|
|
|
|
wrapped_lines: &[WrappedLine],
|
2020-06-02 13:10:48 +02:00
|
|
|
|
colors: &UiColors,
|
2021-03-13 18:51:31 +01:00
|
|
|
|
) {
|
2020-06-02 09:38:42 +02:00
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{bg_color}{fg_color}",
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg_color = color::Fg(colors.text_fg),
|
|
|
|
|
|
bg_color = color::Bg(colors.text_bg),
|
2020-06-02 09:38:42 +02:00
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
2020-06-02 13:10:48 +02:00
|
|
|
|
for (line_index, line) in lines.iter().enumerate() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let trimmed_line = line.trim_end();
|
|
|
|
|
|
|
|
|
|
|
|
if !trimmed_line.is_empty() {
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let pos_y: usize = wrapped_lines[line_index].pos_y;
|
2020-06-02 13:10:48 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
2020-06-02 09:38:42 +02:00
|
|
|
|
"{goto}{text}",
|
2021-03-25 09:59:21 +01:00
|
|
|
|
goto = cursor::Goto(1, pos_y as u16 + 1),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
text = &trimmed_line,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-06-02 09:38:42 +02:00
|
|
|
|
|
|
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{fg_reset}{bg_reset}",
|
|
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
2020-05-22 15:03:39 +02:00
|
|
|
|
}
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Render the Span's `text` field on provided writer using the `span_*g` color.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
2020-05-29 20:36:27 +02:00
|
|
|
|
/// If a Mach is "focused", it is then rendered with the `focused_*g` colors.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// # Note
|
2020-06-01 11:57:23 +02:00
|
|
|
|
///
|
2020-05-25 23:06:00 +02:00
|
|
|
|
/// This writes directly on the writer, avoiding extra allocation.
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn render_span_text(
|
2020-05-28 09:24:33 +02:00
|
|
|
|
stdout: &mut dyn io::Write,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
text: &str,
|
|
|
|
|
|
focused: bool,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
pos: (usize, usize),
|
2020-06-01 15:25:54 +02:00
|
|
|
|
colors: &UiColors,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
) {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// To help identify it, the span thas has focus is rendered with a dedicated color.
|
2020-05-29 20:36:27 +02:00
|
|
|
|
let (fg_color, bg_color) = if focused {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
(&colors.focused_fg, &colors.focused_bg)
|
|
|
|
|
|
} else {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
(&colors.span_fg, &colors.span_bg)
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// Render just the Span's text on top of existing content.
|
2020-05-22 15:03:39 +02:00
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
"{goto}{bg_color}{fg_color}{text}{fg_reset}{bg_reset}",
|
2021-03-25 09:59:21 +01:00
|
|
|
|
goto = cursor::Goto(pos.0 as u16 + 1, pos.1 as u16 + 1),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg_color = color::Fg(*fg_color),
|
|
|
|
|
|
bg_color = color::Bg(*bg_color),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
text = &text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Render a Span's `hint` field on the provided writer.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// This renders the hint according to some provided style:
|
|
|
|
|
|
/// - just colors
|
2021-03-23 00:08:47 +01:00
|
|
|
|
/// - styled (bold, italic, underlined) with colors
|
2020-05-25 23:06:00 +02:00
|
|
|
|
/// - surrounding the hint's text with some delimiters, see
|
|
|
|
|
|
/// `HintStyle::Delimited`.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Note
|
2021-03-23 00:08:47 +01:00
|
|
|
|
///
|
2020-05-25 23:06:00 +02:00
|
|
|
|
/// This writes directly on the writer, avoiding extra allocation.
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn render_span_hint(
|
2020-05-28 09:24:33 +02:00
|
|
|
|
stdout: &mut dyn io::Write,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_text: &str,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
pos: (usize, usize),
|
2020-06-01 15:25:54 +02:00
|
|
|
|
colors: &UiColors,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_style: &Option<HintStyle>,
|
|
|
|
|
|
) {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
let fg_color = color::Fg(colors.hint_fg);
|
|
|
|
|
|
let bg_color = color::Bg(colors.hint_bg);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let fg_reset = color::Fg(color::Reset);
|
|
|
|
|
|
let bg_reset = color::Bg(color::Reset);
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let goto = cursor::Goto(pos.0 as u16 + 1, pos.1 as u16 + 1);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
match hint_style {
|
|
|
|
|
|
None => {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{goto}{bg_color}{fg_color}{hint}{fg_reset}{bg_reset}",
|
2020-06-02 11:17:35 +02:00
|
|
|
|
goto = goto,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_color = fg_color,
|
|
|
|
|
|
bg_color = bg_color,
|
|
|
|
|
|
fg_reset = fg_reset,
|
|
|
|
|
|
bg_reset = bg_reset,
|
|
|
|
|
|
hint = hint_text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
}
|
|
|
|
|
|
Some(hint_style) => match hint_style {
|
2020-05-29 16:23:46 +02:00
|
|
|
|
HintStyle::Bold => {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{goto}{bg_color}{fg_color}{sty}{hint}{sty_reset}{fg_reset}{bg_reset}",
|
2020-06-02 11:17:35 +02:00
|
|
|
|
goto = goto,
|
2020-05-29 16:23:46 +02:00
|
|
|
|
fg_color = fg_color,
|
|
|
|
|
|
bg_color = bg_color,
|
|
|
|
|
|
fg_reset = fg_reset,
|
|
|
|
|
|
bg_reset = bg_reset,
|
|
|
|
|
|
sty = style::Bold,
|
2021-03-23 22:20:21 +01:00
|
|
|
|
sty_reset = style::Reset, // NoBold is not sufficient
|
2020-05-29 16:23:46 +02:00
|
|
|
|
hint = hint_text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
}
|
2020-05-28 07:07:51 +02:00
|
|
|
|
HintStyle::Italic => {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{goto}{bg_color}{fg_color}{sty}{hint}{sty_reset}{fg_reset}{bg_reset}",
|
2020-06-02 11:17:35 +02:00
|
|
|
|
goto = goto,
|
2020-05-28 07:07:51 +02:00
|
|
|
|
fg_color = fg_color,
|
|
|
|
|
|
bg_color = bg_color,
|
|
|
|
|
|
fg_reset = fg_reset,
|
|
|
|
|
|
bg_reset = bg_reset,
|
|
|
|
|
|
sty = style::Italic,
|
|
|
|
|
|
sty_reset = style::NoItalic,
|
|
|
|
|
|
hint = hint_text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
}
|
2020-05-25 23:06:00 +02:00
|
|
|
|
HintStyle::Underline => {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{goto}{bg_color}{fg_color}{sty}{hint}{sty_reset}{fg_reset}{bg_reset}",
|
2020-06-02 11:17:35 +02:00
|
|
|
|
goto = goto,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_color = fg_color,
|
|
|
|
|
|
bg_color = bg_color,
|
|
|
|
|
|
fg_reset = fg_reset,
|
|
|
|
|
|
bg_reset = bg_reset,
|
|
|
|
|
|
sty = style::Underline,
|
|
|
|
|
|
sty_reset = style::NoUnderline,
|
|
|
|
|
|
hint = hint_text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
}
|
|
|
|
|
|
HintStyle::Surround(opening, closing) => {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
"{goto}{bg_color}{fg_color}{bra}{hint}{bra_close}{fg_reset}{bg_reset}",
|
2020-06-02 11:17:35 +02:00
|
|
|
|
goto = goto,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_color = fg_color,
|
|
|
|
|
|
bg_color = bg_color,
|
|
|
|
|
|
fg_reset = fg_reset,
|
|
|
|
|
|
bg_reset = bg_reset,
|
|
|
|
|
|
bra = opening,
|
|
|
|
|
|
bra_close = closing,
|
|
|
|
|
|
hint = hint_text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Convenience function that renders both the text span and its hint,
|
2020-06-01 11:57:23 +02:00
|
|
|
|
/// if focused.
|
2021-03-22 23:56:35 +01:00
|
|
|
|
fn render_span(&self, stdout: &mut dyn io::Write, span: &textbuf::Span<'a>, focused: bool) {
|
|
|
|
|
|
let text = span.text;
|
2020-06-01 11:57:23 +02:00
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let (pos_x, pos_y) = self.adjusted_span_position(span);
|
|
|
|
|
|
let (pos_x, pos_y) = self.map_coords_to_wrapped_space(pos_x, pos_y);
|
2020-06-01 11:57:23 +02:00
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
ViewController::render_span_text(
|
2020-06-01 11:57:23 +02:00
|
|
|
|
stdout,
|
|
|
|
|
|
text,
|
|
|
|
|
|
focused,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(pos_x, pos_y),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
self.rendering_colors,
|
2020-06-01 11:57:23 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if !focused {
|
|
|
|
|
|
// If not focused, render the hint (e.g. "eo") as an overlay on
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// top of the rendered text span, aligned at its leading or the
|
2020-06-01 11:57:23 +02:00
|
|
|
|
// trailing edge.
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let offset = match self.hint_alignment {
|
2020-06-01 11:57:23 +02:00
|
|
|
|
HintAlignment::Leading => 0,
|
2021-03-22 23:56:35 +01:00
|
|
|
|
HintAlignment::Trailing => text.len() - span.hint.len(),
|
2020-06-01 11:57:23 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
ViewController::render_span_hint(
|
2020-06-01 11:57:23 +02:00
|
|
|
|
stdout,
|
2021-03-22 23:56:35 +01:00
|
|
|
|
&span.hint,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(pos_x + offset, pos_y),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
self.rendering_colors,
|
2020-06-01 11:57:23 +02:00
|
|
|
|
&self.hint_style,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-01 15:25:54 +02:00
|
|
|
|
/// Full nender the Ui on the provided writer.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// This renders in 3 phases:
|
|
|
|
|
|
/// - all lines are rendered verbatim
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// - each Span's `text` is rendered as an overlay on top of it
|
|
|
|
|
|
/// - each Span's `hint` text is rendered as a final overlay
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
2021-03-25 09:59:21 +01:00
|
|
|
|
/// Depending on the value of `self.hint_alignment`, the hint can be
|
|
|
|
|
|
/// rendered on the leading edge of the underlying Span's `text`, or on
|
|
|
|
|
|
/// the trailing edge.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// # Note
|
2021-03-25 09:59:21 +01:00
|
|
|
|
///
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Multibyte characters are taken into account, so that the Span's `text`
|
2020-05-25 23:06:00 +02:00
|
|
|
|
/// and `hint` are rendered in their proper position.
|
2021-03-13 18:51:31 +01:00
|
|
|
|
fn full_render(&self, stdout: &mut dyn io::Write) {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
// 1. Trim all lines and render non-empty ones.
|
2021-03-19 09:00:42 +01:00
|
|
|
|
ViewController::render_base_text(
|
2020-06-02 13:10:48 +02:00
|
|
|
|
stdout,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
self.model.lines,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
&self.wrapped_lines,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
self.rendering_colors,
|
2020-06-02 13:10:48 +02:00
|
|
|
|
);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
for (index, span) in self.model.spans.iter().enumerate() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let focused = index == self.focus_index;
|
2021-03-22 23:56:35 +01:00
|
|
|
|
self.render_span(stdout, span, focused);
|
2020-06-01 11:57:23 +02:00
|
|
|
|
}
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2020-06-01 11:57:23 +02:00
|
|
|
|
stdout.flush().unwrap();
|
|
|
|
|
|
}
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Render the previous span with its hint, and render the newly focused
|
|
|
|
|
|
/// span without its hint. This is more efficient than a full render.
|
2020-06-01 11:57:23 +02:00
|
|
|
|
fn diff_render(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
stdout: &mut dyn io::Write,
|
|
|
|
|
|
old_focus_index: usize,
|
|
|
|
|
|
new_focus_index: usize,
|
|
|
|
|
|
) {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// Render the previously focused span as non-focused
|
|
|
|
|
|
let span = self.model.spans.get(old_focus_index).unwrap();
|
2020-06-01 11:57:23 +02:00
|
|
|
|
let focused = false;
|
2021-03-22 23:56:35 +01:00
|
|
|
|
self.render_span(stdout, span, focused);
|
2020-06-01 10:34:20 +02:00
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// Render the previously focused span as non-focused
|
|
|
|
|
|
let span = self.model.spans.get(new_focus_index).unwrap();
|
2020-06-01 11:57:23 +02:00
|
|
|
|
let focused = true;
|
2021-03-22 23:56:35 +01:00
|
|
|
|
self.render_span(stdout, span, focused);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
stdout.flush().unwrap();
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 06:48:09 +01:00
|
|
|
|
// }}}
|
|
|
|
|
|
// Listening {{{1
|
|
|
|
|
|
|
2020-05-29 18:43:17 +02:00
|
|
|
|
/// Listen to keys entered on stdin, moving focus accordingly, or
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// selecting one span.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// # Panics
|
2020-05-29 18:43:17 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// - This function panics if termion cannot read the entered keys on stdin.
|
|
|
|
|
|
fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> Event {
|
2020-05-28 09:24:33 +02:00
|
|
|
|
use termion::input::TermRead; // Trait for `reader.keys().next()`.
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
if self.model.spans.is_empty() {
|
2020-05-29 18:43:17 +02:00
|
|
|
|
return Event::Exit;
|
2020-05-21 08:28:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-29 18:43:17 +02:00
|
|
|
|
let mut typed_hint = String::new();
|
2021-03-17 07:55:24 +01:00
|
|
|
|
let mut uppercased = false;
|
|
|
|
|
|
let mut output_destination = self.default_output_destination.clone();
|
2020-05-29 18:43:17 +02:00
|
|
|
|
|
2020-05-29 20:36:27 +02:00
|
|
|
|
self.full_render(writer);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
|
// This is an option of a result of a key... Let's pop error cases first.
|
2020-05-28 09:24:33 +02:00
|
|
|
|
let next_key = reader.keys().next();
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
if next_key.is_none() {
|
|
|
|
|
|
// Nothing in the buffer. Wait for a bit...
|
2020-06-01 16:01:20 +02:00
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis(25));
|
2020-05-25 23:06:00 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2020-05-21 08:28:27 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let key_res = next_key.unwrap();
|
|
|
|
|
|
if let Err(err) = key_res {
|
|
|
|
|
|
// Termion not being able to read from stdin is an unrecoverable error.
|
2021-04-12 22:37:27 +02:00
|
|
|
|
panic!("{}", err);
|
2020-05-21 08:28:27 +02:00
|
|
|
|
}
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
match key_res.unwrap() {
|
2020-05-28 09:24:33 +02:00
|
|
|
|
event::Key::Esc => {
|
2020-05-29 18:43:17 +02:00
|
|
|
|
break;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// Move focus to next/prev span.
|
2020-06-01 11:57:23 +02:00
|
|
|
|
event::Key::Up => {
|
|
|
|
|
|
let (old_index, focused_index) = self.prev_focus_index();
|
|
|
|
|
|
self.diff_render(writer, old_index, focused_index);
|
|
|
|
|
|
}
|
|
|
|
|
|
event::Key::Down => {
|
|
|
|
|
|
let (old_index, focused_index) = self.next_focus_index();
|
|
|
|
|
|
self.diff_render(writer, old_index, focused_index);
|
|
|
|
|
|
}
|
|
|
|
|
|
event::Key::Left => {
|
|
|
|
|
|
let (old_index, focused_index) = self.prev_focus_index();
|
|
|
|
|
|
self.diff_render(writer, old_index, focused_index);
|
|
|
|
|
|
}
|
|
|
|
|
|
event::Key::Right => {
|
|
|
|
|
|
let (old_index, focused_index) = self.next_focus_index();
|
|
|
|
|
|
self.diff_render(writer, old_index, focused_index);
|
|
|
|
|
|
}
|
2020-05-30 22:23:33 +02:00
|
|
|
|
event::Key::Char(_ch @ 'n') => {
|
2020-06-01 11:57:23 +02:00
|
|
|
|
let (old_index, focused_index) = if self.model.reverse {
|
|
|
|
|
|
self.prev_focus_index()
|
2020-05-30 22:23:33 +02:00
|
|
|
|
} else {
|
2020-06-01 11:57:23 +02:00
|
|
|
|
self.next_focus_index()
|
|
|
|
|
|
};
|
|
|
|
|
|
self.diff_render(writer, old_index, focused_index);
|
2020-05-30 22:23:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
event::Key::Char(_ch @ 'N') => {
|
2020-06-01 11:57:23 +02:00
|
|
|
|
let (old_index, focused_index) = if self.model.reverse {
|
|
|
|
|
|
self.next_focus_index()
|
2020-05-30 22:23:33 +02:00
|
|
|
|
} else {
|
2020-06-01 11:57:23 +02:00
|
|
|
|
self.prev_focus_index()
|
|
|
|
|
|
};
|
|
|
|
|
|
self.diff_render(writer, old_index, focused_index);
|
2020-05-30 22:23:33 +02:00
|
|
|
|
}
|
2020-05-30 19:28:54 +02:00
|
|
|
|
|
2020-06-01 11:57:23 +02:00
|
|
|
|
// Yank/copy
|
2021-03-14 20:53:50 +01:00
|
|
|
|
event::Key::Char(_ch @ 'y') | event::Key::Char(_ch @ '\n') => {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
let text = self.model.spans.get(self.focus_index).unwrap().text;
|
|
|
|
|
|
return Event::Select(Selection {
|
2021-03-17 07:55:24 +01:00
|
|
|
|
text: text.to_string(),
|
|
|
|
|
|
uppercased: false,
|
|
|
|
|
|
output_destination,
|
|
|
|
|
|
});
|
2020-06-01 10:33:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
event::Key::Char(_ch @ 'Y') => {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
let text = self.model.spans.get(self.focus_index).unwrap().text;
|
|
|
|
|
|
return Event::Select(Selection {
|
2021-03-17 07:55:24 +01:00
|
|
|
|
text: text.to_string(),
|
|
|
|
|
|
uppercased: true,
|
|
|
|
|
|
output_destination,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
event::Key::Char(_ch @ ' ') => {
|
|
|
|
|
|
output_destination.toggle();
|
|
|
|
|
|
let message = format!("output destination: `{}`", output_destination);
|
2021-03-17 23:32:38 +01:00
|
|
|
|
duct::cmd!("tmux", "display-message", &message)
|
|
|
|
|
|
.run()
|
2021-03-17 07:55:24 +01:00
|
|
|
|
.expect("could not make tmux display the message.");
|
|
|
|
|
|
continue;
|
2020-06-01 10:33:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-14 20:53:50 +01:00
|
|
|
|
// Use a Trie or another data structure to determine
|
2020-05-29 18:43:17 +02:00
|
|
|
|
// if the entered key belongs to a longer hint.
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// Attempts at finding a span with a corresponding hint.
|
2021-03-14 20:53:50 +01:00
|
|
|
|
//
|
|
|
|
|
|
// If any of the typed character is caps, the typed hint is
|
|
|
|
|
|
// deemed as uppercased.
|
2020-05-28 09:24:33 +02:00
|
|
|
|
event::Key::Char(ch) => {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let key = ch.to_string();
|
|
|
|
|
|
let lower_key = key.to_lowercase();
|
|
|
|
|
|
|
2021-03-14 20:53:50 +01:00
|
|
|
|
uppercased = uppercased || (key != lower_key);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
typed_hint.push_str(&lower_key);
|
|
|
|
|
|
|
2020-06-01 12:01:53 +02:00
|
|
|
|
let node = self
|
2021-03-22 08:48:51 +01:00
|
|
|
|
.model
|
2020-05-31 22:45:36 +02:00
|
|
|
|
.lookup_trie
|
2020-06-01 12:01:53 +02:00
|
|
|
|
.get_node(&typed_hint.chars().collect::<Vec<char>>());
|
|
|
|
|
|
|
|
|
|
|
|
if node.is_none() {
|
2021-03-17 07:55:24 +01:00
|
|
|
|
// A key outside the alphabet was entered.
|
2020-06-01 12:01:53 +02:00
|
|
|
|
return Event::Exit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let node = node.unwrap();
|
|
|
|
|
|
if node.is_leaf() {
|
|
|
|
|
|
// The last key of a hint was entered.
|
2021-03-22 23:56:35 +01:00
|
|
|
|
let span_index = node.value().expect(
|
2020-06-01 12:01:53 +02:00
|
|
|
|
"By construction, the Lookup Trie should have a value for each leaf.",
|
|
|
|
|
|
);
|
2021-03-22 23:56:35 +01:00
|
|
|
|
let span = self.model.spans.get(*span_index).expect("By construction, the value in a leaf should correspond to an existing hint.");
|
|
|
|
|
|
let text = span.text.to_string();
|
|
|
|
|
|
return Event::Select(Selection {
|
2021-03-17 07:55:24 +01:00
|
|
|
|
text,
|
|
|
|
|
|
uppercased,
|
|
|
|
|
|
output_destination,
|
|
|
|
|
|
});
|
2020-06-01 12:01:53 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
// The prefix of a hint was entered, but we
|
|
|
|
|
|
// still need more keys.
|
|
|
|
|
|
continue;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unknown keys are ignored.
|
|
|
|
|
|
_ => (),
|
2020-05-21 08:28:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-01 11:57:23 +02:00
|
|
|
|
// End of event processing loop.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2020-05-29 18:43:17 +02:00
|
|
|
|
Event::Exit
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-22 06:48:09 +01:00
|
|
|
|
// }}}
|
|
|
|
|
|
// Presenting {{{1
|
|
|
|
|
|
|
2020-06-01 15:25:54 +02:00
|
|
|
|
/// Configure the terminal and display the `Ui`.
|
2020-05-29 18:43:17 +02:00
|
|
|
|
///
|
|
|
|
|
|
/// - Setup steps: switch to alternate screen, switch to raw mode, hide the cursor.
|
|
|
|
|
|
/// - Teardown steps: show cursor, back to main screen.
|
2021-03-17 07:55:24 +01:00
|
|
|
|
pub fn present(&mut self) -> Option<Selection> {
|
2020-05-28 09:24:33 +02:00
|
|
|
|
use termion::raw::IntoRawMode;
|
|
|
|
|
|
|
|
|
|
|
|
let mut stdin = termion::async_stdin();
|
2022-11-07 00:02:27 +01:00
|
|
|
|
let mut stdout = io::stdout()
|
|
|
|
|
|
.into_alternate_screen()
|
|
|
|
|
|
.expect("Cannot access alternate screen.")
|
|
|
|
|
|
.into_raw_mode()
|
|
|
|
|
|
.expect("Cannot access alternate screen.");
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2022-11-07 00:02:27 +01:00
|
|
|
|
// stdout.write(cursor::Hide.into()).unwrap();
|
2020-05-29 18:43:17 +02:00
|
|
|
|
write!(stdout, "{}", cursor::Hide).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let selection = match self.listen(&mut stdin, &mut stdout) {
|
|
|
|
|
|
Event::Exit => None,
|
2021-03-22 23:56:35 +01:00
|
|
|
|
Event::Select(selection) => Some(selection),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
write!(stdout, "{}", cursor::Show).unwrap();
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2020-05-29 18:43:17 +02:00
|
|
|
|
selection
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
2021-03-22 06:48:09 +01:00
|
|
|
|
|
|
|
|
|
|
// }}}
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
/// Compute each line's actual y position and size if displayed in a terminal of width
|
2020-06-02 13:10:48 +02:00
|
|
|
|
/// `term_width`.
|
2021-03-25 09:59:21 +01:00
|
|
|
|
fn compute_wrapped_lines(lines: &[&str], term_width: u16) -> Vec<WrappedLine> {
|
2020-06-02 13:10:48 +02:00
|
|
|
|
lines
|
|
|
|
|
|
.iter()
|
2021-03-25 09:59:21 +01:00
|
|
|
|
.scan(0, |position, &line| {
|
2020-06-04 00:09:59 +02:00
|
|
|
|
// Save the value to return (yield is in unstable).
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let value = *position;
|
2020-06-02 13:10:48 +02:00
|
|
|
|
|
2020-06-04 00:09:59 +02:00
|
|
|
|
let line_width = line.trim_end().chars().count() as isize;
|
|
|
|
|
|
|
|
|
|
|
|
// Amount of extra y space taken by this line.
|
|
|
|
|
|
// If the line has n chars, on a term of width n, this does not
|
|
|
|
|
|
// produce an extra line; it needs to exceed the width by 1 char.
|
2021-03-25 09:59:21 +01:00
|
|
|
|
// In case the width is 0, we need to first clamp line_width - 1.
|
2020-06-04 00:09:59 +02:00
|
|
|
|
let extra = cmp::max(0, line_width - 1) as usize / term_width as usize;
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
// Update the position of the next line.
|
|
|
|
|
|
*position += 1 + extra;
|
2020-06-02 13:10:48 +02:00
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
Some(WrappedLine {
|
|
|
|
|
|
pos_y: value,
|
|
|
|
|
|
// size: 1 + extra,
|
|
|
|
|
|
})
|
2020-06-02 13:10:48 +02:00
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-01 15:25:54 +02:00
|
|
|
|
/// Returned value after the `Ui` has finished listening to events.
|
2020-06-01 10:14:16 +02:00
|
|
|
|
enum Event {
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Exit with no selected spans,
|
2020-06-01 10:14:16 +02:00
|
|
|
|
Exit,
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// The selected span of text and whether it was selected with uppercase.
|
|
|
|
|
|
Select(Selection),
|
2020-06-01 10:14:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-02 20:03:16 +02:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
use super::*;
|
2022-11-07 00:02:27 +01:00
|
|
|
|
use crate::{textbuf::alphabet, ui::colors};
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_render_all_lines() {
|
|
|
|
|
|
let content = "some text
|
2020-05-22 15:03:39 +02:00
|
|
|
|
* e006b06 - (12 days ago) swapper: Make quotes
|
|
|
|
|
|
path: /usr/local/bin/git
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
path: /usr/local/bin/cargo";
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let lines: Vec<&str> = content.split('\n').collect();
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let wrapped_lines: Vec<WrappedLine> = vec![
|
|
|
|
|
|
WrappedLine { pos_y: 0 },
|
|
|
|
|
|
WrappedLine { pos_y: 1 },
|
|
|
|
|
|
WrappedLine { pos_y: 2 },
|
|
|
|
|
|
WrappedLine { pos_y: 3 },
|
|
|
|
|
|
WrappedLine { pos_y: 4 },
|
|
|
|
|
|
WrappedLine { pos_y: 5 },
|
|
|
|
|
|
];
|
2020-06-02 13:10:48 +02:00
|
|
|
|
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-29 20:36:27 +02:00
|
|
|
|
};
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
2021-03-25 09:59:21 +01:00
|
|
|
|
ViewController::render_base_text(&mut writer, &lines, &wrapped_lines, &colors);
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let goto1 = cursor::Goto(1, 1);
|
|
|
|
|
|
let goto2 = cursor::Goto(1, 2);
|
|
|
|
|
|
let goto3 = cursor::Goto(1, 3);
|
|
|
|
|
|
let goto6 = cursor::Goto(1, 6);
|
|
|
|
|
|
assert_eq!(
|
2020-05-29 20:36:27 +02:00
|
|
|
|
writer,
|
|
|
|
|
|
format!(
|
2020-06-02 09:38:42 +02:00
|
|
|
|
"{bg}{fg}{g1}some text{g2}* e006b06 - (12 days ago) swapper: Make quotes{g3}path: /usr/local/bin/git{g6}path: /usr/local/bin/cargo{fg_reset}{bg_reset}",
|
2020-05-29 20:36:27 +02:00
|
|
|
|
g1 = goto1, g2 = goto2, g3 = goto3, g6 = goto6,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(colors.text_fg),
|
|
|
|
|
|
bg = color::Bg(colors.text_bg),
|
2020-05-29 20:36:27 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
)
|
|
|
|
|
|
.as_bytes()
|
|
|
|
|
|
);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
}
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
#[test]
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn test_render_focused_span_text() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
|
|
|
|
|
let text = "https://en.wikipedia.org/wiki/Barcelona";
|
|
|
|
|
|
let focused = true;
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let position: (usize, usize) = (3, 1);
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
ViewController::render_span_text(&mut writer, text, focused, position, &colors);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
writer,
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
|
|
|
|
|
|
goto = cursor::Goto(4, 2),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(colors.focused_fg),
|
|
|
|
|
|
bg = color::Bg(colors.focused_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
text = &text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.as_bytes()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
#[test]
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn test_render_span_text() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
|
|
|
|
|
let text = "https://en.wikipedia.org/wiki/Barcelona";
|
|
|
|
|
|
let focused = false;
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let position: (usize, usize) = (3, 1);
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
ViewController::render_span_text(&mut writer, text, focused, position, &colors);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
writer,
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
|
|
|
|
|
|
goto = cursor::Goto(4, 2),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(colors.span_fg),
|
|
|
|
|
|
bg = color::Bg(colors.span_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
text = &text,
|
|
|
|
|
|
)
|
|
|
|
|
|
.as_bytes()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn test_render_unstyled_span_hint() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
|
|
|
|
|
let hint_text = "eo";
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let position: (usize, usize) = (3, 1);
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let offset = 0;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let hint_style = None;
|
|
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
ViewController::render_span_hint(
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&mut writer,
|
|
|
|
|
|
hint_text,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(position.0 + offset, position.1),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&colors,
|
|
|
|
|
|
&hint_style,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
writer,
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
|
|
|
|
|
|
goto = cursor::Goto(4, 2),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(colors.hint_fg),
|
|
|
|
|
|
bg = color::Bg(colors.hint_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
text = "eo",
|
|
|
|
|
|
)
|
|
|
|
|
|
.as_bytes()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn test_render_underlined_span_hint() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
|
|
|
|
|
let hint_text = "eo";
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let position: (usize, usize) = (3, 1);
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let offset = 0;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let hint_style = Some(HintStyle::Underline);
|
|
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
ViewController::render_span_hint(
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&mut writer,
|
|
|
|
|
|
hint_text,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(position.0 + offset, position.1),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&colors,
|
|
|
|
|
|
&hint_style,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
writer,
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{goto}{bg}{fg}{sty}{text}{sty_reset}{fg_reset}{bg_reset}",
|
|
|
|
|
|
goto = cursor::Goto(4, 2),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(colors.hint_fg),
|
|
|
|
|
|
bg = color::Bg(colors.hint_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
sty = style::Underline,
|
|
|
|
|
|
sty_reset = style::NoUnderline,
|
|
|
|
|
|
text = "eo",
|
|
|
|
|
|
)
|
|
|
|
|
|
.as_bytes()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
2021-03-23 00:08:47 +01:00
|
|
|
|
fn test_render_bracketed_span_hint() {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
|
|
|
|
|
let hint_text = "eo";
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let position: (usize, usize) = (3, 1);
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-25 09:59:21 +01:00
|
|
|
|
let offset = 0;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let hint_style = Some(HintStyle::Surround('{', '}'));
|
|
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
ViewController::render_span_hint(
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&mut writer,
|
|
|
|
|
|
hint_text,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
(position.0 + offset, position.1),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&colors,
|
|
|
|
|
|
&hint_style,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
writer,
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{goto}{bg}{fg}{bra}{text}{bra_close}{fg_reset}{bg_reset}",
|
|
|
|
|
|
goto = cursor::Goto(4, 2),
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(colors.hint_fg),
|
|
|
|
|
|
bg = color::Bg(colors.hint_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
|
|
|
|
|
bra = '{',
|
|
|
|
|
|
bra_close = '}',
|
|
|
|
|
|
text = "eo",
|
|
|
|
|
|
)
|
|
|
|
|
|
.as_bytes()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
#[test]
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Simulates rendering without any span.
|
|
|
|
|
|
fn test_render_full_without_available_spans() {
|
2021-03-22 08:48:51 +01:00
|
|
|
|
let buffer = "lorem 127.0.0.1 lorem
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
|
|
|
|
|
Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
2021-03-22 08:48:51 +01:00
|
|
|
|
let lines = buffer.split('\n').collect::<Vec<_>>();
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2021-03-22 08:48:51 +01:00
|
|
|
|
let use_all_patterns = false;
|
2020-06-01 07:30:00 +02:00
|
|
|
|
let named_pat = vec![];
|
2020-06-04 09:45:58 +02:00
|
|
|
|
let custom_patterns = vec![];
|
2021-03-20 07:46:43 +01:00
|
|
|
|
let alphabet = alphabet::Alphabet("abcd".to_string());
|
2020-06-04 09:45:58 +02:00
|
|
|
|
let reverse = false;
|
2021-03-22 07:17:54 +01:00
|
|
|
|
let unique_hint = false;
|
2021-03-19 23:29:36 +01:00
|
|
|
|
let mut model = textbuf::Model::new(
|
2021-03-22 08:48:51 +01:00
|
|
|
|
&lines,
|
2020-06-04 09:45:58 +02:00
|
|
|
|
&alphabet,
|
|
|
|
|
|
use_all_patterns,
|
|
|
|
|
|
&named_pat,
|
|
|
|
|
|
&custom_patterns,
|
|
|
|
|
|
reverse,
|
2021-03-22 07:17:54 +01:00
|
|
|
|
unique_hint,
|
2020-06-04 09:45:58 +02:00
|
|
|
|
);
|
2020-06-02 13:10:48 +02:00
|
|
|
|
let term_width: u16 = 80;
|
2022-11-07 00:02:27 +01:00
|
|
|
|
let wrapped_lines = compute_wrapped_lines(model.lines, term_width);
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let rendering_colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
let hint_alignment = HintAlignment::Leading;
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// create a Ui without any span
|
2021-03-19 09:00:42 +01:00
|
|
|
|
let ui = ViewController {
|
2020-06-01 09:33:40 +02:00
|
|
|
|
model: &mut model,
|
2020-06-02 13:10:48 +02:00
|
|
|
|
term_width,
|
2021-03-25 09:59:21 +01:00
|
|
|
|
wrapped_lines,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
focus_index: 0,
|
2020-06-01 10:06:50 +02:00
|
|
|
|
focus_wrap_around: false,
|
2021-03-17 07:55:24 +01:00
|
|
|
|
default_output_destination: OutputDestination::Tmux,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
rendering_colors: &rendering_colors,
|
2020-06-01 15:34:54 +02:00
|
|
|
|
hint_alignment: &hint_alignment,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_style: None,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let mut writer = vec![];
|
2020-06-01 15:25:54 +02:00
|
|
|
|
ui.full_render(&mut writer);
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
let goto1 = cursor::Goto(1, 1);
|
|
|
|
|
|
let goto3 = cursor::Goto(1, 3);
|
|
|
|
|
|
|
|
|
|
|
|
let expected = format!(
|
2020-06-02 09:38:42 +02:00
|
|
|
|
"{bg}{fg}{goto1}lorem 127.0.0.1 lorem\
|
|
|
|
|
|
{goto3}Barcelona https://en.wikipedia.org/wiki/Barcelona -{fg_reset}{bg_reset}",
|
2020-05-25 23:06:00 +02:00
|
|
|
|
goto1 = goto1,
|
|
|
|
|
|
goto3 = goto3,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(rendering_colors.text_fg),
|
|
|
|
|
|
bg = color::Bg(rendering_colors.text_bg),
|
2020-05-29 20:36:27 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
);
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
// println!("{:?}", writer);
|
|
|
|
|
|
// println!("{:?}", expected.as_bytes());
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
assert_eq!(writer, expected.as_bytes());
|
|
|
|
|
|
}
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
#[test]
|
2021-03-22 23:56:35 +01:00
|
|
|
|
/// Simulates rendering with available spans.
|
|
|
|
|
|
fn test_render_full_with_spans() {
|
2021-03-22 08:48:51 +01:00
|
|
|
|
let buffer = "lorem 127.0.0.1 lorem
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
|
|
|
|
|
Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
2021-03-22 08:48:51 +01:00
|
|
|
|
let lines = buffer.split('\n').collect::<Vec<_>>();
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-06-04 09:45:58 +02:00
|
|
|
|
let use_all_patterns = true;
|
2020-06-01 07:30:00 +02:00
|
|
|
|
let named_pat = vec![];
|
2020-06-04 09:45:58 +02:00
|
|
|
|
let custom_patterns = vec![];
|
2021-03-20 07:46:43 +01:00
|
|
|
|
let alphabet = alphabet::Alphabet("abcd".to_string());
|
2020-05-30 22:23:33 +02:00
|
|
|
|
let reverse = true;
|
2021-03-22 07:17:54 +01:00
|
|
|
|
let unique_hint = false;
|
2021-03-19 23:29:36 +01:00
|
|
|
|
let mut model = textbuf::Model::new(
|
2021-03-22 08:48:51 +01:00
|
|
|
|
&lines,
|
2020-06-04 09:45:58 +02:00
|
|
|
|
&alphabet,
|
|
|
|
|
|
use_all_patterns,
|
|
|
|
|
|
&named_pat,
|
|
|
|
|
|
&custom_patterns,
|
|
|
|
|
|
reverse,
|
2021-03-22 07:17:54 +01:00
|
|
|
|
unique_hint,
|
2020-06-04 09:45:58 +02:00
|
|
|
|
);
|
2020-06-01 10:06:50 +02:00
|
|
|
|
let wrap_around = false;
|
2021-03-17 07:55:24 +01:00
|
|
|
|
let default_output_destination = OutputDestination::Tmux;
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2020-06-01 15:25:54 +02:00
|
|
|
|
let rendering_colors = UiColors {
|
2022-11-07 00:02:27 +01:00
|
|
|
|
text_fg: colors::BLACK,
|
|
|
|
|
|
text_bg: colors::WHITE,
|
|
|
|
|
|
focused_fg: colors::RED,
|
|
|
|
|
|
focused_bg: colors::BLUE,
|
|
|
|
|
|
span_fg: colors::GREEN,
|
|
|
|
|
|
span_bg: colors::MAGENTA,
|
|
|
|
|
|
hint_fg: colors::YELLOW,
|
|
|
|
|
|
hint_bg: colors::CYAN,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
let hint_alignment = HintAlignment::Leading;
|
|
|
|
|
|
let hint_style = None;
|
|
|
|
|
|
|
2021-03-19 09:00:42 +01:00
|
|
|
|
let ui = ViewController::new(
|
2020-06-01 09:33:40 +02:00
|
|
|
|
&mut model,
|
2020-06-01 10:06:50 +02:00
|
|
|
|
wrap_around,
|
2021-03-17 07:55:24 +01:00
|
|
|
|
default_output_destination,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
&rendering_colors,
|
2020-06-01 15:34:54 +02:00
|
|
|
|
&hint_alignment,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
hint_style,
|
|
|
|
|
|
);
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let mut writer = vec![];
|
2020-06-01 15:25:54 +02:00
|
|
|
|
ui.full_render(&mut writer);
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let expected_content = {
|
|
|
|
|
|
let goto1 = cursor::Goto(1, 1);
|
|
|
|
|
|
let goto3 = cursor::Goto(1, 3);
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
format!(
|
2020-06-02 09:38:42 +02:00
|
|
|
|
"{bg}{fg}{goto1}lorem 127.0.0.1 lorem\
|
|
|
|
|
|
{goto3}Barcelona https://en.wikipedia.org/wiki/Barcelona -{fg_reset}{bg_reset}",
|
2020-05-25 23:06:00 +02:00
|
|
|
|
goto1 = goto1,
|
|
|
|
|
|
goto3 = goto3,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
fg = color::Fg(rendering_colors.text_fg),
|
|
|
|
|
|
bg = color::Bg(rendering_colors.text_bg),
|
2020-05-29 20:36:27 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset)
|
2020-05-25 23:06:00 +02:00
|
|
|
|
)
|
|
|
|
|
|
};
|
2020-06-02 20:03:16 +02:00
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
let expected_span1_text = {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let goto7_1 = cursor::Goto(7, 1);
|
|
|
|
|
|
format!(
|
2021-03-22 23:56:35 +01:00
|
|
|
|
"{goto7_1}{span_bg}{span_fg}127.0.0.1{fg_reset}{bg_reset}",
|
2020-05-25 23:06:00 +02:00
|
|
|
|
goto7_1 = goto7_1,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
span_fg = color::Fg(rendering_colors.span_fg),
|
|
|
|
|
|
span_bg = color::Bg(rendering_colors.span_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset)
|
|
|
|
|
|
)
|
|
|
|
|
|
};
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
let expected_span1_hint = {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let goto7_1 = cursor::Goto(7, 1);
|
|
|
|
|
|
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{goto7_1}{hint_bg}{hint_fg}b{fg_reset}{bg_reset}",
|
|
|
|
|
|
goto7_1 = goto7_1,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
hint_fg = color::Fg(rendering_colors.hint_fg),
|
|
|
|
|
|
hint_bg = color::Bg(rendering_colors.hint_bg),
|
2020-05-25 23:06:00 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset)
|
|
|
|
|
|
)
|
|
|
|
|
|
};
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
let expected_span2_text = {
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let goto11_3 = cursor::Goto(11, 3);
|
|
|
|
|
|
format!(
|
2020-05-23 09:30:09 +02:00
|
|
|
|
"{goto11_3}{focus_bg}{focus_fg}https://en.wikipedia.org/wiki/Barcelona{fg_reset}{bg_reset}",
|
2020-05-22 15:03:39 +02:00
|
|
|
|
goto11_3 = goto11_3,
|
2022-11-07 00:02:27 +01:00
|
|
|
|
focus_fg = color::Fg(rendering_colors.focused_fg),
|
|
|
|
|
|
focus_bg = color::Bg(rendering_colors.focused_bg),
|
2020-05-22 15:03:39 +02:00
|
|
|
|
fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
bg_reset = color::Bg(color::Reset)
|
|
|
|
|
|
)
|
2020-05-25 23:06:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
// Because reverse is true, this second span is focused,
|
2020-06-01 10:34:20 +02:00
|
|
|
|
// then the hint should not be rendered.
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
2021-03-23 00:08:47 +01:00
|
|
|
|
// let expected_span2_hint = {
|
2020-06-01 10:34:20 +02:00
|
|
|
|
// let goto11_3 = cursor::Goto(11, 3);
|
|
|
|
|
|
|
|
|
|
|
|
// format!(
|
|
|
|
|
|
// "{goto11_3}{hint_bg}{hint_fg}a{fg_reset}{bg_reset}",
|
|
|
|
|
|
// goto11_3 = goto11_3,
|
|
|
|
|
|
// hint_fg = color::Fg(rendering_colors.hint_fg.as_ref()),
|
|
|
|
|
|
// hint_bg = color::Bg(rendering_colors.hint_bg.as_ref()),
|
|
|
|
|
|
// fg_reset = color::Fg(color::Reset),
|
|
|
|
|
|
// bg_reset = color::Bg(color::Reset)
|
|
|
|
|
|
// )
|
|
|
|
|
|
// };
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
let expected = [
|
|
|
|
|
|
expected_content,
|
2021-03-23 00:08:47 +01:00
|
|
|
|
expected_span1_text,
|
|
|
|
|
|
expected_span1_hint,
|
|
|
|
|
|
expected_span2_text,
|
|
|
|
|
|
// expected_span2_hint,
|
2020-05-25 23:06:00 +02:00
|
|
|
|
]
|
|
|
|
|
|
.concat();
|
2020-05-22 15:03:39 +02:00
|
|
|
|
|
2020-05-25 23:06:00 +02:00
|
|
|
|
// println!("{:?}", writer);
|
|
|
|
|
|
// println!("{:?}", expected.as_bytes());
|
|
|
|
|
|
|
|
|
|
|
|
// let diff_point = writer
|
|
|
|
|
|
// .iter()
|
|
|
|
|
|
// .zip(expected.as_bytes().iter())
|
|
|
|
|
|
// .enumerate()
|
|
|
|
|
|
// .find(|(_idx, (&l, &r))| l != r);
|
|
|
|
|
|
// println!("{:?}", diff_point);
|
|
|
|
|
|
|
2021-03-22 23:56:35 +01:00
|
|
|
|
assert_eq!(2, ui.model.spans.len());
|
2020-05-25 23:06:00 +02:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(writer, expected.as_bytes());
|
|
|
|
|
|
}
|
2020-06-02 20:03:16 +02:00
|
|
|
|
}
|