datafusion_rustyline/
history.rs

1//! History API
2
3#[cfg(unix)]
4use libc;
5use std::collections::vec_deque;
6use std::collections::VecDeque;
7use std::fs::File;
8use std::iter::DoubleEndedIterator;
9use std::ops::Index;
10use std::path::Path;
11
12use super::Result;
13use config::{Config, HistoryDuplicates};
14
15/// Search direction
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum Direction {
18    Forward,
19    Reverse,
20}
21
22/// Current state of the history.
23#[derive(Default)]
24pub struct History {
25    entries: VecDeque<String>,
26    max_len: usize,
27    ignore_space: bool,
28    ignore_dups: bool,
29}
30
31impl History {
32    pub fn new() -> History {
33        Self::with_config(Config::default())
34    }
35    pub fn with_config(config: Config) -> History {
36        History {
37            entries: VecDeque::new(),
38            max_len: config.max_history_size(),
39            ignore_space: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
40            ignore_dups: config.history_ignore_space(),
41        }
42    }
43
44    /// Return the history entry at position `index`, starting from 0.
45    pub fn get(&self, index: usize) -> Option<&String> {
46        self.entries.get(index)
47    }
48
49    /// Return the last history entry (i.e. previous command)
50    pub fn last(&self) -> Option<&String> {
51        self.entries.back()
52    }
53
54    /// Add a new entry in the history.
55    pub fn add<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
56        if self.max_len == 0 {
57            return false;
58        }
59        if line.as_ref().is_empty()
60            || (self.ignore_space
61                && line
62                    .as_ref()
63                    .chars()
64                    .next()
65                    .map_or(true, |c| c.is_whitespace()))
66        {
67            return false;
68        }
69        if self.ignore_dups {
70            if let Some(s) = self.entries.back() {
71                if s == line.as_ref() {
72                    return false;
73                }
74            }
75        }
76        if self.entries.len() == self.max_len {
77            self.entries.pop_front();
78        }
79        self.entries.push_back(line.into());
80        true
81    }
82
83    /// Return the number of entries in the history.
84    pub fn len(&self) -> usize {
85        self.entries.len()
86    }
87    /// Return true if the history has no entry.
88    pub fn is_empty(&self) -> bool {
89        self.entries.is_empty()
90    }
91
92    /// Set the maximum length for the history. This function can be called even
93    /// if there is already some history, the function will make sure to retain
94    /// just the latest `len` elements if the new history length value is
95    /// smaller than the amount of items already inside the history.
96    ///
97    /// Like [stifle_history](http://cnswww.cns.cwru.
98    /// edu/php/chet/readline/history.html#IDX11).
99    pub fn set_max_len(&mut self, len: usize) {
100        self.max_len = len;
101        if len == 0 {
102            self.entries.clear();
103            return;
104        }
105        loop {
106            if self.entries.len() <= len {
107                break;
108            }
109            self.entries.pop_front();
110        }
111    }
112
113    /// Save the history in the specified file.
114    // TODO append_history
115    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30
116    // TODO history_truncate_file
117    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX31
118    pub fn save<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
119        use std::io::{BufWriter, Write};
120
121        if self.is_empty() {
122            return Ok(());
123        }
124        let old_umask = umask();
125        let f = File::create(path);
126        restore_umask(old_umask);
127        let file = try!(f);
128        fix_perm(&file);
129        let mut wtr = BufWriter::new(file);
130        for entry in &self.entries {
131            try!(wtr.write_all(entry.as_bytes()));
132            try!(wtr.write_all(b"\n"));
133        }
134        // https://github.com/rust-lang/rust/issues/32677#issuecomment-204833485
135        try!(wtr.flush());
136        Ok(())
137    }
138
139    /// Load the history from the specified file.
140    ///
141    /// # Errors
142    /// Will return `Err` if path does not already exist or could not be read.
143    pub fn load<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
144        use std::io::{BufRead, BufReader};
145
146        let file = try!(File::open(&path));
147        let rdr = BufReader::new(file);
148        for line in rdr.lines() {
149            self.add(try!(line).as_ref()); // TODO truncate to MAX_LINE
150        }
151        Ok(())
152    }
153
154    /// Clear history
155    pub fn clear(&mut self) {
156        self.entries.clear()
157    }
158
159    /// Search history (start position inclusive [0, len-1]).
160    ///
161    /// Return the absolute index of the nearest history entry that matches
162    /// `term`.
163    ///
164    /// Return None if no entry contains `term` between [start, len -1] for
165    /// forward search
166    /// or between [0, start] for reverse search.
167    pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
168        let test = |entry: &String| entry.contains(term);
169        self.search_match(term, start, dir, test)
170    }
171
172    /// Anchored search
173    pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
174        let test = |entry: &String| entry.starts_with(term);
175        self.search_match(term, start, dir, test)
176    }
177
178    fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize>
179    where
180        F: Fn(&String) -> bool,
181    {
182        if term.is_empty() || start >= self.len() {
183            return None;
184        }
185        match dir {
186            Direction::Reverse => {
187                let index = self
188                    .entries
189                    .iter()
190                    .rev()
191                    .skip(self.entries.len() - 1 - start)
192                    .position(test);
193                index.and_then(|index| Some(start - index))
194            }
195            Direction::Forward => {
196                let index = self.entries.iter().skip(start).position(test);
197                index.and_then(|index| Some(index + start))
198            }
199        }
200    }
201
202    /// Return a forward iterator.
203    pub fn iter(&self) -> Iter {
204        Iter(self.entries.iter())
205    }
206}
207
208impl Index<usize> for History {
209    type Output = String;
210
211    fn index(&self, index: usize) -> &String {
212        &self.entries[index]
213    }
214}
215
216impl<'a> IntoIterator for &'a History {
217    type Item = &'a String;
218    type IntoIter = Iter<'a>;
219
220    fn into_iter(self) -> Iter<'a> {
221        self.iter()
222    }
223}
224
225/// History iterator.
226pub struct Iter<'a>(vec_deque::Iter<'a, String>);
227
228impl<'a> Iterator for Iter<'a> {
229    type Item = &'a String;
230
231    fn next(&mut self) -> Option<&'a String> {
232        self.0.next()
233    }
234
235    fn size_hint(&self) -> (usize, Option<usize>) {
236        self.0.size_hint()
237    }
238}
239
240impl<'a> DoubleEndedIterator for Iter<'a> {
241    fn next_back(&mut self) -> Option<&'a String> {
242        self.0.next_back()
243    }
244}
245
246#[cfg(windows)]
247fn umask() -> u16 {
248    0
249}
250#[cfg(unix)]
251fn umask() -> libc::mode_t {
252    unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) }
253}
254#[cfg(windows)]
255fn restore_umask(_: u16) {}
256#[cfg(unix)]
257fn restore_umask(old_umask: libc::mode_t) {
258    unsafe {
259        libc::umask(old_umask);
260    }
261}
262
263#[cfg(windows)]
264fn fix_perm(_: &File) {}
265#[cfg(unix)]
266fn fix_perm(file: &File) {
267    use std::os::unix::io::AsRawFd;
268    unsafe {
269        libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR);
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    extern crate tempdir;
276    use super::{Direction, History};
277    use config::Config;
278    use std::path::Path;
279
280    fn init() -> History {
281        let mut history = History::new();
282        assert!(history.add("line1"));
283        assert!(history.add("line2"));
284        assert!(history.add("line3"));
285        history
286    }
287
288    #[test]
289    fn new() {
290        let history = History::new();
291        assert_eq!(0, history.entries.len());
292    }
293
294    #[test]
295    fn add() {
296        let config = Config::builder().history_ignore_space(true).build();
297        let mut history = History::with_config(config);
298        assert_eq!(config.max_history_size(), history.max_len);
299        assert!(history.add("line1"));
300        assert!(history.add("line2"));
301        assert!(!history.add("line2"));
302        assert!(!history.add(""));
303        assert!(!history.add(" line3"));
304    }
305
306    #[test]
307    fn set_max_len() {
308        let mut history = init();
309        history.set_max_len(1);
310        assert_eq!(1, history.entries.len());
311        assert_eq!(Some(&"line3".to_owned()), history.last());
312    }
313
314    #[test]
315    fn save() {
316        let mut history = init();
317        let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
318        let history_path = td.path().join(".history");
319
320        history.save(&history_path).unwrap();
321        history.load(&history_path).unwrap();
322        td.close().unwrap();
323    }
324
325    #[test]
326    fn search() {
327        let history = init();
328        assert_eq!(None, history.search("", 0, Direction::Forward));
329        assert_eq!(None, history.search("none", 0, Direction::Forward));
330        assert_eq!(None, history.search("line", 3, Direction::Forward));
331
332        assert_eq!(Some(0), history.search("line", 0, Direction::Forward));
333        assert_eq!(Some(1), history.search("line", 1, Direction::Forward));
334        assert_eq!(Some(2), history.search("line3", 1, Direction::Forward));
335    }
336
337    #[test]
338    fn reverse_search() {
339        let history = init();
340        assert_eq!(None, history.search("", 2, Direction::Reverse));
341        assert_eq!(None, history.search("none", 2, Direction::Reverse));
342        assert_eq!(None, history.search("line", 3, Direction::Reverse));
343
344        assert_eq!(Some(2), history.search("line", 2, Direction::Reverse));
345        assert_eq!(Some(1), history.search("line", 1, Direction::Reverse));
346        assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse));
347    }
348}