aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@atuin.sh>2026-04-04 04:00:37 +0100
committerGitHub <noreply@github.com>2026-04-04 04:00:37 +0100
commitf68c3de3b7bd15a5f16ca09352277d297574787e (patch)
tree32cf481b6af1b1ff035a7b7fde89f93c2a8bab9d /crates
parentfeat: option to disable mouse support (#3372) (diff)
downloadatuin-f68c3de3b7bd15a5f16ca09352277d297574787e.zip
feat: add support for deleting all matching commands via keybindings (#3375)
Diffstat (limited to 'crates')
-rw-r--r--crates/atuin-client/src/history/store.rs14
-rw-r--r--crates/atuin/src/command/client/search.rs10
-rw-r--r--crates/atuin/src/command/client/search/interactive.rs37
-rw-r--r--crates/atuin/src/command/client/search/keybindings/actions.rs3
-rw-r--r--crates/atuin/src/command/client/search/keybindings/defaults.rs1
5 files changed, 52 insertions, 13 deletions
diff --git a/crates/atuin-client/src/history/store.rs b/crates/atuin-client/src/history/store.rs
index d166564f..ce7b43a1 100644
--- a/crates/atuin-client/src/history/store.rs
+++ b/crates/atuin-client/src/history/store.rs
@@ -180,6 +180,20 @@ impl HistoryStore {
self.push_record(record).await
}
+ /// Delete a batch of history entries via the record store.
+ /// Returns the record IDs so the caller can run incremental_build when ready.
+ pub async fn delete_entries(
+ &self,
+ entries: impl IntoIterator<Item = History>,
+ ) -> Result<Vec<RecordId>> {
+ let mut record_ids = Vec::new();
+ for entry in entries {
+ let (id, _) = self.delete(entry.id).await?;
+ record_ids.push(id);
+ }
+ Ok(record_ids)
+ }
+
pub async fn push(&self, history: History) -> Result<(RecordId, RecordIdx)> {
// TODO(ellie): move the history store to its own file
// it's tiny rn so fine as is
diff --git a/crates/atuin/src/command/client/search.rs b/crates/atuin/src/command/client/search.rs
index 7c72e13d..3d348473 100644
--- a/crates/atuin/src/command/client/search.rs
+++ b/crates/atuin/src/command/client/search.rs
@@ -265,15 +265,11 @@ impl Cmd {
while !entries.is_empty() {
for entry in &entries {
eprintln!("deleting {}", entry.id);
-
- if settings.sync.records {
- let (id, _) = history_store.delete(entry.id.clone()).await?;
- history_store.incremental_build(&db, &[id]).await?;
- } else {
- db.delete(entry.clone()).await?;
- }
}
+ let ids = history_store.delete_entries(entries).await?;
+ history_store.incremental_build(&db, &ids).await?;
+
entries =
run_non_interactive(settings, opt_filter.clone(), &query, &db).await?;
}
diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs
index 8e5f8551..ee38ddaa 100644
--- a/crates/atuin/src/command/client/search/interactive.rs
+++ b/crates/atuin/src/command/client/search/interactive.rs
@@ -62,6 +62,7 @@ pub enum InputAction {
AcceptInspecting,
Copy(usize),
Delete(usize),
+ DeleteAllMatching(usize),
ReturnOriginal,
ReturnQuery,
Continue,
@@ -643,6 +644,7 @@ impl State {
}
Action::Copy => InputAction::Copy(self.results_state.selected()),
Action::Delete => InputAction::Delete(self.results_state.selected()),
+ Action::DeleteAll => InputAction::DeleteAllMatching(self.results_state.selected()),
Action::ReturnOriginal => InputAction::ReturnOriginal,
Action::ReturnQuery => InputAction::ReturnQuery,
Action::Exit => Self::handle_key_exit(settings),
@@ -1836,15 +1838,37 @@ pub async fn history(
let entry = results.remove(index);
- if settings.sync.records {
- let (id, _) = history_store.delete(entry.id).await?;
- history_store.incremental_build(&db, &[id]).await?;
- } else {
- db.delete(entry.clone()).await?;
- }
+ let ids = history_store.delete_entries([entry]).await?;
+ history_store.incremental_build(&db, &ids).await?;
app.tab_index = 0;
},
+ InputAction::DeleteAllMatching(index) => {
+ if results.is_empty() {
+ break;
+ }
+
+ let command = results[index].command.clone();
+
+ // Remove matching entries from the visible results
+ results.retain(|e| e.command != command);
+
+ // Query the DB for ALL entries with this command and delete them
+ let all_matching = db.query_history(
+ &format!(
+ "select * from history where command = '{}' and deleted_at is null",
+ command.replace('\'', "''")
+ )
+ ).await?;
+
+ let ids = history_store.delete_entries(all_matching).await?;
+ history_store.incremental_build(&db, &ids).await?;
+
+ app.results_len = results.len();
+ app.results_state = ListState::default();
+ app.inspecting_state.reset();
+ app.tab_index = 0;
+ },
InputAction::SwitchContext(index) => {
if let Some(index) = index && let Some(entry) = results.get(index) {
app.search.custom_context = Some(entry.id.clone());
@@ -2006,6 +2030,7 @@ pub async fn history(
InputAction::Continue
| InputAction::Redraw
| InputAction::Delete(_)
+ | InputAction::DeleteAllMatching(_)
| InputAction::SwitchContext(_) => {
unreachable!("should have been handled!")
}
diff --git a/crates/atuin/src/command/client/search/keybindings/actions.rs b/crates/atuin/src/command/client/search/keybindings/actions.rs
index 66e2709e..ff2ef7de 100644
--- a/crates/atuin/src/command/client/search/keybindings/actions.rs
+++ b/crates/atuin/src/command/client/search/keybindings/actions.rs
@@ -46,6 +46,7 @@ pub enum Action {
// Commands — other
Copy,
Delete,
+ DeleteAll,
ReturnOriginal,
ReturnQuery,
Exit,
@@ -125,6 +126,7 @@ impl Action {
"return-selection" => Ok(Action::ReturnSelection),
"copy" => Ok(Action::Copy),
"delete" => Ok(Action::Delete),
+ "delete-all" => Ok(Action::DeleteAll),
"return-original" => Ok(Action::ReturnOriginal),
"return-query" => Ok(Action::ReturnQuery),
"exit" => Ok(Action::Exit),
@@ -191,6 +193,7 @@ impl Action {
Action::ReturnSelectionNth(n) => format!("return-selection-{n}"),
Action::Copy => "copy".to_string(),
Action::Delete => "delete".to_string(),
+ Action::DeleteAll => "delete-all".to_string(),
Action::ReturnOriginal => "return-original".to_string(),
Action::ReturnQuery => "return-query".to_string(),
Action::Exit => "exit".to_string(),
diff --git a/crates/atuin/src/command/client/search/keybindings/defaults.rs b/crates/atuin/src/command/client/search/keybindings/defaults.rs
index f19bf377..e9b3972c 100644
--- a/crates/atuin/src/command/client/search/keybindings/defaults.rs
+++ b/crates/atuin/src/command/client/search/keybindings/defaults.rs
@@ -404,6 +404,7 @@ pub fn default_prefix_keymap() -> Keymap {
let mut km = Keymap::new();
km.bind(key("d"), Action::Delete);
+ km.bind(key("D"), Action::DeleteAll);
km.bind(key("a"), Action::CursorStart);
km.bind_conditional(
key("c"),