blob: cdf5522383a200b172b8d4a082f921c9afd5ae09 [file] [log] [blame]
//! History API
use std::collections::VecDeque;
use std::path::Path;
use std::fs::File;
use super::Result;
pub struct History {
entries: VecDeque<String>,
max_len: usize,
}
const DEFAULT_HISTORY_MAX_LEN: usize = 100;
impl History {
pub fn new() -> History {
History {
entries: VecDeque::new(),
max_len: DEFAULT_HISTORY_MAX_LEN,
}
}
/// Return the history entry at position `index`, starting from 0.
pub fn get(&self, index: usize) -> Option<&String> {
self.entries.get(index)
}
/// Add a new entry in the history.
pub fn add(&mut self, line: &str) -> bool {
if self.max_len == 0 {
return false;
}
if line.is_empty() || line.chars().next().map_or(true, |c| c.is_whitespace()) {
// ignorespace
return false;
}
if let Some(s) = self.entries.back() {
if s == line {
// ignoredups
return false;
}
}
if self.entries.len() == self.max_len {
self.entries.pop_front();
}
self.entries.push_back(String::from(line));
true
}
/// Returns the number of entries in the history.
pub fn len(&self) -> usize {
self.entries.len()
}
/// Returns true if the history has no entry.
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
/// just the latest `len` elements if the new history length value is smaller
/// than the amount of items already inside the history.
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.
pub fn save<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
use std::io::{BufWriter, Write};
if self.is_empty() {
return Ok(());
}
let file = try!(File::create(path));
let mut wtr = BufWriter::new(file);
for entry in &self.entries {
try!(wtr.write_all(&entry.as_bytes()));
try!(wtr.write_all(b"\n"));
}
Ok(())
}
/// Load the history from the specified file.
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
}
Ok(())
}
/// Clear history
pub fn clear(&mut self) {
self.entries.clear()
}
/// Search history (start position inclusive [0, len-1])
pub fn search(&self, term: &str, start: usize, reverse: bool) -> Option<usize> {
if term.is_empty() || start >= self.len() {
return None;
}
if reverse {
let index = self.entries
.iter()
.rev()
.skip(self.entries.len() - 1 - start)
.position(|entry| entry.contains(term));
index.and_then(|index| Some(start - index))
} else {
let index = self.entries.iter().skip(start).position(|entry| entry.contains(term));
index.and_then(|index| Some(index + start))
}
}
}
impl Default for History {
fn default() -> History {
History::new()
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use std::path::Path;
fn init() -> super::History {
let mut history = super::History::new();
assert!(history.add("line1"));
assert!(history.add("line2"));
assert!(history.add("line3"));
history
}
#[test]
fn new() {
let history = super::History::new();
assert_eq!(super::DEFAULT_HISTORY_MAX_LEN, history.max_len);
assert_eq!(0, history.entries.len());
}
#[test]
fn add() {
let mut history = super::History::new();
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_string()), history.entries.back());
}
#[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, false));
assert_eq!(None, history.search("none", 0, false));
assert_eq!(None, history.search("line", 3, false));
assert_eq!(Some(0), history.search("line", 0, false));
assert_eq!(Some(1), history.search("line", 1, false));
assert_eq!(Some(2), history.search("line3", 1, false));
}
#[test]
fn reverse_search() {
let history = init();
assert_eq!(None, history.search("", 2, true));
assert_eq!(None, history.search("none", 2, true));
assert_eq!(None, history.search("line", 3, true));
assert_eq!(Some(2), history.search("line", 2, true));
assert_eq!(Some(1), history.search("line", 1, true));
assert_eq!(Some(0), history.search("line1", 1, true));
}
}