nu_protocol/config/
history.rs

1use super::{config_update_string_enum, prelude::*};
2use crate::{self as nu_protocol, ConfigWarning};
3
4#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
5pub enum HistoryFileFormat {
6    /// Store history as an SQLite database with additional context
7    Sqlite,
8    /// store history as a plain text file where every line is one command (without any context such as timestamps)
9    Plaintext,
10}
11
12impl HistoryFileFormat {
13    pub fn default_file_name(self) -> std::path::PathBuf {
14        match self {
15            HistoryFileFormat::Plaintext => "history.txt",
16            HistoryFileFormat::Sqlite => "history.sqlite3",
17        }
18        .into()
19    }
20}
21
22impl FromStr for HistoryFileFormat {
23    type Err = &'static str;
24
25    fn from_str(s: &str) -> Result<Self, Self::Err> {
26        match s.to_ascii_lowercase().as_str() {
27            "sqlite" => Ok(Self::Sqlite),
28            "plaintext" => Ok(Self::Plaintext),
29            #[cfg(feature = "sqlite")]
30            _ => Err("'sqlite' or 'plaintext'"),
31            #[cfg(not(feature = "sqlite"))]
32            _ => Err("'plaintext'"),
33        }
34    }
35}
36
37impl UpdateFromValue for HistoryFileFormat {
38    fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
39        config_update_string_enum(self, value, path, errors);
40
41        #[cfg(not(feature = "sqlite"))]
42        if *self == HistoryFileFormat::Sqlite {
43            *self = HistoryFileFormat::Plaintext;
44            errors.warn(ConfigWarning::IncompatibleOptions {
45                label: "SQLite-based history file only supported with the `sqlite` feature, falling back to plain text history", 
46                span: value.span(),
47                help: "Compile Nushell with `sqlite` feature enabled",
48            });
49        }
50    }
51}
52
53#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
54pub struct HistoryConfig {
55    pub max_size: i64,
56    pub sync_on_enter: bool,
57    pub file_format: HistoryFileFormat,
58    pub isolation: bool,
59}
60
61impl HistoryConfig {
62    pub fn file_path(&self) -> Option<std::path::PathBuf> {
63        nu_path::nu_config_dir().map(|mut history_path| {
64            history_path.push(self.file_format.default_file_name());
65            history_path.into()
66        })
67    }
68}
69
70impl Default for HistoryConfig {
71    fn default() -> Self {
72        Self {
73            max_size: 100_000,
74            sync_on_enter: true,
75            file_format: HistoryFileFormat::Plaintext,
76            isolation: false,
77        }
78    }
79}
80
81impl UpdateFromValue for HistoryConfig {
82    fn update<'a>(
83        &mut self,
84        value: &'a Value,
85        path: &mut ConfigPath<'a>,
86        errors: &mut ConfigErrors,
87    ) {
88        let Value::Record { val: record, .. } = value else {
89            errors.type_mismatch(path, Type::record(), value);
90            return;
91        };
92
93        // might not be correct if file format was changed away from sqlite rather than isolation,
94        // but this is an edge case and the span of the relevant value here should be close enough
95        let mut isolation_span = value.span();
96
97        for (col, val) in record.iter() {
98            let path = &mut path.push(col);
99            match col.as_str() {
100                "isolation" => {
101                    isolation_span = val.span();
102                    self.isolation.update(val, path, errors)
103                }
104                "sync_on_enter" => self.sync_on_enter.update(val, path, errors),
105                "max_size" => self.max_size.update(val, path, errors),
106                "file_format" => self.file_format.update(val, path, errors),
107                _ => errors.unknown_option(path, val),
108            }
109        }
110
111        // Listing all formats separately in case additional ones are added
112        match (self.isolation, self.file_format) {
113            (true, HistoryFileFormat::Plaintext) => {
114                errors.warn(ConfigWarning::IncompatibleOptions {
115                    label: "history isolation only compatible with SQLite format",
116                    span: isolation_span,
117                    help: r#"disable history isolation, or set $env.config.history.file_format = "sqlite""#,
118                });
119            }
120            (true, HistoryFileFormat::Sqlite) => (),
121            (false, _) => (),
122        }
123    }
124}