Repository: Canop/bacon (language: rust)

Issue:
Feature Request: Pause?
When working through a large set of warnings, I would like to be able to pause `bacon` to stop re-rendering on each save. This is because on every save the list gets progressively re-rendered / re-ordered which makes it difficult to handle a set of known warnings.

The reason I would like to use bacon here instead of just just directly running `cargo check` is because it provides a nice summary view and I would like to "unpause" occasionally to start the background compilation again.


Discussion / hints:
This makes sense.
There is no implementation in the rust std library to pause `Child` instances however it would be possible to just freeze accepting new output.
@TheTollingBell There's no technical problem here, I just lack time for writing the implementation at the moment
In fact, I think I just encountered a situation in which I'd like to look at a lot of warnings, and ignore most of them, by running clippy with `-W clippy::pedantic`:

![image](https://github.com/user-attachments/assets/a397f1e7-e43e-497f-b810-ee296225dbeb)

I wonder if what would be needed isn't a shortcut (with remote call API so that it can be called from an editor) to *acknowledge* a warning and make it go away (for a session). Maybe along "pausing" the results. What's your opinion on that  @AlJohri ?

Fix patch:
diff --git a/src/app.rs b/src/app.rs
index 81759b4..d7e7249 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -98,8 +98,12 @@ pub fn run(
         let mut action: Option<&Action> = None;
         select! {
             recv(watch_receiver) -> _ => {
-                task_executor.die();
-                task_executor = state.start_computation(&mut executor)?;
+                if state.auto_refresh.is_enabled() {
+                    task_executor.die();
+                    task_executor = state.start_computation(&mut executor)?;
+                } else {
+                    state.auto_refresh = AutoRefresh::PausedWithMisses;
+                }
             }
             recv(executor.line_receiver) -> info => {
                 if let Ok(info) = info {
@@ -192,6 +196,31 @@ pub fn run(
                         let scroll_command = *scroll_command;
                         state.apply_scroll_command(scroll_command);
                     }
+                    Internal::Pause => {
+                        state.auto_refresh = AutoRefresh::Paused;
+                    }
+                    Internal::Unpause => {
+                        if state.auto_refresh == AutoRefresh::PausedWithMisses {
+                            state.clear();
+                            task_executor.die();
+                            task_executor = state.start_computation(&mut executor)?;
+                        }
+                        state.auto_refresh = AutoRefresh::Enabled;
+                    }
+                    Internal::TogglePause => match state.auto_refresh {
+                        AutoRefresh::Enabled => {
+                            state.auto_refresh = AutoRefresh::Paused;
+                        }
+                        AutoRefresh::Paused => {
+                            state.auto_refresh = AutoRefresh::Enabled;
+                        }
+                        AutoRefresh::PausedWithMisses => {
+                            state.auto_refresh = AutoRefresh::Enabled;
+                            state.clear();
+                            task_executor.die();
+                            task_executor = state.start_computation(&mut executor)?;
+                        }
+                    },
                 },
                 Action::Job(job_ref) => {
                     next_job = Some((*job_ref).clone());
diff --git a/src/auto_refresh.rs b/src/auto_refresh.rs
new file mode 100644
index 0000000..d7ba7fe
--- /dev/null
+++ b/src/auto_refresh.rs
@@ -0,0 +1,16 @@
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum AutoRefresh {
+    Paused,
+    PausedWithMisses,
+    Enabled,
+}
+
+impl AutoRefresh {
+    pub fn is_enabled(self) -> bool {
+        matches!(self, AutoRefresh::Enabled)
+    }
+
+    pub fn is_paused(self) -> bool {
+        matches!(self, AutoRefresh::Paused | AutoRefresh::PausedWithMisses)
+    }
+}
diff --git a/src/help_line.rs b/src/help_line.rs
index 49f215b..b8efa84 100644
--- a/src/help_line.rs
+++ b/src/help_line.rs
@@ -8,6 +8,8 @@ pub struct HelpLine {
     toggle_backtrace: Option<String>,
     help: Option<String>,
     close_help: Option<String>,
+    pause: Option<String>,
+    unpause: Option<String>,
 }
 
 impl HelpLine {
@@ -36,6 +38,14 @@ impl HelpLine {
             .shortest_internal_key(Internal::Back)
             .or_else(|| kb.shortest_internal_key(Internal::Help))
             .map(|k| format!("*{k}* to close this help"));
+        let pause = kb
+            .shortest_internal_key(Internal::Pause)
+            .or(kb.shortest_internal_key(Internal::TogglePause))
+            .map(|k| format!("*{k}* to pause"));
+        let unpause = kb
+            .shortest_internal_key(Internal::Unpause)
+            .or(kb.shortest_internal_key(Internal::TogglePause))
+            .map(|k| format!("*{k}* to unpause"));
         Self {
             quit,
             toggle_summary,
@@ -44,6 +54,8 @@ impl HelpLine {
             toggle_backtrace,
             help,
             close_help,
+            pause,
+            unpause,
         }
     }
     pub fn markdown(
@@ -56,6 +68,11 @@ impl HelpLine {
                 parts.push(s);
             }
         } else {
+            if state.auto_refresh.is_paused() {
+                if let Some(s) = &self.unpause {
+                    parts.push(s);
+                }
+            }
             if let CommandResult::Report(report) = &state.cmd_result {
                 if report.suggest_backtrace {
                     if let Some(s) = &self.toggle_backtrace {
@@ -76,6 +93,11 @@ impl HelpLine {
                     parts.push(s);
                 }
             }
+            if state.auto_refresh.is_enabled() {
+                if let Some(s) = &self.pause {
+                    parts.push(s);
+                }
+            }
             if let Some(s) = &self.help {
                 parts.push(s);
             }
diff --git a/src/internal.rs b/src/internal.rs
index e15211b..fa1d82a 100644
--- a/src/internal.rs
+++ b/src/internal.rs
@@ -17,6 +17,9 @@ pub enum Internal {
     ToggleRawOutput,
     ToggleSummary,
     ToggleWrap,
+    Pause,
+    Unpause,
+    TogglePause, // either pause or unpause
 }
 
 impl fmt::Display for Internal {
@@ -35,6 +38,9 @@ impl fmt::Display for Internal {
             Self::ToggleRawOutput => write!(f, "toggle raw output"),
             Self::ToggleSummary => write!(f, "toggle summary"),
             Self::ToggleWrap => write!(f, "toggle wrap"),
+            Self::Pause => write!(f, "pause"),
+            Self::Unpause => write!(f, "unpause"),
+            Self::TogglePause => write!(f, "toggle pause"),
         }
     }
 }
@@ -55,6 +61,9 @@ impl std::str::FromStr for Internal {
             "toggle-backtrace" => Ok(Self::ToggleBacktrace),
             "toggle-summary" => Ok(Self::ToggleSummary),
             "toggle-wrap" => Ok(Self::ToggleWrap),
+            "pause" => Ok(Self::Pause),
+            "unpause" => Ok(Self::Unpause),
+            "toggle pause" => Ok(Self::TogglePause),
             _ => Err(()),
         }
     }
diff --git a/src/keybindings.rs b/src/keybindings.rs
index 1972b2e..a80dbef 100644
--- a/src/keybindings.rs
+++ b/src/keybindings.rs
@@ -41,6 +41,7 @@ impl Default for KeyBindings {
         bindings.set(key!(esc), Internal::Back);
         bindings.set(key!(ctrl - d), JobRef::Default);
         bindings.set(key!(i), JobRef::Initial);
+        bindings.set(key!(p), Internal::TogglePause);
         // keybindings for some common jobs
         bindings.set(key!(a), JobRef::from_job_name("check-all"));
         bindings.set(key!(c), JobRef::from_job_name("clippy"));
diff --git a/src/main.rs b/src/main.rs
index 12b2c41..d822955 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
 mod action;
 mod app;
 mod args;
+mod auto_refresh;
 mod cli;
 mod command_output;
 mod command_result;
@@ -39,6 +40,7 @@ mod wrapped_report;
 pub use {
     action::*,
     args::*,
+    auto_refresh::*,
     cli::*,
     command_output::*,
     command_result::*,
diff --git a/src/state.rs b/src/state.rs
index d79f167..961b45c 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -61,6 +61,8 @@ pub struct AppState<'s> {
     help_page: Option<HelpPage>,
     /// display the raw output instead of the report
     raw_output: bool,
+    /// whether auto-refresh is enabled
+    pub auto_refresh: AutoRefresh,
 }
 
 impl<'s> AppState<'s> {
@@ -95,6 +97,7 @@ impl<'s> AppState<'s> {
             help_page: None,
             mission,
             raw_output: false,
+            auto_refresh: AutoRefresh::Enabled,
         })
     }
 
diff --git a/website/docs/config.md b/website/docs/config.md
index a2db2a9..4b02160 100644
--- a/website/docs/config.md
+++ b/website/docs/config.md
@@ -167,6 +167,9 @@ scroll-lines(-1) | <kbd>↑</kbd> | move one line up
 scroll-lines(1) | <kbd>↓</kbd> | move one line down
 scroll-pages(-1) | <kbd>PageUp</kbd> | move one page up
 scroll-pages(1) | <kbd>PageDown</kbd> | move one page down
+pause |  | disable automatic job execution on change
+unpause |  | enable automatic job execution on change
+toggle pause | <kbd>p</kbd> | toggle pause
 
 The `scroll-lines` and `scroll-pages` internals are parameterized.
 You can for example define a shortcut to move down 5 lines:
diff --git a/website/docs/cookbook.md b/website/docs/cookbook.md
index c3011cc..c63cb79 100644
--- a/website/docs/cookbook.md
+++ b/website/docs/cookbook.md
@@ -51,6 +51,12 @@ command = [
 need_stdout = false
 ```
 
+You may also add some modifiers on spot sessions, eg
+
+```bash
+bacon clippy -- -W clippy::pedantic
+```
+
 # Check for other platforms
 
 You may define specific jobs for specific targets:


Test patch: