shrub_core/shell/
history.rs1use std::collections::BTreeSet;
4use std::path::PathBuf;
5
6use crate::history::HistoryControl;
7use crate::{error, openfiles};
8
9impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
10 pub(super) fn load_history(&self) -> Result<Option<crate::history::History>, error::Error> {
11 const MAX_FILE_SIZE_FOR_HISTORY_IMPORT: u64 = 1024 * 1024 * 1024; let Some(history_path) = self.history_file_path() else {
14 return Ok(None);
15 };
16
17 let mut options = std::fs::File::options();
18 options.read(true);
19
20 let mut history_file =
21 self.open_file(&options, history_path, &self.default_exec_params())?;
22
23 if let openfiles::OpenFile::File(file) = &mut history_file {
25 let file_metadata = file.metadata()?;
26 let file_size = file_metadata.len();
27
28 if file_size == 0 {
33 return Ok(None);
34 }
35
36 if file_size > MAX_FILE_SIZE_FOR_HISTORY_IMPORT {
39 return Err(error::ErrorKind::HistoryFileTooLargeToImport.into());
40 }
41 }
42
43 let history = crate::history::History::import(history_file)?;
44 Ok(Some(history))
45 }
46
47 pub fn history_file_path(&self) -> Option<PathBuf> {
49 self.env_str("HISTFILE")
50 .filter(|s| !s.is_empty())
51 .map(|s| PathBuf::from(s.into_owned()))
52 }
53
54 pub fn history_time_format(&self) -> Option<String> {
56 self.env_str("HISTTIMEFORMAT").map(|s| s.into_owned())
57 }
58
59 pub fn save_history(&mut self) -> Result<(), error::Error> {
66 if let Some(history_file_path) = self.history_file_path()
67 && let Some(history) = &mut self.history
68 {
69 let write_timestamps = self.env.is_set("HISTTIMEFORMAT");
72
73 history.flush(
75 history_file_path,
76 true, true, write_timestamps,
79 )?;
80 }
81
82 Ok(())
83 }
84
85 pub fn add_to_history(&mut self, command: &str) -> Result<(), error::Error> {
99 let controls = if self.options.interactive {
100 self.env_str("HISTCONTROL")
101 .map(|value| HistoryControl::parse_list(&value))
102 .unwrap_or_default()
103 } else {
104 BTreeSet::new()
105 };
106
107 let Some(history) = &mut self.history else {
108 return Ok(());
109 };
110
111 if controls.contains(&HistoryControl::IgnoreSpace) && command.starts_with(' ') {
112 return Ok(());
113 }
114
115 if command.trim().is_empty() {
118 return Ok(());
119 }
120
121 if controls.contains(&HistoryControl::IgnoreDups)
122 && history
123 .last()
124 .is_some_and(|last| last.command_line == command)
125 {
126 return Ok(());
127 }
128 if controls.contains(&HistoryControl::EraseDups) {
129 history.retain(|item| item.command_line != command);
130 }
131
132 history.add(crate::history::Item {
133 id: 0,
134 command_line: command.to_owned(),
135 timestamp: Some(chrono::Utc::now()),
136 dirty: true,
137 })?;
138
139 Ok(())
140 }
141}