@@ -4,6 +4,7 @@ use crossterm::style::Stylize;
44use eyre:: Result ;
55use rustyline:: completion:: {
66 Completer ,
7+ FilenameCompleter ,
78 extract_word,
89} ;
910use rustyline:: error:: ReadlineError ;
@@ -61,11 +62,63 @@ pub fn generate_prompt(current_profile: Option<&str>) -> String {
6162 "> " . to_string ( )
6263}
6364
64- pub struct ChatCompleter { }
65+ /// Complete commands that start with a slash
66+ fn complete_command ( word : & str , start : usize ) -> ( usize , Vec < String > ) {
67+ (
68+ start,
69+ COMMANDS
70+ . iter ( )
71+ . filter ( |p| p. starts_with ( word) )
72+ . map ( |s| ( * s) . to_owned ( ) )
73+ . collect ( ) ,
74+ )
75+ }
76+
77+ /// A wrapper around FilenameCompleter that provides enhanced path detection
78+ /// and completion capabilities for the chat interface.
79+ pub struct PathCompleter {
80+ /// The underlying filename completer from rustyline
81+ filename_completer : FilenameCompleter ,
82+ }
83+
84+ impl PathCompleter {
85+ /// Creates a new PathCompleter instance
86+ pub fn new ( ) -> Self {
87+ Self {
88+ filename_completer : FilenameCompleter :: new ( ) ,
89+ }
90+ }
91+
92+ /// Attempts to complete a file path at the given position in the line
93+ pub fn complete_path (
94+ & self ,
95+ line : & str ,
96+ pos : usize ,
97+ ctx : & Context < ' _ > ,
98+ ) -> Result < ( usize , Vec < String > ) , ReadlineError > {
99+ // Use the filename completer to get path completions
100+ match self . filename_completer . complete ( line, pos, ctx) {
101+ Ok ( ( pos, completions) ) => {
102+ // Convert the filename completer's pairs to strings
103+ let file_completions: Vec < String > = completions. iter ( ) . map ( |pair| pair. replacement . clone ( ) ) . collect ( ) ;
104+
105+ // Return the completions if we have any
106+ Ok ( ( pos, file_completions) )
107+ } ,
108+ Err ( err) => Err ( err) ,
109+ }
110+ }
111+ }
112+
113+ pub struct ChatCompleter {
114+ path_completer : PathCompleter ,
115+ }
65116
66117impl ChatCompleter {
67118 fn new ( ) -> Self {
68- Self { }
119+ Self {
120+ path_completer : PathCompleter :: new ( ) ,
121+ }
69122 }
70123}
71124
@@ -79,18 +132,21 @@ impl Completer for ChatCompleter {
79132 _ctx : & Context < ' _ > ,
80133 ) -> Result < ( usize , Vec < Self :: Candidate > ) , ReadlineError > {
81134 let ( start, word) = extract_word ( line, pos, None , |c| c. is_space ( ) ) ;
82- Ok ( (
83- start,
84- if word. starts_with ( '/' ) {
85- COMMANDS
86- . iter ( )
87- . filter ( |p| p. starts_with ( word) )
88- . map ( |s| ( * s) . to_owned ( ) )
89- . collect ( )
90- } else {
91- Vec :: new ( )
92- } ,
93- ) )
135+
136+ // Handle command completion
137+ if word. starts_with ( '/' ) {
138+ return Ok ( complete_command ( word, start) ) ;
139+ }
140+
141+ // Handle file path completion as fallback
142+ if let Ok ( ( pos, completions) ) = self . path_completer . complete_path ( line, pos, _ctx) {
143+ if !completions. is_empty ( ) {
144+ return Ok ( ( pos, completions) ) ;
145+ }
146+ }
147+
148+ // Default: no completions
149+ Ok ( ( start, Vec :: new ( ) ) )
94150 }
95151}
96152
@@ -159,8 +215,50 @@ mod tests {
159215
160216 #[ test]
161217 fn test_generate_prompt ( ) {
218+ // Test default prompt (no profile)
162219 assert_eq ! ( generate_prompt( None ) , "> " ) ;
220+ // Test default profile (should be same as no profile)
163221 assert_eq ! ( generate_prompt( Some ( "default" ) ) , "> " ) ;
164- assert ! ( generate_prompt( Some ( "test-profile" ) ) . contains( "test-profile" ) ) ;
222+ // Test custom profile
223+ assert_eq ! ( generate_prompt( Some ( "test-profile" ) ) , "[test-profile] > " ) ;
224+ // Test another custom profile
225+ assert_eq ! ( generate_prompt( Some ( "dev" ) ) , "[dev] > " ) ;
226+ }
227+
228+ #[ test]
229+ fn test_chat_completer_command_completion ( ) {
230+ let completer = ChatCompleter :: new ( ) ;
231+ let line = "/h" ;
232+ let pos = 2 ; // Position at the end of "/h"
233+
234+ // Create a mock context with empty history
235+ let empty_history = DefaultHistory :: new ( ) ;
236+ let ctx = Context :: new ( & empty_history) ;
237+
238+ // Get completions
239+ let ( start, completions) = completer. complete ( line, pos, & ctx) . unwrap ( ) ;
240+
241+ // Verify start position
242+ assert_eq ! ( start, 0 ) ;
243+
244+ // Verify completions contain expected commands
245+ assert ! ( completions. contains( & "/help" . to_string( ) ) ) ;
246+ }
247+
248+ #[ test]
249+ fn test_chat_completer_no_completion ( ) {
250+ let completer = ChatCompleter :: new ( ) ;
251+ let line = "Hello, how are you?" ;
252+ let pos = line. len ( ) ;
253+
254+ // Create a mock context with empty history
255+ let empty_history = DefaultHistory :: new ( ) ;
256+ let ctx = Context :: new ( & empty_history) ;
257+
258+ // Get completions
259+ let ( _, completions) = completer. complete ( line, pos, & ctx) . unwrap ( ) ;
260+
261+ // Verify no completions are returned for regular text
262+ assert ! ( completions. is_empty( ) ) ;
165263 }
166264}
0 commit comments