Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4f71b89
basic sqlite history
phiresky Apr 1, 2022
dd93aba
fix test compilation
phiresky Apr 1, 2022
4c03e68
final touches for MVP
phiresky Apr 1, 2022
a669fb3
better documentation
phiresky Apr 1, 2022
187f867
fix for empty history
phiresky Apr 2, 2022
01b62e7
partial change to non-generic history
phiresky Apr 16, 2022
3e76ae7
mostly working
phiresky Apr 16, 2022
317a69f
fix tests and ci
phiresky Apr 18, 2022
7d4b7ca
fixes, format
phiresky Apr 18, 2022
5fc904e
move history item to new file
phiresky Apr 18, 2022
f756189
fix some comments, fix test compile errors
phiresky Apr 20, 2022
4d60705
ci features matrix
phiresky Apr 20, 2022
13b32b9
fix index creation
phiresky Apr 18, 2022
56a210c
fix file-based tests
phiresky May 8, 2022
7c6b09a
move logic for not saving empty entries to engine
phiresky May 8, 2022
9cab40f
fix update last command on empty, set up application_id and check ver…
phiresky May 8, 2022
37afda0
Merge remote-tracking branch 'origin/main' into sqlite-history-2
phiresky May 8, 2022
556115b
add specific error variants
phiresky May 8, 2022
285ea4b
format
phiresky May 11, 2022
c772fad
fix compile errors
phiresky May 11, 2022
74c8f71
fix fmt
fdncred May 11, 2022
d84fd86
sqlite with bashisms
elferherrera May 21, 2022
a9c9d3a
Merge branch 'main' of https://github.com/nushell/reedline into sqlit…
elferherrera May 21, 2022
428544d
hide with features
elferherrera May 21, 2022
014bd48
cargo fmt
elferherrera May 21, 2022
a8d8703
improve performance of bashisms selectors
phiresky Jun 6, 2022
33b7610
Merge remote-tracking branch 'origin/main' into sqlite-history-2
phiresky Jun 6, 2022
6d803bb
Style: Remove commented out code
sholderbach Jun 6, 2022
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
Prev Previous commit
Next Next commit
partial change to non-generic history
  • Loading branch information
phiresky committed Apr 16, 2022
commit 01b62e7e71b7413380212e6861674c3d50680bc9
6 changes: 3 additions & 3 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use {
enums::{EventStatus, ReedlineEvent},
highlighter::SimpleMatchHighlighter,
hinter::Hinter,
history::{FileBackedHistory, History, HistoryNavigationQuery},
menu::{Menu, MenuEvent, ReedlineMenu},
history::{FileBackedHistory, HistoryNavigationQuery, Result as HistoryResult},
painting::{Painter, PromptLines},
prompt::{PromptEditMode, PromptHistorySearchStatus},
utils::text_manipulation,
Expand All @@ -22,6 +21,7 @@ use {
std::{borrow::Borrow, io, time::Duration},
};

use crate::{history::History, ReedlineMenu, Menu, MenuEvent};
#[cfg(feature = "bashisms")]
use crate::menu_functions::{parse_selection_char, ParseAction};

Expand Down Expand Up @@ -350,7 +350,7 @@ impl Reedline {
}

/// Update the underlying [`History`] to/from disk
pub fn sync_history(&mut self) -> std::io::Result<()> {
pub fn sync_history(&mut self) -> HistoryResult<()> {
// TODO: check for interactions in the non-submitting events
self.history.sync()
}
Expand Down
87 changes: 82 additions & 5 deletions src/history/base.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use std::time::Duration;

use chrono::Utc;
use serde::{de::DeserializeOwned, Serialize};

use crate::core_editor::LineBuffer;

// todo: better error type
pub type Result<T> = std::result::Result<T, String>;

/// Browsing modes for a [`History`]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HistoryNavigationQuery {
Expand All @@ -13,13 +21,82 @@ pub enum HistoryNavigationQuery {
// Fuzzy Search
}

/// Interface of a history datastructure that supports stateful navigation via [`HistoryNavigationQuery`].
pub trait History: Send {
/// Append entry to the history, if capacity management is part of the implementation may perform that as well
fn append(&mut self, entry: &str);
#[derive(Copy, Clone)]
pub struct HistoryItemId(pub(crate) i64);
impl HistoryItemId {
pub fn new(i: i64) -> HistoryItemId {
HistoryItemId(i)
}
}
/// This trait represents additional context to be added to a history (see [HistoryItem])
pub trait HistoryEntryContext: Serialize + DeserializeOwned + Default + Send {}
impl HistoryEntryContext for () {}
#[derive(Clone)]
pub struct HistoryItem<ExtraInfo: HistoryEntryContext = ()> {
pub id: Option<HistoryItemId>,
pub start_timestamp: chrono::DateTime<Utc>,
pub command_line: String,
pub session_id: Option<i64>,
pub hostname: Option<String>,
pub cwd: Option<String>,
pub duration: Option<Duration>,
pub exit_status: Option<i64>,
pub more_info: Option<ExtraInfo>, // pub more_info: Option<Box<dyn HistoryEntryContext>>
}

impl Default for HistoryItem {
fn default() -> HistoryItem {
todo!()
}
}

pub enum SearchDirection {
Backward,
Forward,
}

pub enum CommandLineSearch {
Prefix(String),
Substring(String),
Exact(String),
}

pub struct SearchFilter {
pub command_line: Option<CommandLineSearch>,
pub hostname: Option<String>,
pub cwd_exact: Option<String>,
pub cwd_prefix: Option<String>,
pub exit_successful: Option<bool>,
}

pub trait History {
// returns the same history item but with the correct id set
fn save(&mut self, h: HistoryItem) -> Result<HistoryItem>;
fn load(&mut self, id: HistoryItemId) -> Result<HistoryItem>;
fn search(
&self,
start: chrono::DateTime<Utc>,
direction: SearchDirection,
end: Option<chrono::DateTime<Utc>>,
limit: Option<i64>,
filter: SearchFilter,
) -> Result<Vec<HistoryItem>>;

fn update(
&mut self,
id: HistoryItemId,
updater: Box<dyn FnOnce(HistoryItem) -> HistoryItem>,
) -> Result<()>;

fn entry_count(&self) -> Result<i64>;
fn delete(&mut self, h: HistoryItemId) -> Result<()>;
fn sync(&mut self) -> Result<()>;
}

/// Interface of a history datastructure that supports stateful navigation via [`HistoryNavigationQuery`].
pub trait HistoryCursor: Send {
/// Chronologic interaction over all entries present in the history
fn iter_chronologic(&self) -> Box<dyn DoubleEndedIterator<Item=String> + '_>;
fn iter_chronologic(&self) -> Box<dyn DoubleEndedIterator<Item = String> + '_>;

/// This moves the cursor backwards respecting the navigation query that is set
/// - Results in a no-op if the cursor is at the initial point
Expand Down
135 changes: 135 additions & 0 deletions src/history/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@

impl HistoryCursor for FileBackedHistory {
fn iter_chronologic(&self) -> Box<(dyn DoubleEndedIterator<Item = String> + '_)> {
Box::new(self.entries.iter().map(|e| e.to_string()))
}

fn back(&mut self) {
match self.query.clone() {
HistoryNavigationQuery::Normal(_) => {
if self.cursor > 0 {
self.cursor -= 1;
}
}
HistoryNavigationQuery::PrefixSearch(prefix) => {
self.back_with_criteria(&|entry| entry.starts_with(&prefix));
}
HistoryNavigationQuery::SubstringSearch(substring) => {
self.back_with_criteria(&|entry| entry.contains(&substring));
}
}
}

fn forward(&mut self) {
match self.query.clone() {
HistoryNavigationQuery::Normal(_) => {
if self.cursor < self.entries.len() {
self.cursor += 1;
}
}
HistoryNavigationQuery::PrefixSearch(prefix) => {
self.forward_with_criteria(&|entry| entry.starts_with(&prefix));
}
HistoryNavigationQuery::SubstringSearch(substring) => {
self.forward_with_criteria(&|entry| entry.contains(&substring));
}
}
}

fn string_at_cursor(&self) -> Option<String> {
self.entries.get(self.cursor).cloned()
}

fn set_navigation(&mut self, navigation: HistoryNavigationQuery) {
self.query = navigation;
self.reset_cursor();
}

fn get_navigation(&self) -> HistoryNavigationQuery {
self.query.clone()
}

fn query_entries(&self, search: &str) -> Vec<String> {
self.iter_chronologic()
.rev()
.filter(|entry| entry.contains(search))
.collect::<Vec<String>>()
}

fn max_values(&self) -> usize {
self.entries.len()
}

/// Writes unwritten history contents to disk.
///
/// If file would exceed `capacity` truncates the oldest entries.
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..);

let mut f_lock = fd_lock::RwLock::new(
OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(fname)?,
);
let mut writer_guard = f_lock.write()?;
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)))
.collect::<Result<VecDeque<_>, _>>()?;
if from_file.len() + own_entries.len() > self.capacity {
(
from_file.split_off(from_file.len() - (self.capacity - own_entries.len())),
true,
)
} else {
(from_file, false)
}
};

{
let mut writer = BufWriter::new(writer_guard.deref_mut());
if truncate {
writer.seek(SeekFrom::Start(0))?;

for line in &foreign_entries {
writer.write_all(encode_entry(line).as_bytes())?;
writer.write_all("\n".as_bytes())?;
}
} else {
writer.seek(SeekFrom::End(0))?;
}
for line in own_entries {
writer.write_all(encode_entry(line).as_bytes())?;
writer.write_all("\n".as_bytes())?;
}
writer.flush()?;
}
if truncate {
let file = writer_guard.deref_mut();
let file_len = file.stream_position()?;
file.set_len(file_len)?;
}

let own_entries = self.entries.drain(self.len_on_disk..);
foreign_entries.extend(own_entries);
self.entries = foreign_entries;

self.len_on_disk = self.entries.len();
}

self.reset_cursor();

Ok(())
}

/// Reset the internal browsing cursor
fn reset_cursor(&mut self) {
self.cursor = self.entries.len();
}
}
Loading