pod-spawn-ui完了・設定UI関連のチケット作成

This commit is contained in:
2026-04-27 17:38:32 +09:00
parent 978d542855
commit bc9eb3aa1c
6 changed files with 232 additions and 116 deletions
+28 -5
View File
@@ -10,6 +10,7 @@ mod ui;
use std::io;
use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Duration;
use crossterm::event::{
@@ -76,11 +77,18 @@ fn parse_args() -> Mode {
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> ExitCode {
let mode = parse_args();
enable_raw_mode()?;
execute!(io::stdout(), EnableBracketedPaste)?;
if let Err(e) = enable_raw_mode() {
eprintln!("tui: failed to enter raw mode: {e}");
return ExitCode::FAILURE;
}
if let Err(e) = execute!(io::stdout(), EnableBracketedPaste) {
let _ = disable_raw_mode();
eprintln!("tui: {e}");
return ExitCode::FAILURE;
}
let result = match mode {
Mode::Spawn => run_spawn().await,
@@ -90,13 +98,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} => run_attach(pod_name, socket_override).await,
};
// Always restore the terminal, even on error or panic-after-result.
// Always restore the terminal first so any pending eprintln below
// shows up cleanly in scrollback rather than inside an active
// alternate-screen buffer.
let mut stdout = io::stdout();
let _ = execute!(stdout, LeaveAlternateScreen, DisableBracketedPaste);
let _ = disable_raw_mode();
let _ = execute!(stdout, crossterm::cursor::Show);
result
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
// SpawnError has already been painted into the inline
// viewport's final frame, so it's already visible in the
// user's scrollback — printing it again would be a noisy
// duplicate. Other errors (attach-mode failures, terminal
// setup hiccups, etc.) need surfacing here.
if e.downcast_ref::<spawn::SpawnError>().is_none() {
eprintln!("tui: {e}");
}
ExitCode::FAILURE
}
}
}
async fn run_attach(
+24 -4
View File
@@ -135,6 +135,7 @@ pub async fn run() -> Result<SpawnOutcome, SpawnError> {
name_cursor: default_name.chars().count(),
name: default_name,
message: None,
editing: true,
};
let mut terminal = make_inline_terminal()?;
@@ -153,6 +154,7 @@ pub async fn run() -> Result<SpawnOutcome, SpawnError> {
break;
}
Some(Action::Cancel) => {
form.editing = false;
form.message = Some(("cancelled".to_string(), MessageKind::Info));
terminal.draw(|f| draw_form(f, &form))?;
drop(terminal);
@@ -170,7 +172,11 @@ pub async fn run() -> Result<SpawnOutcome, SpawnError> {
let overlay_toml = build_overlay_toml(&form);
// Phase 2: launch pod and wait for ready line.
// Phase 2: launch pod and wait for ready line. Drop the cursor
// out of the name field — subsequent frames are passive status
// updates, not input — so the cursor doesn't end up parked there
// when the inline terminal is finally dropped.
form.editing = false;
form.message = Some(("starting pod...".to_string(), MessageKind::Progress));
terminal.draw(|f| draw_form(f, &form))?;
@@ -425,6 +431,12 @@ struct Form {
/// char-based bookkeeping in case we relax `is_safe_name_char`.
name_cursor: usize,
message: Option<(String, MessageKind)>,
/// True while the dialog is accepting name input. Drives whether
/// the rendered frame parks the terminal cursor inside the name
/// field — when false (post-confirm / cancel / failure frames) the
/// cursor stays out so it does not collide with the shell prompt
/// after the inline terminal is dropped.
editing: bool,
}
impl Form {
@@ -499,9 +511,16 @@ fn draw_form(f: &mut Frame<'_>, form: &Form) {
f.render_widget(Paragraph::new(hint_line()), layout[3]);
f.render_widget(Paragraph::new(message_line(form)), layout[4]);
// Place the cursor inside the name field (col 8 = " name: ".len()).
let cursor_col = 2 + "name: ".len() + form.name_cursor;
f.set_cursor_position((layout[1].x + cursor_col as u16, layout[1].y));
if form.editing {
// Place the cursor inside the name field while the user is
// editing. Skipped on post-confirm frames so the inline
// viewport's drop leaves the cursor at the bottom of the
// rendered area rather than parked on the name line, which
// would let the shell prompt (or any later eprintln) clobber
// the rendered name field after exit.
let cursor_col = 2 + "name: ".len() + form.name_cursor;
f.set_cursor_position((layout[1].x + cursor_col as u16, layout[1].y));
}
}
fn name_line(form: &Form) -> Line<'_> {
@@ -578,6 +597,7 @@ mod tests {
name: name.to_string(),
name_cursor: name.chars().count(),
message: None,
editing: true,
}
}