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

Introduce History trait #604

Merged
merged 28 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cad603e
[WIP] Introduce History trait
gwenn Feb 23, 2022
7a61085
Fix tests
gwenn Feb 23, 2022
aa72d32
Update TODOs
gwenn Feb 23, 2022
ba60748
Inline Iterator impl
gwenn Feb 23, 2022
3a78fcd
How to make History trait iterable ?
gwenn Feb 23, 2022
60553f0
Make FileHistory optional
gwenn Feb 24, 2022
ef18770
Draft an History impl based on SQLite
gwenn Mar 13, 2022
2869b28
Merge remote-tracking branch 'kkawakam/master' into history_trait
gwenn Apr 11, 2022
9353d03
Merge remote-tracking branch 'upstream/master' into history_trait
gwenn Apr 20, 2022
79a8224
Merge remote-tracking branch 'kkawakam/master' into history_trait
gwenn May 29, 2022
f65f542
Merge remote-tracking branch 'kkawakam/master' into history_trait
gwenn May 29, 2022
cedb49c
Merge remote-tracking branch 'kkawakam/master' into history_trait
gwenn Jun 12, 2022
432f274
Merge remote-tracking branch 'kkawakam/master' into history_trait
gwenn Sep 4, 2022
af074c1
Merge remote-tracking branch 'upstream/master' into history_trait
gwenn Oct 30, 2022
f81ffa6
Upgrade rusqlite dependency
gwenn Nov 5, 2022
dab8255
Remove History#last method
gwenn Nov 5, 2022
b60dbb7
Partial impl of SQLiteHistory
gwenn Nov 5, 2022
708d2b1
Partial impl of SQLiteHistory
gwenn Nov 6, 2022
8a38840
Partial impl of SQLiteHistory
gwenn Nov 7, 2022
129b06b
Partially fix and test SQLiteHistory
gwenn Nov 12, 2022
b323397
Finalize SQLiteHistory
gwenn Nov 13, 2022
abd2585
Fix SQLiteHistory add_entry
gwenn Nov 14, 2022
a20bc5f
Fix compilation error
gwenn Nov 14, 2022
0f5eeb3
Fix test on append
gwenn Nov 19, 2022
dd3f232
Fix History::get
gwenn Nov 19, 2022
72cda00
Generate schema in a exclusive construction
gwenn Nov 27, 2022
736ad9c
Clean sqlite_history example
gwenn Dec 11, 2022
ea70b0f
Merge remote-tracking branch 'kkawakam/master' into history_trait
gwenn Jan 15, 2023
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
13 changes: 10 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ cfg-if = "1.0"
# https://rustsec.org/advisories/RUSTSEC-2020-0053.html
dirs-next = { version = "2.0", optional = true }
# For History
fd-lock = "3.0.0"
fd-lock = { version = "3.0.0", optional = true }
rusqlite = { version = "0.28.0", optional = true, default-features = false, features = ["bundled", "backup"] }
libc = "0.2"
log = "0.4"
unicode-width = "0.1"
Expand Down Expand Up @@ -59,14 +60,20 @@ assert_matches = "1.2"
rustyline-derive = { version = "0.7.0", path = "rustyline-derive" }

[features]
default = ["custom-bindings", "with-dirs"]
default = ["custom-bindings", "with-dirs", "with-file-history"]
custom-bindings = ["radix_trie"]
with-dirs = ["dirs-next"]
with-file-history = ["fd-lock"]
with-sqlite-history = ["rusqlite"]
with-fuzzy = ["skim"]
case_insensitive_history_search = ["regex"]

[[example]]
name = "sqlite_history"
required-features = ["with-sqlite-history"]

[package.metadata.docs.rs]
features = ["custom-bindings", "with-dirs", "with-fuzzy"]
features = ["custom-bindings", "with-dirs", "with-file-history", "with-fuzzy"]
all-features = false
no-default-features = true
default-target = "x86_64-unknown-linux-gnu"
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://gi

```rust
use rustyline::error::ReadlineError;
use rustyline::{Editor, Result};
use rustyline::{DefaultEditor, Result};

fn main() -> Result<()> {
// `()` can be used when no completer is required
let mut rl = Editor::<()>::new()?;
let mut rl = DefaultEditor::new()?;
#[cfg(feature = "with-file-history")]
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
}
Expand All @@ -53,6 +54,7 @@ fn main() -> Result<()> {
}
}
}
#[cfg(feature = "with-file-history")]
rl.save_history("history.txt")
}
```
Expand Down
5 changes: 3 additions & 2 deletions examples/custom_key_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::highlight::Highlighter;
use rustyline::hint::HistoryHinter;
use rustyline::history::DefaultHistory;
use rustyline::{
Cmd, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, KeyEvent, RepeatCount,
Result,
Expand Down Expand Up @@ -85,7 +86,7 @@ impl ConditionalEventHandler for TabEventHandler {
}

fn main() -> Result<()> {
let mut rl = Editor::<MyHelper>::new()?;
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
rl.set_helper(Some(MyHelper(HistoryHinter {})));

let ceh = Box::new(CompleteHintHandler);
Expand All @@ -102,7 +103,7 @@ fn main() -> Result<()> {

loop {
let line = rl.readline("> ")?;
rl.add_history_entry(line.as_str());
rl.add_history_entry(line.as_str())?;
println!("Line: {line}");
}
}
3 changes: 2 additions & 1 deletion examples/diy_hints.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;

use rustyline::hint::{Hint, Hinter};
use rustyline::history::DefaultHistory;
use rustyline::Context;
use rustyline::{Editor, Result};
use rustyline_derive::{Completer, Helper, Highlighter, Validator};
Expand Down Expand Up @@ -85,7 +86,7 @@ fn main() -> Result<()> {
println!("This is a DIY hint hack of rustyline");
let h = DIYHinter { hints: diy_hints() };

let mut rl: Editor<DIYHinter> = Editor::new()?;
let mut rl: Editor<DIYHinter, DefaultHistory> = Editor::new()?;
rl.set_helper(Some(h));

loop {
Expand Down
2 changes: 1 addition & 1 deletion examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn main() -> rustyline::Result<()> {
let readline = rl.readline(&p);
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str());
rl.add_history_entry(line.as_str())?;
println!("Line: {line}");
}
Err(ReadlineError::Interrupted) => {
Expand Down
6 changes: 3 additions & 3 deletions examples/external_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use std::time::Duration;

use rand::{thread_rng, Rng};

use rustyline::{Editor, ExternalPrinter, Result};
use rustyline::{DefaultEditor, ExternalPrinter, Result};

fn main() -> Result<()> {
let mut rl = Editor::<()>::new()?;
let mut rl = DefaultEditor::new()?;
let mut printer = rl.create_external_printer()?;
thread::spawn(move || {
let mut rng = thread_rng();
Expand All @@ -23,7 +23,7 @@ fn main() -> Result<()> {

loop {
let line = rl.readline("> ")?;
rl.add_history_entry(line.as_str());
rl.add_history_entry(line.as_str())?;
println!("Line: {line}");
}
}
4 changes: 2 additions & 2 deletions examples/minimal.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use rustyline::{Editor, Result};
use rustyline::{DefaultEditor, Result};

/// Minimal REPL
fn main() -> Result<()> {
env_logger::init();
let mut rl = Editor::<()>::new()?;
let mut rl = DefaultEditor::new()?;
loop {
let line = rl.readline("> ")?; // read
println!("Line: {line}"); // eval / print
Expand Down
6 changes: 3 additions & 3 deletions examples/numeric_input.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use rustyline::{
Cmd, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, KeyCode, KeyEvent,
Modifiers, RepeatCount, Result,
Cmd, ConditionalEventHandler, DefaultEditor, Event, EventContext, EventHandler, KeyCode,
KeyEvent, Modifiers, RepeatCount, Result,
};

struct FilteringEventHandler;
Expand All @@ -19,7 +19,7 @@ impl ConditionalEventHandler for FilteringEventHandler {
}

fn main() -> Result<()> {
let mut rl = Editor::<()>::new()?;
let mut rl = DefaultEditor::new()?;

rl.bind_sequence(
Event::Any,
Expand Down
17 changes: 17 additions & 0 deletions examples/sqlite_history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use rustyline::{Config, Editor, Result};

fn main() -> Result<()> {
let config = Config::builder().auto_add_history(true).build();
let history = if false {
// memory
rustyline::sqlite_history::SQLiteHistory::with_config(config)?
} else {
// file
rustyline::sqlite_history::SQLiteHistory::open(config, "history.sqlite3")?
};
let mut rl: Editor<(), _> = Editor::with_history(config, history)?;
loop {
let line = rl.readline("> ")?;
println!("{line}");
}
}
21 changes: 11 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Customize line editor
use crate::Result;
use std::default::Default;

/// User preferences
Expand Down Expand Up @@ -322,20 +323,18 @@ impl Builder {
}

/// Set the maximum length for the history.
#[must_use]
pub fn max_history_size(mut self, max_size: usize) -> Self {
self.set_max_history_size(max_size);
self
pub fn max_history_size(mut self, max_size: usize) -> Result<Self> {
self.set_max_history_size(max_size)?;
Ok(self)
}

/// Tell if lines which match the previous history entry are saved or not
/// in the history list.
///
/// By default, they are ignored.
#[must_use]
pub fn history_ignore_dups(mut self, yes: bool) -> Self {
self.set_history_ignore_dups(yes);
self
pub fn history_ignore_dups(mut self, yes: bool) -> Result<Self> {
self.set_history_ignore_dups(yes)?;
Ok(self)
}

/// Tell if lines which begin with a space character are saved or not in
Expand Down Expand Up @@ -470,16 +469,18 @@ pub trait Configurer {
fn config_mut(&mut self) -> &mut Config;

/// Set the maximum length for the history.
fn set_max_history_size(&mut self, max_size: usize) {
fn set_max_history_size(&mut self, max_size: usize) -> Result<()> {
self.config_mut().set_max_history_size(max_size);
Ok(())
}

/// Tell if lines which match the previous history entry are saved or not
/// in the history list.
///
/// By default, they are ignored.
fn set_history_ignore_dups(&mut self, yes: bool) {
fn set_history_ignore_dups(&mut self, yes: bool) -> Result<()> {
self.config_mut().set_history_ignore_dups(yes);
Ok(())
}

/// Tell if lines which begin with a space character are saved or not in
Expand Down
50 changes: 30 additions & 20 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,16 +643,22 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
} else if self.ctx.history_index == 0 && prev {
return Ok(());
}
if prev {
self.ctx.history_index -= 1;
let (idx, dir) = if prev {
(self.ctx.history_index - 1, SearchDirection::Reverse)
} else {
self.ctx.history_index += 1;
}
if self.ctx.history_index < history.len() {
let buf = history.get(self.ctx.history_index).unwrap();
self.changes.begin();
self.line.update(buf, buf.len(), &mut self.changes);
self.changes.end();
(self.ctx.history_index, SearchDirection::Forward)
};
if idx < history.len() {
if let Some(r) = history.get(idx, dir)? {
let buf = r.entry;
self.ctx.history_index = r.idx;
self.changes.begin();
self.line.update(&buf, buf.len(), &mut self.changes);
self.changes.end();
} else {
return Ok(());
}
} else {
// Restore current edited line
self.restore();
Expand Down Expand Up @@ -680,10 +686,10 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
&self.line.as_str()[..self.line.pos()],
self.ctx.history_index,
dir,
) {
)? {
self.ctx.history_index = sr.idx;
self.changes.begin();
self.line.update(sr.entry, sr.pos, &mut self.changes);
self.line.update(&sr.entry, sr.pos, &mut self.changes);
self.changes.end();
self.refresh_line()
} else {
Expand All @@ -708,11 +714,15 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
return Ok(());
}
if first {
self.ctx.history_index = 0;
let buf = history.get(self.ctx.history_index).unwrap();
self.changes.begin();
self.line.update(buf, buf.len(), &mut self.changes);
self.changes.end();
if let Some(r) = history.get(0, SearchDirection::Forward)? {
let buf = r.entry;
self.ctx.history_index = r.idx;
self.changes.begin();
self.line.update(&buf, buf.len(), &mut self.changes);
self.changes.end();
} else {
return Ok(());
}
} else {
self.ctx.history_index = history.len();
// Restore current edited line
Expand All @@ -737,7 +747,7 @@ pub fn init_state<'out, H: Helper>(
line: &str,
pos: usize,
helper: Option<&'out H>,
history: &'out crate::history::History,
history: &'out crate::history::DefaultHistory,
) -> State<'out, 'static, H> {
State {
out,
Expand All @@ -758,15 +768,15 @@ pub fn init_state<'out, H: Helper>(
#[cfg(test)]
mod test {
use super::init_state;
use crate::history::History;
use crate::history::{DefaultHistory, History};
use crate::tty::Sink;

#[test]
fn edit_history_next() {
let mut out = Sink::default();
let mut history = History::new();
history.add("line0");
history.add("line1");
let mut history = DefaultHistory::new();
history.add("line0").unwrap();
history.add("line1").unwrap();
let line = "current edited line";
let helper: Option<()> = None;
let mut s = init_state(&mut out, line, 6, helper.as_ref(), &history);
Expand Down
14 changes: 14 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub enum ReadlineError {
/// Something went wrong calling a Windows API
#[cfg(windows)]
SystemError(clipboard_win::SystemError),
/// Error related to SQLite history backend
#[cfg(feature = "with-sqlite-history")]
SQLiteError(rusqlite::Error),
}

impl fmt::Display for ReadlineError {
Expand All @@ -44,6 +47,8 @@ impl fmt::Display for ReadlineError {
ReadlineError::Decode(ref err) => err.fmt(f),
#[cfg(windows)]
ReadlineError::SystemError(ref err) => err.fmt(f),
#[cfg(feature = "with-sqlite-history")]
ReadlineError::SQLiteError(ref err) => err.fmt(f),
}
}
}
Expand All @@ -61,6 +66,8 @@ impl Error for ReadlineError {
ReadlineError::Decode(ref err) => Some(err),
#[cfg(windows)]
ReadlineError::SystemError(_) => None,
#[cfg(feature = "with-sqlite-history")]
ReadlineError::SQLiteError(ref err) => Some(err),
}
}
}
Expand Down Expand Up @@ -131,3 +138,10 @@ impl From<clipboard_win::SystemError> for ReadlineError {
ReadlineError::SystemError(err)
}
}

#[cfg(feature = "with-sqlite-history")]
impl From<rusqlite::Error> for ReadlineError {
fn from(err: rusqlite::Error) -> Self {
ReadlineError::SQLiteError(err)
}
}
5 changes: 3 additions & 2 deletions src/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ impl Hinter for HistoryHinter {
if let Some(sr) = ctx
.history
.starts_with(line, start, SearchDirection::Reverse)
.unwrap_or(None)
{
if sr.entry == line {
return None;
Expand All @@ -80,12 +81,12 @@ impl Hinter for HistoryHinter {
#[cfg(test)]
mod test {
use super::{Hinter, HistoryHinter};
use crate::history::History;
use crate::history::DefaultHistory;
use crate::Context;

#[test]
pub fn empty_history() {
let history = History::new();
let history = DefaultHistory::new();
let ctx = Context::new(&history);
let hinter = HistoryHinter {};
let hint = hinter.hint("test", 4, &ctx);
Expand Down
Loading