1#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum Direction {
18 Forward,
19 Reverse,
20}
21
22#[derive(Default)]
24pub struct History {
25 entries: VecDeque<String>,
26 max_len: usize,
27 pub(crate) ignore_space: bool,
28 pub(crate) ignore_dups: bool,
29}
30
31impl History {
32 pub fn new() -> History {
33 Self::with_config(Config::default())
34 }
35
36 pub fn with_config(config: Config) -> History {
37 History {
38 entries: VecDeque::new(),
39 max_len: config.max_history_size(),
40 ignore_space: config.history_ignore_space(),
41 ignore_dups: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
42 }
43 }
44
45 pub fn get(&self, index: usize) -> Option<&String> {
47 self.entries.get(index)
48 }
49
50 pub fn last(&self) -> Option<&String> {
52 self.entries.back()
53 }
54
55 pub fn add<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
57 if self.max_len == 0 {
58 return false;
59 }
60 if line.as_ref().is_empty()
61 || (self.ignore_space && 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 pub fn len(&self) -> usize {
85 self.entries.len()
86 }
87
88 pub fn is_empty(&self) -> bool {
90 self.entries.is_empty()
91 }
92
93 pub fn set_max_len(&mut self, len: usize) {
101 self.max_len = len;
102 if len == 0 {
103 self.entries.clear();
104 return;
105 }
106 loop {
107 if self.entries.len() <= len {
108 break;
109 }
110 self.entries.pop_front();
111 }
112 }
113
114 pub fn save<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
120 use std::io::{BufWriter, Write};
121
122 if self.is_empty() {
123 return Ok(());
124 }
125 let old_umask = umask();
126 let f = File::create(path);
127 restore_umask(old_umask);
128 let file = try!(f);
129 fix_perm(&file);
130 let mut wtr = BufWriter::new(file);
131 for entry in &self.entries {
132 try!(wtr.write_all(entry.as_bytes()));
133 try!(wtr.write_all(b"\n"));
134 }
135 try!(wtr.flush());
137 Ok(())
138 }
139
140 pub fn load<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
145 use std::io::{BufRead, BufReader};
146
147 let file = try!(File::open(&path));
148 let rdr = BufReader::new(file);
149 for line in rdr.lines() {
150 self.add(&*try!(line)); }
152 Ok(())
153 }
154
155 pub fn clear(&mut self) {
157 self.entries.clear()
158 }
159
160 pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
169 let test = |entry: &String| entry.contains(term);
170 self.search_match(term, start, dir, test)
171 }
172
173 pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
175 let test = |entry: &String| entry.starts_with(term);
176 self.search_match(term, start, dir, test)
177 }
178
179 fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize>
180 where
181 F: Fn(&String) -> bool,
182 {
183 if term.is_empty() || start >= self.len() {
184 return None;
185 }
186 match dir {
187 Direction::Reverse => {
188 let index = self
189 .entries
190 .iter()
191 .rev()
192 .skip(self.entries.len() - 1 - start)
193 .position(test);
194 index.and_then(|index| Some(start - index))
195 }
196 Direction::Forward => {
197 let index = self.entries.iter().skip(start).position(test);
198 index.and_then(|index| Some(index + start))
199 }
200 }
201 }
202
203 pub fn iter(&self) -> Iter {
205 Iter(self.entries.iter())
206 }
207}
208
209impl Index<usize> for History {
210 type Output = String;
211
212 fn index(&self, index: usize) -> &String {
213 &self.entries[index]
214 }
215}
216
217impl<'a> IntoIterator for &'a History {
218 type IntoIter = Iter<'a>;
219 type Item = &'a String;
220
221 fn into_iter(self) -> Iter<'a> {
222 self.iter()
223 }
224}
225
226pub struct Iter<'a>(vec_deque::Iter<'a, String>);
228
229impl<'a> Iterator for Iter<'a> {
230 type Item = &'a String;
231
232 fn next(&mut self) -> Option<&'a String> {
233 self.0.next()
234 }
235
236 fn size_hint(&self) -> (usize, Option<usize>) {
237 self.0.size_hint()
238 }
239}
240
241impl<'a> DoubleEndedIterator for Iter<'a> {
242 fn next_back(&mut self) -> Option<&'a String> {
243 self.0.next_back()
244 }
245}
246
247#[cfg(windows)]
248fn umask() -> u16 {
249 0
250}
251#[cfg(unix)]
252fn umask() -> libc::mode_t {
253 unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) }
254}
255#[cfg(windows)]
256fn restore_umask(_: u16) {}
257#[cfg(unix)]
258fn restore_umask(old_umask: libc::mode_t) {
259 unsafe {
260 libc::umask(old_umask);
261 }
262}
263
264#[cfg(windows)]
265fn fix_perm(_: &File) {}
266#[cfg(unix)]
267fn fix_perm(file: &File) {
268 use std::os::unix::io::AsRawFd;
269 unsafe {
270 libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR);
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 extern crate tempdir;
277 use super::{Direction, History};
278 use config::Config;
279 use std::path::Path;
280
281 fn init() -> History {
282 let mut history = History::new();
283 assert!(history.add("line1"));
284 assert!(history.add("line2"));
285 assert!(history.add("line3"));
286 history
287 }
288
289 #[test]
290 fn new() {
291 let history = History::new();
292 assert_eq!(0, history.entries.len());
293 }
294
295 #[test]
296 fn add() {
297 let config = Config::builder().history_ignore_space(true).build();
298 let mut history = History::with_config(config);
299 assert_eq!(config.max_history_size(), history.max_len);
300 assert!(history.add("line1"));
301 assert!(history.add("line2"));
302 assert!(!history.add("line2"));
303 assert!(!history.add(""));
304 assert!(!history.add(" line3"));
305 }
306
307 #[test]
308 fn set_max_len() {
309 let mut history = init();
310 history.set_max_len(1);
311 assert_eq!(1, history.entries.len());
312 assert_eq!(Some(&"line3".to_owned()), history.last());
313 }
314
315 #[test]
316 fn save() {
317 let mut history = init();
318 let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
319 let history_path = td.path().join(".history");
320
321 history.save(&history_path).unwrap();
322 history.load(&history_path).unwrap();
323 td.close().unwrap();
324 }
325
326 #[test]
327 fn search() {
328 let history = init();
329 assert_eq!(None, history.search("", 0, Direction::Forward));
330 assert_eq!(None, history.search("none", 0, Direction::Forward));
331 assert_eq!(None, history.search("line", 3, Direction::Forward));
332
333 assert_eq!(Some(0), history.search("line", 0, Direction::Forward));
334 assert_eq!(Some(1), history.search("line", 1, Direction::Forward));
335 assert_eq!(Some(2), history.search("line3", 1, Direction::Forward));
336 }
337
338 #[test]
339 fn reverse_search() {
340 let history = init();
341 assert_eq!(None, history.search("", 2, Direction::Reverse));
342 assert_eq!(None, history.search("none", 2, Direction::Reverse));
343 assert_eq!(None, history.search("line", 3, Direction::Reverse));
344
345 assert_eq!(Some(2), history.search("line", 2, Direction::Reverse));
346 assert_eq!(Some(1), history.search("line", 1, Direction::Reverse));
347 assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse));
348 }
349}