diff --git a/src/history/base.rs b/src/history/base.rs index 63b5bd4..16b8b49 100644 --- a/src/history/base.rs +++ b/src/history/base.rs @@ -165,6 +165,8 @@ pub trait History: Send { id: HistoryItemId, updater: &dyn Fn(HistoryItem) -> HistoryItem, ) -> Result<()>; + /// delete all history items + fn clear(&mut self) -> Result<()>; /// remove an item from this history fn delete(&mut self, h: HistoryItemId) -> Result<()>; /// ensure that this history is written to disk @@ -322,4 +324,52 @@ mod test { Ok(()) } + + #[test] + fn clear_history() -> Result<()> { + let mut history = create_filled_example_history()?; + assert_ne!(history.count_all()?, 0); + history.clear().unwrap(); + assert_eq!(history.count_all()?, 0); + + Ok(()) + } + + // test that clear() works as expected across multiple instances of History + #[test] + fn clear_history_with_backing_file() -> Result<()> { + #[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))] + fn open_history() -> Box { + Box::new( + crate::SqliteBackedHistory::with_file("target/test-history.db".into()).unwrap(), + ) + } + + #[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))] + fn open_history() -> Box { + Box::new( + crate::FileBackedHistory::with_file(100, "target/test-history.txt".into()).unwrap(), + ) + } + + // create history, add a few entries + let mut history = open_history(); + history.save(create_item(1, "/home/me", "cd ~/Downloads", 0))?; // 1 + history.save(create_item(1, "/home/me/Downloads", "unzp foo.zip", 1))?; // 2 + assert_eq!(history.count_all()?, 2); + drop(history); + + // open it again and clear it + let mut history = open_history(); + assert_eq!(history.count_all()?, 2); + history.clear().unwrap(); + assert_eq!(history.count_all()?, 0); + drop(history); + + // open it once more and confirm that the cleared data is gone forever + let history = open_history(); + assert_eq!(history.count_all()?, 0); + + Ok(()) + } } diff --git a/src/history/file_backed.rs b/src/history/file_backed.rs index 7026036..ca4d491 100644 --- a/src/history/file_backed.rs +++ b/src/history/file_backed.rs @@ -178,6 +178,19 @@ impl History for FileBackedHistory { )) } + fn clear(&mut self) -> Result<()> { + self.entries.clear(); + self.len_on_disk = 0; + + if let Some(file) = &self.file { + if let Err(err) = std::fs::remove_file(file) { + return Err(ReedlineError(ReedlineErrorVariants::IOError(err))); + } + } + + Ok(()) + } + fn delete(&mut self, _h: super::HistoryItemId) -> Result<()> { Err(ReedlineError( ReedlineErrorVariants::HistoryFeatureUnsupported { diff --git a/src/history/sqlite_backed.rs b/src/history/sqlite_backed.rs index 9aafe23..d5c9e3c 100644 --- a/src/history/sqlite_backed.rs +++ b/src/history/sqlite_backed.rs @@ -136,6 +136,14 @@ impl History for SqliteBackedHistory { Ok(()) } + fn clear(&mut self) -> Result<()> { + self.db + .execute("delete from history", params![]) + .map_err(map_sqlite_err)?; + + Ok(()) + } + fn delete(&mut self, h: HistoryItemId) -> Result<()> { let changed = self .db diff --git a/src/result.rs b/src/result.rs index 4a88d72..5170381 100644 --- a/src/result.rs +++ b/src/result.rs @@ -15,6 +15,8 @@ pub(crate) enum ReedlineErrorVariants { history: &'static str, feature: &'static str, }, + #[error("I/O error: {0}")] + IOError(std::io::Error), } /// separate struct to not expose anything to the public (for now)