Skip to content

Commit b64f157

Browse files
authored
refactor(chat): Update prompt validator for multi-line input (#1044)
Add the MultiLineValidator implementation in prompt.rs to support multi-line editing in the q chat terminal. Also, add custom bindings to use Alt+Enter or Ctrl+j to move to next line
1 parent 9c8de3a commit b64f157

File tree

3 files changed

+67
-31
lines changed

3 files changed

+67
-31
lines changed

crates/q_cli/src/cli/chat/input_source.rs

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,12 @@ impl InputSource {
3636
pub fn read_line(&mut self, prompt: Option<&str>) -> Result<Option<String>, ReadlineError> {
3737
match &mut self.0 {
3838
inner::Inner::Readline(rl) => {
39-
let mut prompt = prompt.unwrap_or_default();
40-
let mut line = String::new();
41-
loop {
42-
let curr_line = rl.readline(prompt);
43-
match curr_line {
44-
Ok(l) => {
45-
if l.trim().is_empty() {
46-
continue;
47-
} else if l.ends_with("\\") {
48-
line.push_str(&l);
49-
line.pop();
50-
prompt = ">> ";
51-
continue;
52-
} else {
53-
line.push_str(&l);
54-
let _ = rl.add_history_entry(line.as_str());
55-
return Ok(Some(line));
56-
}
57-
},
58-
Err(ReadlineError::Interrupted | ReadlineError::Eof) => {
59-
return Ok(None);
60-
},
61-
Err(err) => {
62-
return Err(err);
63-
},
64-
}
39+
let prompt = prompt.unwrap_or_default();
40+
let curr_line = rl.readline(prompt);
41+
match curr_line {
42+
Ok(line) => Ok(Some(line)),
43+
Err(ReadlineError::Interrupted | ReadlineError::Eof) => Ok(None),
44+
Err(err) => Err(err),
6545
}
6646
},
6747
inner::Inner::Mock { index, lines } => {

crates/q_cli/src/cli/chat/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,15 @@ const WELCOME_TEXT: &str = color_print::cstr! {"
102102
<em>/help</em> <black!>Show the help dialogue</black!>
103103
<em>/quit</em> <black!>Quit the application</black!>
104104
105+
<cyan!>Use Alt+Enter to provide multi-line prompts.</cyan!>
105106
106107
"};
107108

108109
const HELP_TEXT: &str = color_print::cstr! {"
109110
110111
<magenta,em>q</magenta,em> (Amazon Q Chat)
111112
113+
<cyan,em>Commands:</cyan,em>
112114
<em>/clear</em> <black!>Clear the conversation history</black!>
113115
<em>/acceptall</em> <black!>Toggles acceptance prompting for the session.</black!>
114116
<em>/issue</em> <black!>Report an issue or make a feature request.</black!>
@@ -128,7 +130,9 @@ const HELP_TEXT: &str = color_print::cstr! {"
128130
<em>rm</em> <black!>Remove file(s) from context [--global]</black!>
129131
<em>clear</em> <black!>Clear all files from current context [--global]</black!>
130132
133+
<cyan,em>Tips:</cyan,em>
131134
<em>!{command}</em> <black!>Quickly execute a command in your current session</black!>
135+
<em>Alt+Enter</em> <black!>Insert new-line to provide multi-line prompt. Alternatively, [Ctrl+j]</black!>
132136
133137
"};
134138

crates/q_cli/src/cli/chat/prompt.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,25 @@ use rustyline::highlight::{
1313
Highlighter,
1414
};
1515
use rustyline::history::DefaultHistory;
16+
use rustyline::validate::{
17+
ValidationContext,
18+
ValidationResult,
19+
Validator,
20+
};
1621
use rustyline::{
22+
Cmd,
1723
Completer,
1824
CompletionType,
1925
Config,
2026
Context,
2127
EditMode,
2228
Editor,
29+
EventHandler,
2330
Helper,
2431
Hinter,
25-
Validator,
32+
KeyCode,
33+
KeyEvent,
34+
Modifiers,
2635
};
2736
use winnow::stream::AsChar;
2837

@@ -151,14 +160,44 @@ impl Completer for ChatCompleter {
151160
}
152161
}
153162

154-
#[derive(Helper, Completer, Hinter, Validator)]
163+
/// Custom validator for multi-line input
164+
pub struct MultiLineValidator;
165+
166+
impl Validator for MultiLineValidator {
167+
fn validate(&self, ctx: &mut ValidationContext<'_>) -> rustyline::Result<ValidationResult> {
168+
let input = ctx.input();
169+
170+
if input.trim().is_empty() {
171+
return Ok(ValidationResult::Incomplete);
172+
}
173+
174+
// Check for explicit multi-line markers
175+
if input.starts_with("```") && !input.ends_with("```") {
176+
return Ok(ValidationResult::Incomplete);
177+
}
178+
179+
// Check for backslash continuation
180+
if input.ends_with('\\') {
181+
return Ok(ValidationResult::Incomplete);
182+
}
183+
184+
Ok(ValidationResult::Valid(None))
185+
}
186+
}
187+
188+
#[derive(Helper, Completer, Hinter)]
155189
pub struct ChatHelper {
156190
#[rustyline(Completer)]
157191
completer: ChatCompleter,
158-
#[rustyline(Validator)]
159-
validator: (),
160192
#[rustyline(Hinter)]
161193
hinter: (),
194+
validator: MultiLineValidator,
195+
}
196+
197+
impl Validator for ChatHelper {
198+
fn validate(&self, ctx: &mut ValidationContext<'_>) -> rustyline::Result<ValidationResult> {
199+
self.validator.validate(ctx)
200+
}
162201
}
163202

164203
impl Highlighter for ChatHelper {
@@ -203,10 +242,23 @@ pub fn rl() -> Result<Editor<ChatHelper, DefaultHistory>> {
203242
let h = ChatHelper {
204243
completer: ChatCompleter::new(),
205244
hinter: (),
206-
validator: (),
245+
validator: MultiLineValidator,
207246
};
208247
let mut rl = Editor::with_config(config)?;
209248
rl.set_helper(Some(h));
249+
250+
// Add custom keybinding for Alt+Enter to insert a newline
251+
rl.bind_sequence(
252+
KeyEvent(KeyCode::Enter, Modifiers::ALT),
253+
EventHandler::Simple(Cmd::Insert(1, "\n".to_string())),
254+
);
255+
256+
// Add custom keybinding for Ctrl+J to insert a newline
257+
rl.bind_sequence(
258+
KeyEvent(KeyCode::Char('j'), Modifiers::CTRL),
259+
EventHandler::Simple(Cmd::Insert(1, "\n".to_string())),
260+
);
261+
210262
Ok(rl)
211263
}
212264

0 commit comments

Comments
 (0)