Newer
Older
//! History API
use std::collections::VecDeque;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Forward,
Reverse,
}
pub struct History {
entries: VecDeque<String>,
max_len: usize,
ignore_space: bool,
ignore_dups: bool,
pub fn new() -> History {
Self::with_config(Config::default())
}
pub fn with_config(config: Config) -> History {
max_len: config.max_history_size(),
ignore_space: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
ignore_dups: config.history_ignore_space(),
/// Return the history entry at position `index`, starting from 0.
/// Return the last history entry (i.e. previous command)
pub fn last(&self) -> Option<&String> {
self.entries.back()
}
line.as_ref()
.chars()
.next()
.map_or(true, |c| c.is_whitespace()))
if self.ignore_dups {
if let Some(s) = self.entries.back() {
}
if self.entries.len() == self.max_len {
self.entries.pop_front();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
/// Set the maximum length for the history. This function can be called even
/// if there is already some history, the function will make sure to retain
/// smaller than the amount of items already inside the history.
///
/// Like [stifle_history](http://cnswww.cns.cwru.
/// edu/php/chet/readline/history.html#IDX11).
pub fn set_max_len(&mut self, len: usize) {
self.max_len = len;
if len == 0 {
self.entries.clear();
return;
}
loop {
if self.entries.len() <= len {
break;
}
self.entries.pop_front();
}
}
/// Save the history in the specified file.
// TODO append_history
// http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30
// TODO history_truncate_file
// http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX31
try!(wtr.write_all(entry.as_bytes()));
}
/// Load the history from the specified file.
/// # Errors
/// Will return `Err` if path does not already exist or could not be read.
pub fn load<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
use std::io::{BufRead, BufReader};
let file = try!(File::open(&path));
let rdr = BufReader::new(file);
for line in rdr.lines() {
self.add(try!(line).as_ref()); // TODO truncate to MAX_LINE
}
/// Clear history
pub fn clear(&mut self) {
self.entries.clear()
}
/// Return the absolute index of the nearest history entry that matches
/// `term`.
/// Return None if no entry contains `term` between [start, len -1] for
/// forward search
pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
let test = |entry: &String| entry.contains(term);
self.search_match(term, start, dir, test)
}
pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
let test = |entry: &String| entry.starts_with(term);
self.search_match(term, start, dir, test)
}
fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize>
if term.is_empty() || start >= self.len() {
return None;
}
match dir {
Direction::Reverse => {
let index = self.entries
.iter()
.rev()
.skip(self.entries.len() - 1 - start)
index.and_then(|index| Some(start - index))
}
Direction::Forward => {
let index = self.entries.iter().skip(start).position(test);
index.and_then(|index| Some(index + start))
}
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/// Return a forward iterator.
pub fn iter(&self) -> Iter {
Iter(self.entries.iter())
}
}
impl Index<usize> for History {
type Output = String;
fn index(&self, index: usize) -> &String {
&self.entries[index]
}
}
impl<'a> IntoIterator for &'a History {
type Item = &'a String;
type IntoIter = Iter<'a>;
fn into_iter(self) -> Iter<'a> {
self.iter()
}
}
/// History iterator.
pub struct Iter<'a>(vec_deque::Iter<'a, String>);
impl<'a> Iterator for Iter<'a> {
type Item = &'a String;
fn next(&mut self) -> Option<&'a String> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> DoubleEndedIterator for Iter<'a> {
fn next_back(&mut self) -> Option<&'a String> {
self.0.next_back()
}
#[cfg(windows)]
fn umask() -> u16 {
0
}
#[cfg(unix)]
unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) }
}
#[cfg(windows)]
fn restore_umask(_: u16) {}
#[cfg(unix)]
unsafe {
libc::umask(old_umask);
}
}
#[cfg(windows)]
#[cfg(unix)]
fn fix_perm(file: &File) {
use std::os::unix::io::AsRawFd;
unsafe {
libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR);
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use std::path::Path;
use super::{Direction, History};
let mut history = History::new();
assert!(history.add("line1"));
assert!(history.add("line2"));
assert!(history.add("line3"));
let history = History::new();
assert_eq!(0, history.entries.len());
}
#[test]
fn add() {
let config = Config::builder().history_ignore_space(true).build();
let mut history = History::with_config(config);
assert_eq!(config.max_history_size(), history.max_len);
assert!(history.add("line1"));
assert!(history.add("line2"));
assert!(!history.add("line2"));
assert!(!history.add(""));
assert!(!history.add(" line3"));
}
#[test]
fn set_max_len() {
let mut history = init();
history.set_max_len(1);
assert_eq!(1, history.entries.len());
assert_eq!(Some(&"line3".to_owned()), history.last());
}
#[test]
fn save() {
let mut history = init();
let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
let history_path = td.path().join(".history");
history.save(&history_path).unwrap();
history.load(&history_path).unwrap();
td.close().unwrap();
}
#[test]
fn search() {
let history = init();
assert_eq!(None, history.search("", 0, Direction::Forward));
assert_eq!(None, history.search("none", 0, Direction::Forward));
assert_eq!(None, history.search("line", 3, Direction::Forward));
assert_eq!(Some(0), history.search("line", 0, Direction::Forward));
assert_eq!(Some(1), history.search("line", 1, Direction::Forward));
assert_eq!(Some(2), history.search("line3", 1, Direction::Forward));
}
#[test]
fn reverse_search() {
let history = init();
assert_eq!(None, history.search("", 2, Direction::Reverse));
assert_eq!(None, history.search("none", 2, Direction::Reverse));
assert_eq!(None, history.search("line", 3, Direction::Reverse));
assert_eq!(Some(2), history.search("line", 2, Direction::Reverse));
assert_eq!(Some(1), history.search("line", 1, Direction::Reverse));
assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse));