Skip to content

Commit

Permalink
Merge pull request #604 from gwenn/history_trait
Browse files Browse the repository at this point in the history
Introduce History trait
  • Loading branch information
gwenn authored Jan 15, 2023
2 parents c3cbd4f + ea70b0f commit c54601d
Show file tree
Hide file tree
Showing 17 changed files with 1,453 additions and 443 deletions.
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

0 comments on commit c54601d

Please sign in to comment.