From 90b65d59f5dde888f81c42e3c812670929b1740a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 26 Dec 2023 20:31:43 +0100 Subject: upgrade to latest verison of tui-crates and native crossterm events. (#203) --- Cargo.lock | 88 +++++++++-------------- Cargo.toml | 7 +- Makefile | 1 - src/interactive/app/eventloop.rs | 56 ++++++++------- src/interactive/app/tests/journeys_readonly.rs | 44 ++++++------ src/interactive/app/tests/journeys_with_writes.rs | 12 ++-- src/interactive/app/tests/utils.rs | 15 ++-- src/interactive/widgets/glob.rs | 5 +- src/interactive/widgets/help.rs | 16 +++-- src/interactive/widgets/mark.rs | 28 +++++--- src/main.rs | 4 +- src/options.rs | 2 +- 12 files changed, 144 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8c5572..681376e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.42", ] [[package]] @@ -283,13 +283,12 @@ dependencies = [ [[package]] name = "crosstermion" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4fb1963117df5f418acb1b1c9214992e587c9a576158a7177e38fbcf33bf9ea" +checksum = "400415b86f4dc01b9e4e129e822dad900e546287319da7ab229654978d3e07e1" dependencies = [ "crossterm", "ratatui", - "termion", "tui-react", ] @@ -314,7 +313,7 @@ dependencies = [ "gix-glob", "gix-path", "human_format", - "itertools 0.12.0", + "itertools", "jwalk", "num_cpus", "once_cell", @@ -524,15 +523,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.0" @@ -558,17 +548,6 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" -[[package]] -name = "libredox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" -dependencies = [ - "bitflags 2.4.1", - "libc", - "redox_syscall", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -640,12 +619,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - [[package]] name = "objc" version = "0.2.7" @@ -759,19 +732,19 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" +checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" dependencies = [ "bitflags 2.4.1", "cassowary", "crossterm", "indoc", - "itertools 0.11.0", + "itertools", "lru", "paste", + "stability", "strum", - "termion", "unicode-segmentation", "unicode-width", ] @@ -805,12 +778,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_termios" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - [[package]] name = "regex-automata" version = "0.4.3" @@ -846,7 +813,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.42", ] [[package]] @@ -885,6 +852,16 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "stability" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "strsim" version = "0.10.0" @@ -910,14 +887,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.42", ] [[package]] name = "syn" -version = "2.0.42" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -925,15 +902,14 @@ dependencies = [ ] [[package]] -name = "termion" -version = "2.0.3" +name = "syn" +version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4648c7def6f2043b2568617b9f9b75eae88ca185dbc1f1fda30e95a85d49d7d" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" dependencies = [ - "libc", - "libredox", - "numtoa", - "redox_termios", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -953,7 +929,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.42", ] [[package]] @@ -988,9 +964,9 @@ dependencies = [ [[package]] name = "tui-react" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93d64ce9d859e49cba06be4904ecda0fb94b1e22f51e027dc44c122bea9ed5b" +checksum = "2ffacd73e2d4666c1aec4e85ab986354909e276f1dbff5c75f86bcebce566511" dependencies = [ "log", "ratatui", @@ -1318,5 +1294,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.42", ] diff --git a/Cargo.toml b/Cargo.toml index a15e1c7..0c8e35a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ include = ["src/**/*", "Cargo.*", "LICENSE", "README.md", "CHANGELOG.md", "!**/t [features] default = ["tui-crossplatform", "trash-move"] -tui-unix = ["crosstermion/tui-react-termion", "tui-shared"] tui-crossplatform = ["crosstermion/tui-react-crossterm", "tui-shared"] tui-shared = ["tui", "tui-react", "open", "unicode-segmentation", "unicode-width"] @@ -33,9 +32,9 @@ chrono = { version = "0.4.31", default-features = false, features = ["std"] } # 'tui' related unicode-segmentation = { version = "1.3.0", optional = true } unicode-width = { version = "0.1.5", optional = true } -crosstermion = { version = "0.12.0", default-features = false, optional = true } -tui = { package = "ratatui", version = "0.24.0", optional = true, default-features = false } -tui-react = { version = "0.21.0", optional = true } +crosstermion = { version = "0.13.0", default-features = false, optional = true } +tui = { package = "ratatui", version = "0.25.0", optional = true, default-features = false } +tui-react = { version = "0.22.0", optional = true } open = { version = "5.0", optional = true } wild = "2.0.4" owo-colors = "4.0.0" diff --git a/Makefile b/Makefile index 5b633aa..d0cbce1 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,6 @@ check:## run cargo-check with various features cargo check --all cargo check --all-features cargo check --no-default-features - cargo check --no-default-features --features tui-unix cargo check --no-default-features --features tui-crossplatform cargo check --no-default-features --features trash-move diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index 00b37e5..c25b350 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -6,7 +6,8 @@ use crate::interactive::{ SortMode, }; use anyhow::Result; -use crosstermion::input::{input_channel, Event, Key}; +use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crosstermion::input::{input_channel, Event}; use dua::{ traverse::{EntryData, Traversal}, WalkOptions, WalkResult, @@ -104,7 +105,7 @@ impl AppState { where B: Backend, { - use crosstermion::input::Key::*; + use crosstermion::crossterm::event::KeyCode::*; use FocussedPane::*; { @@ -115,7 +116,8 @@ impl AppState { for event in events { let key = match event { Event::Key(key) => key, - Event::Resize(_, _) => Alt('\r'), + Event::Resize(_, _) => refresh_key(), + _ => continue, }; self.reset_message(); @@ -123,20 +125,20 @@ impl AppState { let glob_focussed = self.focussed == Glob; let mut tree_view = self.tree_view(traversal); let mut handled = true; - match key { + match key.code { Esc => { if let Some(value) = self.handle_quit(&mut tree_view, window) { return value; } } - Char('\t') => { + Tab => { self.cycle_focus(window); } Char('/') if !glob_focussed => { self.toggle_glob_search(window); } Char('?') if !glob_focussed => self.toggle_help_pane(window), - Ctrl('c') if !glob_focussed => { + Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) && !glob_focussed => { return Ok(ProcessingResult::ExitRequested(WalkResult { num_errors: tree_view.traversal.io_errors, })) @@ -165,14 +167,12 @@ impl AppState { } Glob => { let glob_pane = window.glob_pane.as_mut().expect("glob pane"); - match key { - Char('\n') => { - self.search_glob_pattern(&mut tree_view, &glob_pane.input) - } + match key.code { + Enter => self.search_glob_pattern(&mut tree_view, &glob_pane.input), _ => glob_pane.process_events(key), } } - Main => match key { + Main => match key.code { Char('O') => self.open_that(&tree_view), Char(' ') => self.mark_entry( CursorMode::KeepPosition, @@ -180,12 +180,6 @@ impl AppState { window, &tree_view, ), - Char('d') => self.mark_entry( - CursorMode::Advance, - MarkEntryMode::Toggle, - window, - &tree_view, - ), Char('x') => self.mark_entry( CursorMode::Advance, MarkEntryMode::MarkForDeletion, @@ -195,24 +189,34 @@ impl AppState { Char('a') => { self.mark_all_entries(MarkEntryMode::Toggle, window, &tree_view) } - Char('u') | Char('h') | Backspace | Left => { - self.exit_node_with_traversal(&tree_view) - } - Char('o') | Char('l') | Char('\n') | Right => { + Char('o') | Char('l') | Enter | Right => { self.enter_node_with_traversal(&tree_view) } Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop), Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom), - Ctrl('u') | PageUp => self.change_entry_selection(CursorDirection::PageUp), + PageUp => self.change_entry_selection(CursorDirection::PageUp), + Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => { + self.change_entry_selection(CursorDirection::PageUp) + } Char('k') | Up => self.change_entry_selection(CursorDirection::Up), Char('j') | Down => self.change_entry_selection(CursorDirection::Down), - Ctrl('d') | PageDown => { + PageDown => self.change_entry_selection(CursorDirection::PageDown), + Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { self.change_entry_selection(CursorDirection::PageDown) } Char('s') => self.cycle_sorting(&tree_view), Char('m') => self.cycle_mtime_sorting(&tree_view), Char('c') => self.cycle_count_sorting(&tree_view), Char('g') => display.byte_vis.cycle(), + Char('d') => self.mark_entry( + CursorMode::Advance, + MarkEntryMode::Toggle, + window, + &tree_view, + ), + Char('u') | Char('h') | Backspace | Left => { + self.exit_node_with_traversal(&tree_view) + } _ => {} }, }; @@ -353,7 +357,7 @@ impl TerminalApp { &mut self.traversal, &mut self.display, terminal, - std::iter::once(Event::Key(Key::Alt('\r'))), + std::iter::once(Event::Key(refresh_key())), ) .ok(); } @@ -482,3 +486,7 @@ pub enum Interaction { #[allow(dead_code)] None, } + +fn refresh_key() -> KeyEvent { + KeyEvent::new(KeyCode::Char('\r'), KeyModifiers::ALT) +} diff --git a/src/interactive/app/tests/journeys_readonly.rs b/src/interactive/app/tests/journeys_readonly.rs index 9e06c3f..6781508 100644 --- a/src/interactive/app/tests/journeys_readonly.rs +++ b/src/interactive/app/tests/journeys_readonly.rs @@ -1,7 +1,9 @@ use anyhow::Result; +use crosstermion::crossterm::event::KeyCode; use pretty_assertions::assert_eq; use std::ffi::OsString; +use crate::interactive::app::tests::utils::into_codes; use crate::interactive::{ app::tests::{ utils::{ @@ -65,28 +67,28 @@ fn simple_user_journey_read_only() -> Result<()> { // SORTING { // when hitting the M key - app.process_events(&mut terminal, into_keys(b"m".iter()))?; + app.process_events(&mut terminal, into_codes("m"))?; assert_eq!( app.state.sorting, SortMode::MTimeDescending, "it sets the sort mode to descending by mtime" ); // when hitting the M key again - app.process_events(&mut terminal, into_keys(b"m".iter()))?; + app.process_events(&mut terminal, into_codes("m"))?; assert_eq!( app.state.sorting, SortMode::MTimeAscending, "it sets the sort mode to ascending by mtime" ); // when hitting the C key - app.process_events(&mut terminal, into_keys(b"c".iter()))?; + app.process_events(&mut terminal, into_codes("c"))?; assert_eq!( app.state.sorting, SortMode::CountDescending, "it sets the sort mode to descending by count" ); // when hitting the C key again - app.process_events(&mut terminal, into_keys(b"c".iter()))?; + app.process_events(&mut terminal, into_codes("c"))?; assert_eq!( app.state.sorting, SortMode::CountAscending, @@ -98,7 +100,7 @@ fn simple_user_journey_read_only() -> Result<()> { "it recomputes the cached entries" ); // when hitting the S key - app.process_events(&mut terminal, into_keys(b"s".iter()))?; + app.process_events(&mut terminal, into_codes("s"))?; assert_eq!( app.state.sorting, SortMode::SizeDescending, @@ -110,14 +112,14 @@ fn simple_user_journey_read_only() -> Result<()> { "it recomputes the cached entries" ); // when hitting the S key again - app.process_events(&mut terminal, into_keys(b"s".iter()))?; + app.process_events(&mut terminal, into_codes("s"))?; assert_eq!( app.state.sorting, SortMode::SizeAscending, "it sets the sort mode to ascending by size" ); // hit the S key again to get Descending - the rest depends on it - app.process_events(&mut terminal, into_keys(b"s".iter()))?; + app.process_events(&mut terminal, into_codes("s"))?; assert_eq!(app.state.sorting, SortMode::SizeDescending,); assert_eq!( @@ -130,35 +132,35 @@ fn simple_user_journey_read_only() -> Result<()> { // Entry-Navigation { // when hitting the j key - app.process_events(&mut terminal, into_keys(b"j".iter()))?; + app.process_events(&mut terminal, into_codes("j"))?; assert_eq!( node_by_name(&app, fixture_str(long_root)), node_by_index(&app, *app.state.navigation().selected.as_ref().unwrap()), "it moves the cursor down and selects the next entry based on the current sort mode" ); // when hitting it while there is nowhere to go - app.process_events(&mut terminal, into_keys(b"j".iter()))?; + app.process_events(&mut terminal, into_codes("j"))?; assert_eq!( node_by_name(&app, fixture_str(long_root)), node_by_index(&app, *app.state.navigation().selected.as_ref().unwrap()), "it stays at the previous position" ); // when hitting the k key - app.process_events(&mut terminal, into_keys(b"k".iter()))?; + app.process_events(&mut terminal, into_codes("k"))?; assert_eq!( node_by_name(&app, fixture_str(short_root)), node_by_index(&app, *app.state.navigation().selected.as_ref().unwrap()), "it moves the cursor up and selects the next entry based on the current sort mode" ); // when hitting the k key again - app.process_events(&mut terminal, into_keys(b"k".iter()))?; + app.process_events(&mut terminal, into_codes("k"))?; assert_eq!( node_by_name(&app, fixture_str(short_root)), node_by_index(&app, *app.state.navigation().selected.as_ref().unwrap()), "it stays at the current cursor position as there is nowhere to go" ); // when hitting the o key with a directory selected - app.process_events(&mut terminal, into_keys(b"o".iter()))?; + app.process_events(&mut terminal, into_codes("o"))?; { let new_root_idx = index_by_name(&app, fixture_str(short_root)); assert_eq!( @@ -173,7 +175,7 @@ fn simple_user_journey_read_only() -> Result<()> { ); // when hitting the u key while inside a sub-directory - app.process_events(&mut terminal, into_keys(b"u".iter()))?; + app.process_events(&mut terminal, into_codes("u"))?; { assert_eq!( app.traversal.root_index, @@ -189,7 +191,7 @@ fn simple_user_journey_read_only() -> Result<()> { } // when hitting the u key while inside of the root directory // We are moving the cursor down just to have a non-default selection - app.process_events(&mut terminal, into_keys(b"ju".iter()))?; + app.process_events(&mut terminal, into_codes("ju"))?; { assert_eq!( app.traversal.root_index, @@ -207,9 +209,9 @@ fn simple_user_journey_read_only() -> Result<()> { // Deletion { // when hitting the 'd' key (also move cursor back to start) - app.process_events(&mut terminal, into_keys(b"k".iter()))?; + app.process_events(&mut terminal, into_codes("k"))?; let previously_selected_index = *app.state.navigation().selected.as_ref().unwrap(); - app.process_events(&mut terminal, into_keys(b"d".iter()))?; + app.process_events(&mut terminal, into_codes("d"))?; { assert_eq!( Some(1), @@ -231,7 +233,7 @@ fn simple_user_journey_read_only() -> Result<()> { // when hitting the 'd' key again { - app.process_events(&mut terminal, into_keys(b"d".iter()))?; + app.process_events(&mut terminal, into_codes("d"))?; assert_eq!( Some(2), @@ -248,7 +250,7 @@ fn simple_user_journey_read_only() -> Result<()> { // when hitting the 'd' key once again { - app.process_events(&mut terminal, into_keys(b"d".iter()))?; + app.process_events(&mut terminal, into_codes("d"))?; assert_eq!( Some(1), @@ -265,7 +267,7 @@ fn simple_user_journey_read_only() -> Result<()> { } // when hitting the spacebar (after moving up to the first entry) { - app.process_events(&mut terminal, into_keys(b"k ".iter()))?; + app.process_events(&mut terminal, into_codes("k "))?; assert_eq!( None, @@ -284,7 +286,7 @@ fn simple_user_journey_read_only() -> Result<()> { // Marking { // select something - app.process_events(&mut terminal, into_keys(b" j ".iter()))?; + app.process_events(&mut terminal, into_codes(" j "))?; assert_eq!( Some(false), app.window.mark_pane.as_ref().map(|p| p.has_focus()), @@ -298,7 +300,7 @@ fn simple_user_journey_read_only() -> Result<()> { ); // when advancing the selection to the marker pane - app.process_events(&mut terminal, into_keys(b"\t".iter()))?; + app.process_events(&mut terminal, into_keys(Some(KeyCode::Tab)))?; { assert_eq!( Some(true), diff --git a/src/interactive/app/tests/journeys_with_writes.rs b/src/interactive/app/tests/journeys_with_writes.rs index d14f8b4..9a941e6 100644 --- a/src/interactive/app/tests/journeys_with_writes.rs +++ b/src/interactive/app/tests/journeys_with_writes.rs @@ -1,9 +1,9 @@ use crate::interactive::app::tests::utils::{ - initialized_app_and_terminal_from_paths, into_keys, WritableFixture, + initialized_app_and_terminal_from_paths, into_codes, WritableFixture, }; use anyhow::Result; +use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crosstermion::input::Event; -use crosstermion::input::Key; use pretty_assertions::assert_eq; #[test] @@ -13,7 +13,7 @@ fn basic_user_journey_with_deletion() -> Result<()> { let (mut terminal, mut app) = initialized_app_and_terminal_from_paths(&[fixture.root.clone()])?; // With a selection of items - app.process_events(&mut terminal, into_keys(b"doddd".iter()))?; + app.process_events(&mut terminal, into_codes("doddd"))?; assert_eq!( app.window.mark_pane.as_ref().map(|p| p.marked().len()), @@ -26,7 +26,11 @@ fn basic_user_journey_with_deletion() -> Result<()> { // When selecting the marker window and pressing the combination to delete entries app.process_events( &mut terminal, - vec![Event::Key(Key::Char('\t')), Event::Key(Key::Ctrl('r'))].into_iter(), + vec![ + Event::Key(KeyCode::Tab.into()), + Event::Key(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL)), + ] + .into_iter(), )?; assert!( app.window.mark_pane.is_none(), diff --git a/src/interactive/app/tests/utils.rs b/src/interactive/app/tests/utils.rs index 5c76f17..f6bc354 100644 --- a/src/interactive/app/tests/utils.rs +++ b/src/interactive/app/tests/utils.rs @@ -1,4 +1,5 @@ use anyhow::{Context, Error, Result}; +use crosstermion::crossterm::event::KeyCode; use dua::{ traverse::{EntryData, Tree, TreeIndex}, ByteFormat, TraversalSorting, WalkOptions, @@ -20,13 +21,15 @@ use tui_react::Terminal; use crate::interactive::{app::tests::FIXTURE_PATH, Interaction, TerminalApp}; pub fn into_keys<'a>( - bytes: impl Iterator + 'a, + codes: impl IntoIterator + 'a, ) -> impl Iterator + 'a { - bytes.map(|b| { - crosstermion::input::Event::Key(crosstermion::input::Key::Char( - std::char::from_u32(*b as u32).unwrap(), - )) - }) + codes + .into_iter() + .map(|code| crosstermion::input::Event::Key(code.into())) +} + +pub fn into_codes(input: &str) -> impl Iterator + '_ { + into_keys(input.chars().map(KeyCode::Char)) } pub fn node_by_index(app: &TerminalApp, id: TreeIndex) -> &EntryData { diff --git a/src/interactive/widgets/glob.rs b/src/interactive/widgets/glob.rs index 0ef95fd..01c8e91 100644 --- a/src/interactive/widgets/glob.rs +++ b/src/interactive/widgets/glob.rs @@ -36,9 +36,8 @@ pub struct GlobPane { impl GlobPane { pub fn process_events(&mut self, key: Key) { - use crosstermion::input::Key::*; - - match key { + use crosstermion::crossterm::event::KeyCode::*; + match key.code { Char(to_insert) => { self.enter_char(to_insert); } diff --git a/src/interactive/widgets/help.rs b/src/interactive/widgets/help.rs index 0ec8b8c..0edc53e 100644 --- a/src/interactive/widgets/help.rs +++ b/src/interactive/widgets/help.rs @@ -1,5 +1,7 @@ use crate::interactive::CursorDirection; -use crosstermion::{input::Key, input::Key::*}; +pub use crosstermion::crossterm::event::KeyCode::*; +use crosstermion::crossterm::event::KeyModifiers; +use crosstermion::input::Key; use std::{borrow::Borrow, cell::RefCell}; use tui::{ buffer::Buffer, @@ -34,13 +36,19 @@ fn margin(r: Rect, margin: u16) -> Rect { impl HelpPane { pub fn process_events(&mut self, key: Key) { - match key { + match key.code { Char('H') => self.scroll_help(CursorDirection::ToTop), Char('G') => self.scroll_help(CursorDirection::ToBottom), - Ctrl('u') | PageUp => self.scroll_help(CursorDirection::PageUp), + Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => { + self.scroll_help(CursorDirection::PageUp) + } + Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { + self.scroll_help(CursorDirection::PageDown) + } + PageUp => self.scroll_help(CursorDirection::PageUp), + PageDown => self.scroll_help(CursorDirection::PageDown), Char('k') | Up => self.scroll_help(CursorDirection::Up), Char('j') | Down => self.scroll_help(CursorDirection::Down), - Ctrl('d') | PageDown => self.scroll_help(CursorDirection::PageDown), _ => {} }; } diff --git a/src/interactive/widgets/mark.rs b/src/interactive/widgets/mark.rs index 63ed54f..dc0021e 100644 --- a/src/interactive/widgets/mark.rs +++ b/src/interactive/widgets/mark.rs @@ -3,7 +3,8 @@ use crate::interactive::{ app::tree_view::TreeView, fit_string_graphemes_with_ellipsis, widgets::entry_color, CursorDirection, }; -use crosstermion::{input::Key, input::Key::*}; +use crosstermion::crossterm::event::KeyModifiers; +use crosstermion::input::Key; use dua::{traverse::TreeIndex, ByteFormat}; use itertools::Itertools; use std::{ @@ -114,21 +115,32 @@ impl MarkPane { self.marked.into_values().map(|v| v.path) } pub fn process_events(mut self, key: Key) -> Option<(Self, Option)> { + use crosstermion::crossterm::event::KeyCode::*; let action = None; - match key { - Ctrl('r') => return Some(self.prepare_deletion(MarkMode::Delete)), + match key.code { + Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => { + return Some(self.prepare_deletion(MarkMode::Delete)) + } #[cfg(feature = "trash-move")] - Ctrl('t') => return Some(self.prepare_deletion(MarkMode::Trash)), - Char('x') | Char('d') | Char(' ') => { - return self.remove_selected().map(|s| (s, action)) + Char('t') if key.modifiers.contains(KeyModifiers::CONTROL) => { + return Some(self.prepare_deletion(MarkMode::Trash)) } Char('a') => return None, Char('H') => self.change_selection(CursorDirection::ToTop), Char('G') => self.change_selection(CursorDirection::ToBottom), - Ctrl('u') | PageUp => self.change_selection(CursorDirection::PageUp), + PageUp => self.change_selection(CursorDirection::PageUp), + Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => { + self.change_selection(CursorDirection::PageUp) + } + Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { + self.change_selection(CursorDirection::PageDown) + } + PageDown => self.change_selection(CursorDirection::PageDown), Char('k') | Up => self.change_selection(CursorDirection::Up), Char('j') | Down => self.change_selection(CursorDirection::Down), - Ctrl('d') | PageDown => self.change_selection(CursorDirection::PageDown), + Char('x') | Char('d') | Char(' ') => { + return self.remove_selected().map(|s| (s, action)) + } _ => {} }; Some((self, action)) diff --git a/src/main.rs b/src/main.rs index a610f6f..47be1f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use dua::TraversalSorting; use std::{fs, io, io::Write, path::PathBuf, process}; mod crossdev; -#[cfg(any(feature = "tui-unix", feature = "tui-crossplatform"))] +#[cfg(feature = "tui-crossplatform")] mod interactive; mod options; @@ -31,7 +31,7 @@ fn main() -> Result<()> { ignore_dirs: opt.ignore_dirs, }; let res = match opt.command { - #[cfg(any(feature = "tui-unix", feature = "tui-crossplatform"))] + #[cfg(feature = "tui-crossplatform")] Some(Interactive { input }) => { use crate::interactive::{Interaction, TerminalApp}; use anyhow::{anyhow, Context}; diff --git a/src/options.rs b/src/options.rs index 6711795..13705b3 100644 --- a/src/options.rs +++ b/src/options.rs @@ -84,7 +84,7 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Command { /// Launch the terminal user interface - #[cfg(any(feature = "tui-unix", feature = "tui-crossplatform"))] + #[cfg(feature = "tui-crossplatform")] #[clap(name = "interactive", visible_alias = "i")] Interactive { /// One or more input files or directories. If unset, we will use all entries in the current working directory. -- cgit v1.2.3 From b5b8aa26b648d8a034667bca8320ba7952a27780 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 26 Dec 2023 20:50:06 +0100 Subject: fix: avoid duplicate key input on windows (#203). On Windows, key-states like press/release/repeat are made available separately, which means we should avoid responding to key-releases as it would incorrectly double the actual user inputs. --- src/interactive/app/eventloop.rs | 4 ++-- src/interactive/widgets/glob.rs | 4 ++++ src/interactive/widgets/help.rs | 5 ++++- src/interactive/widgets/mark.rs | 5 ++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index c25b350..18e4bf7 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -6,7 +6,7 @@ use crate::interactive::{ SortMode, }; use anyhow::Result; -use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use crosstermion::input::{input_channel, Event}; use dua::{ traverse::{EntryData, Traversal}, @@ -115,7 +115,7 @@ impl AppState { for event in events { let key = match event { - Event::Key(key) => key, + Event::Key(key) if key.kind != KeyEventKind::Release => key, Event::Resize(_, _) => refresh_key(), _ => continue, }; diff --git a/src/interactive/widgets/glob.rs b/src/interactive/widgets/glob.rs index 01c8e91..bad9fd9 100644 --- a/src/interactive/widgets/glob.rs +++ b/src/interactive/widgets/glob.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Context, Result}; use bstr::BString; +use crosstermion::crossterm::event::KeyEventKind; use crosstermion::input::Key; use dua::traverse::{Tree, TreeIndex}; use petgraph::Direction; @@ -37,6 +38,9 @@ pub struct GlobPane { impl GlobPane { pub fn process_events(&mut self, key: Key) { use crosstermion::crossterm::event::KeyCode::*; + if key.kind == KeyEventKind::Release { + return; + } match key.code { Char(to_insert) => { self.enter_char(to_insert); diff --git a/src/interactive/widgets/help.rs b/src/interactive/widgets/help.rs index 0edc53e..092fb00 100644 --- a/src/interactive/widgets/help.rs +++ b/src/interactive/widgets/help.rs @@ -1,6 +1,6 @@ use crate::interactive::CursorDirection; pub use crosstermion::crossterm::event::KeyCode::*; -use crosstermion::crossterm::event::KeyModifiers; +use crosstermion::crossterm::event::{KeyEventKind, KeyModifiers}; use crosstermion::input::Key; use std::{borrow::Borrow, cell::RefCell}; use tui::{ @@ -36,6 +36,9 @@ fn margin(r: Rect, margin: u16) -> Rect { impl HelpPane { pub fn process_events(&mut self, key: Key) { + if key.kind == KeyEventKind::Release { + return; + } match key.code { Char('H') => self.scroll_help(CursorDirection::ToTop), Char('G') => self.scroll_help(CursorDirection::ToBottom), diff --git a/src/interactive/widgets/mark.rs b/src/interactive/widgets/mark.rs index dc0021e..a774c73 100644 --- a/src/interactive/widgets/mark.rs +++ b/src/interactive/widgets/mark.rs @@ -3,7 +3,7 @@ use crate::interactive::{ app::tree_view::TreeView, fit_string_graphemes_with_ellipsis, widgets::entry_color, CursorDirection, }; -use crosstermion::crossterm::event::KeyModifiers; +use crosstermion::crossterm::event::{KeyEventKind, KeyModifiers}; use crosstermion::input::Key; use dua::{traverse::TreeIndex, ByteFormat}; use itertools::Itertools; @@ -117,6 +117,9 @@ impl MarkPane { pub fn process_events(mut self, key: Key) -> Option<(Self, Option)> { use crosstermion::crossterm::event::KeyCode::*; let action = None; + if key.kind == KeyEventKind::Release { + return Some((self, action)); + } match key.code { Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => { return Some(self.prepare_deletion(MarkMode::Delete)) -- cgit v1.2.3