mirror of
https://github.com/nushell/reedline.git
synced 2024-08-17 20:00:49 +03:00
Allow history searching via session id (#562)
* add the ability to search history with session id * clippy
This commit is contained in:
parent
27f4417191
commit
61c6409fb7
@ -66,7 +66,17 @@ fn main() -> Result<()> {
|
||||
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()
|
||||
.with_history_session_id(history_session_id)
|
||||
.with_history(history)
|
||||
.with_completer(completer)
|
||||
.with_quick_completions(true)
|
||||
@ -145,10 +155,34 @@ fn main() -> Result<()> {
|
||||
line_editor.clear_scrollback()?;
|
||||
continue;
|
||||
}
|
||||
// Get the full history
|
||||
if buffer.trim() == "history" {
|
||||
line_editor.print_history()?;
|
||||
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" {
|
||||
let hstry = Box::new(line_editor.history_mut());
|
||||
hstry
|
||||
|
100
src/engine.rs
100
src/engine.rs
@ -158,14 +158,15 @@ impl Reedline {
|
||||
let hinter = None;
|
||||
let validator = None;
|
||||
let edit_mode = Box::<Emacs>::default();
|
||||
let hist_session_id = Self::create_history_session_id();
|
||||
let hist_session_id = None;
|
||||
|
||||
Reedline {
|
||||
editor: Editor::default(),
|
||||
history,
|
||||
history_cursor: HistoryCursor::new(HistoryNavigationQuery::Normal(
|
||||
LineBuffer::default(),
|
||||
)),
|
||||
history_cursor: HistoryCursor::new(
|
||||
HistoryNavigationQuery::Normal(LineBuffer::default()),
|
||||
hist_session_id,
|
||||
),
|
||||
history_session_id: hist_session_id,
|
||||
history_last_run_id: None,
|
||||
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
|
||||
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) {
|
||||
Ok(n) => n.as_nanos() as i64,
|
||||
Err(_) => 0,
|
||||
@ -202,6 +203,14 @@ impl Reedline {
|
||||
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
|
||||
/// # Example
|
||||
/// ```rust
|
||||
@ -380,6 +389,13 @@ impl Reedline {
|
||||
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.
|
||||
/// 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.
|
||||
@ -397,7 +413,7 @@ impl Reedline {
|
||||
pub fn print_history(&mut self) -> Result<()> {
|
||||
let history: Vec<_> = self
|
||||
.history
|
||||
.search(SearchQuery::everything(SearchDirection::Forward))
|
||||
.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.expect("todo: error handling");
|
||||
|
||||
for (i, entry) in history.iter().enumerate() {
|
||||
@ -406,6 +422,40 @@ impl Reedline {
|
||||
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
|
||||
pub fn history(&self) -> &dyn History {
|
||||
&*self.history
|
||||
@ -1066,8 +1116,10 @@ impl Reedline {
|
||||
fn previous_history(&mut self) {
|
||||
if self.input_mode != InputMode::HistoryTraversal {
|
||||
self.input_mode = InputMode::HistoryTraversal;
|
||||
self.history_cursor =
|
||||
HistoryCursor::new(self.get_history_navigation_based_on_line_buffer());
|
||||
self.history_cursor = HistoryCursor::new(
|
||||
self.get_history_navigation_based_on_line_buffer(),
|
||||
self.get_history_session_id(),
|
||||
);
|
||||
}
|
||||
|
||||
self.history_cursor
|
||||
@ -1082,8 +1134,10 @@ impl Reedline {
|
||||
fn next_history(&mut self) {
|
||||
if self.input_mode != InputMode::HistoryTraversal {
|
||||
self.input_mode = InputMode::HistoryTraversal;
|
||||
self.history_cursor =
|
||||
HistoryCursor::new(self.get_history_navigation_based_on_line_buffer());
|
||||
self.history_cursor = HistoryCursor::new(
|
||||
self.get_history_navigation_based_on_line_buffer(),
|
||||
self.get_history_session_id(),
|
||||
);
|
||||
}
|
||||
|
||||
self.history_cursor
|
||||
@ -1118,8 +1172,10 @@ impl Reedline {
|
||||
///
|
||||
/// This mode uses a separate prompt and handles keybindings slightly differently!
|
||||
fn enter_history_search(&mut self) {
|
||||
self.history_cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::SubstringSearch("".to_string()));
|
||||
self.history_cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::SubstringSearch("".to_string()),
|
||||
self.get_history_session_id(),
|
||||
);
|
||||
self.input_mode = InputMode::HistorySearch;
|
||||
}
|
||||
|
||||
@ -1133,11 +1189,14 @@ impl Reedline {
|
||||
let navigation = self.history_cursor.get_navigation();
|
||||
if let HistoryNavigationQuery::SubstringSearch(mut substring) = navigation {
|
||||
substring.push(*c);
|
||||
self.history_cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::SubstringSearch(substring));
|
||||
self.history_cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::SubstringSearch(substring),
|
||||
self.get_history_session_id(),
|
||||
);
|
||||
} else {
|
||||
self.history_cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::SubstringSearch(String::from(*c)),
|
||||
self.get_history_session_id(),
|
||||
);
|
||||
}
|
||||
self.history_cursor
|
||||
@ -1152,6 +1211,7 @@ impl Reedline {
|
||||
|
||||
self.history_cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::SubstringSearch(new_substring.to_string()),
|
||||
self.get_history_session_id(),
|
||||
);
|
||||
self.history_cursor
|
||||
.back(self.history.as_mut())
|
||||
@ -1275,7 +1335,7 @@ impl Reedline {
|
||||
start_id: None,
|
||||
end_id: None,
|
||||
limit: Some(1), // fetch the latest one entries
|
||||
filter: SearchFilter::anything(),
|
||||
filter: SearchFilter::anything(self.get_history_session_id()),
|
||||
})
|
||||
.unwrap_or_else(|_| Vec::new())
|
||||
.get(index.saturating_sub(1))
|
||||
@ -1295,7 +1355,7 @@ impl Reedline {
|
||||
start_id: None,
|
||||
end_id: None,
|
||||
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())
|
||||
.get(index.saturating_sub(1))
|
||||
@ -1315,7 +1375,7 @@ impl Reedline {
|
||||
start_id: None,
|
||||
end_id: None,
|
||||
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())
|
||||
.get(index)
|
||||
@ -1328,7 +1388,9 @@ impl Reedline {
|
||||
}),
|
||||
ParseAction::LastToken => self
|
||||
.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())
|
||||
.get(0)
|
||||
.and_then(|history| history.command_line.split_whitespace().rev().next())
|
||||
@ -1533,7 +1595,7 @@ impl Reedline {
|
||||
self.repaint(prompt)?;
|
||||
if !buffer.is_empty() {
|
||||
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");
|
||||
self.history_last_run_id = entry.id;
|
||||
}
|
||||
|
@ -20,7 +20,10 @@ impl Hinter for DefaultHinter {
|
||||
) -> String {
|
||||
self.current_hint = if line.chars().count() >= self.min_chars {
|
||||
history
|
||||
.search(SearchQuery::last_with_prefix(line.to_string()))
|
||||
.search(SearchQuery::last_with_prefix(
|
||||
line.to_string(),
|
||||
history.session(),
|
||||
))
|
||||
.expect("todo: error handling")
|
||||
.get(0)
|
||||
.map_or_else(String::new, |entry| {
|
||||
|
@ -1,8 +1,6 @@
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::{core_editor::LineBuffer, HistoryItem, Result};
|
||||
|
||||
use super::HistoryItemId;
|
||||
use crate::{core_editor::LineBuffer, HistoryItem, HistorySessionId, Result};
|
||||
use chrono::Utc;
|
||||
|
||||
/// Browsing modes for a [`History`]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -53,18 +51,23 @@ pub struct SearchFilter {
|
||||
pub cwd_prefix: Option<String>,
|
||||
/// Filter whether the command completed
|
||||
pub exit_successful: Option<bool>,
|
||||
/// Filter on the session id
|
||||
pub session: Option<HistorySessionId>,
|
||||
}
|
||||
|
||||
impl SearchFilter {
|
||||
/// Create a search filter with a [`CommandLineSearch`]
|
||||
pub fn from_text_search(cmd: CommandLineSearch) -> SearchFilter {
|
||||
let mut s = SearchFilter::anything();
|
||||
pub fn from_text_search(
|
||||
cmd: CommandLineSearch,
|
||||
session: Option<HistorySessionId>,
|
||||
) -> SearchFilter {
|
||||
let mut s = SearchFilter::anything(session);
|
||||
s.command_line = Some(cmd);
|
||||
s
|
||||
}
|
||||
|
||||
/// No filter constraint
|
||||
pub const fn anything() -> SearchFilter {
|
||||
/// anything within this session
|
||||
pub fn anything(session: Option<HistorySessionId>) -> SearchFilter {
|
||||
SearchFilter {
|
||||
command_line: None,
|
||||
not_command_line: None,
|
||||
@ -72,6 +75,7 @@ impl SearchFilter {
|
||||
cwd_exact: None,
|
||||
cwd_prefix: None,
|
||||
exit_successful: None,
|
||||
session,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +109,7 @@ impl SearchQuery {
|
||||
start_id: None,
|
||||
end_id: 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`
|
||||
pub fn last_with_prefix(prefix: String) -> SearchQuery {
|
||||
SearchQuery::last_with_search(SearchFilter::from_text_search(CommandLineSearch::Prefix(
|
||||
prefix,
|
||||
)))
|
||||
pub fn last_with_prefix(prefix: String, session: Option<HistorySessionId>) -> SearchQuery {
|
||||
SearchQuery::last_with_search(SearchFilter::from_text_search(
|
||||
CommandLineSearch::Prefix(prefix),
|
||||
session,
|
||||
))
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
direction,
|
||||
start_time: None,
|
||||
@ -138,7 +146,7 @@ impl SearchQuery {
|
||||
start_id: None,
|
||||
end_id: 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>;
|
||||
/// return the total number of history items
|
||||
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
|
||||
fn search(&self, query: SearchQuery) -> Result<Vec<HistoryItem>>;
|
||||
@ -176,6 +184,8 @@ pub trait History: Send {
|
||||
fn delete(&mut self, h: HistoryItemId) -> Result<()>;
|
||||
/// ensure that this history is written to disk
|
||||
fn sync(&mut self) -> std::io::Result<()>;
|
||||
/// get the history session id
|
||||
fn session(&self) -> Option<HistorySessionId>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -265,7 +275,7 @@ mod test {
|
||||
let history = create_filled_example_history()?;
|
||||
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 });
|
||||
@ -275,7 +285,7 @@ mod test {
|
||||
#[test]
|
||||
fn get_latest() -> Result<()> {
|
||||
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])?;
|
||||
Ok(())
|
||||
@ -286,7 +296,7 @@ mod test {
|
||||
let history = create_filled_example_history()?;
|
||||
let res = history.search(SearchQuery {
|
||||
limit: Some(1),
|
||||
..SearchQuery::everything(SearchDirection::Forward)
|
||||
..SearchQuery::everything(SearchDirection::Forward, None)
|
||||
})?;
|
||||
search_returned(&*history, res, vec![if IS_FILE_BASED { 0 } else { 1 }])?;
|
||||
Ok(())
|
||||
@ -296,8 +306,11 @@ mod test {
|
||||
fn search_prefix() -> Result<()> {
|
||||
let history = create_filled_example_history()?;
|
||||
let res = history.search(SearchQuery {
|
||||
filter: SearchFilter::from_text_search(CommandLineSearch::Prefix("ls ".to_string())),
|
||||
..SearchQuery::everything(SearchDirection::Backward)
|
||||
filter: SearchFilter::from_text_search(
|
||||
CommandLineSearch::Prefix("ls ".to_string()),
|
||||
None,
|
||||
),
|
||||
..SearchQuery::everything(SearchDirection::Backward, None)
|
||||
})?;
|
||||
search_returned(&*history, res, vec![9, 6])?;
|
||||
|
||||
@ -308,10 +321,11 @@ mod test {
|
||||
fn search_includes() -> Result<()> {
|
||||
let history = create_filled_example_history()?;
|
||||
let res = history.search(SearchQuery {
|
||||
filter: SearchFilter::from_text_search(CommandLineSearch::Substring(
|
||||
"foo.zip".to_string(),
|
||||
)),
|
||||
..SearchQuery::everything(SearchDirection::Forward)
|
||||
filter: SearchFilter::from_text_search(
|
||||
CommandLineSearch::Substring("foo.zip".to_string()),
|
||||
None,
|
||||
),
|
||||
..SearchQuery::everything(SearchDirection::Forward, None)
|
||||
})?;
|
||||
search_returned(&*history, res, vec![2, 3])?;
|
||||
Ok(())
|
||||
@ -321,9 +335,12 @@ mod test {
|
||||
fn search_includes_limit() -> Result<()> {
|
||||
let history = create_filled_example_history()?;
|
||||
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),
|
||||
..SearchQuery::everything(SearchDirection::Forward)
|
||||
..SearchQuery::everything(SearchDirection::Forward, None)
|
||||
})?;
|
||||
search_returned(&*history, res, vec![1, 4])?;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{History, HistoryNavigationQuery};
|
||||
use crate::{History, HistoryNavigationQuery, HistorySessionId};
|
||||
|
||||
use super::base::CommandLineSearch;
|
||||
use super::base::SearchDirection;
|
||||
@ -13,14 +13,16 @@ pub struct HistoryCursor {
|
||||
query: HistoryNavigationQuery,
|
||||
current: Option<HistoryItem>,
|
||||
skip_dupes: bool,
|
||||
session: Option<HistorySessionId>,
|
||||
}
|
||||
|
||||
impl HistoryCursor {
|
||||
pub const fn new(query: HistoryNavigationQuery) -> HistoryCursor {
|
||||
pub fn new(query: HistoryNavigationQuery, session: Option<HistorySessionId>) -> HistoryCursor {
|
||||
HistoryCursor {
|
||||
query,
|
||||
current: None,
|
||||
skip_dupes: true,
|
||||
session,
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,13 +40,14 @@ impl HistoryCursor {
|
||||
|
||||
fn get_search_filter(&self) -> SearchFilter {
|
||||
let filter = match self.query.clone() {
|
||||
HistoryNavigationQuery::Normal(_) => SearchFilter::anything(),
|
||||
HistoryNavigationQuery::Normal(_) => SearchFilter::anything(self.session),
|
||||
HistoryNavigationQuery::PrefixSearch(prefix) => {
|
||||
SearchFilter::from_text_search(CommandLineSearch::Prefix(prefix))
|
||||
}
|
||||
HistoryNavigationQuery::SubstringSearch(substring) => {
|
||||
SearchFilter::from_text_search(CommandLineSearch::Substring(substring))
|
||||
SearchFilter::from_text_search(CommandLineSearch::Prefix(prefix), self.session)
|
||||
}
|
||||
HistoryNavigationQuery::SubstringSearch(substring) => SearchFilter::from_text_search(
|
||||
CommandLineSearch::Substring(substring),
|
||||
self.session,
|
||||
),
|
||||
};
|
||||
if let (true, Some(current)) = (self.skip_dupes, &self.current) {
|
||||
SearchFilter {
|
||||
@ -112,20 +115,20 @@ mod tests {
|
||||
let hist = Box::<FileBackedHistory>::default();
|
||||
(
|
||||
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) {
|
||||
let hist = Box::new(FileBackedHistory::with_file(cap, path.to_owned()).unwrap());
|
||||
(
|
||||
hist,
|
||||
HistoryCursor::new(HistoryNavigationQuery::Normal(LineBuffer::default())),
|
||||
HistoryCursor::new(HistoryNavigationQuery::Normal(LineBuffer::default()), None),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_all_entry_texts(hist: &dyn History) -> Vec<String> {
|
||||
let res = hist
|
||||
.search(SearchQuery::everything(SearchDirection::Forward))
|
||||
.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.unwrap();
|
||||
let actual: Vec<_> = res.iter().map(|e| e.command_line.to_string()).collect();
|
||||
actual
|
||||
@ -207,8 +210,10 @@ mod tests {
|
||||
hist.save(HistoryItem::from_command_line("test"))?;
|
||||
hist.save(HistoryItem::from_command_line("find me"))?;
|
||||
|
||||
let mut cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string()));
|
||||
let mut cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::PrefixSearch("find".to_string()),
|
||||
None,
|
||||
);
|
||||
|
||||
cursor.back(&*hist)?;
|
||||
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("find me"))?;
|
||||
|
||||
let mut cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string()));
|
||||
let mut cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::PrefixSearch("find".to_string()),
|
||||
None,
|
||||
);
|
||||
cursor.back(&*hist)?;
|
||||
assert_eq!(cursor.string_at_cursor(), Some("find me".to_string()));
|
||||
cursor.back(&*hist)?;
|
||||
@ -253,8 +260,10 @@ mod tests {
|
||||
hist.save(HistoryItem::from_command_line("test"))?;
|
||||
hist.save(HistoryItem::from_command_line("find me"))?;
|
||||
|
||||
let mut cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string()));
|
||||
let mut cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::PrefixSearch("find".to_string()),
|
||||
None,
|
||||
);
|
||||
cursor.back(&*hist)?;
|
||||
assert_eq!(cursor.string_at_cursor(), Some("find me".to_string()));
|
||||
cursor.back(&*hist)?;
|
||||
@ -279,8 +288,10 @@ mod tests {
|
||||
hist.save(HistoryItem::from_command_line("test"))?;
|
||||
hist.save(HistoryItem::from_command_line("find me once"))?;
|
||||
|
||||
let mut cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string()));
|
||||
let mut cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::PrefixSearch("find".to_string()),
|
||||
None,
|
||||
);
|
||||
cursor.back(&*hist)?;
|
||||
assert_eq!(cursor.string_at_cursor(), Some("find me once".to_string()));
|
||||
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 as well"))?;
|
||||
|
||||
let mut cursor =
|
||||
HistoryCursor::new(HistoryNavigationQuery::PrefixSearch("find".to_string()));
|
||||
let mut cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::PrefixSearch("find".to_string()),
|
||||
None,
|
||||
);
|
||||
cursor.back(&*hist)?;
|
||||
assert_eq!(
|
||||
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("prefix substring suffix"))?;
|
||||
|
||||
let mut cursor = HistoryCursor::new(HistoryNavigationQuery::SubstringSearch(
|
||||
"substring".to_string(),
|
||||
));
|
||||
let mut cursor = HistoryCursor::new(
|
||||
HistoryNavigationQuery::SubstringSearch("substring".to_string()),
|
||||
None,
|
||||
);
|
||||
cursor.back(&*hist)?;
|
||||
assert_eq!(
|
||||
cursor.string_at_cursor(),
|
||||
@ -351,7 +365,10 @@ mod tests {
|
||||
let (mut hist, _) = create_history();
|
||||
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);
|
||||
Ok(())
|
||||
|
@ -3,7 +3,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
result::{ReedlineError, ReedlineErrorVariants},
|
||||
Result,
|
||||
HistorySessionId, Result,
|
||||
};
|
||||
|
||||
use std::{
|
||||
@ -30,6 +30,7 @@ pub struct FileBackedHistory {
|
||||
entries: VecDeque<String>,
|
||||
file: Option<PathBuf>,
|
||||
len_on_disk: usize, // Keep track what was previously written to disk
|
||||
session: Option<HistorySessionId>,
|
||||
}
|
||||
|
||||
impl Default for FileBackedHistory {
|
||||
@ -268,6 +269,10 @@ impl History for FileBackedHistory {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn session(&self) -> Option<HistorySessionId> {
|
||||
self.session
|
||||
}
|
||||
}
|
||||
|
||||
impl FileBackedHistory {
|
||||
@ -285,6 +290,7 @@ impl FileBackedHistory {
|
||||
entries: VecDeque::new(),
|
||||
file: None,
|
||||
len_on_disk: 0,
|
||||
session: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
use chrono::Utc;
|
||||
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
|
||||
use rusqlite::ToSql;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
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 {
|
||||
fn from(id: HistorySessionId) -> Self {
|
||||
id.0
|
||||
|
@ -16,6 +16,7 @@ const SQLITE_APPLICATION_ID: i32 = 1151497937;
|
||||
/// to add information such as a timestamp, running directory, result...
|
||||
pub struct SqliteBackedHistory {
|
||||
db: rusqlite::Connection,
|
||||
session: Option<HistorySessionId>,
|
||||
}
|
||||
|
||||
fn deserialize_history_item(row: &rusqlite::Row) -> rusqlite::Result<HistoryItem> {
|
||||
@ -167,6 +168,10 @@ impl History for SqliteBackedHistory {
|
||||
// no-op (todo?)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn session(&self) -> Option<HistorySessionId> {
|
||||
self.session
|
||||
}
|
||||
}
|
||||
fn map_sqlite_err(err: rusqlite::Error) -> ReedlineError {
|
||||
// TODO: better error mapping
|
||||
@ -243,8 +248,9 @@ impl SqliteBackedHistory {
|
||||
",
|
||||
)
|
||||
.map_err(map_sqlite_err)?;
|
||||
Ok(SqliteBackedHistory { db })
|
||||
Ok(SqliteBackedHistory { db, session: None })
|
||||
}
|
||||
|
||||
fn construct_query<'a>(
|
||||
&self,
|
||||
query: &'a SearchQuery,
|
||||
@ -331,6 +337,10 @@ impl SqliteBackedHistory {
|
||||
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 ");
|
||||
if wheres.is_empty() {
|
||||
wheres = "true".to_string();
|
||||
|
Loading…
Reference in New Issue
Block a user