mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 20:52:01 +03:00
fix(cli-rs): refresh the home, prompt and sidebar footers on a resize
This commit is contained in:
@@ -42,14 +42,6 @@ impl Footer {
|
||||
status: RefCell::default(),
|
||||
});
|
||||
inner.refresh(tui);
|
||||
// The visibility follows the terminal size (`useTerminalDimensions`).
|
||||
let weak = Rc::downgrade(&inner);
|
||||
let owner = tui.weak();
|
||||
inner.root.on_resize(move |_, _| {
|
||||
if let (Some(inner), Some(tui)) = (weak.upgrade(), owner.upgrade()) {
|
||||
inner.refresh(&tui);
|
||||
}
|
||||
});
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
@@ -125,10 +117,9 @@ impl Slotted<Tui> for Footer {
|
||||
}
|
||||
|
||||
fn sync(&self, tui: &Tui, changes: &Changes) {
|
||||
// It reads the default location's MCP servers and the terminal size, which its root's resize misses
|
||||
// when only the height changes.
|
||||
let resized = self.0.shown.borrow().as_ref().is_some_and(|model| model.size != tui.app.size());
|
||||
if !changes.is_empty() && (resized || tui.data.read(|state| changes.location(state.default_location()))) {
|
||||
// It reads the default location's MCP servers and the terminal size; the home syncs every change on a
|
||||
// resize.
|
||||
if tui.data.read(|state| changes.location(state.default_location())) {
|
||||
self.0.refresh(tui);
|
||||
}
|
||||
self.0.status.borrow().sync(tui, changes);
|
||||
|
||||
@@ -412,12 +412,26 @@ pub fn mount(tui: &Tui, session: Option<&SessionID>) -> Prompt {
|
||||
}
|
||||
});
|
||||
inner.layers.borrow_mut().push(mentions);
|
||||
// `useCursorStyle` and the animations follow the config, as the metadata row's auto-accept mark does.
|
||||
// `useCursorStyle` and the animations follow the config, as the metadata row's auto-accept mark does; the
|
||||
// footer's shortcuts follow the keybinds.
|
||||
tui.settings.on_change(bind!(inner, |inner, tui| {
|
||||
let cursor = crate::features::system_commands::cursor(&tui.config());
|
||||
inner.textarea.cursor_style(cursor.0, cursor.1);
|
||||
inner.staging.set_animations(opencode_ui::motion::enabled(tui.config().info.animations));
|
||||
inner.refresh(tui);
|
||||
inner.relayout(tui);
|
||||
}));
|
||||
// The metadata row and the footer follow the terminal size (`useTerminalDimensions`).
|
||||
let weak = Rc::downgrade(&inner);
|
||||
let resize = tui.app.on_resize(move |_, _, _| {
|
||||
if let Some(inner) = weak.upgrade()
|
||||
&& let Some(tui) = inner.tui.upgrade()
|
||||
{
|
||||
inner.relayout(&tui);
|
||||
}
|
||||
});
|
||||
let app = tui.app.clone();
|
||||
inner.layers.borrow_mut().push(Disposer::new(move || {
|
||||
app.remove_handler(resize);
|
||||
}));
|
||||
// Home drops the last session's selection; a session follows its directory (`editor.reconnect`).
|
||||
match session {
|
||||
@@ -708,6 +722,13 @@ impl Inner {
|
||||
*spinner = Some(mounted);
|
||||
}
|
||||
|
||||
/// Repaints after a change outside the store, the terminal size or the settings, which the footer slots read too.
|
||||
fn relayout(&self, tui: &Tui) {
|
||||
self.refresh(tui);
|
||||
let everything = Changes::everything();
|
||||
self.footer_slots.borrow().1.iter().for_each(|slot| slot.sync(tui, &everything));
|
||||
}
|
||||
|
||||
/// Mounts the `prompt.footer` slots again when their input changed (E.14).
|
||||
fn mount_footer(&self, tui: &Tui) {
|
||||
let input = opencode_plugin::SlotInput {
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct Home {
|
||||
/// The global form overlay (E.9b); while it holds the keys, the prompt must not take the focus.
|
||||
forms: crate::features::home_forms::HomeForms,
|
||||
/// The `home.footer` slot's plugin claims (P.1).
|
||||
slot: opencode_plugin::SlotMount<Tui>,
|
||||
slot: std::rc::Rc<opencode_plugin::SlotMount<Tui>>,
|
||||
}
|
||||
|
||||
impl Home {
|
||||
@@ -59,18 +59,19 @@ impl Home {
|
||||
.child(&composer)
|
||||
.child(&spacer());
|
||||
let footer = View::new(app).column().width(Length::percent(100.0)).shrink(0.0);
|
||||
let slot = crate::plugins::host::slot(
|
||||
let slot = std::rc::Rc::new(crate::plugins::host::slot(
|
||||
tui,
|
||||
&footer,
|
||||
opencode_plugin::slot::HOME_FOOTER,
|
||||
&Default::default(),
|
||||
// The footer itself is the `opencode.home.footer` plugin's claim (E.14).
|
||||
|_| {},
|
||||
);
|
||||
));
|
||||
let root = View::new(app).column().grow(1.0).basis(Length::Auto).min_height(0).child(¢er).child(&footer);
|
||||
let forms = crate::features::home_forms::mount(tui, &root);
|
||||
// The logo and the side padding follow the terminal size (`useTerminalDimensions`).
|
||||
// The logo, the side padding and the footer follow the terminal size (`useTerminalDimensions`).
|
||||
let size = std::cell::Cell::new((width, height));
|
||||
let footer_slot = slot.clone();
|
||||
root.on_resize(bind!(tui, |tui, cx, _| {
|
||||
let (width, height) = cx.app().size();
|
||||
if size.replace((width, height)) == (width, height) {
|
||||
@@ -78,6 +79,7 @@ impl Home {
|
||||
}
|
||||
center.padding_x(padding(width));
|
||||
paint_logo(&tui.app, &logo, &tui.page(), width, height);
|
||||
footer_slot.sync(&tui, &Changes::everything());
|
||||
tui.app.request_redraw();
|
||||
}));
|
||||
Self { root, prompt, forms, slot }
|
||||
|
||||
@@ -120,8 +120,15 @@ pub fn mount(tui: &Tui, row: &View, session: &SessionID) -> Sidebar {
|
||||
let (Some(inner), Some(tui)) = (weak.upgrade(), owner.upgrade()) else {
|
||||
return;
|
||||
};
|
||||
if size.replace(cx.app().size()) != cx.app().size() {
|
||||
inner.layout(&tui);
|
||||
if size.replace(cx.app().size()) == cx.app().size() {
|
||||
return;
|
||||
}
|
||||
inner.layout(&tui);
|
||||
// The footer's card follows the terminal height.
|
||||
if inner.placed.get() != Place::Hidden
|
||||
&& let Some(footer) = inner.slots.borrow().last()
|
||||
{
|
||||
footer.sync(&tui, &Changes::everything());
|
||||
}
|
||||
});
|
||||
tui.settings.on_change(bind!(inner, |inner, tui| inner.layout(tui)));
|
||||
|
||||
@@ -28,6 +28,7 @@ mod plugins;
|
||||
mod prompt;
|
||||
mod queue;
|
||||
mod reconnect;
|
||||
mod resize;
|
||||
mod review_ms1;
|
||||
mod review_ms34;
|
||||
mod root;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//! Footers that follow the terminal size (`useTerminalDimensions`) refresh on a resize alone, with no store
|
||||
//! change: the home footer, the prompt footer and the sidebar footer.
|
||||
|
||||
use crate::support::{self, DIRECTORY, Fake, Harness};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use opencode_tui::{Deps, Route};
|
||||
|
||||
fn mount(width: u16, height: u16, route: Route) -> Harness {
|
||||
let fake = Fake::new(|seen| support::base(seen, DIRECTORY));
|
||||
support::mount(
|
||||
width,
|
||||
height,
|
||||
Deps { config: support::config(support::still()), route, ..support::deps(Arc::new(fake)) },
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_home_footer_hides_when_the_terminal_gets_short() {
|
||||
let h = mount(80, 20, Route::Home);
|
||||
h.wait_for("ctrl+p commands");
|
||||
h.wait_for("Model M");
|
||||
h.app.advance(std::time::Duration::from_millis(10));
|
||||
let screen = h.app.text();
|
||||
assert!(screen.lines().last().unwrap().trim_end().ends_with("local"), "{screen}");
|
||||
h.app.resize(80, 12);
|
||||
h.wait("the home footer stays below 14 rows", || !h.app.text().contains("local"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_prompt_footer_shows_its_hints_when_the_terminal_gets_wide() {
|
||||
let h = mount(36, 20, Route::Home);
|
||||
h.wait_for("Model M");
|
||||
h.app.advance(std::time::Duration::from_millis(10));
|
||||
assert!(!h.app.text().contains("ctrl+p commands"), "{}", h.app.text());
|
||||
h.app.resize(80, 20);
|
||||
h.wait_for("ctrl+p commands");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_sidebar_card_shows_when_the_terminal_gets_tall() {
|
||||
let h = mount(140, 18, support::session_route("ses_a"));
|
||||
h.wait_for("Fix the parser");
|
||||
h.app.advance(std::time::Duration::from_millis(10));
|
||||
assert!(!h.app.text().contains("Getting started"), "{}", h.app.text());
|
||||
h.app.resize(140, 26);
|
||||
h.wait_for("Getting started");
|
||||
}
|
||||
Reference in New Issue
Block a user