[TUI] Remove bottom padding (#4854)
We don't need the bottom padding. It currently just makes the footer look off-centered. Before: <img width="1905" height="478" alt="image" src="https://github.com/user-attachments/assets/c2a18b38-b8fd-4317-bbbb-2843bca02ba1" /> After: <img width="617" height="479" alt="image" src="https://github.com/user-attachments/assets/f42470c5-4b24-4a02-b15c-e2aad03e3b42" />
This commit is contained in:
@@ -81,7 +81,7 @@ pub(crate) struct BottomPaneParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BottomPane {
|
impl BottomPane {
|
||||||
const BOTTOM_PAD_LINES: u16 = 1;
|
const BOTTOM_PAD_LINES: u16 = 0;
|
||||||
pub fn new(params: BottomPaneParams) -> Self {
|
pub fn new(params: BottomPaneParams) -> Self {
|
||||||
let enhanced_keys_supported = params.enhanced_keys_supported;
|
let enhanced_keys_supported = params.enhanced_keys_supported;
|
||||||
Self {
|
Self {
|
||||||
@@ -522,10 +522,29 @@ impl WidgetRef for &BottomPane {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::app_event::AppEvent;
|
use crate::app_event::AppEvent;
|
||||||
|
use insta::assert_snapshot;
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use tokio::sync::mpsc::unbounded_channel;
|
use tokio::sync::mpsc::unbounded_channel;
|
||||||
|
|
||||||
|
fn snapshot_buffer(buf: &Buffer) -> String {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
for y in 0..buf.area().height {
|
||||||
|
let mut row = String::new();
|
||||||
|
for x in 0..buf.area().width {
|
||||||
|
row.push(buf[(x, y)].symbol().chars().next().unwrap_or(' '));
|
||||||
|
}
|
||||||
|
lines.push(row);
|
||||||
|
}
|
||||||
|
lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_snapshot(pane: &BottomPane, area: Rect) -> String {
|
||||||
|
let mut buf = Buffer::empty(area);
|
||||||
|
(&pane).render_ref(area, &mut buf);
|
||||||
|
snapshot_buffer(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
fn exec_request() -> ApprovalRequest {
|
fn exec_request() -> ApprovalRequest {
|
||||||
ApprovalRequest::Exec {
|
ApprovalRequest::Exec {
|
||||||
id: "1".to_string(),
|
id: "1".to_string(),
|
||||||
@@ -685,7 +704,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bottom_padding_present_with_status_above_composer() {
|
fn status_and_composer_fill_height_without_bottom_padding() {
|
||||||
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
||||||
let tx = AppEventSender::new(tx_raw);
|
let tx = AppEventSender::new(tx_raw);
|
||||||
let mut pane = BottomPane::new(BottomPaneParams {
|
let mut pane = BottomPane::new(BottomPaneParams {
|
||||||
@@ -700,43 +719,21 @@ mod tests {
|
|||||||
// Activate spinner (status view replaces composer) with no live ring.
|
// Activate spinner (status view replaces composer) with no live ring.
|
||||||
pane.set_task_running(true);
|
pane.set_task_running(true);
|
||||||
|
|
||||||
// Use height == desired_height; expect 1 status row at top and 2 bottom padding rows.
|
// Use height == desired_height; expect spacer + status + composer rows without trailing padding.
|
||||||
let height = pane.desired_height(30);
|
let height = pane.desired_height(30);
|
||||||
assert!(
|
assert!(
|
||||||
height >= 3,
|
height >= 3,
|
||||||
"expected at least 3 rows with bottom padding; got {height}"
|
"expected at least 3 rows to render spacer, status, and composer; got {height}"
|
||||||
);
|
);
|
||||||
let area = Rect::new(0, 0, 30, height);
|
let area = Rect::new(0, 0, 30, height);
|
||||||
let mut buf = Buffer::empty(area);
|
assert_snapshot!(
|
||||||
(&pane).render_ref(area, &mut buf);
|
"status_and_composer_fill_height_without_bottom_padding",
|
||||||
|
render_snapshot(&pane, area)
|
||||||
// Row 1 contains the status header (row 0 is the spacer)
|
|
||||||
let mut top = String::new();
|
|
||||||
for x in 0..area.width {
|
|
||||||
top.push(buf[(x, 1)].symbol().chars().next().unwrap_or(' '));
|
|
||||||
}
|
|
||||||
assert!(
|
|
||||||
top.trim_start().starts_with("• Working"),
|
|
||||||
"expected top row to start with '• Working': {top:?}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
top.contains("Working"),
|
|
||||||
"expected Working header on top row: {top:?}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Last row should be blank padding; the row above should generally contain composer content.
|
|
||||||
let mut r_last = String::new();
|
|
||||||
for x in 0..area.width {
|
|
||||||
r_last.push(buf[(x, height - 1)].symbol().chars().next().unwrap_or(' '));
|
|
||||||
}
|
|
||||||
assert!(
|
|
||||||
r_last.trim().is_empty(),
|
|
||||||
"expected last row blank: {r_last:?}"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bottom_padding_shrinks_when_tiny() {
|
fn status_hidden_when_height_too_small() {
|
||||||
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
||||||
let tx = AppEventSender::new(tx_raw);
|
let tx = AppEventSender::new(tx_raw);
|
||||||
let mut pane = BottomPane::new(BottomPaneParams {
|
let mut pane = BottomPane::new(BottomPaneParams {
|
||||||
@@ -750,37 +747,18 @@ mod tests {
|
|||||||
|
|
||||||
pane.set_task_running(true);
|
pane.set_task_running(true);
|
||||||
|
|
||||||
// Height=2 → status on one row, composer on the other.
|
// Height=2 → composer takes the full space; status collapses when there is no room.
|
||||||
let area2 = Rect::new(0, 0, 20, 2);
|
let area2 = Rect::new(0, 0, 20, 2);
|
||||||
let mut buf2 = Buffer::empty(area2);
|
assert_snapshot!(
|
||||||
(&pane).render_ref(area2, &mut buf2);
|
"status_hidden_when_height_too_small_height_2",
|
||||||
let mut row0 = String::new();
|
render_snapshot(&pane, area2)
|
||||||
let mut row1 = String::new();
|
|
||||||
for x in 0..area2.width {
|
|
||||||
row0.push(buf2[(x, 0)].symbol().chars().next().unwrap_or(' '));
|
|
||||||
row1.push(buf2[(x, 1)].symbol().chars().next().unwrap_or(' '));
|
|
||||||
}
|
|
||||||
let has_composer = row0.contains("Ask Codex") || row1.contains("Ask Codex");
|
|
||||||
assert!(
|
|
||||||
has_composer,
|
|
||||||
"expected composer to be visible on one of the rows: row0={row0:?}, row1={row1:?}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
row0.contains("Working") || row1.contains("Working"),
|
|
||||||
"expected status header to be visible at height=2: row0={row0:?}, row1={row1:?}"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Height=1 → no padding; single row is the composer (status hidden).
|
// Height=1 → no padding; single row is the composer (status hidden).
|
||||||
let area1 = Rect::new(0, 0, 20, 1);
|
let area1 = Rect::new(0, 0, 20, 1);
|
||||||
let mut buf1 = Buffer::empty(area1);
|
assert_snapshot!(
|
||||||
(&pane).render_ref(area1, &mut buf1);
|
"status_hidden_when_height_too_small_height_1",
|
||||||
let mut only = String::new();
|
render_snapshot(&pane, area1)
|
||||||
for x in 0..area1.width {
|
|
||||||
only.push(buf1[(x, 0)].symbol().chars().next().unwrap_or(' '));
|
|
||||||
}
|
|
||||||
assert!(
|
|
||||||
only.contains("Ask Codex"),
|
|
||||||
"expected composer with no padding: {only:?}"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
source: tui/src/bottom_pane/mod.rs
|
||||||
|
expression: "render_snapshot(&pane, area)"
|
||||||
|
---
|
||||||
|
|
||||||
|
• Working (0s • esc to interru
|
||||||
|
|
||||||
|
|
||||||
|
› Ask Codex to do anything
|
||||||
|
|
||||||
|
? for shortcuts
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: tui/src/bottom_pane/mod.rs
|
||||||
|
expression: "render_snapshot(&pane, area1)"
|
||||||
|
---
|
||||||
|
› Ask Codex to do an
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: tui/src/bottom_pane/mod.rs
|
||||||
|
expression: "render_snapshot(&pane, area2)"
|
||||||
|
---
|
||||||
|
|
||||||
|
› Ask Codex to do an
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 1470
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
"› Ask Codex to do anything "
|
|
||||||
" "
|
" "
|
||||||
|
"› Ask Codex to do anything "
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 1500
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
"• Thinking (0s • esc to interrupt) "
|
" "
|
||||||
"› Ask Codex to do anything "
|
"› Ask Codex to do anything "
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 1500
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
|
"• Thinking (0s • esc to interrupt) "
|
||||||
"› Ask Codex to do anything "
|
"› Ask Codex to do anything "
|
||||||
" "
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 409
|
||||||
expression: "format!(\"{buf:?}\")"
|
expression: "format!(\"{buf:?}\")"
|
||||||
---
|
---
|
||||||
Buffer {
|
Buffer {
|
||||||
area: Rect { x: 0, y: 0, width: 80, height: 15 },
|
area: Rect { x: 0, y: 0, width: 80, height: 14 },
|
||||||
content: [
|
content: [
|
||||||
" ",
|
" ",
|
||||||
" ",
|
" ",
|
||||||
@@ -19,7 +20,6 @@ Buffer {
|
|||||||
" 3. No, and tell Codex what to do differently esc ",
|
" 3. No, and tell Codex what to do differently esc ",
|
||||||
" ",
|
" ",
|
||||||
" Press enter to confirm or esc to cancel ",
|
" Press enter to confirm or esc to cancel ",
|
||||||
" ",
|
|
||||||
],
|
],
|
||||||
styles: [
|
styles: [
|
||||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||||
@@ -34,6 +34,5 @@ Buffer {
|
|||||||
x: 47, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
x: 47, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||||
x: 50, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
x: 50, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||||
x: 2, y: 13, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
x: 2, y: 13, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||||
x: 0, y: 14, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 1577
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
@@ -9,4 +10,3 @@ expression: terminal.backend()
|
|||||||
"› Ask Codex to do anything "
|
"› Ask Codex to do anything "
|
||||||
" "
|
" "
|
||||||
" ? for shortcuts "
|
" ? for shortcuts "
|
||||||
" "
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 1548
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
@@ -16,4 +17,3 @@ expression: terminal.backend()
|
|||||||
" 3. No, and tell Codex what to do differently esc "
|
" 3. No, and tell Codex what to do differently esc "
|
||||||
" "
|
" "
|
||||||
" Press enter to confirm or esc to cancel "
|
" Press enter to confirm or esc to cancel "
|
||||||
" "
|
|
||||||
|
|||||||
Reference in New Issue
Block a user