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 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 pub fn get(&self, index: usize) -> Option<&String> {
46 self.entries.get(index)
47 }
48
49 pub fn last(&self) -> Option<&String> {
51 self.entries.back()
52 }
53
54 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 pub fn len(&self) -> usize {
85 self.entries.len()
86 }
87 pub fn is_empty(&self) -> bool {
89 self.entries.is_empty()
90 }
91
92 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 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 try!(wtr.flush());
136 Ok(())
137 }
138
139 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()); }
151 Ok(())
152 }
153
154 pub fn clear(&mut self) {
156 self.entries.clear()
157 }
158
159 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 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 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
225pub 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}