mirror of
https://github.com/kristoferssolo/traxor.git
synced 2025-10-21 20:10:35 +00:00
feat(move): make completions case agnostic
This commit is contained in:
parent
f2650c7090
commit
805b65067b
59
Cargo.lock
generated
59
Cargo.lock
generated
@ -1464,9 +1464,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.9"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -1771,44 +1771,42 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.23"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8"
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_write",
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_write"
|
name = "toml_datetime"
|
||||||
version = "0.1.2"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e"
|
||||||
|
dependencies = [
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b679217f2848de74cabd3e8fc5e6d66f40b7da40f8e1954d92054d9010690fd5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
@ -2336,9 +2334,6 @@ name = "winnow"
|
|||||||
version = "0.7.11"
|
version = "0.7.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
|
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
|
|||||||
@ -9,13 +9,13 @@ edition = "2021"
|
|||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
ratatui = { version = "0.29" }
|
ratatui = { version = "0.29" }
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tracing-appender = "0.2"
|
tracing-appender = "0.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
toml = "0.8"
|
toml = "0.9"
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
transmission-rpc = "0.5"
|
transmission-rpc = "0.5"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
|
|||||||
@ -8,6 +8,8 @@ pub mod utils;
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use ratatui::widgets::TableState;
|
use ratatui::widgets::TableState;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use tokio::fs;
|
||||||
use types::Selected;
|
use types::Selected;
|
||||||
pub use {tab::Tab, torrent::Torrents};
|
pub use {tab::Tab, torrent::Torrents};
|
||||||
|
|
||||||
@ -24,6 +26,8 @@ pub struct App<'a> {
|
|||||||
pub input: String,
|
pub input: String,
|
||||||
pub cursor_position: usize,
|
pub cursor_position: usize,
|
||||||
pub input_mode: bool,
|
pub input_mode: bool,
|
||||||
|
pub completions: Vec<String>,
|
||||||
|
pub completion_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
@ -41,9 +45,22 @@ impl<'a> App<'a> {
|
|||||||
input: String::new(),
|
input: String::new(),
|
||||||
cursor_position: 0,
|
cursor_position: 0,
|
||||||
input_mode: false,
|
input_mode: false,
|
||||||
|
completions: Vec::new(),
|
||||||
|
completion_idx: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn complete_input(&mut self) -> Result<()> {
|
||||||
|
let path = PathBuf::from(&self.input);
|
||||||
|
let (base_path, partial_name) = split_path_components(path);
|
||||||
|
let matches = find_matching_entries(&base_path, &partial_name).await?;
|
||||||
|
|
||||||
|
self.update_completions(matches);
|
||||||
|
self.update_input_with_matches();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles the tick event of the terminal.
|
/// Handles the tick event of the terminal.
|
||||||
pub async fn tick(&mut self) -> Result<()> {
|
pub async fn tick(&mut self) -> Result<()> {
|
||||||
self.torrents.update().await?;
|
self.torrents.update().await?;
|
||||||
@ -196,4 +213,64 @@ impl<'a> App<'a> {
|
|||||||
.collect();
|
.collect();
|
||||||
Selected::List(selected_torrents)
|
Selected::List(selected_torrents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_completions(&mut self, matches: Vec<String>) {
|
||||||
|
if matches.is_empty() {
|
||||||
|
self.completions.clear();
|
||||||
|
self.completion_idx = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches != self.completions {
|
||||||
|
self.completions = matches;
|
||||||
|
self.completion_idx = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.completion_idx = (self.completion_idx + 1) % self.completions.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_input_with_matches(&mut self) {
|
||||||
|
if let Some(completion) = self.completions.get(self.completion_idx) {
|
||||||
|
self.input = completion.clone();
|
||||||
|
self.cursor_position = self.input.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_path_components(path: PathBuf) -> (PathBuf, String) {
|
||||||
|
if path.is_dir() {
|
||||||
|
return (path, String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let partial = path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let base = path
|
||||||
|
.parent()
|
||||||
|
.unwrap_or_else(|| Path::new("/"))
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
(base, partial)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_matching_entries(base_path: &Path, partial_name: &str) -> Result<Vec<String>> {
|
||||||
|
let mut entries = fs::read_dir(&base_path).await?;
|
||||||
|
let mut matches = Vec::new();
|
||||||
|
|
||||||
|
while let Some(entry) = entries.next_entry().await? {
|
||||||
|
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
if file_name
|
||||||
|
.to_lowercase()
|
||||||
|
.starts_with(&partial_name.to_lowercase())
|
||||||
|
{
|
||||||
|
matches.push(format!("{}/{}", base_path.to_string_lossy(), file_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(matches)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,29 +3,33 @@ use color_eyre::Result;
|
|||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use tracing::{event, info_span, Level};
|
use tracing::{event, info_span, Level};
|
||||||
|
|
||||||
fn handle_input(key_event: KeyEvent, app: &mut App) -> Option<Action> {
|
async fn handle_input(key_event: KeyEvent, app: &mut App<'_>) -> Result<Option<Action>> {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
KeyCode::Enter => Some(Action::Submit),
|
KeyCode::Enter => Ok(Some(Action::Submit)),
|
||||||
|
KeyCode::Tab => {
|
||||||
|
app.complete_input().await?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
app.input.push(c);
|
app.input.push(c);
|
||||||
app.cursor_position = app.input.len();
|
app.cursor_position = app.input.len();
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
app.input.pop();
|
app.input.pop();
|
||||||
app.cursor_position = app.input.len();
|
app.cursor_position = app.input.len();
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
KeyCode::Esc => Some(Action::Cancel),
|
KeyCode::Esc => Ok(Some(Action::Cancel)),
|
||||||
_ => None,
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the key events of [`App`].
|
/// Handles the key events of [`App`].
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn get_action(key_event: KeyEvent, app: &mut App) -> Option<Action> {
|
pub async fn get_action(key_event: KeyEvent, app: &mut App<'_>) -> Result<Option<Action>> {
|
||||||
if app.input_mode {
|
if app.input_mode {
|
||||||
return handle_input(key_event, app);
|
return handle_input(key_event, app).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = info_span!("get_action");
|
let span = info_span!("get_action");
|
||||||
@ -54,10 +58,10 @@ pub fn get_action(key_event: KeyEvent, app: &mut App) -> Option<Action> {
|
|||||||
|
|
||||||
for (action, keybind) in actions {
|
for (action, keybind) in actions {
|
||||||
if matches_keybind(&key_event, keybind) {
|
if matches_keybind(&key_event, keybind) {
|
||||||
return Some(action);
|
return Ok(Some(action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the updates of [`App`].
|
/// Handles the updates of [`App`].
|
||||||
@ -140,7 +144,6 @@ fn parse_keybind(key_str: &str) -> (KeyModifiers, Option<KeyCode>) {
|
|||||||
key_code = Some(KeyCode::F(num));
|
key_code = Some(KeyCode::F(num));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
single_char if single_char.len() == 1 => {
|
single_char if single_char.len() == 1 => {
|
||||||
if let Some(c) = single_char.chars().next() {
|
if let Some(c) = single_char.chars().next() {
|
||||||
key_code = Some(KeyCode::Char(c));
|
key_code = Some(KeyCode::Char(c));
|
||||||
|
|||||||
@ -40,7 +40,7 @@ async fn main() -> Result<()> {
|
|||||||
match tui.events.next()? {
|
match tui.events.next()? {
|
||||||
Event::Tick => app.tick().await?,
|
Event::Tick => app.tick().await?,
|
||||||
Event::Key(key_event) => {
|
Event::Key(key_event) => {
|
||||||
if let Some(action) = get_action(key_event, &mut app) {
|
if let Some(action) = get_action(key_event, &mut app).await? {
|
||||||
update(&mut app, action).await?;
|
update(&mut app, action).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user