1use super::{config_update_string_enum, prelude::*};
2use crate::{self as nu_protocol, ConfigWarning};
3use std::path::PathBuf;
4
5#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
6pub enum HistoryFileFormat {
7 Sqlite,
9 Plaintext,
11}
12
13impl HistoryFileFormat {
14 pub fn default_file_name(self) -> std::path::PathBuf {
15 match self {
16 HistoryFileFormat::Plaintext => "history.txt",
17 HistoryFileFormat::Sqlite => "history.sqlite3",
18 }
19 .into()
20 }
21}
22
23impl FromStr for HistoryFileFormat {
24 type Err = &'static str;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 match s.to_ascii_lowercase().as_str() {
28 "sqlite" => Ok(Self::Sqlite),
29 "plaintext" => Ok(Self::Plaintext),
30 #[cfg(feature = "sqlite")]
31 _ => Err("'sqlite' or 'plaintext'"),
32 #[cfg(not(feature = "sqlite"))]
33 _ => Err("'plaintext'"),
34 }
35 }
36}
37
38impl UpdateFromValue for HistoryFileFormat {
39 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
40 config_update_string_enum(self, value, path, errors);
41
42 #[cfg(not(feature = "sqlite"))]
43 if *self == HistoryFileFormat::Sqlite {
44 *self = HistoryFileFormat::Plaintext;
45 errors.warn(ConfigWarning::IncompatibleOptions {
46 label: "SQLite-based history file only supported with the `sqlite` feature, falling back to plain text history",
47 span: value.span(),
48 help: "Compile Nushell with `sqlite` feature enabled",
49 });
50 }
51 }
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
55pub enum HistoryPath {
56 Default,
57 Custom(PathBuf),
58 Disabled,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
62pub struct HistoryConfig {
63 pub max_size: i64,
64 pub sync_on_enter: bool,
65 pub file_format: HistoryFileFormat,
66 pub isolation: bool,
67 pub path: HistoryPath,
68 pub ignore_space_prefixed: bool,
69}
70
71impl IntoValue for HistoryPath {
72 fn into_value(self, span: Span) -> Value {
73 match self {
74 HistoryPath::Default => Value::string("", span),
75 HistoryPath::Disabled => Value::nothing(span),
76 HistoryPath::Custom(path) => Value::string(path.display().to_string(), span),
77 }
78 }
79}
80
81impl IntoValue for HistoryConfig {
82 fn into_value(self, span: Span) -> Value {
83 Value::record(
84 record! {
85 "max_size" => self.max_size.into_value(span),
86 "sync_on_enter" => self.sync_on_enter.into_value(span),
87 "file_format" => self.file_format.into_value(span),
88 "isolation" => self.isolation.into_value(span),
89 "path" => self.path.into_value(span),
90 "ignore_space_prefixed" => self.ignore_space_prefixed.into_value(span),
91 },
92 span,
93 )
94 }
95}
96
97impl HistoryConfig {
98 pub fn file_path(&self) -> Option<PathBuf> {
99 let path = match &self.path {
100 HistoryPath::Custom(path) => Some(path.clone()),
101 HistoryPath::Disabled => None,
102 HistoryPath::Default => nu_path::nu_config_dir().map(|mut history_path| {
103 history_path.push(self.file_format.default_file_name());
104 history_path.into()
105 }),
106 }?;
107
108 if path.is_dir() {
109 return Some(path.join(self.file_format.default_file_name()));
110 }
111
112 Some(path)
113 }
114}
115
116impl Default for HistoryConfig {
117 fn default() -> Self {
118 Self {
119 max_size: 100_000,
120 sync_on_enter: true,
121 file_format: HistoryFileFormat::Plaintext,
122 isolation: false,
123 path: HistoryPath::Default,
124 ignore_space_prefixed: true,
125 }
126 }
127}
128
129impl UpdateFromValue for HistoryConfig {
130 fn update<'a>(
131 &mut self,
132 value: &'a Value,
133 path: &mut ConfigPath<'a>,
134 errors: &mut ConfigErrors,
135 ) {
136 let Value::Record { val: record, .. } = value else {
137 errors.type_mismatch(path, Type::record(), value);
138 return;
139 };
140
141 let mut isolation_span = value.span();
144
145 for (col, val) in record.iter() {
146 let path = &mut path.push(col);
147 match col.as_str() {
148 "isolation" => {
149 isolation_span = val.span();
150 self.isolation.update(val, path, errors)
151 }
152 "sync_on_enter" => self.sync_on_enter.update(val, path, errors),
153 "max_size" => self.max_size.update(val, path, errors),
154 "file_format" => self.file_format.update(val, path, errors),
155 "path" => match val {
156 Value::String { val: s, .. } => {
157 if s.is_empty() {
158 self.path = HistoryPath::Default;
159 continue;
160 }
161
162 self.path = HistoryPath::Custom(PathBuf::from(s));
163 }
164 Value::Nothing { .. } => {
165 self.path = HistoryPath::Disabled;
166 }
167 _ => {
168 errors.type_mismatch(path, Type::custom("string or nothing"), val);
169 }
170 },
171 "ignore_space_prefixed" => self.ignore_space_prefixed.update(val, path, errors),
172 _ => errors.unknown_option(path, val),
173 }
174 }
175
176 match (self.isolation, self.file_format) {
178 (true, HistoryFileFormat::Plaintext) => {
179 errors.warn(ConfigWarning::IncompatibleOptions {
180 label: "history isolation only compatible with SQLite format",
181 span: isolation_span,
182 help: r#"disable history isolation, or set $env.config.history.file_format = "sqlite""#,
183 });
184 }
185 (true, HistoryFileFormat::Sqlite) => (),
186 (false, _) => (),
187 }
188 }
189}