mirror of
https://github.com/nushell/reedline.git
synced 2024-09-11 15:55:50 +03:00
History filter (#566)
* add `HistoryFilter` and use it in engine, to allow not storing items with a given prefix * use `with_history_exclusion_prefix` in demo * review * impl history filter on engine * keep 1 filterered history item * don't impl History on Box<T: History>
This commit is contained in:
parent
65c4e7a419
commit
97f754425a
@ -78,6 +78,7 @@ fn main() -> Result<()> {
|
|||||||
let mut line_editor = Reedline::create()
|
let mut line_editor = Reedline::create()
|
||||||
.with_history_session_id(history_session_id)
|
.with_history_session_id(history_session_id)
|
||||||
.with_history(history)
|
.with_history(history)
|
||||||
|
.with_history_exclusion_prefix(Some(" ".to_string()))
|
||||||
.with_completer(completer)
|
.with_completer(completer)
|
||||||
.with_quick_completions(true)
|
.with_quick_completions(true)
|
||||||
.with_partial_completions(true)
|
.with_partial_completions(true)
|
||||||
|
105
src/engine.rs
105
src/engine.rs
@ -99,6 +99,9 @@ pub struct Reedline {
|
|||||||
history_session_id: Option<HistorySessionId>,
|
history_session_id: Option<HistorySessionId>,
|
||||||
// none if history doesn't support this
|
// none if history doesn't support this
|
||||||
history_last_run_id: Option<HistoryItemId>,
|
history_last_run_id: Option<HistoryItemId>,
|
||||||
|
history_exclusion_prefix: Option<String>,
|
||||||
|
history_excluded_item: Option<HistoryItem>,
|
||||||
|
history_cursor_on_excluded: bool,
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
|
|
||||||
// Validator
|
// Validator
|
||||||
@ -166,6 +169,8 @@ impl Drop for Reedline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Reedline {
|
impl Reedline {
|
||||||
|
const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX);
|
||||||
|
|
||||||
/// Create a new [`Reedline`] engine with a local [`History`] that is not synchronized to a file.
|
/// Create a new [`Reedline`] engine with a local [`History`] that is not synchronized to a file.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn create() -> Self {
|
pub fn create() -> Self {
|
||||||
@ -187,6 +192,9 @@ impl Reedline {
|
|||||||
),
|
),
|
||||||
history_session_id: hist_session_id,
|
history_session_id: hist_session_id,
|
||||||
history_last_run_id: None,
|
history_last_run_id: None,
|
||||||
|
history_exclusion_prefix: None,
|
||||||
|
history_excluded_item: None,
|
||||||
|
history_cursor_on_excluded: false,
|
||||||
input_mode: InputMode::Regular,
|
input_mode: InputMode::Regular,
|
||||||
painter,
|
painter,
|
||||||
edit_mode,
|
edit_mode,
|
||||||
@ -357,6 +365,27 @@ impl Reedline {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A builder which configures history exclusion for your instance of the Reedline engine
|
||||||
|
/// # Example
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// // Create a reedline instance with history that will *not* include commands starting with a space
|
||||||
|
///
|
||||||
|
/// use reedline::{FileBackedHistory, Reedline};
|
||||||
|
///
|
||||||
|
/// let history = Box::new(
|
||||||
|
/// FileBackedHistory::with_file(5, "history.txt".into())
|
||||||
|
/// .expect("Error configuring history with file"),
|
||||||
|
/// );
|
||||||
|
/// let mut line_editor = Reedline::create()
|
||||||
|
/// .with_history(history)
|
||||||
|
/// .with_history_exclusion_prefix(Some(" ".into()));
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_history_exclusion_prefix(mut self, ignore_prefix: Option<String>) -> Self {
|
||||||
|
self.history_exclusion_prefix = ignore_prefix;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// A builder that configures the validator for your instance of the Reedline engine
|
/// A builder that configures the validator for your instance of the Reedline engine
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -513,14 +542,16 @@ impl Reedline {
|
|||||||
&mut self,
|
&mut self,
|
||||||
f: &dyn Fn(HistoryItem) -> HistoryItem,
|
f: &dyn Fn(HistoryItem) -> HistoryItem,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
if let Some(r) = &self.history_last_run_id {
|
match &self.history_last_run_id {
|
||||||
self.history.update(*r, f)?;
|
Some(Self::FILTERED_ITEM_ID) => {
|
||||||
} else {
|
self.history_excluded_item = Some(f(self.history_excluded_item.take().unwrap()));
|
||||||
return Err(ReedlineError(ReedlineErrorVariants::OtherHistoryError(
|
Ok(())
|
||||||
|
}
|
||||||
|
Some(r) => self.history.update(*r, f),
|
||||||
|
None => Err(ReedlineError(ReedlineErrorVariants::OtherHistoryError(
|
||||||
"No command run",
|
"No command run",
|
||||||
)));
|
))),
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for input and provide the user with a specified [`Prompt`].
|
/// Wait for input and provide the user with a specified [`Prompt`].
|
||||||
@ -1147,17 +1178,26 @@ impl Reedline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn previous_history(&mut self) {
|
fn previous_history(&mut self) {
|
||||||
|
if self.history_cursor_on_excluded {
|
||||||
|
self.history_cursor_on_excluded = false;
|
||||||
|
}
|
||||||
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 = HistoryCursor::new(
|
self.history_cursor = HistoryCursor::new(
|
||||||
self.get_history_navigation_based_on_line_buffer(),
|
self.get_history_navigation_based_on_line_buffer(),
|
||||||
self.get_history_session_id(),
|
self.get_history_session_id(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.history_excluded_item.is_some() {
|
||||||
|
self.history_cursor_on_excluded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.history_cursor
|
if !self.history_cursor_on_excluded {
|
||||||
.back(self.history.as_ref())
|
self.history_cursor
|
||||||
.expect("todo: error handling");
|
.back(self.history.as_ref())
|
||||||
|
.expect("todo: error handling");
|
||||||
|
}
|
||||||
self.update_buffer_from_history();
|
self.update_buffer_from_history();
|
||||||
self.editor.move_to_start(UndoBehavior::HistoryNavigation);
|
self.editor.move_to_start(UndoBehavior::HistoryNavigation);
|
||||||
self.editor
|
self.editor
|
||||||
@ -1173,9 +1213,25 @@ impl Reedline {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.history_cursor
|
if self.history_cursor_on_excluded {
|
||||||
.forward(self.history.as_ref())
|
self.history_cursor_on_excluded = false;
|
||||||
.expect("todo: error handling");
|
} else {
|
||||||
|
let cursor_was_on_item = self.history_cursor.string_at_cursor().is_some();
|
||||||
|
self.history_cursor
|
||||||
|
.forward(self.history.as_ref())
|
||||||
|
.expect("todo: error handling");
|
||||||
|
|
||||||
|
if cursor_was_on_item
|
||||||
|
&& self.history_cursor.string_at_cursor().is_none()
|
||||||
|
&& self.history_excluded_item.is_some()
|
||||||
|
{
|
||||||
|
self.history_cursor_on_excluded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.history_cursor.string_at_cursor().is_none() && !self.history_cursor_on_excluded {
|
||||||
|
self.input_mode = InputMode::Regular;
|
||||||
|
}
|
||||||
self.update_buffer_from_history();
|
self.update_buffer_from_history();
|
||||||
self.editor.move_to_end(UndoBehavior::HistoryNavigation);
|
self.editor.move_to_end(UndoBehavior::HistoryNavigation);
|
||||||
}
|
}
|
||||||
@ -1264,6 +1320,14 @@ impl Reedline {
|
|||||||
/// Not used for the separate modal reverse search!
|
/// Not used for the separate modal reverse search!
|
||||||
fn update_buffer_from_history(&mut self) {
|
fn update_buffer_from_history(&mut self) {
|
||||||
match self.history_cursor.get_navigation() {
|
match self.history_cursor.get_navigation() {
|
||||||
|
_ if self.history_cursor_on_excluded => self.editor.set_buffer(
|
||||||
|
self.history_excluded_item
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.command_line
|
||||||
|
.clone(),
|
||||||
|
UndoBehavior::HistoryNavigation,
|
||||||
|
),
|
||||||
HistoryNavigationQuery::Normal(original) => {
|
HistoryNavigationQuery::Normal(original) => {
|
||||||
if let Some(buffer_to_paint) = self.history_cursor.string_at_cursor() {
|
if let Some(buffer_to_paint) = self.history_cursor.string_at_cursor() {
|
||||||
self.editor
|
self.editor
|
||||||
@ -1629,8 +1693,21 @@ impl Reedline {
|
|||||||
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.get_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;
|
if self
|
||||||
|
.history_exclusion_prefix
|
||||||
|
.as_ref()
|
||||||
|
.map(|prefix| buffer.starts_with(prefix))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
entry.id = Some(Self::FILTERED_ITEM_ID);
|
||||||
|
self.history_last_run_id = entry.id;
|
||||||
|
self.history_excluded_item = Some(entry);
|
||||||
|
} else {
|
||||||
|
entry = self.history.save(entry).expect("todo: error handling");
|
||||||
|
self.history_last_run_id = entry.id;
|
||||||
|
self.history_excluded_item = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.run_edit_commands(&[EditCommand::Clear]);
|
self.run_edit_commands(&[EditCommand::Clear]);
|
||||||
self.editor.reset_undo_stack();
|
self.editor.reset_undo_stack();
|
||||||
|
@ -78,7 +78,12 @@ impl History for FileBackedHistory {
|
|||||||
fn load(&self, id: HistoryItemId) -> Result<super::HistoryItem> {
|
fn load(&self, id: HistoryItemId) -> Result<super::HistoryItem> {
|
||||||
Ok(FileBackedHistory::construct_entry(
|
Ok(FileBackedHistory::construct_entry(
|
||||||
Some(id),
|
Some(id),
|
||||||
self.entries[id.0 as usize].clone(),
|
self.entries
|
||||||
|
.get(id.0 as usize)
|
||||||
|
.ok_or(ReedlineError(ReedlineErrorVariants::OtherHistoryError(
|
||||||
|
"Item does not exist",
|
||||||
|
)))?
|
||||||
|
.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ 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};
|
||||||
|
|
||||||
/// Unique ID for the [`HistoryItem`]
|
/// Unique ID for the [`HistoryItem`]. More recent items have higher ids than older ones.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct HistoryItemId(pub(crate) i64);
|
pub struct HistoryItemId(pub(crate) i64);
|
||||||
impl HistoryItemId {
|
impl HistoryItemId {
|
||||||
@ -52,7 +52,7 @@ impl From<HistorySessionId> for i64 {
|
|||||||
/// This trait represents additional arbitrary context to be added to a history (optional, see [`HistoryItem`])
|
/// This trait represents additional arbitrary context to be added to a history (optional, see [`HistoryItem`])
|
||||||
pub trait HistoryItemExtraInfo: Serialize + DeserializeOwned + Default + Send {}
|
pub trait HistoryItemExtraInfo: Serialize + DeserializeOwned + Default + Send {}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||||
/// something that is serialized as null and deserialized by ignoring everything
|
/// something that is serialized as null and deserialized by ignoring everything
|
||||||
pub struct IgnoreAllExtraInfo;
|
pub struct IgnoreAllExtraInfo;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user