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]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -1771,44 +1771,42 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
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"
|
||||
checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
name = "toml_datetime"
|
||||
version = "0.7.0"
|
||||
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]]
|
||||
name = "tower"
|
||||
@ -2336,9 +2334,6 @@ name = "winnow"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
|
||||
@ -9,13 +9,13 @@ edition = "2021"
|
||||
color-eyre = "0.6"
|
||||
crossterm = "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-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
thiserror = "2.0"
|
||||
tracing-appender = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
toml = "0.9"
|
||||
dirs = "6.0"
|
||||
transmission-rpc = "0.5"
|
||||
url = "2.5"
|
||||
|
||||
@ -8,6 +8,8 @@ pub mod utils;
|
||||
use crate::config::Config;
|
||||
use color_eyre::Result;
|
||||
use ratatui::widgets::TableState;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::fs;
|
||||
use types::Selected;
|
||||
pub use {tab::Tab, torrent::Torrents};
|
||||
|
||||
@ -24,6 +26,8 @@ pub struct App<'a> {
|
||||
pub input: String,
|
||||
pub cursor_position: usize,
|
||||
pub input_mode: bool,
|
||||
pub completions: Vec<String>,
|
||||
pub completion_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
@ -41,9 +45,22 @@ impl<'a> App<'a> {
|
||||
input: String::new(),
|
||||
cursor_position: 0,
|
||||
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.
|
||||
pub async fn tick(&mut self) -> Result<()> {
|
||||
self.torrents.update().await?;
|
||||
@ -196,4 +213,64 @@ impl<'a> App<'a> {
|
||||
.collect();
|
||||
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 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 {
|
||||
KeyCode::Enter => Some(Action::Submit),
|
||||
KeyCode::Enter => Ok(Some(Action::Submit)),
|
||||
KeyCode::Tab => {
|
||||
app.complete_input().await?;
|
||||
Ok(None)
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.input.push(c);
|
||||
app.cursor_position = app.input.len();
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.input.pop();
|
||||
app.cursor_position = app.input.len();
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
KeyCode::Esc => Some(Action::Cancel),
|
||||
_ => None,
|
||||
KeyCode::Esc => Ok(Some(Action::Cancel)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the key events of [`App`].
|
||||
#[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 {
|
||||
return handle_input(key_event, app);
|
||||
return handle_input(key_event, app).await;
|
||||
}
|
||||
|
||||
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 {
|
||||
if matches_keybind(&key_event, keybind) {
|
||||
return Some(action);
|
||||
return Ok(Some(action));
|
||||
}
|
||||
}
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handles the updates of [`App`].
|
||||
@ -140,7 +144,6 @@ fn parse_keybind(key_str: &str) -> (KeyModifiers, Option<KeyCode>) {
|
||||
key_code = Some(KeyCode::F(num));
|
||||
}
|
||||
}
|
||||
|
||||
single_char if single_char.len() == 1 => {
|
||||
if let Some(c) = single_char.chars().next() {
|
||||
key_code = Some(KeyCode::Char(c));
|
||||
|
||||
@ -40,7 +40,7 @@ async fn main() -> Result<()> {
|
||||
match tui.events.next()? {
|
||||
Event::Tick => app.tick().await?,
|
||||
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?;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user