Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions src/commands/project.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Result;
use clap::Subcommand;
use dialoguer::{Input, Password};

use crate::config::{Config, Project};

Expand All @@ -9,7 +10,7 @@ pub enum ProjectCommand {
Add {
name: String,
#[arg(long)]
url: String,
url: Option<String>,
#[arg(long)]
api_key: Option<String>,
},
Expand All @@ -19,18 +20,34 @@ pub enum ProjectCommand {
List,
/// Set the default project
Use { name: String },
/// Update an existing project
Update { name: String },
/// Show the current default project
Current,
}

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::<String>::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,
};
Expand All @@ -50,14 +67,41 @@ 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 } => {
let mut config = Config::load()?;
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::<String>::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)?;
Expand Down
8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
50 changes: 27 additions & 23 deletions src/tui/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 6 additions & 8 deletions src/tui/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
103 changes: 49 additions & 54 deletions src/tui/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
}
_ => {}
Expand All @@ -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);
}
Expand Down
Loading