mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-17 15:34:54 +00:00
@@ -1505,6 +1505,9 @@ impl ChatComposer {
|
||||
modifiers: KeyModifiers::NONE,
|
||||
..
|
||||
} => {
|
||||
if self.handle_backslash_continuation() {
|
||||
return (InputResult::None, true);
|
||||
}
|
||||
let command_text = self.textarea.text().to_string();
|
||||
let first_line = command_text.lines().next().unwrap_or("");
|
||||
if let Some((name, rest)) = parse_slash_name(first_line)
|
||||
@@ -1558,6 +1561,55 @@ impl ChatComposer {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_backslash_continuation(&mut self) -> bool {
|
||||
let text = self.textarea.text();
|
||||
if text.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut iter = text.char_indices().rev();
|
||||
let Some((last_idx, last_char)) = iter.next() else {
|
||||
return false;
|
||||
};
|
||||
if matches!(last_char, ' ' | '\t') {
|
||||
return false;
|
||||
}
|
||||
if last_char != '\\' {
|
||||
return false;
|
||||
}
|
||||
|
||||
let trailing_backslashes = text[..last_idx]
|
||||
.chars()
|
||||
.rev()
|
||||
.take_while(|c| *c == '\\')
|
||||
.count()
|
||||
+ 1;
|
||||
if trailing_backslashes % 2 == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let line_start = text[..last_idx].rfind('\n').map(|idx| idx + 1).unwrap_or(0);
|
||||
let line_before = &text[line_start..last_idx];
|
||||
let indentation: String = line_before
|
||||
.chars()
|
||||
.take_while(|c| matches!(*c, ' ' | '\t'))
|
||||
.collect();
|
||||
let replacement = if indentation.is_empty() {
|
||||
String::from("\n")
|
||||
} else {
|
||||
format!("\n{indentation}")
|
||||
};
|
||||
let backslash_end = last_idx + '\\'.len_utf8();
|
||||
self.textarea.replace_range(last_idx..backslash_end, &replacement);
|
||||
|
||||
self.history.reset_navigation();
|
||||
self.post_paste_space_guard = None;
|
||||
self.pending_pastes
|
||||
.retain(|(placeholder, _)| self.textarea.text().contains(placeholder));
|
||||
self.typed_anything = true;
|
||||
true
|
||||
}
|
||||
|
||||
/// Handle generic Input events that modify the textarea content.
|
||||
fn handle_input_basic(&mut self, input: KeyEvent) -> (InputResult, bool) {
|
||||
let text_before = self.textarea.text().to_string();
|
||||
|
||||
@@ -103,6 +103,11 @@ impl ComposerInput {
|
||||
WidgetRef::render_ref(&self.inner, area, buf);
|
||||
}
|
||||
|
||||
/// Return the current text contents of the composer.
|
||||
pub fn text(&self) -> &str {
|
||||
self.inner.text()
|
||||
}
|
||||
|
||||
/// Return true if a paste-burst detection is currently active.
|
||||
pub fn is_in_paste_burst(&self) -> bool {
|
||||
self.inner.is_in_paste_burst()
|
||||
|
||||
@@ -208,6 +208,77 @@ fn composer_input_shift_enter_no_submit() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_input_backslash_continuation_inserts_newline() {
|
||||
let mut composer = ComposerInput::new();
|
||||
composer.handle_paste("Hello\\".to_string());
|
||||
|
||||
let enter = make_key(KeyCode::Enter, KeyModifiers::NONE);
|
||||
match composer.input(enter) {
|
||||
ComposerAction::None => {
|
||||
assert_eq!(composer.text(), "Hello\n");
|
||||
assert!(!composer.is_empty(), "composer should retain text after continuation");
|
||||
}
|
||||
ComposerAction::Submitted(_) => panic!("continuation should not submit"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_input_backslash_continuation_trailing_space_submits() {
|
||||
let mut composer = ComposerInput::new();
|
||||
composer.handle_paste("Hello\\ ".to_string());
|
||||
|
||||
match composer.input(make_key(KeyCode::Enter, KeyModifiers::NONE)) {
|
||||
ComposerAction::Submitted(text) => {
|
||||
assert_eq!(text, "Hello\\ ");
|
||||
assert!(composer.is_empty(), "composer should clear after submission");
|
||||
}
|
||||
ComposerAction::None => panic!("trailing space should cancel continuation"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_input_backslash_even_count_submits() {
|
||||
let mut composer = ComposerInput::new();
|
||||
composer.handle_paste(String::from(r"Path: C\\"));
|
||||
|
||||
match composer.input(make_key(KeyCode::Enter, KeyModifiers::NONE)) {
|
||||
ComposerAction::Submitted(text) => assert_eq!(text, String::from(r"Path: C\\")),
|
||||
ComposerAction::None => panic!("even number of backslashes should submit"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_input_backslash_multiple_continuations() {
|
||||
let mut composer = ComposerInput::new();
|
||||
composer.handle_paste("line1\\".to_string());
|
||||
|
||||
assert!(matches!(
|
||||
composer.input(make_key(KeyCode::Enter, KeyModifiers::NONE)),
|
||||
ComposerAction::None
|
||||
));
|
||||
assert_eq!(composer.text(), "line1\n");
|
||||
|
||||
composer.handle_paste("line2\\".to_string());
|
||||
assert!(matches!(
|
||||
composer.input(make_key(KeyCode::Enter, KeyModifiers::NONE)),
|
||||
ComposerAction::None
|
||||
));
|
||||
assert_eq!(composer.text(), "line1\nline2\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_input_backslash_continuation_preserves_indent() {
|
||||
let mut composer = ComposerInput::new();
|
||||
composer.handle_paste(" indented\\".to_string());
|
||||
|
||||
assert!(matches!(
|
||||
composer.input(make_key(KeyCode::Enter, KeyModifiers::NONE)),
|
||||
ComposerAction::None
|
||||
));
|
||||
assert_eq!(composer.text(), " indented\n ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_input_ctrl_c_aborts_without_submit() {
|
||||
let mut composer = ComposerInput::new();
|
||||
|
||||
Reference in New Issue
Block a user