Skip to main content

nu_protocol/config/
mod.rs

1//! Module containing the internal representation of user configuration
2
3use crate::FromValue;
4use crate::{self as nu_protocol};
5use helper::*;
6use prelude::*;
7use std::collections::HashMap;
8
9pub use ansi_coloring::UseAnsiColoring;
10pub use clip::ClipConfig;
11pub use completions::{
12    CompletionAlgorithm, CompletionConfig, CompletionSort, ExternalCompleterConfig,
13};
14pub use datetime_format::DatetimeFormatConfig;
15pub use display_errors::DisplayErrors;
16pub use filesize::FilesizeConfig;
17pub use helper::extract_value;
18pub use hinter::HinterConfig;
19pub use history::{HistoryConfig, HistoryFileFormat, HistoryPath};
20pub use hooks::Hooks;
21pub use ls::LsConfig;
22pub use output::{BannerKind, ErrorStyle};
23pub use plugin_gc::{PluginGcConfig, PluginGcConfigs};
24pub use reedline::{CursorShapeConfig, EditBindings, NuCursorShape, ParsedKeybinding, ParsedMenu};
25pub use rm::RmConfig;
26pub use shell_integration::ShellIntegrationConfig;
27pub use table::{FooterMode, TableConfig, TableIndent, TableIndexMode, TableMode, TrimStrategy};
28
29mod ansi_coloring;
30mod clip;
31mod completions;
32mod datetime_format;
33mod display_errors;
34mod error;
35mod filesize;
36mod helper;
37mod hinter;
38mod history;
39mod hooks;
40mod ls;
41mod output;
42mod plugin_gc;
43mod prelude;
44mod reedline;
45mod rm;
46mod shell_integration;
47mod table;
48
49#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
50pub struct Config {
51    pub filesize: FilesizeConfig,
52    pub table: TableConfig,
53    pub ls: LsConfig,
54    pub clip: ClipConfig,
55    pub color_config: HashMap<String, Value>,
56    pub footer_mode: FooterMode,
57    pub float_precision: i64,
58    pub recursion_limit: i64,
59    pub use_ansi_coloring: UseAnsiColoring,
60    pub completions: CompletionConfig,
61    pub edit_mode: EditBindings,
62    pub show_hints: bool,
63    pub hinter: HinterConfig,
64    pub history: HistoryConfig,
65    pub keybindings: Vec<ParsedKeybinding>,
66    pub abbreviations: HashMap<String, String>,
67    pub menus: Vec<ParsedMenu>,
68    pub hooks: Hooks,
69    pub rm: RmConfig,
70    pub shell_integration: ShellIntegrationConfig,
71    pub buffer_editor: Value,
72    pub show_banner: BannerKind,
73    pub bracketed_paste: bool,
74    pub render_right_prompt_on_last_line: bool,
75    pub explore: HashMap<String, Value>,
76    pub cursor_shape: CursorShapeConfig,
77    pub datetime_format: DatetimeFormatConfig,
78    pub error_style: ErrorStyle,
79    pub error_lines: i64,
80    pub display_errors: DisplayErrors,
81    pub use_kitty_protocol: bool,
82    pub highlight_resolved_externals: bool,
83    pub auto_cd_implicit: bool,
84    /// Configuration for plugins.
85    ///
86    /// Users can provide configuration for a plugin through this entry.  The entry name must
87    /// match the registered plugin name so `plugin add nu_plugin_example` will be able to place
88    /// its configuration under a `nu_plugin_example` column.
89    pub plugins: HashMap<String, Value>,
90    /// Configuration for plugin garbage collection.
91    pub plugin_gc: PluginGcConfigs,
92}
93
94impl Default for Config {
95    fn default() -> Config {
96        Config {
97            show_banner: BannerKind::default(),
98
99            table: TableConfig::default(),
100            rm: RmConfig::default(),
101            ls: LsConfig::default(),
102
103            datetime_format: DatetimeFormatConfig::default(),
104
105            explore: HashMap::new(),
106
107            history: HistoryConfig::default(),
108
109            completions: CompletionConfig::default(),
110
111            recursion_limit: 50,
112
113            filesize: FilesizeConfig::default(),
114
115            cursor_shape: CursorShapeConfig::default(),
116
117            clip: ClipConfig::default(),
118
119            color_config: HashMap::new(),
120            footer_mode: FooterMode::RowCount(25),
121            float_precision: 2,
122            buffer_editor: Value::nothing(Span::unknown()),
123            use_ansi_coloring: UseAnsiColoring::default(),
124            bracketed_paste: true,
125            edit_mode: EditBindings::default(),
126            show_hints: true,
127            hinter: HinterConfig::default(),
128
129            shell_integration: ShellIntegrationConfig::default(),
130
131            render_right_prompt_on_last_line: false,
132
133            hooks: Hooks::new(),
134
135            menus: Vec::new(),
136
137            keybindings: Vec::new(),
138            abbreviations: HashMap::new(),
139
140            error_style: ErrorStyle::default(),
141            error_lines: 1,
142            display_errors: DisplayErrors::default(),
143
144            use_kitty_protocol: false,
145            highlight_resolved_externals: false,
146
147            auto_cd_implicit: false,
148
149            plugins: HashMap::new(),
150            plugin_gc: PluginGcConfigs::default(),
151        }
152    }
153}
154
155impl UpdateFromValue for Config {
156    fn update<'a>(
157        &mut self,
158        value: &'a Value,
159        path: &mut ConfigPath<'a>,
160        errors: &mut ConfigErrors,
161    ) {
162        let Value::Record { val: record, .. } = value else {
163            errors.type_mismatch(path, Type::record(), value);
164            return;
165        };
166
167        for (col, val) in record.iter() {
168            let path = &mut path.push(col);
169            match col.as_str() {
170                "ls" => self.ls.update(val, path, errors),
171                "rm" => self.rm.update(val, path, errors),
172                "history" => self.history.update(val, path, errors),
173                "completions" => self.completions.update(val, path, errors),
174                "cursor_shape" => self.cursor_shape.update(val, path, errors),
175                "table" => self.table.update(val, path, errors),
176                "filesize" => self.filesize.update(val, path, errors),
177                "explore" => self.explore.update(val, path, errors),
178                "color_config" => self.color_config.update(val, path, errors),
179                "clip" => self.clip.update(val, path, errors),
180                "footer_mode" => self.footer_mode.update(val, path, errors),
181                "float_precision" => self.float_precision.update(val, path, errors),
182                "use_ansi_coloring" => self.use_ansi_coloring.update(val, path, errors),
183                "edit_mode" => self.edit_mode.update(val, path, errors),
184                "show_hints" => self.show_hints.update(val, path, errors),
185                "hinter" => self.hinter.update(val, path, errors),
186                "shell_integration" => self.shell_integration.update(val, path, errors),
187                "buffer_editor" => match val {
188                    Value::Nothing { .. } | Value::String { .. } => {
189                        self.buffer_editor = val.clone();
190                    }
191                    Value::List { vals, .. }
192                        if vals.iter().all(|val| matches!(val, Value::String { .. })) =>
193                    {
194                        self.buffer_editor = val.clone();
195                    }
196                    _ => errors.type_mismatch(
197                        path,
198                        Type::custom("string, list<string>, or nothing"),
199                        val,
200                    ),
201                },
202                "show_banner" => self.show_banner.update(val, path, errors),
203                "display_errors" => self.display_errors.update(val, path, errors),
204                "render_right_prompt_on_last_line" => self
205                    .render_right_prompt_on_last_line
206                    .update(val, path, errors),
207                "bracketed_paste" => self.bracketed_paste.update(val, path, errors),
208                "use_kitty_protocol" => self.use_kitty_protocol.update(val, path, errors),
209                "highlight_resolved_externals" => {
210                    self.highlight_resolved_externals.update(val, path, errors)
211                }
212                "auto_cd_implicit" => self.auto_cd_implicit.update(val, path, errors),
213                "plugins" => self.plugins.update(val, path, errors),
214                "plugin_gc" => self.plugin_gc.update(val, path, errors),
215                "menus" => match Vec::from_value(val.clone()) {
216                    Ok(menus) => self.menus = menus,
217                    Err(err) => errors.error(err.into()),
218                },
219                "keybindings" => match Vec::from_value(val.clone()) {
220                    Ok(keybindings) => self.keybindings = keybindings,
221                    Err(err) => errors.error(err.into()),
222                },
223                "abbreviations" => self.abbreviations.update(val, path, errors),
224                "hooks" => self.hooks.update(val, path, errors),
225                "datetime_format" => self.datetime_format.update(val, path, errors),
226                "error_style" => self.error_style.update(val, path, errors),
227                "error_lines" => {
228                    if let Ok(lines) = val.as_int() {
229                        if lines >= 0 {
230                            self.error_lines = lines;
231                        } else {
232                            errors.invalid_value(path, "an int greater than or equal to 0", val);
233                        }
234                    } else {
235                        errors.type_mismatch(path, Type::Int, val);
236                    }
237                }
238                "recursion_limit" => {
239                    if let Ok(limit) = val.as_int() {
240                        if limit > 1 {
241                            self.recursion_limit = limit;
242                        } else {
243                            errors.invalid_value(path, "an int greater than 1", val);
244                        }
245                    } else {
246                        errors.type_mismatch(path, Type::Int, val);
247                    }
248                }
249                _ => errors.unknown_option(path, val),
250            }
251        }
252    }
253}
254
255impl Config {
256    pub fn update_from_value(
257        &mut self,
258        old: &Config,
259        value: &Value,
260    ) -> Result<Option<ShellWarning>, ShellError> {
261        self.update_from_value_with_options(old, value, false)
262    }
263
264    /// Like [`Config::update_from_value`], but allows callers to indicate that runtime-locked
265    /// options should refuse to change.
266    ///
267    /// `history_locked_after_startup` should be set to `true` once the REPL has finished
268    /// initializing reedline's history backend. After that point, changing any of the
269    /// startup-only history fields (`path`, `max_size`, `file_format`, `isolation`) has no
270    /// effect on the live history, so we reject the assignment with a clear error instead of
271    /// silently ignoring it.
272    pub fn update_from_value_with_options(
273        &mut self,
274        old: &Config,
275        value: &Value,
276        history_locked_after_startup: bool,
277    ) -> Result<Option<ShellWarning>, ShellError> {
278        // Current behaviour is that config errors are displayed, but do not prevent the rest
279        // of the config from being updated (fields with errors are skipped/not updated).
280        // Errors are simply collected one-by-one and wrapped into a ShellError variant at the end.
281        let mut errors =
282            ConfigErrors::new(old).with_history_locked_after_startup(history_locked_after_startup);
283        let mut path = ConfigPath::new();
284
285        self.update(value, &mut path, &mut errors);
286
287        errors.check()
288    }
289}