File completion in CLI (#3822)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2025-08-04 17:42:49 +02:00
committed by GitHub
parent 562dea3f23
commit 092ece780c
+43 -8
View File
@@ -1,8 +1,8 @@
use rustyline::completion::{Completer, Pair}; use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::highlight::{CmdKind, Highlighter}; use rustyline::highlight::{CmdKind, Highlighter};
use rustyline::hint::Hinter; use rustyline::hint::Hinter;
use rustyline::validate::Validator; use rustyline::validate::Validator;
use rustyline::{Helper, Result}; use rustyline::{Context, Helper, Result};
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
@@ -11,12 +11,16 @@ use super::CompletionCache;
/// Completer for Goose CLI commands /// Completer for Goose CLI commands
pub struct GooseCompleter { pub struct GooseCompleter {
completion_cache: Arc<std::sync::RwLock<CompletionCache>>, completion_cache: Arc<std::sync::RwLock<CompletionCache>>,
filename_completer: FilenameCompleter,
} }
impl GooseCompleter { impl GooseCompleter {
/// Create a new GooseCompleter with a reference to the Session's completion cache /// Create a new GooseCompleter with a reference to the Session's completion cache
pub fn new(completion_cache: Arc<std::sync::RwLock<CompletionCache>>) -> Self { pub fn new(completion_cache: Arc<std::sync::RwLock<CompletionCache>>) -> Self {
Self { completion_cache } Self {
completion_cache,
filename_completer: FilenameCompleter::new(),
}
} }
/// Complete prompt names for the /prompt command /// Complete prompt names for the /prompt command
@@ -254,6 +258,35 @@ impl GooseCompleter {
// No completions available // No completions available
Ok((line.len(), vec![])) Ok((line.len(), vec![]))
} }
/// Complete file paths
fn complete_file_path(&self, line: &str, ctx: &Context) -> Result<(usize, Vec<Pair>)> {
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(last_part) = parts.last() {
// Skip filename completion for words starting with special characters
if last_part.starts_with('/') && last_part.len() == 1 {
// Just a slash - no completion
return Ok((line.len(), vec![]));
}
if last_part.starts_with('-') || last_part.contains('=') {
// Skip flag or key-value pairs
return Ok((line.len(), vec![]));
}
// Complete the partial path
let pos = line.len() - last_part.len();
let (start, candidates) =
self.filename_completer
.complete(last_part, last_part.len(), ctx)?;
// Return the completion results, with adjusted position
return Ok((pos + start, candidates));
}
Ok((line.len(), vec![]))
}
} }
impl Completer for GooseCompleter { impl Completer for GooseCompleter {
@@ -263,7 +296,7 @@ impl Completer for GooseCompleter {
&self, &self,
line: &str, line: &str,
pos: usize, pos: usize,
_ctx: &rustyline::Context<'_>, ctx: &Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>)> { ) -> Result<(usize, Vec<Self::Candidate>)> {
// If the cursor is not at the end of the line, don't try to complete // If the cursor is not at the end of the line, don't try to complete
if pos < line.len() { if pos < line.len() {
@@ -338,10 +371,12 @@ impl Completer for GooseCompleter {
if line.starts_with("/mode") { if line.starts_with("/mode") {
return self.complete_mode_flags(line); return self.complete_mode_flags(line);
} }
return Ok((pos, vec![]));
} }
// Default: no completions // For normal text (not slash commands), try file path completion
Ok((pos, vec![])) self.complete_file_path(line, ctx)
} }
} }
@@ -352,7 +387,7 @@ impl Helper for GooseCompleter {}
impl Hinter for GooseCompleter { impl Hinter for GooseCompleter {
type Hint = String; type Hint = String;
fn hint(&self, line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> { fn hint(&self, line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
// Only show hint when line is empty // Only show hint when line is empty
if line.is_empty() { if line.is_empty() {
Some("Press Enter to send, Ctrl-J for new line".to_string()) Some("Press Enter to send, Ctrl-J for new line".to_string())
@@ -390,7 +425,7 @@ impl Validator for GooseCompleter {
fn validate( fn validate(
&self, &self,
_ctx: &mut rustyline::validate::ValidationContext, _ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> { ) -> Result<rustyline::validate::ValidationResult> {
Ok(rustyline::validate::ValidationResult::Valid(None)) Ok(rustyline::validate::ValidationResult::Valid(None))
} }
} }