Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add History::delete for FileBackedHistory #736

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use {

#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
use reedline::FileBackedHistory;
use reedline::{CursorConfig, MenuBuilder};
use reedline::{CursorConfig, HistoryItemId, MenuBuilder};

fn main() -> reedline::Result<()> {
println!("Ctrl-D to quit");
Expand Down Expand Up @@ -175,6 +175,18 @@ fn main() -> reedline::Result<()> {
line_editor.print_history_session()?;
continue;
}
// Delete history entry of a certain id
if buffer.trim().starts_with("history remove-item") {
let parts: Vec<&str> = buffer.split_whitespace().collect();
if parts.len() == 3 {
if let Ok(id) = parts[2].parse::<i64>() {
line_editor.history_mut().delete(HistoryItemId::new(id))?;
continue;
}
}
println!("Invalid command. Use: history remove-item <id>");
continue;
}
// Get this history session identifier
if buffer.trim() == "history sessionid" {
line_editor.print_history_session_id()?;
Expand Down
5 changes: 3 additions & 2 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,9 @@ impl Reedline {
.search(SearchQuery::everything(SearchDirection::Forward, None))
.expect("todo: error handling");

for (i, entry) in history.iter().enumerate() {
self.print_line(&format!("{}\t{}", i, entry.command_line))?;
for entry in history.iter() {
let Some(id) = entry.id else { continue };
self.print_line(&format!("{}\t{}", id, entry.command_line))?;
}
Ok(())
}
Expand Down
56 changes: 45 additions & 11 deletions src/history/file_backed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub struct FileBackedHistory {
file: Option<PathBuf>,
len_on_disk: usize, // Keep track what was previously written to disk
session: Option<HistorySessionId>,
overwrite: bool, // Will overwrite file on next sync, not just new entries
}

impl Default for FileBackedHistory {
Expand Down Expand Up @@ -205,23 +206,30 @@ impl History for FileBackedHistory {
Ok(())
}

fn delete(&mut self, _h: super::HistoryItemId) -> Result<()> {
Err(ReedlineError(
ReedlineErrorVariants::HistoryFeatureUnsupported {
history: "FileBackedHistory",
feature: "removing entries",
},
))
fn delete(&mut self, h: super::HistoryItemId) -> Result<()> {
let id = h.0 as usize;
let num_entries = self.entries.len();
// Check if the id is valid
if id >= num_entries {
return Err(ReedlineError(ReedlineErrorVariants::OtherHistoryError(
"Given id is out of range.",
)));
}

// Remove the item with the specified id
self.entries.remove(id);
self.overwrite = true;

Ok(())
}

/// Writes unwritten history contents to disk.
/// Syncs current state with disk.
///
/// Normally, that means reading entries from the file and appending new entries to it.
/// If file would exceed `capacity` truncates the oldest entries.
/// If necessary, the whole file is overwritten.
fn sync(&mut self) -> std::io::Result<()> {
if let Some(fname) = &self.file {
// The unwritten entries
let own_entries = self.entries.range(self.len_on_disk..);

if let Some(base_dir) = fname.parent() {
std::fs::create_dir_all(base_dir)?;
}
Expand All @@ -231,14 +239,39 @@ impl History for FileBackedHistory {
.create(true)
.write(true)
.read(true)
.truncate(self.overwrite)
.open(fname)?,
);
let mut writer_guard = f_lock.write()?;

if self.overwrite {
self.overwrite = false;
let mut writer = BufWriter::new(writer_guard.deref_mut());
for line in &self.entries {
writer.write_all(encode_entry(line).as_bytes())?;
writer.write_all("\n".as_bytes())?;
}
writer.flush()?;
return Ok(());
}

// The unwritten entries
let own_entries = self.entries.range(self.len_on_disk..);

let (mut foreign_entries, truncate) = {
let reader = BufReader::new(writer_guard.deref());
let mut from_file = reader
.lines()
.map(|o| o.map(|i| decode_entry(&i)))
.filter(|e| {
if let Ok(entry) = e {
if entry.starts_with(' ') {
self.overwrite = true;
return false;
}
}
true
})
.collect::<std::io::Result<VecDeque<_>>>()?;
if from_file.len() + own_entries.len() > self.capacity {
(
Expand Down Expand Up @@ -306,6 +339,7 @@ impl FileBackedHistory {
file: None,
len_on_disk: 0,
session: None,
overwrite: false,
})
}

Expand Down
Loading