Skip to main content

brush_core/shell/
history.rs

1//! History management for shells.
2
3use std::path::PathBuf;
4
5use crate::{error, openfiles};
6
7impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
8    pub(super) fn load_history(&self) -> Result<Option<crate::history::History>, error::Error> {
9        const MAX_FILE_SIZE_FOR_HISTORY_IMPORT: u64 = 1024 * 1024 * 1024; // 1 GiB
10
11        let Some(history_path) = self.history_file_path() else {
12            return Ok(None);
13        };
14
15        let mut options = std::fs::File::options();
16        options.read(true);
17
18        let mut history_file =
19            self.open_file(&options, history_path, &self.default_exec_params())?;
20
21        // Check on the file's size.
22        if let openfiles::OpenFile::File(file) = &mut history_file {
23            let file_metadata = file.metadata()?;
24            let file_size = file_metadata.len();
25
26            // If the file is empty, no reason to try reading it. Note that this will also
27            // end up excluding non-regular files that report a 0 file size but appear
28            // to have contents when read.
29            if file_size == 0 {
30                return Ok(None);
31            }
32
33            // Bail if the file is unrealistically large. For now we just refuse to import it.
34            if file_size > MAX_FILE_SIZE_FOR_HISTORY_IMPORT {
35                return Err(error::ErrorKind::HistoryFileTooLargeToImport.into());
36            }
37        }
38
39        Ok(Some(crate::history::History::import(history_file)?))
40    }
41
42    /// Returns the path to the history file used by the shell, if one is set.
43    pub fn history_file_path(&self) -> Option<PathBuf> {
44        self.env_str("HISTFILE")
45            .map(|s| PathBuf::from(s.into_owned()))
46    }
47
48    /// Returns the path to the history file used by the shell, if one is set.
49    pub fn history_time_format(&self) -> Option<String> {
50        self.env_str("HISTTIMEFORMAT").map(|s| s.into_owned())
51    }
52
53    /// Saves history back to any backing storage.
54    pub fn save_history(&mut self) -> Result<(), error::Error> {
55        if let Some(history_file_path) = self.history_file_path()
56            && let Some(history) = &mut self.history
57        {
58            // See if there's *any* time format configured. That triggers writing out
59            // timestamps.
60            let write_timestamps = self.env.is_set("HISTTIMEFORMAT");
61
62            // TODO(history): Observe options.append_to_history_file
63            history.flush(
64                history_file_path,
65                true, /* append? */
66                true, /* unsaved items only? */
67                write_timestamps,
68            )?;
69        }
70
71        Ok(())
72    }
73
74    /// Adds a command to history.
75    pub fn add_to_history(&mut self, command: &str) -> Result<(), error::Error> {
76        if let Some(history) = &mut self.history {
77            // Trim.
78            let command = command.trim();
79
80            // For now, discard empty commands.
81            if command.is_empty() {
82                return Ok(());
83            }
84
85            // Add it to history.
86            history.add(crate::history::Item {
87                id: 0,
88                command_line: command.to_owned(),
89                timestamp: Some(chrono::Utc::now()),
90                dirty: true,
91            })?;
92        }
93
94        Ok(())
95    }
96}