diff --git a/src/commands/project.rs b/src/commands/project.rs index 441dbc4..dab31ea 100644 --- a/src/commands/project.rs +++ b/src/commands/project.rs @@ -1,5 +1,6 @@ use anyhow::Result; use clap::Subcommand; +use dialoguer::{Input, Password}; use crate::config::{Config, Project}; @@ -9,7 +10,7 @@ pub enum ProjectCommand { Add { name: String, #[arg(long)] - url: String, + url: Option, #[arg(long)] api_key: Option, }, @@ -19,6 +20,8 @@ pub enum ProjectCommand { List, /// Set the default project Use { name: String }, + /// Update an existing project + Update { name: String }, /// Show the current default project Current, } @@ -26,11 +29,25 @@ pub enum ProjectCommand { pub async fn run(cmd: &ProjectCommand) -> Result<()> { match cmd { ProjectCommand::Add { name, url, api_key } => { + let resolved_url = match url { + Some(u) => u.clone(), + None => Input::::new().with_prompt("URL").interact_text()?, + }; + let resolved_key = match api_key { + Some(k) => Some(k.clone()), + None => { + let key: String = Password::new() + .with_prompt("API key (leave empty for none)") + .allow_empty_password(true) + .interact()?; + if key.is_empty() { None } else { Some(key) } + } + }; let mut config = Config::load()?; let project = Project { r#type: None, - url: url.clone(), - api_key: api_key.clone(), + url: resolved_url, + api_key: resolved_key, data_dir: None, binary: None, }; @@ -50,7 +67,12 @@ pub async fn run(cmd: &ProjectCommand) -> Result<()> { } else { "" }; - println!(" {}{} — {}", name, marker, project.url); + let key_info = if project.api_key.is_some() { + " [api key set]" + } else { + "" + }; + println!(" {}{} — {}{}", name, marker, project.url, key_info); } } ProjectCommand::Use { name } => { @@ -58,6 +80,28 @@ pub async fn run(cmd: &ProjectCommand) -> Result<()> { config.set_default(name)?; println!("Default project set to '{}'.", name); } + ProjectCommand::Update { name } => { + let config = Config::load()?; + let (_, existing) = config.get_project(Some(name))?; + let url = Input::::new() + .with_prompt(format!("URL (current: {})", existing.url)) + .interact_text()?; + let key: String = Password::new() + .with_prompt("API key (leave empty to remove)") + .allow_empty_password(true) + .interact()?; + let api_key = if key.is_empty() { None } else { Some(key) }; + let project = Project { + r#type: existing.r#type.clone(), + url, + api_key, + data_dir: existing.data_dir.clone(), + binary: existing.binary.clone(), + }; + let mut config = Config::load()?; + config.update_project(name, project)?; + println!("Project '{}' updated.", name); + } ProjectCommand::Current => { let config = Config::load()?; let (name, project) = config.get_project(None)?; diff --git a/src/config.rs b/src/config.rs index a6297d5..0338850 100644 --- a/src/config.rs +++ b/src/config.rs @@ -124,6 +124,14 @@ impl Config { self.save() } + pub fn update_project(&mut self, name: &str, project: Project) -> Result<()> { + if !self.projects.contains_key(name) { + bail!("Project '{}' not found.", name); + } + self.projects.insert(name.to_string(), project); + self.save() + } + pub fn remove_project(&mut self, name: &str) -> Result<()> { if name == self.default { bail!( diff --git a/src/tui/chat.rs b/src/tui/chat.rs index dbbaf8c..033f94a 100644 --- a/src/tui/chat.rs +++ b/src/tui/chat.rs @@ -426,8 +426,8 @@ pub async fn run_interactive_chat( state.show_sources = !state.show_sources; } KeyCode::Esc => break, - KeyCode::Enter if !state.loading => { - if !state.input.is_empty() { + KeyCode::Enter if !state.loading && !state.input.is_empty() => { + { // Push to history state.input_history.push(state.input.clone()); state.history_pos = state.input_history.len(); @@ -576,33 +576,37 @@ pub async fn run_interactive_chat( state.auto_scroll = true; } } + KeyCode::Up + if !state.loading + && !state.input_history.is_empty() + && state.history_pos > 0 => + { + // First time pressing up: stash current input + if state.history_pos == state.input_history.len() { + state.input_stash = state.input.clone(); + } + state.history_pos -= 1; + state.input = state.input_history[state.history_pos].clone(); + } + KeyCode::Up if !state.loading => {} + KeyCode::Down + if !state.loading && state.history_pos < state.input_history.len() => + { + state.history_pos += 1; + if state.history_pos == state.input_history.len() { + // Restore stashed input + state.input = state.input_stash.clone(); + } else { + state.input = state.input_history[state.history_pos].clone(); + } + } + KeyCode::Down if !state.loading => {} KeyCode::Char(c) if !state.loading => { state.input.push(c); } KeyCode::Backspace if !state.loading => { state.input.pop(); } - KeyCode::Up if !state.loading => { - if !state.input_history.is_empty() && state.history_pos > 0 { - // First time pressing up: stash current input - if state.history_pos == state.input_history.len() { - state.input_stash = state.input.clone(); - } - state.history_pos -= 1; - state.input = state.input_history[state.history_pos].clone(); - } - } - KeyCode::Down if !state.loading => { - if state.history_pos < state.input_history.len() { - state.history_pos += 1; - if state.history_pos == state.input_history.len() { - // Restore stashed input - state.input = state.input_stash.clone(); - } else { - state.input = state.input_history[state.history_pos].clone(); - } - } - } KeyCode::Up => { state.auto_scroll = false; state.scroll = state.scroll.saturating_sub(1); diff --git a/src/tui/search.rs b/src/tui/search.rs index ae27a38..3f2ed59 100644 --- a/src/tui/search.rs +++ b/src/tui/search.rs @@ -160,16 +160,14 @@ pub async fn run_interactive_search(client: MeiliClient, uid: &str) -> Result<() needs_search = true; state.last_search = Instant::now(); } - KeyCode::Up => { - if state.selected > 0 { - state.selected -= 1; - } + KeyCode::Up if state.selected > 0 => { + state.selected -= 1; } - KeyCode::Down => { - if state.selected + 1 < state.results.len() { - state.selected += 1; - } + KeyCode::Up => {} + KeyCode::Down if state.selected + 1 < state.results.len() => { + state.selected += 1; } + KeyCode::Down => {} KeyCode::Enter => { if state.expanded.is_some() { state.expanded = None; diff --git a/src/tui/settings.rs b/src/tui/settings.rs index d3310ff..dfa9fec 100644 --- a/src/tui/settings.rs +++ b/src/tui/settings.rs @@ -903,33 +903,36 @@ fn handle_editor_input(key: event::KeyEvent, state: &mut State) -> Action { KeyCode::Esc => { exit_editor(state); } - KeyCode::Up if shift && *ordered => { - // Reorder: swap with previous checked item - if items[*cursor].checked && *cursor > 0 && items[*cursor - 1].checked { - items.swap(*cursor, *cursor - 1); - *cursor -= 1; - } + KeyCode::Up + if shift + && *ordered + && items[*cursor].checked + && *cursor > 0 + && items[*cursor - 1].checked => + { + items.swap(*cursor, *cursor - 1); + *cursor -= 1; } - KeyCode::Down if shift && *ordered => { - // Reorder: swap with next checked item - if items[*cursor].checked + KeyCode::Up if shift && *ordered => {} + KeyCode::Down + if shift + && *ordered + && items[*cursor].checked && *cursor + 1 < items.len() - && items[*cursor + 1].checked - { - items.swap(*cursor, *cursor + 1); - *cursor += 1; - } + && items[*cursor + 1].checked => + { + items.swap(*cursor, *cursor + 1); + *cursor += 1; } - KeyCode::Up => { - if *cursor > 0 { - *cursor -= 1; - } + KeyCode::Down if shift && *ordered => {} + KeyCode::Up if *cursor > 0 => { + *cursor -= 1; } - KeyCode::Down => { - if *cursor + 1 < items.len() { - *cursor += 1; - } + KeyCode::Up => {} + KeyCode::Down if *cursor + 1 < items.len() => { + *cursor += 1; } + KeyCode::Down => {} KeyCode::Char(' ') => { if *ordered { let name = items[*cursor].name.clone(); @@ -1014,37 +1017,31 @@ fn handle_editor_input(key: event::KeyEvent, state: &mut State) -> Action { KeyCode::Esc => { exit_editor(state); } - KeyCode::Up if shift => { - if *cursor > 0 { - items.swap(*cursor, *cursor - 1); - *cursor -= 1; - } + KeyCode::Up if shift && *cursor > 0 => { + items.swap(*cursor, *cursor - 1); + *cursor -= 1; } - KeyCode::Down if shift => { - if *cursor + 1 < items.len() { - items.swap(*cursor, *cursor + 1); - *cursor += 1; - } + KeyCode::Up if shift => {} + KeyCode::Down if shift && *cursor + 1 < items.len() => { + items.swap(*cursor, *cursor + 1); + *cursor += 1; } - KeyCode::Up => { - if *cursor > 0 { - *cursor -= 1; - } + KeyCode::Down if shift => {} + KeyCode::Up if *cursor > 0 => { + *cursor -= 1; } - KeyCode::Down => { - if *cursor + 1 < items.len() { - *cursor += 1; - } + KeyCode::Up => {} + KeyCode::Down if *cursor + 1 < items.len() => { + *cursor += 1; } + KeyCode::Down => {} KeyCode::Char('a') => { *adding = true; } - KeyCode::Char('d') | KeyCode::Delete => { - if !items.is_empty() { - items.remove(*cursor); - if *cursor >= items.len() && !items.is_empty() { - *cursor = items.len() - 1; - } + KeyCode::Char('d') | KeyCode::Delete if !items.is_empty() => { + items.remove(*cursor); + if *cursor >= items.len() && !items.is_empty() { + *cursor = items.len() - 1; } } _ => {} @@ -1057,16 +1054,14 @@ fn handle_editor_input(key: event::KeyEvent, state: &mut State) -> Action { KeyCode::Esc => { exit_editor(state); } - KeyCode::Up => { - if *cursor > 0 { - *cursor -= 1; - } + KeyCode::Up if *cursor > 0 => { + *cursor -= 1; } - KeyCode::Down => { - if *cursor + 1 < options.len() { - *cursor += 1; - } + KeyCode::Up => {} + KeyCode::Down if *cursor + 1 < options.len() => { + *cursor += 1; } + KeyCode::Down => {} KeyCode::Enter | KeyCode::Char(' ') => { exit_editor(state); }