feat: Ctrl+J for newline in Rust TUI, default to one line of height (#926)

While the `TextArea` used in the Rust TUI is "multiline," it is not like
an HTML `<textarea>` in that it does not wrap, so there was not much
benefit to setting `MIN_TEXTAREA_ROWS` to `3`, so this PR changes it to
`1`. Though there are now three ways to "increase" the height due to
actual linebreaks:

* paste in multiline content (this worked before this PR)
* pressing `Ctrl+J` will insert a newline
* if you have your terminal emulator set such that it is possible to
press something that `crossterm` interprets as "Enter plus some
modifier," then now that will also work

Now things look a bit more compact on startup:

<img width="745" alt="image"
src="https://github.com/user-attachments/assets/86e2857f-f31c-46f5-a80b-1ab2120b266e"
/>
This commit is contained in:
Michael Bolin
2025-05-13 21:42:14 -07:00
committed by GitHub
parent 5bf9445351
commit 1bf00a3a95

View File

@@ -29,7 +29,7 @@ use crate::user_approval_widget::ApprovalRequest;
use crate::user_approval_widget::UserApprovalWidget;
/// Minimum number of visible text rows inside the textarea.
const MIN_TEXTAREA_ROWS: usize = 3;
const MIN_TEXTAREA_ROWS: usize = 1;
/// Number of terminal rows consumed by the textarea border (top + bottom).
const TEXTAREA_BORDER_LINES: u16 = 2;
@@ -176,6 +176,24 @@ impl<'a> BottomPane<'a> {
self.request_redraw()?;
Ok(InputResult::Submitted(text))
}
Input {
key: Key::Enter, ..
}
| Input {
key: Key::Char('j'),
ctrl: true,
alt: false,
shift: false,
} => {
// If the user has their terminal emulator configured so
// Enter+Shift (or any modifier) sends a different key
// event, we should let them insert a newline.
//
// We also allow Ctrl+J to insert a newline.
self.textarea.insert_newline();
self.request_redraw()?;
Ok(InputResult::None)
}
input => {
self.textarea.input(input);
self.request_redraw()?;
@@ -284,7 +302,6 @@ impl WidgetRef for &BottomPane<'_> {
// for all variants of PaneState.
fn update_border_for_input_focus(textarea: &mut TextArea, state: &PaneState, has_focus: bool) {
struct BlockState {
title: &'static str,
right_title: Line<'static>,
border_style: Style,
}
@@ -297,26 +314,23 @@ fn update_border_for_input_focus(textarea: &mut TextArea, state: &PaneState, has
let block_state = if has_focus && accepting_input {
BlockState {
title: "use Enter to send for now (Ctrl-D to quit)",
right_title: Line::from("press enter to send").alignment(Alignment::Right),
right_title: Line::from("Enter to send | Ctrl+D to quit | Ctrl+J for newline")
.alignment(Alignment::Right),
border_style: Style::default(),
}
} else {
BlockState {
title: "",
right_title: Line::from(""),
border_style: Style::default().dim(),
}
};
let BlockState {
title,
right_title,
border_style,
} = block_state;
textarea.set_block(
ratatui::widgets::Block::default()
.title_bottom(title)
.title_bottom(right_title)
.borders(ratatui::widgets::Borders::ALL)
.border_type(BorderType::Rounded)