diff --git a/crates/but/src/command/legacy/status/tui/mod.rs b/crates/but/src/command/legacy/status/tui/mod.rs index ad3030d9bc..fcc5a8e8f2 100644 --- a/crates/but/src/command/legacy/status/tui/mod.rs +++ b/crates/but/src/command/legacy/status/tui/mod.rs @@ -976,21 +976,41 @@ impl App { cmd.args(args); let _suspend_guard = terminal_guard.suspend()?; - cmd.spawn()?.wait()?; + let status = cmd.spawn()?.wait()?; - let mut input_channel = out - .prepare_for_terminal_input() - .context("failed to prepare input")?; - - input_channel.prompt_single_line("\npress enter to continue...")?; + self.prompt_to_continue(out)?; drop(_suspend_guard); - messages.extend([Message::EnterNormalMode, Message::Reload(None)]); + if status.success() { + messages.extend([Message::EnterNormalMode, Message::Reload(None)]); + } else { + self.push_transient_error(anyhow::Error::msg(format!( + "command exited with status {}", + format_exit_status(status) + ))); + } + + Ok(()) + } + + /// Prompts the user to press enter before returning from a command execution. + fn prompt_to_continue(&mut self, out: &mut OutputChannel) -> anyhow::Result<()> { + if let Some(mut input_channel) = out.prepare_for_terminal_input() { + input_channel.prompt_single_line("\npress enter to continue...")?; + } Ok(()) } + /// Adds a transient error popup message that auto-dismisses after a short duration. + fn push_transient_error(&mut self, err: anyhow::Error) { + self.errors.push(AppError { + inner: Arc::new(err), + dismiss_at: Instant::now() + Duration::from_secs(5), + }); + } + /// Returns the currently selected commit id when the selected line is a commit. fn selected_commit_id(&self) -> Option { let selection = self.cursor.selected_line(&self.status_lines)?; @@ -1679,6 +1699,15 @@ fn has_unassigned_changes(ctx: &mut Context) -> anyhow::Result { .any(|assignment| assignment.stack_id.is_none())) } +/// Formats an exit status for human-readable error messages. +fn format_exit_status(status: std::process::ExitStatus) -> String { + if let Some(code) = status.code() { + code.to_string() + } else { + status.to_string() + } +} + #[derive(Debug)] pub(super) struct AppError { pub(super) inner: Arc, diff --git a/crates/but/src/command/legacy/status/tui/tests/command_tests.rs b/crates/but/src/command/legacy/status/tui/tests/command_tests.rs new file mode 100644 index 0000000000..01c5895481 --- /dev/null +++ b/crates/but/src/command/legacy/status/tui/tests/command_tests.rs @@ -0,0 +1,38 @@ +use but_testsupport::Sandbox; +use crossterm::event::KeyCode; +use snapbox::{file, str}; + +use super::utils::test_tui; + +#[test] +fn command_mode_runs_successful_command_and_returns_to_normal_mode() { + let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap(); + env.setup_metadata(&["A"]).unwrap(); + + let mut tui = test_tui(env); + + tui.input_then_render(':') + .assert_rendered_eq(file!["snapshots/command_mode_success_001.txt"]); + + tui.input_then_render("--help") + .assert_rendered_eq(file!["snapshots/command_mode_success_002.txt"]); + + tui.input_then_render(KeyCode::Enter) + .assert_rendered_eq(file!["snapshots/command_mode_success_003.txt"]); +} + +#[test] +fn command_mode_keeps_input_when_command_exits_non_zero() { + let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap(); + env.setup_metadata(&["A"]).unwrap(); + + let mut tui = test_tui(env); + + tui.input_then_render(':'); + + tui.input_then_render("--definitely-not-a-real-flag"); + + tui.input_then_render(KeyCode::Enter) + .assert_rendered_eq(file!["snapshots/command_mode_failure_001.txt"]) + .assert_current_line_eq(str!["╭┄zz [unstaged changes]"]); +} diff --git a/crates/but/src/command/legacy/status/tui/tests/mod.rs b/crates/but/src/command/legacy/status/tui/tests/mod.rs index d852239096..6f900b23a4 100644 --- a/crates/but/src/command/legacy/status/tui/tests/mod.rs +++ b/crates/but/src/command/legacy/status/tui/tests/mod.rs @@ -4,6 +4,7 @@ use snapbox::{file, str}; use crate::command::legacy::status::tui::tests::utils::test_tui; +mod command_tests; mod commit_tests; mod rub_tests; mod utils; diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_failure_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_failure_001.txt new file mode 100644 index 0000000000..b3d4aba3ba --- /dev/null +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_failure_001.txt @@ -0,0 +1,20 @@ +"╭┄zz [unstaged changes] " +"┊ no changes " +"┊ " +"┊╭┄g0 [A] " +"┊● 9477ae7 add A " +"├╯ " +"┊ " +"┴ 0dc3733 [origin/main] 2000-01-02 add M " +" " +" " +" " +" " +" " +" " +" " +" " +" ┌⚠️ Error───────────────────────────────────┐ " Hidden by multi-width symbols: [(56, " ")] +" │ command exited with status [..] │ " +" └───────────────────────────────────────────┘ " +" command but --definitely-not-a-real-flag " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_001.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_001.txt new file mode 100644 index 0000000000..044a18c3ee --- /dev/null +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_001.txt @@ -0,0 +1,20 @@ +"╭┄zz [unstaged changes] " +"┊ no changes " +"┊ " +"┊╭┄g0 [A] " +"┊● 9477ae7 add A " +"├╯ " +"┊ " +"┴ 0dc3733 [origin/main] 2000-01-02 add M " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" command but " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_002.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_002.txt new file mode 100644 index 0000000000..76f8c7662b --- /dev/null +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_002.txt @@ -0,0 +1,20 @@ +"╭┄zz [unstaged changes] " +"┊ no changes " +"┊ " +"┊╭┄g0 [A] " +"┊● 9477ae7 add A " +"├╯ " +"┊ " +"┴ 0dc3733 [origin/main] 2000-01-02 add M " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" command but --help " diff --git a/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt new file mode 100644 index 0000000000..f8723250b4 --- /dev/null +++ b/crates/but/src/command/legacy/status/tui/tests/snapshots/command_mode_success_003.txt @@ -0,0 +1,20 @@ +"╭┄zz [unstaged changes] " +"┊ no changes " +"┊ " +"┊╭┄g0 [A] " +"┊● 9477ae7 add A " +"├╯ " +"┊ " +"┴ 0dc3733 [origin/main] 2000-01-02 add M " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" normal ↓/j down • ↑/k up • f files • q quit • r rub • c commit • n new commit • enter reword inl"