history.rs 5.5 KB
Newer Older
1 2
use super::*;

3 4 5 6 7 8 9 10 11 12 13
use std::{
    collections::{vec_deque, VecDeque},
    io::{BufRead, BufReader, BufWriter},
    fs::File,
    io::{self, Seek, SeekFrom, Write},
    iter::IntoIterator,
    ops::Index,
    ops::IndexMut,
    path::Path,
    //time::Duration,
};
14

15 16
const DEFAULT_MAX_SIZE: usize = 1000;

17 18
/// Structure encapsulating command history
pub struct History {
Sehny's avatar
Sehny committed
19 20
    // TODO: this should eventually be private
    /// Vector of buffers to store history in
Sehny's avatar
Sehny committed
21
    pub buffers: VecDeque<Buffer>,
22
    /// Store a filename to save history into; if None don't save history
Sehny's avatar
Sehny committed
23
    file_name: Option<String>,
24
    /// Maximal number of buffers stored in the memory
25
    /// TODO: just make this public?
26
    max_buffers_size: usize,
27
    /// Maximal number of lines stored in the file
28
    // TODO: just make this public?
29
    max_file_size: usize,
30 31
    // TODO set from environment variable?
    pub append_duplicate_entries: bool,
32 33 34 35 36 37
}

impl History {
    /// Create new History structure.
    pub fn new() -> History {
        History {
38
            buffers: VecDeque::with_capacity(DEFAULT_MAX_SIZE),
39
            file_name: None,
40 41
            max_buffers_size: DEFAULT_MAX_SIZE,
            max_file_size: DEFAULT_MAX_SIZE,
42
            append_duplicate_entries: false,
43 44 45
        }
    }

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
    /// Set history file name and at the same time load the history.
    pub fn set_file_name_and_load_history<P: AsRef<Path>>(&mut self, path: P) -> io::Result<String> {
        let status;
        let path = path.as_ref();
        let file = if path.exists() {
            status = format!("opening {:?}", path);
            File::open(path)?
        } else {
            status = format!("creating {:?}", path);
            File::create(path)?
        };
        let reader = BufReader::new(file);
        for line in reader.lines() {
            match line {
                Ok(line) => self.buffers.push_back(Buffer::from(line)),
                Err(_) => break,
            }
        }
        self.file_name = path.to_str().map(|s| s.to_owned());
        Ok(status)
    }

    /// Set maximal number of buffers stored in memory
    pub fn set_max_buffers_size(&mut self, size: usize) {
        self.max_buffers_size = size;
    }

    /// Set maximal number of entries in history file
    pub fn set_max_file_size(&mut self, size: usize) {
        self.max_file_size = size;
    }

78
    /// Number of items in history.
79
    #[inline(always)]
80
    pub fn len(&self) -> usize {
Sehny's avatar
Sehny committed
81
        self.buffers.len()
82 83 84
    }

    /// Add a command to the history buffer and remove the oldest commands when the max history
85 86
    /// size has been met. If writing to the disk is enabled, this function will be used for
    /// logging history to the designated history file.
87
    pub fn push(&mut self, new_item: Buffer) -> io::Result<()> {
88 89
        // buffers[0] is the oldest entry
        // the new entry goes to the end
MovingtoMars's avatar
MovingtoMars committed
90 91
        if !self.append_duplicate_entries
            && self.buffers.back().map(|b| b.to_string()) == Some(new_item.to_string())
92 93 94
        {
            return Ok(());
        }
Michael Aaron Murphy's avatar
Michael Aaron Murphy committed
95

96
        self.buffers.push_back(new_item);
97
        while self.buffers.len() > self.max_buffers_size {
98 99
            self.buffers.pop_front();
        }
100
        Ok(())
101 102
    }

103 104 105 106 107 108 109 110
    /// Removes duplicate entries in the history
    pub fn remove_duplicates(&mut self, input: &str) {
        self.buffers.retain(|buffer| {
            let command = buffer.lines().concat();
            command != input
        });
    }

111 112
    /// Go through the history and try to find a buffer which starts the same as the new buffer
    /// given to this function as argument.
113 114 115 116 117
    pub fn get_newest_match<'a, 'b>(
        &'a self,
        curr_position: Option<usize>,
        new_buff: &'b Buffer,
    ) -> Option<&'a Buffer> {
118 119 120
        let pos = curr_position.unwrap_or(self.buffers.len());
        for iter in (0..pos).rev() {
            if let Some(tested) = self.buffers.get(iter) {
Sehny's avatar
Sehny committed
121
                if tested.starts_with(new_buff) {
122
                    return self.buffers.get(iter);
123 124 125 126 127 128
                }
            }
        }
        None
    }

MovingtoMars's avatar
MovingtoMars committed
129
    /// Get the history file name.
130
    #[inline(always)]
MovingtoMars's avatar
MovingtoMars committed
131
    pub fn file_name(&self) -> Option<&str> {
132
        self.file_name.as_ref().map(|s| s.as_str())
133 134
    }

135 136 137 138 139 140 141 142 143 144
    pub fn commit_to_file(&mut self) {
        if let Some(file_name) = self.file_name.clone() {
            // Find how many bytes we need to move backwards
            // in the file to remove all the old commands.
            if self.buffers.len() >= self.max_file_size {
                let pop_out = self.buffers.len() - self.max_file_size;
                for _ in 0..pop_out {
                    self.buffers.pop_front();
                }
            }
145

146 147 148
            let mut file = BufWriter::new(File::create(&file_name)
                // It's safe to unwrap, because the file has be loaded by this time
                .unwrap());
149

150 151 152 153
            // Write the commands to the history file.
            for command in self.buffers.iter().cloned() {
                let _ = file.write_all(&String::from(command).as_bytes());
                let _ = file.write_all(b"\n");
Sehny's avatar
Sehny committed
154 155
            }
        }
156 157 158 159 160 161 162 163
    }
}

impl<'a> IntoIterator for &'a History {
    type Item = &'a Buffer;
    type IntoIter = vec_deque::Iter<'a, Buffer>;

    fn into_iter(self) -> Self::IntoIter {
164
        self.buffers.iter()
165
    }
166 167 168 169 170 171
}

impl Index<usize> for History {
    type Output = Buffer;

    fn index(&self, index: usize) -> &Buffer {
Sehny's avatar
Sehny committed
172
        &self.buffers[index]
173 174 175 176 177
    }
}

impl IndexMut<usize> for History {
    fn index_mut(&mut self, index: usize) -> &mut Buffer {
Sehny's avatar
Sehny committed
178
        &mut self.buffers[index]
179
    }
Sehny's avatar
Sehny committed
180
}