1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-03 06:16:17 +03:00

refactor text selection and add more tests (fix #6)

Этот коммит содержится в:
rhysd
2023-11-12 17:39:48 +09:00
родитель 5a2649abe9
Коммит a54ce36d22
11 изменённых файлов: 1338 добавлений и 918 удалений

Просмотреть файл

@@ -10,12 +10,11 @@ use std::iter;
use tui::text::Spans as Line;
use unicode_width::UnicodeWidthChar as _;
#[derive(Debug)]
enum Boundary {
Cursor(Style),
Select(Style),
#[cfg(feature = "search")]
Search(Style),
Select(Style),
End,
}
@@ -25,8 +24,8 @@ impl Boundary {
match b {
Boundary::Cursor(_) => 3,
#[cfg(feature = "search")]
Boundary::Search(_) => 1,
Boundary::Select(_) => 2,
Boundary::Search(_) => 2,
Boundary::Select(_) => 1,
Boundary::End => 0,
}
}
@@ -36,10 +35,10 @@ impl Boundary {
fn style(&self) -> Option<Style> {
match self {
Boundary::Cursor(s) => Some(*s),
Boundary::Select(s) => Some(*s),
#[cfg(feature = "search")]
Boundary::Search(s) => Some(*s),
Boundary::End => None,
Boundary::Select(s) => Some(*s),
}
}
}
@@ -98,13 +97,13 @@ impl DisplayTextBuilder {
pub struct LineHighlighter<'a> {
line: &'a str,
spans: Vec<Span<'a>>,
// reminder - the usize is the start of a section, in BYTES, not chars
boundaries: Vec<(Boundary, usize)>, // TODO: Consider smallvec
style_begin: Style,
cursor_at_end: bool,
cursor_style: Style,
tab_len: u8,
mask: Option<char>,
select_at_end: bool,
select_style: Style,
}
@@ -125,6 +124,7 @@ impl<'a> LineHighlighter<'a> {
cursor_style,
tab_len,
mask,
select_at_end: false,
select_style,
}
}
@@ -135,11 +135,6 @@ impl<'a> LineHighlighter<'a> {
.push(Span::styled(format!("{}{} ", pad, row + 1), style));
}
pub fn select(&mut self, start: usize, end: usize, style: Style) {
self.boundaries.push((Boundary::Select(style), start));
self.boundaries.push((Boundary::End, end));
}
pub fn cursor_line(&mut self, cursor_col: usize, style: Style) {
if let Some((start, c)) = self.line.char_indices().nth(cursor_col) {
self.boundaries
@@ -151,60 +146,6 @@ impl<'a> LineHighlighter<'a> {
self.style_begin = style;
}
pub(crate) fn select_line(
&mut self,
row: usize,
line: &str,
start: (usize, usize),
end: (usize, usize),
) {
// is this row selected?
// note that the input coordinates here are in char offsets not bytes
// so a lot of this code is converting from char offsets to byte offsets
if start.0 <= row && end.0 >= row {
let mut indices = line.char_indices();
let llen = line.len();
match (start.0 == row, end.0 == row) {
(true, true) => {
// only line
self.select(
indices.nth(start.1).unwrap_or((llen, 'x')).0,
indices.nth(end.1 - start.1).unwrap_or((llen, 'x')).0,
self.select_style,
);
}
(true, false) => {
// a line in the start of selection
// the +1 extends the highlight one beyond end, to show
// that the newline is included
// same done for middle rows below
self.select(
indices.nth(start.1).unwrap_or((llen, 'x')).0,
llen + 1,
self.select_style,
);
}
(false, true) => {
// last line
if end.1 > 0 {
self.select(
0,
indices.nth(end.1).unwrap_or((llen, 'x')).0,
self.select_style,
);
}
}
(false, false) => {
// in the middle of selection
self.select(0, llen + 1, self.select_style);
}
}
}
}
#[cfg(feature = "search")]
pub fn search(&mut self, matches: impl Iterator<Item = (usize, usize)>, style: Style) {
for (start, end) in matches {
@@ -215,6 +156,36 @@ impl<'a> LineHighlighter<'a> {
}
}
pub fn selection(
&mut self,
row: usize,
start_row: usize,
start_off: usize,
end_row: usize,
end_off: usize,
) {
let (start, end) = if row == start_row {
if start_row == end_row {
(start_off, end_off)
} else {
self.select_at_end = true;
(start_off, self.line.len())
}
} else if row == end_row {
(0, end_off)
} else if start_row < row && row < end_row {
self.select_at_end = true;
(0, self.line.len())
} else {
return;
};
if start != end {
self.boundaries
.push((Boundary::Select(self.select_style), start));
self.boundaries.push((Boundary::End, end));
}
}
pub fn into_spans(self) -> Line<'a> {
let Self {
line,
@@ -225,14 +196,20 @@ impl<'a> LineHighlighter<'a> {
cursor_style,
cursor_at_end,
mask,
select_at_end,
select_style,
} = self;
let mut builder = DisplayTextBuilder::new(tab_len, mask);
if boundaries.is_empty() {
spans.push(Span::styled(builder.build(line), style_begin));
let built = builder.build(line);
if !built.is_empty() {
spans.push(Span::styled(built, style_begin));
}
if cursor_at_end {
spans.push(Span::styled(" ", cursor_style));
} else if select_at_end {
spans.push(Span::styled(" ", select_style));
}
return Line::from(spans);
}
@@ -245,18 +222,10 @@ impl<'a> LineHighlighter<'a> {
let mut style = style_begin;
let mut start = 0;
let mut stack = vec![];
let mut dont_add_cursor = false;
for (next_boundary, end) in boundaries {
if start < end {
// add extra select space at line end to indicate
// that the \n will be deleted / included
if end > line.len() && style == select_style {
spans.push(Span::styled(builder.build(&line[start..end - 1]), style));
spans.push(Span::styled(" ", style));
dont_add_cursor = true;
} else {
spans.push(Span::styled(builder.build(&line[start..end]), style));
}
spans.push(Span::styled(builder.build(&line[start..end]), style));
}
style = if let Some(s) = next_boundary.style() {
@@ -267,20 +236,27 @@ impl<'a> LineHighlighter<'a> {
};
start = end;
}
if start < line.len() {
if start != line.len() {
spans.push(Span::styled(builder.build(&line[start..]), style));
}
if cursor_at_end && !dont_add_cursor {
if cursor_at_end {
spans.push(Span::styled(" ", cursor_style));
} else if select_at_end {
spans.push(Span::styled(" ", select_style));
}
Line::from(spans)
}
}
#[cfg(test)]
// Tests for spans don't work with tui-rs
#[cfg(all(test, feature = "ratatui"))]
mod tests {
use super::*;
use crate::ratatui::style::Color;
use std::fmt::Debug;
use unicode_width::UnicodeWidthStr as _;
fn build(text: &'static str, tab: u8, mask: Option<char>) -> Cow<'static, str> {
@@ -374,5 +350,255 @@ mod tests {
assert_eq!(&build_with_offset(2, "\t\t", 4), "あ あ ");
}
// TODO: Add tests for LineHighlighter
fn assert_spans<T: Debug>(lh: LineHighlighter, want: &[(&str, Style)], context: T) {
let line = lh.into_spans();
let have = line
.spans
.iter()
.map(|s| (s.content.as_ref(), s.style))
.collect::<Vec<_>>();
assert_eq!(&have, want, "Test case: {context:?}");
}
const DEFAULT: Style = Style::new();
const CUR: Style = Style::new().bg(Color::Red); // Cursor
#[allow(unused)]
const SEARCH: Style = Style::new().bg(Color::Green);
const SEL: Style = Style::new().bg(Color::Blue);
const LINE: Style = Style::new().bg(Color::Gray);
const LNUM: Style = Style::new().bg(Color::Yellow);
#[test]
fn into_spans_normal_line() {
let tests = [
("", &[][..]),
("abc", &[("abc", DEFAULT)][..]),
("a\tb\tc", &[("a b c", DEFAULT)][..]),
];
for test in tests {
let (line, want) = test;
let lh = LineHighlighter::new(line, CUR, 4, None, SEL);
assert_spans(lh, want, test);
}
}
#[test]
fn into_spans_cursor_line() {
let tests = [
("", 0, &[(" ", CUR)][..]),
("a", 0, &[("a", CUR)][..]),
("a", 1, &[("a", LINE), (" ", CUR)][..]),
("あいう", 0, &[("", CUR), ("いう", LINE)][..]),
("あいう", 1, &[("", LINE), ("", CUR), ("", LINE)][..]),
("あいう", 2, &[("あい", LINE), ("", CUR)][..]),
("a\tb", 1, &[("a", LINE), (" ", CUR), ("b", LINE)][..]),
];
for test in tests {
let (line, col, want) = test;
let mut lh = LineHighlighter::new(line, CUR, 4, None, SEL);
lh.cursor_line(col, LINE);
assert_spans(lh, want, test);
}
}
#[test]
fn into_spans_line_number() {
let tests = [
(0, 1, &[(" 1 ", LNUM)][..]),
(123, 3, &[(" 124 ", LNUM)][..]),
(123, 5, &[(" 124 ", LNUM)][..]),
];
for test in tests {
let (row, len, want) = test;
let mut lh = LineHighlighter::new("", CUR, 4, None, SEL);
lh.line_number(row, len, LNUM);
assert_spans(lh, want, test);
}
}
#[cfg(feature = "search")]
#[test]
fn into_spans_search() {
let tests = [
("abcde", &[(0, 5)][..], &[("abcde", SEARCH)][..]),
(
"abcde",
&[(0, 1), (2, 3), (4, 5)][..],
&[
("a", SEARCH),
("b", DEFAULT),
("c", SEARCH),
("d", DEFAULT),
("e", SEARCH),
][..],
),
(
"abcde",
&[(1, 2), (3, 4)][..],
&[
("a", DEFAULT),
("b", SEARCH),
("c", DEFAULT),
("d", SEARCH),
("e", DEFAULT),
][..],
),
(
"abcde",
&[(0, 2), (2, 4), (4, 5)][..],
&[("ab", SEARCH), ("cd", SEARCH), ("e", SEARCH)][..],
),
("abcde", &[(1, 1)][..], &[("abcde", DEFAULT)][..]),
(
"あいうえお",
&[(0, 3), (6, 9), (12, 15)][..],
&[
("", SEARCH),
("", DEFAULT),
("", SEARCH),
("", DEFAULT),
("", SEARCH),
][..],
),
(
"\ta\tb\t",
&[(0, 1), (2, 3), (3, 4)][..],
&[
(" ", SEARCH),
("a", DEFAULT),
(" ", SEARCH),
("b", SEARCH),
(" ", DEFAULT),
][..],
),
];
for test in tests {
let (line, matches, want) = test;
let mut lh = LineHighlighter::new(line, CUR, 4, None, SEL);
lh.search(matches.iter().copied(), SEARCH);
assert_spans(lh, want, test);
}
}
#[test]
fn into_spans_selection() {
let tests = [
// (line, (row, start_row, start_off, end_row, end_off), want)
("abc", (0, 1, 0, 2, 0), &[("abc", DEFAULT)][..]),
("abc", (1, 1, 0, 1, 1), &[("a", SEL), ("bc", DEFAULT)][..]),
("abc", (1, 1, 2, 1, 3), &[("ab", DEFAULT), ("c", SEL)][..]),
("abc", (1, 1, 0, 1, 3), &[("abc", SEL)][..]),
("abc", (1, 1, 0, 2, 0), &[("abc", SEL), (" ", SEL)][..]),
(
"abc",
(1, 1, 2, 2, 0),
&[("ab", DEFAULT), ("c", SEL), (" ", SEL)][..],
),
("abc", (1, 1, 3, 2, 0), &[("abc", DEFAULT), (" ", SEL)][..]),
("abc", (2, 1, 0, 3, 0), &[("abc", SEL), (" ", SEL)][..]),
("abc", (2, 1, 0, 2, 0), &[("abc", DEFAULT)][..]),
("abc", (2, 1, 0, 2, 2), &[("ab", SEL), ("c", DEFAULT)][..]),
("abc", (2, 1, 0, 2, 3), &[("abc", SEL)][..]),
(
"ab\t",
(1, 1, 2, 2, 0),
&[("ab", DEFAULT), (" ", SEL), (" ", SEL)][..],
),
("a\tb", (2, 1, 0, 3, 0), &[("a b", SEL), (" ", SEL)][..]),
(
"a\tb",
(2, 1, 0, 2, 2),
&[("a ", SEL), ("b", DEFAULT)][..],
),
];
for test in tests {
let (line, (row, start_row, start_off, end_row, end_off), want) = test;
let mut lh = LineHighlighter::new(line, CUR, 4, None, SEL);
lh.selection(row, start_row, start_off, end_row, end_off);
assert_spans(lh, want, test);
}
}
#[test]
fn into_spans_mixed_highlights() {
let tests = [
(
"cursor on selection",
{
let mut lh = LineHighlighter::new("abcde", CUR, 4, None, SEL);
lh.cursor_line(2, LINE);
lh.selection(0, 0, 1, 0, 4);
lh
},
&[("a", LINE), ("b", SEL), ("c", CUR), ("d", SEL), ("e", LINE)][..],
),
#[cfg(feature = "search")]
(
"cursor + selection + search",
{
let mut lh = LineHighlighter::new("abcdefg", CUR, 4, None, SEL);
lh.cursor_line(3, LINE);
lh.selection(0, 0, 2, 0, 5);
lh.search([(1, 2), (5, 6)].into_iter(), SEARCH);
lh
},
&[
("a", LINE),
("b", SEARCH),
("c", SEL),
("d", CUR),
("e", SEL),
("f", SEARCH),
("g", LINE),
][..],
),
(
"selection + cursor at end",
{
let mut lh = LineHighlighter::new("ab", CUR, 4, None, SEL);
lh.cursor_line(2, LINE);
lh.selection(0, 0, 1, 2, 0);
lh
},
&[("a", LINE), ("b", SEL), (" ", CUR)][..],
),
(
"cursor at start of selection",
{
let mut lh = LineHighlighter::new("abcd", CUR, 4, None, SEL);
lh.cursor_line(1, LINE);
lh.selection(0, 0, 1, 0, 3);
lh
},
&[("a", LINE), ("b", CUR), ("c", SEL), ("d", LINE)][..],
),
(
"cursor at end of selection",
{
let mut lh = LineHighlighter::new("abcd", CUR, 4, None, SEL);
lh.cursor_line(2, LINE);
lh.selection(0, 0, 1, 0, 3);
lh
},
&[("a", LINE), ("b", SEL), ("c", CUR), ("d", LINE)][..],
),
(
"cursor covers selection",
{
let mut lh = LineHighlighter::new("abc", CUR, 4, None, SEL);
lh.cursor_line(1, LINE);
lh.selection(0, 0, 1, 0, 2);
lh
},
&[("a", LINE), ("b", CUR), ("c", LINE)][..],
),
];
for (what, lh, want) in tests {
assert_spans(lh, want, what);
}
}
}

Просмотреть файл

@@ -79,7 +79,6 @@ impl From<MouseEvent> for Input {
let ctrl = mouse.modifiers.contains(KeyModifiers::CONTROL);
let alt = mouse.modifiers.contains(KeyModifiers::ALT);
let shift = mouse.modifiers.contains(KeyModifiers::SHIFT);
Self {
key,
ctrl,
@@ -118,27 +117,34 @@ mod tests {
for (from, to) in [
(
key_event(KeyCode::Char('a'), KeyModifiers::empty()),
input(Key::Char('a'), false, false),
input(Key::Char('a'), false, false, false),
),
(
key_event(KeyCode::Enter, KeyModifiers::empty()),
input(Key::Enter, false, false),
input(Key::Enter, false, false, false),
),
(
key_event(KeyCode::Left, KeyModifiers::CONTROL),
input(Key::Left, true, false),
input(Key::Left, true, false, false),
),
(
key_event(KeyCode::Right, KeyModifiers::SHIFT),
input(Key::Right, false, false, true),
),
(
key_event(KeyCode::Home, KeyModifiers::ALT),
input(Key::Home, false, true),
input(Key::Home, false, true, false),
),
(
key_event(KeyCode::F(1), KeyModifiers::ALT | KeyModifiers::CONTROL),
input(Key::F(1), true, true),
key_event(
KeyCode::F(1),
KeyModifiers::ALT | KeyModifiers::CONTROL | KeyModifiers::SHIFT,
),
input(Key::F(1), true, true, true),
),
(
key_event(KeyCode::NumLock, KeyModifiers::CONTROL),
input(Key::Null, true, false),
input(Key::Null, true, false, false),
),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
@@ -150,26 +156,30 @@ mod tests {
for (from, to) in [
(
mouse_event(MouseEventKind::ScrollDown, KeyModifiers::empty()),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(
mouse_event(MouseEventKind::ScrollUp, KeyModifiers::CONTROL),
input(Key::MouseScrollUp, true, false),
input(Key::MouseScrollUp, true, false, false),
),
(
mouse_event(MouseEventKind::ScrollUp, KeyModifiers::SHIFT),
input(Key::MouseScrollUp, false, false, true),
),
(
mouse_event(MouseEventKind::ScrollDown, KeyModifiers::ALT),
input(Key::MouseScrollDown, false, true),
input(Key::MouseScrollDown, false, true, false),
),
(
mouse_event(
MouseEventKind::ScrollUp,
KeyModifiers::CONTROL | KeyModifiers::ALT,
),
input(Key::MouseScrollUp, true, true),
input(Key::MouseScrollUp, true, true, false),
),
(
mouse_event(MouseEventKind::Moved, KeyModifiers::CONTROL),
input(Key::Null, true, false),
input(Key::Null, true, false, false),
),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
@@ -181,16 +191,16 @@ mod tests {
for (from, to) in [
(
Event::Key(key_event(KeyCode::Char('a'), KeyModifiers::empty())),
input(Key::Char('a'), false, false),
input(Key::Char('a'), false, false, false),
),
(
Event::Mouse(mouse_event(
MouseEventKind::ScrollDown,
KeyModifiers::empty(),
)),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(Event::FocusGained, input(Key::Null, false, false)),
(Event::FocusGained, input(Key::Null, false, false, false)),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
}
@@ -201,7 +211,7 @@ mod tests {
fn ignore_key_release_event() {
let mut from = key_event(KeyCode::Char('a'), KeyModifiers::empty());
from.kind = KeyEventKind::Release;
let to = input(Key::Null, false, false);
let to = input(Key::Null, false, false, false);
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
}

Просмотреть файл

@@ -90,7 +90,8 @@ impl Default for Key {
/// textarea.input(Input {
/// key: Key::Char('a'),
/// ctrl: true,
/// alt: false,shift:false
/// alt: false,
/// shift: false,
/// });
/// ```
#[derive(Debug, Clone, Default, PartialEq, Hash)]
@@ -102,7 +103,7 @@ pub struct Input {
pub ctrl: bool,
/// Alt modifier key. `true` means Alt key was pressed.
pub alt: bool,
// Shift key. `true` means Shift key was pressed.
/// Shift modifier key. `true` means Alt key was pressed.
pub shift: bool,
}
@@ -111,12 +112,12 @@ mod tests {
use super::*;
#[allow(dead_code)]
pub(crate) fn input(key: Key, ctrl: bool, alt: bool) -> Input {
pub(crate) fn input(key: Key, ctrl: bool, alt: bool, shift: bool) -> Input {
Input {
key,
ctrl,
alt,
shift: false,
shift,
}
}

Просмотреть файл

@@ -17,7 +17,6 @@ impl From<KeyEvent> for Input {
fn from(key: KeyEvent) -> Self {
let mut ctrl = false;
let mut alt = false;
let shift = false;
let key = match key {
KeyEvent::Char('\n' | '\r') => Key::Enter,
KeyEvent::Char(c) => Key::Char(c),
@@ -49,7 +48,7 @@ impl From<KeyEvent> for Input {
key,
ctrl,
alt,
shift,
shift: false,
}
}
}
@@ -90,14 +89,23 @@ mod tests {
#[test]
fn key_to_input() {
for (from, to) in [
(KeyEvent::Char('a'), input(Key::Char('a'), false, false)),
(KeyEvent::Ctrl('a'), input(Key::Char('a'), true, false)),
(KeyEvent::Alt('a'), input(Key::Char('a'), false, true)),
(KeyEvent::Char('\n'), input(Key::Enter, false, false)),
(KeyEvent::Char('\r'), input(Key::Enter, false, false)),
(KeyEvent::F(1), input(Key::F(1), false, false)),
(KeyEvent::BackTab, input(Key::Tab, false, false)),
(KeyEvent::Null, input(Key::Null, false, false)),
(
KeyEvent::Char('a'),
input(Key::Char('a'), false, false, false),
),
(
KeyEvent::Ctrl('a'),
input(Key::Char('a'), true, false, false),
),
(
KeyEvent::Alt('a'),
input(Key::Char('a'), false, true, false),
),
(KeyEvent::Char('\n'), input(Key::Enter, false, false, false)),
(KeyEvent::Char('\r'), input(Key::Enter, false, false, false)),
(KeyEvent::F(1), input(Key::F(1), false, false, false)),
(KeyEvent::BackTab, input(Key::Tab, false, false, false)),
(KeyEvent::Null, input(Key::Null, false, false, false)),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
@@ -108,18 +116,24 @@ mod tests {
for (from, to) in [
(
MouseEvent::Press(MouseButton::WheelDown, 1, 1),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(
MouseEvent::Press(MouseButton::WheelUp, 1, 1),
input(Key::MouseScrollUp, false, false),
input(Key::MouseScrollUp, false, false, false),
),
(
MouseEvent::Press(MouseButton::Left, 1, 1),
input(Key::Null, false, false),
input(Key::Null, false, false, false),
),
(
MouseEvent::Release(1, 1),
input(Key::Null, false, false, false),
),
(
MouseEvent::Hold(1, 1),
input(Key::Null, false, false, false),
),
(MouseEvent::Release(1, 1), input(Key::Null, false, false)),
(MouseEvent::Hold(1, 1), input(Key::Null, false, false)),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
@@ -130,13 +144,16 @@ mod tests {
for (from, to) in [
(
Event::Key(KeyEvent::Char('a')),
input(Key::Char('a'), false, false),
input(Key::Char('a'), false, false, false),
),
(
Event::Mouse(MouseEvent::Press(MouseButton::WheelDown, 1, 1)),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(
Event::Unsupported(vec![]),
input(Key::Null, false, false, false),
),
(Event::Unsupported(vec![]), input(Key::Null, false, false)),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
}

Просмотреть файл

@@ -47,6 +47,7 @@ impl From<KeyEvent> for Input {
let ctrl = modifiers.contains(Modifiers::CTRL);
let alt = modifiers.contains(Modifiers::ALT);
let shift = modifiers.contains(Modifiers::SHIFT);
Self {
key,
ctrl,
@@ -83,6 +84,7 @@ impl From<MouseEvent> for Input {
let ctrl = modifiers.contains(Modifiers::CTRL);
let alt = modifiers.contains(Modifiers::ALT);
let shift = modifiers.contains(Modifiers::SHIFT);
Self {
key,
ctrl,
@@ -105,6 +107,7 @@ impl From<PixelMouseEvent> for Input {
let ctrl = modifiers.contains(Modifiers::CTRL);
let alt = modifiers.contains(Modifiers::ALT);
let shift = modifiers.contains(Modifiers::SHIFT);
Self {
key,
ctrl,
@@ -146,27 +149,34 @@ mod tests {
for (from, to) in [
(
key_event(KeyCode::Char('a'), Modifiers::empty()),
input(Key::Char('a'), false, false),
input(Key::Char('a'), false, false, false),
),
(
key_event(KeyCode::Enter, Modifiers::empty()),
input(Key::Enter, false, false),
input(Key::Enter, false, false, false),
),
(
key_event(KeyCode::LeftArrow, Modifiers::CTRL),
input(Key::Left, true, false),
input(Key::Left, true, false, false),
),
(
key_event(KeyCode::RightArrow, Modifiers::SHIFT),
input(Key::Right, false, false, true),
),
(
key_event(KeyCode::Home, Modifiers::ALT),
input(Key::Home, false, true),
input(Key::Home, false, true, false),
),
(
key_event(KeyCode::Function(1), Modifiers::ALT | Modifiers::CTRL),
input(Key::F(1), true, true),
key_event(
KeyCode::Function(1),
Modifiers::ALT | Modifiers::CTRL | Modifiers::SHIFT,
),
input(Key::F(1), true, true, true),
),
(
key_event(KeyCode::NumLock, Modifiers::CTRL),
input(Key::Null, true, false),
input(Key::Null, true, false, false),
),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
@@ -178,30 +188,37 @@ mod tests {
for (from, to) in [
(
mouse_event(MouseButtons::VERT_WHEEL, Modifiers::empty()),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(
mouse_event(
MouseButtons::VERT_WHEEL | MouseButtons::WHEEL_POSITIVE,
Modifiers::empty(),
),
input(Key::MouseScrollUp, false, false),
input(Key::MouseScrollUp, false, false, false),
),
(
mouse_event(MouseButtons::VERT_WHEEL, Modifiers::CTRL),
input(Key::MouseScrollDown, true, false),
input(Key::MouseScrollDown, true, false, false),
),
(
mouse_event(MouseButtons::VERT_WHEEL, Modifiers::SHIFT),
input(Key::MouseScrollDown, false, false, true),
),
(
mouse_event(MouseButtons::VERT_WHEEL, Modifiers::ALT),
input(Key::MouseScrollDown, false, true),
input(Key::MouseScrollDown, false, true, false),
),
(
mouse_event(MouseButtons::VERT_WHEEL, Modifiers::CTRL | Modifiers::ALT),
input(Key::MouseScrollDown, true, true),
mouse_event(
MouseButtons::VERT_WHEEL,
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
),
input(Key::MouseScrollDown, true, true, true),
),
(
mouse_event(MouseButtons::LEFT, Modifiers::empty()),
input(Key::Null, false, false),
input(Key::Null, false, false, false),
),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
@@ -216,22 +233,22 @@ mod tests {
for (from, to) in [
(
InputEvent::Key(key_event(KeyCode::Char('a'), Modifiers::empty())),
input(Key::Char('a'), false, false),
input(Key::Char('a'), false, false, false),
),
(
InputEvent::Mouse(mouse_event(MouseButtons::VERT_WHEEL, Modifiers::empty())),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(
InputEvent::PixelMouse(pixel_mouse_event(
MouseButtons::VERT_WHEEL,
Modifiers::empty(),
)),
input(Key::MouseScrollDown, false, false),
input(Key::MouseScrollDown, false, false, false),
),
(
InputEvent::Paste("x".into()),
input(Key::Null, false, false),
input(Key::Null, false, false, false),
),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);

Разница между файлами не показана из-за своего большого размера Загрузить разницу