Allow history searching via session id (#562)

* add the ability to search history with session id

* clippy
This commit is contained in:
Darren Schroeder 2023-04-18 07:13:49 -05:00 committed by GitHub
parent 27f4417191
commit 61c6409fb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 233 additions and 73 deletions

View File

@ -66,7 +66,17 @@ fn main() -> Result<()> {
emacs: None, emacs: None,
}; };
// Setting history_per_session to true will allow the history to be isolated to the current session
// Setting history_per_session to false will allow the history to be shared across all sessions
let history_per_session = false;
let mut history_session_id = if history_per_session {
Reedline::create_history_session_id()
} else {
None
};
let mut line_editor = Reedline::create() let mut line_editor = Reedline::create()
.with_history_session_id(history_session_id)
.with_history(history) .with_history(history)
.with_completer(completer) .with_completer(completer)
.with_quick_completions(true) .with_quick_completions(true)
@ -145,10 +155,34 @@ fn main() -> Result<()> {
line_editor.clear_scrollback()?; line_editor.clear_scrollback()?;
continue; continue;
} }
// Get the full history
if buffer.trim() == "history" { if buffer.trim() == "history" {
line_editor.print_history()?; line_editor.print_history()?;
continue; continue;
} }
// Get the history only pertinent to the current session
if buffer.trim() == "history session" {
line_editor.print_history_session()?;
continue;
}
// Get this history session identifier
if buffer.trim() == "history sessionid" {
line_editor.print_history_session_id()?;
continue;
}
// Toggle between the full history and the history pertinent to the current session
if buffer.trim() == "toggle history_session" {
let hist_session_id = if history_session_id.is_none() {
// If we never created a history session ID, create one now
let sesh = Reedline::create_history_session_id();
history_session_id = sesh;
sesh
} else {
history_session_id
};
line_editor.toggle_history_session_matching(hist_session_id)?;
continue;
}
if buffer.trim() == "clear-history" { if buffer.trim() == "clear-history" {
let hstry = Box::new(line_editor.history_mut()); let hstry = Box::new(line_editor.history_mut());
hstry hstry

View File

@ -158,14 +158,15 @@ impl Reedline {
let hinter = None; let hinter = None;
let validator = None; let validator = None;
let edit_mode = Box::<Emacs>::default(); let edit_mode = Box::<Emacs>::default();
let hist_session_id = Self::create_history_session_id(); let hist_session_id = None;
Reedline { Reedline {
editor: Editor::default(), editor: Editor::default(),
history, history,
history_cursor: HistoryCursor::new(HistoryNavigationQuery::Normal( history_cursor: HistoryCursor::new(
LineBuffer::default(), HistoryNavigationQuery::Normal(LineBuffer::default()),
)), hist_session_id,
),
history_session_id: hist_session_id, history_session_id: hist_session_id,
history_last_run_id: None, history_last_run_id: None,
input_mode: InputMode::Regular, input_mode: InputMode::Regular,
@ -188,7 +189,7 @@ impl Reedline {
} }
/// Get a new history session id based on the current time and the first commit datetime of reedline /// Get a new history session id based on the current time and the first commit datetime of reedline
fn create_history_session_id() -> Option<HistorySessionId> { pub fn create_history_session_id() -> Option<HistorySessionId> {
let nanos = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { let nanos = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => n.as_nanos() as i64, Ok(n) => n.as_nanos() as i64,
Err(_) => 0, Err(_) => 0,
@ -202,6 +203,14 @@ impl Reedline {
self.history_session_id self.history_session_id
} }
/// Set a new history session id
/// This should be used in situations where the user initially did not have a history_session_id
/// and then later realized they want to have one without restarting the application.
pub fn set_history_session_id(&mut self, session: Option<HistorySessionId>) -> Result<()> {
self.history_session_id = session;
Ok(())
}
/// A builder to include a [`Hinter`] in your instance of the Reedline engine /// A builder to include a [`Hinter`] in your instance of the Reedline engine
/// # Example /// # Example
/// ```rust /// ```rust
@ -380,6 +389,13 @@ impl Reedline {
self self
} }
/// A builder that adds the history item id
#[must_use]
pub fn with_history_session_id(mut self, session: Option<HistorySessionId>) -> Self {
self.history_session_id = session;
self
}
/// A builder that enables reedline changing the cursor shape based on the current edit mode. /// A builder that enables reedline changing the cursor shape based on the current edit mode.
/// The current implementation sets the cursor shape when drawing the prompt. /// The current implementation sets the cursor shape when drawing the prompt.
/// Do not use this if the cursor shape is set elsewhere, e.g. in the terminal settings or by ansi escape sequences. /// Do not use this if the cursor shape is set elsewhere, e.g. in the terminal settings or by ansi escape sequences.
@ -397,7 +413,7 @@ impl Reedline {
pub fn print_history(&mut self) -> Result<()> { pub fn print_history(&mut self) -> Result<()> {
let history: Vec<_> = self let history: Vec<_> = self
.history .history
.search(SearchQuery::everything(SearchDirection::Forward)) .search(SearchQuery::everything(SearchDirection::Forward, None))
.expect("todo: error handling"); .expect("todo: error handling");
for (i, entry) in history.iter().enumerate() { for (i, entry) in history.iter().enumerate() {
@ -406,6 +422,40 @@ impl Reedline {
Ok(()) Ok(())
} }
/// Output the complete [`History`] for this session, chronologically with numbering to the terminal
pub fn print_history_session(&mut self) -> Result<()> {
let history: Vec<_> = self
.history
.search(SearchQuery::everything(
SearchDirection::Forward,
self.get_history_session_id(),
))
.expect("todo: error handling");
for (i, entry) in history.iter().enumerate() {
self.print_line(&format!("{}\t{}", i, entry.command_line))?;
}
Ok(())
}
/// Print the history session id
pub fn print_history_session_id(&mut self) -> Result<()> {
println!("History Session Id: {:?}", self.get_history_session_id());
Ok(())
}
/// Toggle between having a history that uses the history session id and one that does not
pub fn toggle_history_session_matching(
&mut self,
session: Option<HistorySessionId>,
) -> Result<()> {
self.history_session_id = match self.get_history_session_id() {
Some(_) => None,
None => session,
};
Ok(())
}
/// Read-only view of the history /// Read-only view of the history
pub fn history(&self) -> &dyn History { pub fn history(&self) -> &dyn History {
&*self.history &*self.history
@ -1066,8 +1116,10 @@ impl Reedline {
fn previous_history(&mut self) { fn previous_history(&mut self) {
if self.input_mode != InputMode::HistoryTraversal { if self.input_mode != InputMode::HistoryTraversal {
self.input_mode = InputMode::HistoryTraversal; self.input_mode = InputMode::HistoryTraversal;
self.history_cursor = self.history_cursor = HistoryCursor::new(
HistoryCursor::new(self.get_history_navigation_based_on_line_buffer()); self.get_history_navigation_based_on_line_buffer(),
self.get_history_session_id(),
);
} }
self.history_cursor self.history_cursor
@ -1082,8 +1134,10 @@ impl Reedline {
fn next_history(&mut self) { fn next_history(&mut self) {
if self.input_mode != InputMode::HistoryTraversal { if self.input_mode != InputMode::HistoryTraversal {
self.input_mode = InputMode::HistoryTraversal; self.input_mode = InputMode::HistoryTraversal;
self.history_cursor = self.history_cursor = HistoryCursor::new(
HistoryCursor::new(self.get_history_navigation_based_on_line_buffer()); self.get_history_navigation_based_on_line_buffer(),
self.get_history_session_id(),
);
} }
self.history_cursor self.history_cursor
@ -1118,8 +1172,10 @@ impl Reedline {
/// ///
/// This mode uses a separate prompt and handles keybindings slightly differently! /// This mode uses a separate prompt and handles keybindings slightly differently!
fn enter_history_search(&mut self) { fn enter_history_search(&mut self) {
self.history_cursor = self.history_cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::SubstringSearch("".to_string())); HistoryNavigationQuery::SubstringSearch("".to_string()),
self.get_history_session_id(),
);
self.input_mode = InputMode::HistorySearch; self.input_mode = InputMode::HistorySearch;
} }
@ -1133,11 +1189,14 @@ impl Reedline {
let navigation = self.history_cursor.get_navigation(); let navigation = self.history_cursor.get_navigation();
if let HistoryNavigationQuery::SubstringSearch(mut substring) = navigation { if let HistoryNavigationQuery::SubstringSearch(mut substring) = navigation {
substring.push(*c); substring.push(*c);
self.history_cursor = self.history_cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::SubstringSearch(substring)); HistoryNavigationQuery::SubstringSearch(substring),
self.get_history_session_id(),
);
} else { } else {
self.history_cursor = HistoryCursor::new( self.history_cursor = HistoryCursor::new(
HistoryNavigationQuery::SubstringSearch(String::from(*c)), HistoryNavigationQuery::SubstringSearch(String::from(*c)),
self.get_history_session_id(),
); );
} }
self.history_cursor self.history_cursor
@ -1152,6 +1211,7 @@ impl Reedline {
self.history_cursor = HistoryCursor::new( self.history_cursor = HistoryCursor::new(
HistoryNavigationQuery::SubstringSearch(new_substring.to_string()), HistoryNavigationQuery::SubstringSearch(new_substring.to_string()),
self.get_history_session_id(),
); );
self.history_cursor self.history_cursor
.back(self.history.as_mut()) .back(self.history.as_mut())
@ -1275,7 +1335,7 @@ impl Reedline {
start_id: None, start_id: None,
end_id: None, end_id: None,
limit: Some(1), // fetch the latest one entries limit: Some(1), // fetch the latest one entries
filter: SearchFilter::anything(), filter: SearchFilter::anything(self.get_history_session_id()),
}) })
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
.get(index.saturating_sub(1)) .get(index.saturating_sub(1))
@ -1295,7 +1355,7 @@ impl Reedline {
start_id: None, start_id: None,
end_id: None, end_id: None,
limit: Some(index as i64), // fetch the latest n entries limit: Some(index as i64), // fetch the latest n entries
filter: SearchFilter::anything(), filter: SearchFilter::anything(self.get_history_session_id()),
}) })
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
.get(index.saturating_sub(1)) .get(index.saturating_sub(1))
@ -1315,7 +1375,7 @@ impl Reedline {
start_id: None, start_id: None,
end_id: None, end_id: None,
limit: Some((index + 1) as i64), // fetch the oldest n entries limit: Some((index + 1) as i64), // fetch the oldest n entries
filter: SearchFilter::anything(), filter: SearchFilter::anything(self.get_history_session_id()),
}) })
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
.get(index) .get(index)
@ -1328,7 +1388,9 @@ impl Reedline {
}), }),
ParseAction::LastToken => self ParseAction::LastToken => self
.history .history
.search(SearchQuery::last_with_search(SearchFilter::anything())) .search(SearchQuery::last_with_search(SearchFilter::anything(
self.get_history_session_id(),
)))
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
.get(0) .get(0)
.and_then(|history| history.command_line.split_whitespace().rev().next()) .and_then(|history| history.command_line.split_whitespace().rev().next())
@ -1533,7 +1595,7 @@ impl Reedline {
self.repaint(prompt)?; self.repaint(prompt)?;
if !buffer.is_empty() { if !buffer.is_empty() {
let mut entry = HistoryItem::from_command_line(&buffer); let mut entry = HistoryItem::from_command_line(&buffer);
entry.session_id = self.history_session_id; entry.session_id = self.get_history_session_id();
let entry = self.history.save(entry).expect("todo: error handling"); let entry = self.history.save(entry).expect("todo: error handling");
self.history_last_run_id = entry.id; self.history_last_run_id = entry.id;
} }

View File

@ -20,7 +20,10 @@ impl Hinter for DefaultHinter {
) -> String { ) -> String {
self.current_hint = if line.chars().count() >= self.min_chars { self.current_hint = if line.chars().count() >= self.min_chars {
history history
.search(SearchQuery::last_with_prefix(line.to_string())) .search(SearchQuery::last_with_prefix(
line.to_string(),
history.session(),
))
.expect("todo: error handling") .expect("todo: error handling")
.get(0) .get(0)
.map_or_else(String::new, |entry| { .map_or_else(String::new, |entry| {

View File

@ -1,8 +1,6 @@
use chrono::Utc;
use crate::{core_editor::LineBuffer, HistoryItem, Result};
use super::HistoryItemId; use super::HistoryItemId;
use crate::{core_editor::LineBuffer, HistoryItem, HistorySessionId, Result};
use chrono::Utc;
/// Browsing modes for a [`History`] /// Browsing modes for a [`History`]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -53,18 +51,23 @@ pub struct SearchFilter {
pub cwd_prefix: Option<String>, pub cwd_prefix: Option<String>,
/// Filter whether the command completed /// Filter whether the command completed
pub exit_successful: Option<bool>, pub exit_successful: Option<bool>,
/// Filter on the session id
pub session: Option<HistorySessionId>,
} }
impl SearchFilter { impl SearchFilter {
/// Create a search filter with a [`CommandLineSearch`] /// Create a search filter with a [`CommandLineSearch`]
pub fn from_text_search(cmd: CommandLineSearch) -> SearchFilter { pub fn from_text_search(
let mut s = SearchFilter::anything(); cmd: CommandLineSearch,
session: Option<HistorySessionId>,
) -> SearchFilter {
let mut s = SearchFilter::anything(session);
s.command_line = Some(cmd); s.command_line = Some(cmd);
s s
} }
/// No filter constraint /// anything within this session
pub const fn anything() -> SearchFilter { pub fn anything(session: Option<HistorySessionId>) -> SearchFilter {
SearchFilter { SearchFilter {
command_line: None, command_line: None,
not_command_line: None, not_command_line: None,
@ -72,6 +75,7 @@ impl SearchFilter {
cwd_exact: None, cwd_exact: None,
cwd_prefix: None, cwd_prefix: None,
exit_successful: None, exit_successful: None,
session,
} }
} }
} }
@ -105,7 +109,7 @@ impl SearchQuery {
start_id: None, start_id: None,
end_id: None, end_id: None,
limit: None, limit: None,
filter: SearchFilter::from_text_search(CommandLineSearch::Substring(contains)), filter: SearchFilter::from_text_search(CommandLineSearch::Substring(contains), None),
} }
} }
@ -123,14 +127,18 @@ impl SearchQuery {
} }
/// Get the most recent entry starting with the `prefix` /// Get the most recent entry starting with the `prefix`
pub fn last_with_prefix(prefix: String) -> SearchQuery { pub fn last_with_prefix(prefix: String, session: Option<HistorySessionId>) -> SearchQuery {
SearchQuery::last_with_search(SearchFilter::from_text_search(CommandLineSearch::Prefix( SearchQuery::last_with_search(SearchFilter::from_text_search(
prefix, CommandLineSearch::Prefix(prefix),
))) session,
))
} }
/// Query to get all entries in the given [`SearchDirection`] /// Query to get all entries in the given [`SearchDirection`]
pub const fn everything(direction: SearchDirection) -> SearchQuery { pub fn everything(
direction: SearchDirection,
session: Option<HistorySessionId>,
) -> SearchQuery {
SearchQuery { SearchQuery {
direction, direction,
start_time: None, start_time: None,
@ -138,7 +146,7 @@ impl SearchQuery {
start_id: None, start_id: None,
end_id: None, end_id: None,
limit: None, limit: None,
filter: SearchFilter::anything(), filter: SearchFilter::anything(session),
} }
} }
} }
@ -159,7 +167,7 @@ pub trait History: Send {
fn count(&self, query: SearchQuery) -> Result<i64>; fn count(&self, query: SearchQuery) -> Result<i64>;
/// return the total number of history items /// return the total number of history items
fn count_all(&self) -> Result<i64> { fn count_all(&self) -> Result<i64> {
self.count(SearchQuery::everything(SearchDirection::Forward)) self.count(SearchQuery::everything(SearchDirection::Forward, None))
} }
/// return the results of a query /// return the results of a query
fn search(&self, query: SearchQuery) -> Result<Vec<HistoryItem>>; fn search(&self, query: SearchQuery) -> Result<Vec<HistoryItem>>;
@ -176,6 +184,8 @@ pub trait History: Send {
fn delete(&mut self, h: HistoryItemId) -> Result<()>; fn delete(&mut self, h: HistoryItemId) -> Result<()>;
/// ensure that this history is written to disk /// ensure that this history is written to disk
fn sync(&mut self) -> std::io::Result<()>; fn sync(&mut self) -> std::io::Result<()>;
/// get the history session id
fn session(&self) -> Option<HistorySessionId>;
} }
#[cfg(test)] #[cfg(test)]
@ -265,7 +275,7 @@ mod test {
let history = create_filled_example_history()?; let history = create_filled_example_history()?;
println!( println!(
"{:#?}", "{:#?}",
history.search(SearchQuery::everything(SearchDirection::Forward)) history.search(SearchQuery::everything(SearchDirection::Forward, None))
); );
assert_eq!(history.count_all()?, if IS_FILE_BASED { 13 } else { 12 }); assert_eq!(history.count_all()?, if IS_FILE_BASED { 13 } else { 12 });
@ -275,7 +285,7 @@ mod test {
#[test] #[test]
fn get_latest() -> Result<()> { fn get_latest() -> Result<()> {
let history = create_filled_example_history()?; let history = create_filled_example_history()?;
let res = history.search(SearchQuery::last_with_search(SearchFilter::anything()))?; let res = history.search(SearchQuery::last_with_search(SearchFilter::anything(None)))?;
search_returned(&*history, res, vec![12])?; search_returned(&*history, res, vec![12])?;
Ok(()) Ok(())
@ -286,7 +296,7 @@ mod test {
let history = create_filled_example_history()?; let history = create_filled_example_history()?;
let res = history.search(SearchQuery { let res = history.search(SearchQuery {
limit: Some(1), limit: Some(1),
..SearchQuery::everything(SearchDirection::Forward) ..SearchQuery::everything(SearchDirection::Forward, None)
})?; })?;
search_returned(&*history, res, vec![if IS_FILE_BASED { 0 } else { 1 }])?; search_returned(&*history, res, vec![if IS_FILE_BASED { 0 } else { 1 }])?;
Ok(()) Ok(())
@ -296,8 +306,11 @@ mod test {
fn search_prefix() -> Result<()> { fn search_prefix() -> Result<()> {
let history = create_filled_example_history()?; let history = create_filled_example_history()?;
let res = history.search(SearchQuery { let res = history.search(SearchQuery {
filter: SearchFilter::from_text_search(CommandLineSearch::Prefix("ls ".to_string())), filter: SearchFilter::from_text_search(
..SearchQuery::everything(SearchDirection::Backward) CommandLineSearch::Prefix("ls ".to_string()),
None,
),
..SearchQuery::everything(SearchDirection::Backward, None)
})?; })?;
search_returned(&*history, res, vec![9, 6])?; search_returned(&*history, res, vec![9, 6])?;
@ -308,10 +321,11 @@ mod test {
fn search_includes() -> Result<()> { fn search_includes() -> Result<()> {
let history = create_filled_example_history()?; let history = create_filled_example_history()?;
let res = history.search(SearchQuery { let res = history.search(SearchQuery {
filter: SearchFilter::from_text_search(CommandLineSearch::Substring( filter: SearchFilter::from_text_search(
"foo.zip".to_string(), CommandLineSearch::Substring("foo.zip".to_string()),
)), None,
..SearchQuery::everything(SearchDirection::Forward) ),
..SearchQuery::everything(SearchDirection::Forward, None)
})?; })?;
search_returned(&*history, res, vec![2, 3])?; search_returned(&*history, res, vec![2, 3])?;
Ok(()) Ok(())
@ -321,9 +335,12 @@ mod test {
fn search_includes_limit() -> Result<()> { fn search_includes_limit() -> Result<()> {
let history = create_filled_example_history()?; let history = create_filled_example_history()?;
let res = history.search(SearchQuery { let res = history.search(SearchQuery {
filter: SearchFilter::from_text_search(CommandLineSearch::Substring("c".to_string())), filter: SearchFilter::from_text_search(
CommandLineSearch::Substring("c".to_string()),
None,
),
limit: Some(2), limit: Some(2),
..SearchQuery::everything(SearchDirection::Forward) ..SearchQuery::everything(SearchDirection::Forward, None)
})?; })?;
search_returned(&*history, res, vec![1, 4])?; search_returned(&*history, res, vec![1, 4])?;

View File

@ -1,4 +1,4 @@
use crate::{History, HistoryNavigationQuery}; use crate::{History, HistoryNavigationQuery, HistorySessionId};
use super::base::CommandLineSearch; use super::base::CommandLineSearch;
use super::base::SearchDirection; use super::base::SearchDirection;
@ -13,14 +13,16 @@ pub struct HistoryCursor {
query: HistoryNavigationQuery, query: HistoryNavigationQuery,
current: Option<HistoryItem>, current: Option<HistoryItem>,
skip_dupes: bool, skip_dupes: bool,
session: Option<HistorySessionId>,
} }
impl HistoryCursor { impl HistoryCursor {
pub const fn new(query: HistoryNavigationQuery) -> HistoryCursor { pub fn new(query: HistoryNavigationQuery, session: Option<HistorySessionId>) -> HistoryCursor {
HistoryCursor { HistoryCursor {
query, query,
current: None, current: None,
skip_dupes: true, skip_dupes: true,
session,
} }
} }
@ -38,13 +40,14 @@ impl HistoryCursor {
fn get_search_filter(&self) -> SearchFilter { fn get_search_filter(&self) -> SearchFilter {
let filter = match self.query.clone() { let filter = match self.query.clone() {
HistoryNavigationQuery::Normal(_) => SearchFilter::anything(), HistoryNavigationQuery::Normal(_) => SearchFilter::anything(self.session),
HistoryNavigationQuery::PrefixSearch(prefix) => { HistoryNavigationQuery::PrefixSearch(prefix) => {
SearchFilter::from_text_search(CommandLineSearch::Prefix(prefix)) SearchFilter::from_text_search(CommandLineSearch::Prefix(prefix), self.session)
}
HistoryNavigationQuery::SubstringSearch(substring) => {
SearchFilter::from_text_search(CommandLineSearch::Substring(substring))
} }
HistoryNavigationQuery::SubstringSearch(substring) => SearchFilter::from_text_search(
CommandLineSearch::Substring(substring),
self.session,
),
}; };
if let (true, Some(current)) = (self.skip_dupes, &self.current) { if let (true, Some(current)) = (self.skip_dupes, &self.current) {
SearchFilter { SearchFilter {
@ -112,20 +115,20 @@ mod tests {
let hist = Box::<FileBackedHistory>::default(); let hist = Box::<FileBackedHistory>::default();
( (
hist, hist,
HistoryCursor::new(HistoryNavigationQuery::Normal(LineBuffer::default())), HistoryCursor::new(HistoryNavigationQuery::Normal(LineBuffer::default()), None),
) )
} }
fn create_history_at(cap: usize, path: &Path) -> (Box<dyn History>, HistoryCursor) { fn create_history_at(cap: usize, path: &Path) -> (Box<dyn History>, HistoryCursor) {
let hist = Box::new(FileBackedHistory::with_file(cap, path.to_owned()).unwrap()); let hist = Box::new(FileBackedHistory::with_file(cap, path.to_owned()).unwrap());
( (
hist, hist,
HistoryCursor::new(HistoryNavigationQuery::Normal(LineBuffer::default())), HistoryCursor::new(HistoryNavigationQuery::Normal(LineBuffer::default()), None),
) )
} }
fn get_all_entry_texts(hist: &dyn History) -> Vec<String> { fn get_all_entry_texts(hist: &dyn History) -> Vec<String> {
let res = hist let res = hist
.search(SearchQuery::everything(SearchDirection::Forward)) .search(SearchQuery::everything(SearchDirection::Forward, None))
.unwrap(); .unwrap();
let actual: Vec<_> = res.iter().map(|e| e.command_line.to_string()).collect(); let actual: Vec<_> = res.iter().map(|e| e.command_line.to_string()).collect();
actual actual
@ -207,8 +210,10 @@ mod tests {
hist.save(HistoryItem::from_command_line("test"))?; hist.save(HistoryItem::from_command_line("test"))?;
hist.save(HistoryItem::from_command_line("find me"))?; hist.save(HistoryItem::from_command_line("find me"))?;
let mut cursor = let mut cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string())); HistoryNavigationQuery::PrefixSearch("find".to_string()),
None,
);
cursor.back(&*hist)?; cursor.back(&*hist)?;
assert_eq!(cursor.string_at_cursor(), Some("find me".to_string())); assert_eq!(cursor.string_at_cursor(), Some("find me".to_string()));
@ -227,8 +232,10 @@ mod tests {
hist.save(HistoryItem::from_command_line("test"))?; hist.save(HistoryItem::from_command_line("test"))?;
hist.save(HistoryItem::from_command_line("find me"))?; hist.save(HistoryItem::from_command_line("find me"))?;
let mut cursor = let mut cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string())); HistoryNavigationQuery::PrefixSearch("find".to_string()),
None,
);
cursor.back(&*hist)?; cursor.back(&*hist)?;
assert_eq!(cursor.string_at_cursor(), Some("find me".to_string())); assert_eq!(cursor.string_at_cursor(), Some("find me".to_string()));
cursor.back(&*hist)?; cursor.back(&*hist)?;
@ -253,8 +260,10 @@ mod tests {
hist.save(HistoryItem::from_command_line("test"))?; hist.save(HistoryItem::from_command_line("test"))?;
hist.save(HistoryItem::from_command_line("find me"))?; hist.save(HistoryItem::from_command_line("find me"))?;
let mut cursor = let mut cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string())); HistoryNavigationQuery::PrefixSearch("find".to_string()),
None,
);
cursor.back(&*hist)?; cursor.back(&*hist)?;
assert_eq!(cursor.string_at_cursor(), Some("find me".to_string())); assert_eq!(cursor.string_at_cursor(), Some("find me".to_string()));
cursor.back(&*hist)?; cursor.back(&*hist)?;
@ -279,8 +288,10 @@ mod tests {
hist.save(HistoryItem::from_command_line("test"))?; hist.save(HistoryItem::from_command_line("test"))?;
hist.save(HistoryItem::from_command_line("find me once"))?; hist.save(HistoryItem::from_command_line("find me once"))?;
let mut cursor = let mut cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string())); HistoryNavigationQuery::PrefixSearch("find".to_string()),
None,
);
cursor.back(&*hist)?; cursor.back(&*hist)?;
assert_eq!(cursor.string_at_cursor(), Some("find me once".to_string())); assert_eq!(cursor.string_at_cursor(), Some("find me once".to_string()));
cursor.back(&*hist)?; cursor.back(&*hist)?;
@ -299,8 +310,10 @@ mod tests {
hist.save(HistoryItem::from_command_line("find me once"))?; hist.save(HistoryItem::from_command_line("find me once"))?;
hist.save(HistoryItem::from_command_line("find me as well"))?; hist.save(HistoryItem::from_command_line("find me as well"))?;
let mut cursor = let mut cursor = HistoryCursor::new(
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string())); HistoryNavigationQuery::PrefixSearch("find".to_string()),
None,
);
cursor.back(&*hist)?; cursor.back(&*hist)?;
assert_eq!( assert_eq!(
cursor.string_at_cursor(), cursor.string_at_cursor(),
@ -328,9 +341,10 @@ mod tests {
hist.save(HistoryItem::from_command_line("don't find me"))?; hist.save(HistoryItem::from_command_line("don't find me"))?;
hist.save(HistoryItem::from_command_line("prefix substring suffix"))?; hist.save(HistoryItem::from_command_line("prefix substring suffix"))?;
let mut cursor = HistoryCursor::new(HistoryNavigationQuery::SubstringSearch( let mut cursor = HistoryCursor::new(
"substring".to_string(), HistoryNavigationQuery::SubstringSearch("substring".to_string()),
)); None,
);
cursor.back(&*hist)?; cursor.back(&*hist)?;
assert_eq!( assert_eq!(
cursor.string_at_cursor(), cursor.string_at_cursor(),
@ -351,7 +365,10 @@ mod tests {
let (mut hist, _) = create_history(); let (mut hist, _) = create_history();
hist.save(HistoryItem::from_command_line("substring"))?; hist.save(HistoryItem::from_command_line("substring"))?;
let cursor = HistoryCursor::new(HistoryNavigationQuery::SubstringSearch("".to_string())); let cursor = HistoryCursor::new(
HistoryNavigationQuery::SubstringSearch("".to_string()),
None,
);
assert_eq!(cursor.string_at_cursor(), None); assert_eq!(cursor.string_at_cursor(), None);
Ok(()) Ok(())

View File

@ -3,7 +3,7 @@ use super::{
}; };
use crate::{ use crate::{
result::{ReedlineError, ReedlineErrorVariants}, result::{ReedlineError, ReedlineErrorVariants},
Result, HistorySessionId, Result,
}; };
use std::{ use std::{
@ -30,6 +30,7 @@ pub struct FileBackedHistory {
entries: VecDeque<String>, entries: VecDeque<String>,
file: Option<PathBuf>, file: Option<PathBuf>,
len_on_disk: usize, // Keep track what was previously written to disk len_on_disk: usize, // Keep track what was previously written to disk
session: Option<HistorySessionId>,
} }
impl Default for FileBackedHistory { impl Default for FileBackedHistory {
@ -268,6 +269,10 @@ impl History for FileBackedHistory {
} }
Ok(()) Ok(())
} }
fn session(&self) -> Option<HistorySessionId> {
self.session
}
} }
impl FileBackedHistory { impl FileBackedHistory {
@ -285,6 +290,7 @@ impl FileBackedHistory {
entries: VecDeque::new(), entries: VecDeque::new(),
file: None, file: None,
len_on_disk: 0, len_on_disk: 0,
session: None,
} }
} }

View File

@ -1,4 +1,6 @@
use chrono::Utc; use chrono::Utc;
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
use rusqlite::ToSql;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{fmt::Display, time::Duration}; use std::{fmt::Display, time::Duration};
@ -32,6 +34,15 @@ impl Display for HistorySessionId {
} }
} }
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
impl ToSql for HistorySessionId {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::Owned(
rusqlite::types::Value::Integer(self.0),
))
}
}
impl From<HistorySessionId> for i64 { impl From<HistorySessionId> for i64 {
fn from(id: HistorySessionId) -> Self { fn from(id: HistorySessionId) -> Self {
id.0 id.0

View File

@ -16,6 +16,7 @@ const SQLITE_APPLICATION_ID: i32 = 1151497937;
/// to add information such as a timestamp, running directory, result... /// to add information such as a timestamp, running directory, result...
pub struct SqliteBackedHistory { pub struct SqliteBackedHistory {
db: rusqlite::Connection, db: rusqlite::Connection,
session: Option<HistorySessionId>,
} }
fn deserialize_history_item(row: &rusqlite::Row) -> rusqlite::Result<HistoryItem> { fn deserialize_history_item(row: &rusqlite::Row) -> rusqlite::Result<HistoryItem> {
@ -167,6 +168,10 @@ impl History for SqliteBackedHistory {
// no-op (todo?) // no-op (todo?)
Ok(()) Ok(())
} }
fn session(&self) -> Option<HistorySessionId> {
self.session
}
} }
fn map_sqlite_err(err: rusqlite::Error) -> ReedlineError { fn map_sqlite_err(err: rusqlite::Error) -> ReedlineError {
// TODO: better error mapping // TODO: better error mapping
@ -243,8 +248,9 @@ impl SqliteBackedHistory {
", ",
) )
.map_err(map_sqlite_err)?; .map_err(map_sqlite_err)?;
Ok(SqliteBackedHistory { db }) Ok(SqliteBackedHistory { db, session: None })
} }
fn construct_query<'a>( fn construct_query<'a>(
&self, &self,
query: &'a SearchQuery, query: &'a SearchQuery,
@ -331,6 +337,10 @@ impl SqliteBackedHistory {
wheres.push("exit_status != 0"); wheres.push("exit_status != 0");
} }
} }
if let Some(session_id) = query.filter.session {
wheres.push("session_id = :session_id");
params.push((":session_id", Box::new(session_id)));
}
let mut wheres = wheres.join(" and "); let mut wheres = wheres.join(" and ");
if wheres.is_empty() { if wheres.is_empty() {
wheres = "true".to_string(); wheres = "true".to_string();