Jeremy Rose
2025-09-02 10:29:58 -07:00
committed by GitHub
parent 3f8d6021ac
commit e442ecedab
84 changed files with 2896 additions and 2167 deletions

View File

@@ -100,53 +100,47 @@ impl BottomPane {
}
pub fn desired_height(&self, width: u16) -> u16 {
let top_margin = if self.active_view.is_some() { 0 } else { 1 };
// Always reserve one blank row above the pane for visual spacing.
let top_margin = 1;
// Base height depends on whether a modal/overlay is active.
let mut base = if let Some(view) = self.active_view.as_ref() {
view.desired_height(width)
} else {
self.composer.desired_height(width)
let base = match self.active_view.as_ref() {
Some(view) => view.desired_height(width),
None => self.composer.desired_height(width).saturating_add(
self.status
.as_ref()
.map_or(0, |status| status.desired_height(width)),
),
};
// If a status indicator is active and no modal is covering the composer,
// include its height above the composer.
if self.active_view.is_none()
&& let Some(status) = self.status.as_ref()
{
base = base.saturating_add(status.desired_height(width));
}
// Account for bottom padding rows. Top spacing is handled in layout().
base.saturating_add(Self::BOTTOM_PAD_LINES)
.saturating_add(top_margin)
}
fn layout(&self, area: Rect) -> [Rect; 2] {
// Prefer showing the status header when space is extremely tight.
// Drop the top spacer if there is only one row available.
let mut top_margin = if self.active_view.is_some() { 0 } else { 1 };
if area.height <= 1 {
top_margin = 0;
}
let status_height = if self.active_view.is_none() {
if let Some(status) = self.status.as_ref() {
status.desired_height(area.width)
} else {
0
}
// At small heights, bottom pane takes the entire height.
let (top_margin, bottom_margin) = if area.height <= BottomPane::BOTTOM_PAD_LINES + 1 {
(0, 0)
} else {
0
(1, BottomPane::BOTTOM_PAD_LINES)
};
let [_, status, content, _] = Layout::vertical([
Constraint::Max(top_margin),
Constraint::Max(status_height),
Constraint::Min(1),
Constraint::Max(BottomPane::BOTTOM_PAD_LINES),
])
.areas(area);
[status, content]
let area = Rect {
x: area.x,
y: area.y + top_margin,
width: area.width,
height: area.height - top_margin - bottom_margin,
};
match self.active_view.as_ref() {
Some(_) => [Rect::ZERO, area],
None => {
let status_height = self
.status
.as_ref()
.map_or(0, |status| status.desired_height(area.width));
Layout::vertical([Constraint::Max(status_height), Constraint::Min(1)]).areas(area)
}
}
}
pub fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
@@ -705,7 +699,7 @@ mod tests {
pane.set_task_running(true);
// Height=2 → composer visible; status is hidden to preserve composer. Spacer may collapse.
// Height=2 → status on one row, composer on the other.
let area2 = Rect::new(0, 0, 20, 2);
let mut buf2 = Buffer::empty(area2);
(&pane).render_ref(area2, &mut buf2);
@@ -721,8 +715,8 @@ mod tests {
"expected composer to be visible on one of the rows: row0={row0:?}, row1={row1:?}"
);
assert!(
!row0.contains("Working") && !row1.contains("Working"),
"status header should be hidden when height=2"
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).