mirror of
https://github.com/nushell/nushell.git
synced 2025-05-29 19:11:18 +00:00
# Description This PR adds back the functionality to auto-expand tables based on the terminal width, using the logic that if the terminal is over 100 columns to expand. This sets the default config value in both the Rust and the default nushell config. To do so, it also adds back the ability for hooks to be strings of code and not just code blocks. Fixed a couple tests: two which assumed that the builtin display hook didn't use a table -e, and one that assumed a hook couldn't be a string. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass - `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
1564 lines
76 KiB
Rust
1564 lines
76 KiB
Rust
use crate::{ShellError, Span, Value};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
const TRIM_STRATEGY_DEFAULT: TrimStrategy = TrimStrategy::Wrap {
|
|
try_to_keep_words: true,
|
|
};
|
|
|
|
/// Definition of a parsed keybinding from the config object
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub struct ParsedKeybinding {
|
|
pub modifier: Value,
|
|
pub keycode: Value,
|
|
pub event: Value,
|
|
pub mode: Value,
|
|
}
|
|
|
|
/// Definition of a parsed menu from the config object
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub struct ParsedMenu {
|
|
pub name: Value,
|
|
pub marker: Value,
|
|
pub only_buffer_difference: Value,
|
|
pub style: Value,
|
|
pub menu_type: Value,
|
|
pub source: Value,
|
|
}
|
|
|
|
/// Definition of a parsed menu from the config object
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub struct Hooks {
|
|
pub pre_prompt: Option<Value>,
|
|
pub pre_execution: Option<Value>,
|
|
pub env_change: Option<Value>,
|
|
pub display_output: Option<Value>,
|
|
pub command_not_found: Option<Value>,
|
|
}
|
|
|
|
impl Hooks {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
pre_prompt: None,
|
|
pre_execution: None,
|
|
env_change: None,
|
|
display_output: Some(Value::string(
|
|
"if (term size).columns >= 100 { table -e } else { table }",
|
|
Span::unknown(),
|
|
)),
|
|
command_not_found: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Hooks {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Definition of a Nushell CursorShape (to be mapped to crossterm::cursor::CursorShape)
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
|
|
pub enum NuCursorShape {
|
|
UnderScore,
|
|
Line,
|
|
Block,
|
|
BlinkUnderScore,
|
|
BlinkLine,
|
|
BlinkBlock,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub struct Config {
|
|
pub external_completer: Option<usize>,
|
|
pub filesize_metric: bool,
|
|
pub table_mode: String,
|
|
pub table_move_header: bool,
|
|
pub table_show_empty: bool,
|
|
pub use_ls_colors: bool,
|
|
pub color_config: HashMap<String, Value>,
|
|
pub use_grid_icons: bool,
|
|
pub footer_mode: FooterMode,
|
|
pub float_precision: i64,
|
|
pub max_external_completion_results: i64,
|
|
pub filesize_format: String,
|
|
pub use_ansi_coloring: bool,
|
|
pub quick_completions: bool,
|
|
pub partial_completions: bool,
|
|
pub completion_algorithm: String,
|
|
pub edit_mode: String,
|
|
pub max_history_size: i64,
|
|
pub sync_history_on_enter: bool,
|
|
pub history_file_format: HistoryFileFormat,
|
|
pub history_isolation: bool,
|
|
pub keybindings: Vec<ParsedKeybinding>,
|
|
pub menus: Vec<ParsedMenu>,
|
|
pub hooks: Hooks,
|
|
pub rm_always_trash: bool,
|
|
pub shell_integration: bool,
|
|
pub buffer_editor: String,
|
|
pub table_index_mode: TableIndexMode,
|
|
pub cd_with_abbreviations: bool,
|
|
pub case_sensitive_completions: bool,
|
|
pub enable_external_completion: bool,
|
|
pub trim_strategy: TrimStrategy,
|
|
pub show_banner: bool,
|
|
pub bracketed_paste: bool,
|
|
pub show_clickable_links_in_ls: bool,
|
|
pub render_right_prompt_on_last_line: bool,
|
|
pub explore: HashMap<String, Value>,
|
|
pub cursor_shape_vi_insert: NuCursorShape,
|
|
pub cursor_shape_vi_normal: NuCursorShape,
|
|
pub cursor_shape_emacs: NuCursorShape,
|
|
pub datetime_normal_format: Option<String>,
|
|
pub datetime_table_format: Option<String>,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Config {
|
|
Config {
|
|
show_banner: true,
|
|
|
|
use_ls_colors: true,
|
|
show_clickable_links_in_ls: true,
|
|
|
|
rm_always_trash: false,
|
|
|
|
cd_with_abbreviations: false,
|
|
|
|
table_mode: "rounded".into(),
|
|
table_index_mode: TableIndexMode::Always,
|
|
table_show_empty: true,
|
|
trim_strategy: TRIM_STRATEGY_DEFAULT,
|
|
table_move_header: false,
|
|
|
|
datetime_normal_format: None,
|
|
datetime_table_format: None,
|
|
|
|
explore: HashMap::new(),
|
|
|
|
max_history_size: 100_000,
|
|
sync_history_on_enter: true,
|
|
history_file_format: HistoryFileFormat::PlainText,
|
|
history_isolation: false,
|
|
|
|
case_sensitive_completions: false,
|
|
quick_completions: true,
|
|
partial_completions: true,
|
|
completion_algorithm: "prefix".into(),
|
|
enable_external_completion: true,
|
|
max_external_completion_results: 100,
|
|
external_completer: None,
|
|
|
|
filesize_metric: false,
|
|
filesize_format: "auto".into(),
|
|
|
|
cursor_shape_emacs: NuCursorShape::Line,
|
|
cursor_shape_vi_insert: NuCursorShape::Block,
|
|
cursor_shape_vi_normal: NuCursorShape::UnderScore,
|
|
|
|
color_config: HashMap::new(),
|
|
use_grid_icons: true,
|
|
footer_mode: FooterMode::RowCount(25),
|
|
float_precision: 2,
|
|
buffer_editor: String::new(),
|
|
use_ansi_coloring: true,
|
|
bracketed_paste: true,
|
|
edit_mode: "emacs".into(),
|
|
shell_integration: false,
|
|
render_right_prompt_on_last_line: false,
|
|
|
|
hooks: Hooks::new(),
|
|
|
|
menus: Vec::new(),
|
|
|
|
keybindings: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub enum FooterMode {
|
|
/// Never show the footer
|
|
Never,
|
|
/// Always show the footer
|
|
Always,
|
|
/// Only show the footer if there are more than RowCount rows
|
|
RowCount(u64),
|
|
/// Calculate the screen height, calculate row count, if display will be bigger than screen, add the footer
|
|
Auto,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
|
|
pub enum HistoryFileFormat {
|
|
/// Store history as an SQLite database with additional context
|
|
Sqlite,
|
|
/// store history as a plain text file where every line is one command (without any context such as timestamps)
|
|
PlainText,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub enum TableIndexMode {
|
|
/// Always show indexes
|
|
Always,
|
|
/// Never show indexes
|
|
Never,
|
|
/// Show indexes when a table has "index" column
|
|
Auto,
|
|
}
|
|
|
|
/// A Table view configuration, for a situation where
|
|
/// we need to limit cell width in order to adjust for a terminal size.
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub enum TrimStrategy {
|
|
/// Wrapping strategy.
|
|
///
|
|
/// It it's similar to original nu_table, strategy.
|
|
Wrap {
|
|
/// A flag which indicates whether is it necessary to try
|
|
/// to keep word boundaries.
|
|
try_to_keep_words: bool,
|
|
},
|
|
/// Truncating strategy, where we just cut the string.
|
|
/// And append the suffix if applicable.
|
|
Truncate {
|
|
/// Suffix which can be appended to a truncated string after being cut.
|
|
///
|
|
/// It will be applied only when there's enough room for it.
|
|
/// For example in case where a cell width must be 12 chars, but
|
|
/// the suffix takes 13 chars it won't be used.
|
|
suffix: Option<String>,
|
|
},
|
|
}
|
|
|
|
impl TrimStrategy {
|
|
pub fn wrap(dont_split_words: bool) -> Self {
|
|
Self::Wrap {
|
|
try_to_keep_words: dont_split_words,
|
|
}
|
|
}
|
|
|
|
pub fn truncate(suffix: Option<String>) -> Self {
|
|
Self::Truncate { suffix }
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
pub fn into_config(&mut self, config: &Config) -> (Config, Option<ShellError>) {
|
|
// Clone the passed-in config rather than mutating it.
|
|
let mut config = config.clone();
|
|
|
|
// Vec for storing errors.
|
|
// Current Nushell behaviour (Dec 2022) is that having some typo like "always_trash": tru in your config.nu's
|
|
// set-env config record shouldn't abort all config parsing there and then.
|
|
// Thus, errors are simply collected one-by-one and wrapped in a GenericError at the end.
|
|
let mut errors = vec![];
|
|
|
|
// When an unsupported config value is found, ignore it.
|
|
macro_rules! invalid {
|
|
($span:expr, $msg:literal) => {
|
|
errors.push(ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
format!($msg),
|
|
$span,
|
|
Some("This value will be ignored.".into()),
|
|
vec![],
|
|
));
|
|
};
|
|
}
|
|
// Some extra helpers
|
|
macro_rules! try_bool {
|
|
($cols:ident, $vals:ident, $index:ident, $span:expr, $setting:ident) => {
|
|
if let Ok(b) = &$vals[$index].as_bool() {
|
|
config.$setting = *b;
|
|
} else {
|
|
invalid!(Some($span), "should be a bool");
|
|
// Reconstruct
|
|
$vals[$index] = Value::bool(config.$setting, $span);
|
|
}
|
|
};
|
|
}
|
|
macro_rules! try_int {
|
|
($cols:ident, $vals:ident, $index:ident, $span:expr, $setting:ident) => {
|
|
if let Ok(b) = &$vals[$index].as_int() {
|
|
config.$setting = *b;
|
|
} else {
|
|
invalid!(Some($span), "should be an int");
|
|
// Reconstruct
|
|
$vals[$index] = Value::int(config.$setting, $span);
|
|
}
|
|
};
|
|
}
|
|
// When an unsupported config value is found, remove it from this record.
|
|
macro_rules! invalid_key {
|
|
// Because Value::Record discards all of the spans of its
|
|
// column names (by storing them as Strings), the key name cannot be provided
|
|
// as a value, even in key errors.
|
|
($cols:ident, $vals:ident, $index:ident, $span:expr, $msg:literal) => {
|
|
errors.push(ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
format!($msg),
|
|
$span,
|
|
Some("This value will not appear in your $env.config record.".into()),
|
|
vec![],
|
|
));
|
|
$cols.remove($index);
|
|
$vals.remove($index);
|
|
};
|
|
}
|
|
|
|
// Config record (self) mutation rules:
|
|
// * When parsing a config Record, if a config key error occurs, remove the key.
|
|
// * When parsing a config Record, if a config value error occurs, replace the value
|
|
// with a reconstructed Nu value for the current (unaltered) configuration for that setting.
|
|
// For instance:
|
|
// $env.config.ls.use_ls_colors = 2 results in an error, so
|
|
// the current use_ls_colors config setting is converted to a Value::Boolean and inserted in the
|
|
// record in place of the 2.
|
|
if let Value::Record { cols, vals, span } = self {
|
|
let span = *span;
|
|
// Because this whole algorithm removes while iterating, this must iterate in reverse.
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key = cols[index].as_str();
|
|
match key {
|
|
// Grouped options
|
|
"ls" => {
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"use_ls_colors" => {
|
|
try_bool!(cols, vals, index, span, use_ls_colors)
|
|
}
|
|
"clickable_links" => try_bool!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
span,
|
|
show_clickable_links_in_ls
|
|
),
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec!["use_ls_colors".into(), "clickable_links".into()],
|
|
vec![
|
|
Value::bool(config.use_ls_colors, span),
|
|
Value::bool(config.show_clickable_links_in_ls, span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"cd" => {
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"abbreviations" => {
|
|
try_bool!(cols, vals, index, *span, cd_with_abbreviations)
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec!["use_ls_colors".into(), "clickable_links".into()],
|
|
vec![
|
|
Value::bool(config.use_ls_colors, span),
|
|
Value::bool(config.show_clickable_links_in_ls, span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"rm" => {
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"always_trash" => {
|
|
try_bool!(cols, vals, index, *span, rm_always_trash)
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec!["always_trash".into()],
|
|
vec![Value::bool(config.rm_always_trash, span)],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"history" => {
|
|
macro_rules! reconstruct_history_file_format {
|
|
($span:expr) => {
|
|
Value::string(
|
|
match config.history_file_format {
|
|
HistoryFileFormat::Sqlite => "sqlite",
|
|
HistoryFileFormat::PlainText => "plaintext",
|
|
},
|
|
$span,
|
|
)
|
|
};
|
|
}
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"isolation" => {
|
|
try_bool!(cols, vals, index, span, history_isolation)
|
|
}
|
|
"sync_on_enter" => {
|
|
try_bool!(cols, vals, index, span, sync_history_on_enter)
|
|
}
|
|
"max_size" => {
|
|
try_int!(cols, vals, index, span, max_history_size)
|
|
}
|
|
"file_format" => {
|
|
if let Ok(v) = value.as_string() {
|
|
let val_str = v.to_lowercase();
|
|
match val_str.as_ref() {
|
|
"sqlite" => {
|
|
config.history_file_format =
|
|
HistoryFileFormat::Sqlite
|
|
}
|
|
"plaintext" => {
|
|
config.history_file_format =
|
|
HistoryFileFormat::PlainText
|
|
}
|
|
_ => {
|
|
invalid!(Some(span),
|
|
"unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'sqlite' or 'plaintext'"
|
|
);
|
|
// Reconstruct
|
|
vals[index] =
|
|
reconstruct_history_file_format!(span);
|
|
}
|
|
};
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = reconstruct_history_file_format!(span);
|
|
}
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec![
|
|
"sync_on_enter".into(),
|
|
"max_size".into(),
|
|
"file_format".into(),
|
|
"isolation".into(),
|
|
],
|
|
vec![
|
|
Value::bool(config.sync_history_on_enter, span),
|
|
Value::int(config.max_history_size, span),
|
|
reconstruct_history_file_format!(span),
|
|
Value::bool(config.history_isolation, span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"completions" => {
|
|
macro_rules! reconstruct_external_completer {
|
|
($span: expr) => {
|
|
if let Some(block) = config.external_completer {
|
|
Value::Block {
|
|
val: block,
|
|
span: $span,
|
|
}
|
|
} else {
|
|
Value::Nothing { span: $span }
|
|
}
|
|
};
|
|
}
|
|
macro_rules! reconstruct_external {
|
|
($span: expr) => {
|
|
Value::record(
|
|
vec!["max_results".into(), "completer".into(), "enable".into()],
|
|
vec![
|
|
Value::int(config.max_external_completion_results, $span),
|
|
reconstruct_external_completer!($span),
|
|
Value::bool(config.enable_external_completion, $span),
|
|
],
|
|
$span,
|
|
)
|
|
};
|
|
}
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"quick" => {
|
|
try_bool!(cols, vals, index, span, quick_completions)
|
|
}
|
|
"partial" => {
|
|
try_bool!(cols, vals, index, span, partial_completions)
|
|
}
|
|
"algorithm" => {
|
|
if let Ok(v) = value.as_string() {
|
|
let val_str = v.to_lowercase();
|
|
match val_str.as_ref() {
|
|
// This should match the MatchAlgorithm enum in completions::completion_options
|
|
"prefix" | "fuzzy" => {
|
|
config.completion_algorithm = val_str
|
|
}
|
|
_ => {
|
|
invalid!( Some(span),
|
|
"unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'prefix' or 'fuzzy'"
|
|
);
|
|
// Reconstruct
|
|
vals[index] = Value::string(
|
|
config.completion_algorithm.clone(),
|
|
span,
|
|
);
|
|
}
|
|
};
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = Value::string(
|
|
config.completion_algorithm.clone(),
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"case_sensitive" => {
|
|
try_bool!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
span,
|
|
case_sensitive_completions
|
|
)
|
|
}
|
|
"external" => {
|
|
if let Value::Record { cols, vals, span } = &mut vals[index]
|
|
{
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key3 = cols[index].as_str();
|
|
match key3 {
|
|
"max_results" => {
|
|
try_int!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
span,
|
|
max_external_completion_results
|
|
)
|
|
}
|
|
"completer" => {
|
|
if let Ok(v) = value.as_block() {
|
|
config.external_completer = Some(v)
|
|
} else {
|
|
match value {
|
|
Value::Nothing { .. } => {}
|
|
_ => {
|
|
invalid!(
|
|
Some(span),
|
|
"should be a block or null"
|
|
);
|
|
// Reconstruct
|
|
vals[index] = reconstruct_external_completer!(
|
|
span
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"enable" => {
|
|
try_bool!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
span,
|
|
enable_external_completion
|
|
)
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{key2}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(Some(span), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = reconstruct_external!(span);
|
|
}
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct record
|
|
vals[index] = Value::record(
|
|
vec![
|
|
"quick".into(),
|
|
"partial".into(),
|
|
"algorithm".into(),
|
|
"case_sensitive".into(),
|
|
"external".into(),
|
|
],
|
|
vec![
|
|
Value::bool(config.quick_completions, span),
|
|
Value::bool(config.partial_completions, span),
|
|
Value::string(config.completion_algorithm.clone(), span),
|
|
Value::bool(config.case_sensitive_completions, span),
|
|
reconstruct_external!(span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"cursor_shape" => {
|
|
macro_rules! reconstruct_cursor_shape {
|
|
($name:expr, $span:expr) => {
|
|
Value::string(
|
|
match $name {
|
|
NuCursorShape::Line => "line",
|
|
NuCursorShape::Block => "block",
|
|
NuCursorShape::UnderScore => "underscore",
|
|
NuCursorShape::BlinkLine => "blink_line",
|
|
NuCursorShape::BlinkBlock => "blink_block",
|
|
NuCursorShape::BlinkUnderScore => "blink_underscore",
|
|
},
|
|
$span,
|
|
)
|
|
};
|
|
}
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"vi_insert" => {
|
|
if let Ok(v) = value.as_string() {
|
|
let val_str = v.to_lowercase();
|
|
match val_str.as_ref() {
|
|
"line" => {
|
|
config.cursor_shape_vi_insert =
|
|
NuCursorShape::Line;
|
|
}
|
|
"block" => {
|
|
config.cursor_shape_vi_insert =
|
|
NuCursorShape::Block;
|
|
}
|
|
"underscore" => {
|
|
config.cursor_shape_vi_insert =
|
|
NuCursorShape::UnderScore;
|
|
}
|
|
"blink_line" => {
|
|
config.cursor_shape_vi_insert =
|
|
NuCursorShape::BlinkLine;
|
|
}
|
|
"blink_block" => {
|
|
config.cursor_shape_vi_insert =
|
|
NuCursorShape::BlinkBlock;
|
|
}
|
|
"blink_underscore" => {
|
|
config.cursor_shape_vi_insert =
|
|
NuCursorShape::BlinkUnderScore;
|
|
}
|
|
_ => {
|
|
invalid!(Some(span),
|
|
"unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'line', 'block', 'underscore', 'blink_line', 'blink_block', or 'blink_underscore'"
|
|
);
|
|
// Reconstruct
|
|
vals[index] = reconstruct_cursor_shape!(
|
|
config.cursor_shape_vi_insert,
|
|
span
|
|
);
|
|
}
|
|
};
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = reconstruct_cursor_shape!(
|
|
config.cursor_shape_vi_insert,
|
|
span
|
|
);
|
|
}
|
|
}
|
|
"vi_normal" => {
|
|
if let Ok(v) = value.as_string() {
|
|
let val_str = v.to_lowercase();
|
|
match val_str.as_ref() {
|
|
"line" => {
|
|
config.cursor_shape_vi_normal =
|
|
NuCursorShape::Line;
|
|
}
|
|
"block" => {
|
|
config.cursor_shape_vi_normal =
|
|
NuCursorShape::Block;
|
|
}
|
|
"underscore" => {
|
|
config.cursor_shape_vi_normal =
|
|
NuCursorShape::UnderScore;
|
|
}
|
|
"blink_line" => {
|
|
config.cursor_shape_vi_normal =
|
|
NuCursorShape::BlinkLine;
|
|
}
|
|
"blink_block" => {
|
|
config.cursor_shape_vi_normal =
|
|
NuCursorShape::BlinkBlock;
|
|
}
|
|
"blink_underscore" => {
|
|
config.cursor_shape_vi_normal =
|
|
NuCursorShape::BlinkUnderScore;
|
|
}
|
|
_ => {
|
|
invalid!(Some(span),
|
|
"unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'line', 'block', 'underscore', 'blink_line', 'blink_block', or 'blink_underscore'"
|
|
);
|
|
// Reconstruct
|
|
vals[index] = reconstruct_cursor_shape!(
|
|
config.cursor_shape_vi_normal,
|
|
span
|
|
);
|
|
}
|
|
};
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = reconstruct_cursor_shape!(
|
|
config.cursor_shape_vi_normal,
|
|
span
|
|
);
|
|
}
|
|
}
|
|
"emacs" => {
|
|
if let Ok(v) = value.as_string() {
|
|
let val_str = v.to_lowercase();
|
|
match val_str.as_ref() {
|
|
"line" => {
|
|
config.cursor_shape_emacs = NuCursorShape::Line;
|
|
}
|
|
"block" => {
|
|
config.cursor_shape_emacs =
|
|
NuCursorShape::Block;
|
|
}
|
|
"underscore" => {
|
|
config.cursor_shape_emacs =
|
|
NuCursorShape::UnderScore;
|
|
}
|
|
"blink_line" => {
|
|
config.cursor_shape_emacs =
|
|
NuCursorShape::BlinkLine;
|
|
}
|
|
"blink_block" => {
|
|
config.cursor_shape_emacs =
|
|
NuCursorShape::BlinkBlock;
|
|
}
|
|
"blink_underscore" => {
|
|
config.cursor_shape_emacs =
|
|
NuCursorShape::BlinkUnderScore;
|
|
}
|
|
_ => {
|
|
invalid!(Some(span),
|
|
"unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'line', 'block', 'underscore', 'blink_line', 'blink_block', or 'blink_underscore'"
|
|
);
|
|
// Reconstruct
|
|
vals[index] = reconstruct_cursor_shape!(
|
|
config.cursor_shape_emacs,
|
|
span
|
|
);
|
|
}
|
|
};
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = reconstruct_cursor_shape!(
|
|
config.cursor_shape_emacs,
|
|
span
|
|
);
|
|
}
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec!["vi_insert".into(), "vi_normal".into(), "emacs".into()],
|
|
vec![
|
|
reconstruct_cursor_shape!(config.cursor_shape_vi_insert, span),
|
|
reconstruct_cursor_shape!(config.cursor_shape_vi_normal, span),
|
|
reconstruct_cursor_shape!(config.cursor_shape_emacs, span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"table" => {
|
|
macro_rules! reconstruct_index_mode {
|
|
($span:expr) => {
|
|
Value::string(
|
|
match config.table_index_mode {
|
|
TableIndexMode::Always => "always",
|
|
TableIndexMode::Never => "never",
|
|
TableIndexMode::Auto => "auto",
|
|
},
|
|
$span,
|
|
)
|
|
};
|
|
}
|
|
macro_rules! reconstruct_trim_strategy {
|
|
($span:expr) => {
|
|
match &config.trim_strategy {
|
|
TrimStrategy::Wrap { try_to_keep_words } => Value::record(
|
|
vec![
|
|
"methodology".into(),
|
|
"wrapping_try_keep_words".into(),
|
|
],
|
|
vec![
|
|
Value::string("wrapping", $span),
|
|
Value::bool(*try_to_keep_words, $span),
|
|
],
|
|
$span,
|
|
),
|
|
TrimStrategy::Truncate { suffix } => Value::record(
|
|
vec!["methodology".into(), "truncating_suffix".into()],
|
|
match suffix {
|
|
Some(s) => vec![
|
|
Value::string("truncating", $span),
|
|
Value::string(s.clone(), $span),
|
|
],
|
|
None => vec![
|
|
Value::string("truncating", $span),
|
|
Value::Nothing { span: $span },
|
|
],
|
|
},
|
|
$span,
|
|
),
|
|
}
|
|
};
|
|
}
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"mode" => {
|
|
if let Ok(v) = value.as_string() {
|
|
config.table_mode = v;
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
vals[index] =
|
|
Value::string(config.table_mode.clone(), span);
|
|
}
|
|
}
|
|
"header_on_separator" => {
|
|
try_bool!(cols, vals, index, span, table_move_header)
|
|
}
|
|
"index_mode" => {
|
|
if let Ok(b) = value.as_string() {
|
|
let val_str = b.to_lowercase();
|
|
match val_str.as_ref() {
|
|
"always" => {
|
|
config.table_index_mode = TableIndexMode::Always
|
|
}
|
|
"never" => {
|
|
config.table_index_mode = TableIndexMode::Never
|
|
}
|
|
"auto" => {
|
|
config.table_index_mode = TableIndexMode::Auto
|
|
}
|
|
_ => {
|
|
invalid!( Some(span),
|
|
"unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'never', 'always' or 'auto'"
|
|
);
|
|
vals[index] = reconstruct_index_mode!(span);
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
vals[index] = reconstruct_index_mode!(span);
|
|
}
|
|
}
|
|
"trim" => {
|
|
match try_parse_trim_strategy(value, &mut errors) {
|
|
Ok(v) => config.trim_strategy = v,
|
|
Err(e) => {
|
|
// try_parse_trim_strategy() already adds its own errors
|
|
errors.push(e);
|
|
vals[index] = reconstruct_trim_strategy!(span);
|
|
}
|
|
}
|
|
}
|
|
"show_empty" => {
|
|
try_bool!(cols, vals, index, span, table_show_empty)
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec![
|
|
"mode".into(),
|
|
"index_mode".into(),
|
|
"trim".into(),
|
|
"show_empty".into(),
|
|
],
|
|
vec![
|
|
Value::string(config.table_mode.clone(), span),
|
|
reconstruct_index_mode!(span),
|
|
reconstruct_trim_strategy!(span),
|
|
Value::bool(config.table_show_empty, span),
|
|
],
|
|
span,
|
|
)
|
|
}
|
|
}
|
|
"filesize" => {
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
let span = *span;
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"metric" => {
|
|
try_bool!(cols, vals, index, span, filesize_metric)
|
|
}
|
|
"format" => {
|
|
if let Ok(v) = value.as_string() {
|
|
config.filesize_format = v.to_lowercase();
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] =
|
|
Value::string(config.filesize_format.clone(), span);
|
|
}
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec!["metric".into(), "format".into()],
|
|
vec![
|
|
Value::bool(config.filesize_metric, span),
|
|
Value::string(config.filesize_format.clone(), span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
"explore" => {
|
|
if let Ok(map) = create_map(value) {
|
|
config.explore = map;
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record_from_hashmap(&config.explore, span);
|
|
}
|
|
}
|
|
// Misc. options
|
|
"color_config" => {
|
|
if let Ok(map) = create_map(value) {
|
|
config.color_config = map;
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record_from_hashmap(&config.color_config, span);
|
|
}
|
|
}
|
|
"use_grid_icons" => {
|
|
try_bool!(cols, vals, index, span, use_grid_icons);
|
|
}
|
|
"footer_mode" => {
|
|
if let Ok(b) = value.as_string() {
|
|
let val_str = b.to_lowercase();
|
|
config.footer_mode = match val_str.as_ref() {
|
|
"auto" => FooterMode::Auto,
|
|
"never" => FooterMode::Never,
|
|
"always" => FooterMode::Always,
|
|
_ => match &val_str.parse::<u64>() {
|
|
Ok(number) => FooterMode::RowCount(*number),
|
|
_ => FooterMode::Never,
|
|
},
|
|
};
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = Value::String {
|
|
val: match config.footer_mode {
|
|
FooterMode::Auto => "auto".into(),
|
|
FooterMode::Never => "never".into(),
|
|
FooterMode::Always => "always".into(),
|
|
FooterMode::RowCount(number) => number.to_string(),
|
|
},
|
|
span,
|
|
};
|
|
}
|
|
}
|
|
"float_precision" => {
|
|
try_int!(cols, vals, index, span, float_precision);
|
|
}
|
|
"use_ansi_coloring" => {
|
|
try_bool!(cols, vals, index, span, use_ansi_coloring);
|
|
}
|
|
"edit_mode" => {
|
|
if let Ok(v) = value.as_string() {
|
|
config.edit_mode = v.to_lowercase();
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
// Reconstruct
|
|
vals[index] = Value::string(config.edit_mode.clone(), span);
|
|
}
|
|
}
|
|
"shell_integration" => {
|
|
try_bool!(cols, vals, index, span, shell_integration);
|
|
}
|
|
"buffer_editor" => {
|
|
if let Ok(v) = value.as_string() {
|
|
config.buffer_editor = v.to_lowercase();
|
|
} else {
|
|
invalid!(Some(span), "should be a string");
|
|
}
|
|
}
|
|
"show_banner" => {
|
|
try_bool!(cols, vals, index, span, show_banner);
|
|
}
|
|
"render_right_prompt_on_last_line" => {
|
|
try_bool!(cols, vals, index, span, render_right_prompt_on_last_line);
|
|
}
|
|
"bracketed_paste" => {
|
|
try_bool!(cols, vals, index, span, bracketed_paste);
|
|
}
|
|
// Menus
|
|
"menus" => match create_menus(value) {
|
|
Ok(map) => config.menus = map,
|
|
Err(e) => {
|
|
invalid!(Some(span), "should be a valid list of menus");
|
|
errors.push(e);
|
|
// Reconstruct
|
|
vals[index] = Value::List {
|
|
vals: config
|
|
.menus
|
|
.iter()
|
|
.map(
|
|
|ParsedMenu {
|
|
name,
|
|
only_buffer_difference,
|
|
marker,
|
|
style,
|
|
menu_type, // WARNING: this is not the same name as what is used in Config.nu! ("type")
|
|
source,
|
|
}| {
|
|
Value::Record {
|
|
cols: vec![
|
|
"name".into(),
|
|
"only_buffer_difference".into(),
|
|
"marker".into(),
|
|
"style".into(),
|
|
"type".into(),
|
|
"source".into(),
|
|
],
|
|
vals: vec![
|
|
name.clone(),
|
|
only_buffer_difference.clone(),
|
|
marker.clone(),
|
|
style.clone(),
|
|
menu_type.clone(),
|
|
source.clone(),
|
|
],
|
|
span,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
span,
|
|
}
|
|
}
|
|
},
|
|
// Keybindings
|
|
"keybindings" => match create_keybindings(value) {
|
|
Ok(keybindings) => config.keybindings = keybindings,
|
|
Err(e) => {
|
|
invalid!(Some(span), "should be a valid keybindings list");
|
|
errors.push(e);
|
|
// Reconstruct
|
|
vals[index] = Value::List {
|
|
vals: config
|
|
.keybindings
|
|
.iter()
|
|
.map(
|
|
|ParsedKeybinding {
|
|
modifier,
|
|
keycode,
|
|
mode,
|
|
event,
|
|
}| {
|
|
Value::Record {
|
|
cols: vec![
|
|
"modifier".into(),
|
|
"keycode".into(),
|
|
"mode".into(),
|
|
"event".into(),
|
|
],
|
|
vals: vec![
|
|
modifier.clone(),
|
|
keycode.clone(),
|
|
mode.clone(),
|
|
event.clone(),
|
|
],
|
|
span,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
span,
|
|
}
|
|
}
|
|
},
|
|
// Hooks
|
|
"hooks" => match create_hooks(value) {
|
|
Ok(hooks) => config.hooks = hooks,
|
|
Err(e) => {
|
|
invalid!(Some(span), "should be a valid hooks list");
|
|
errors.push(e);
|
|
// Reconstruct
|
|
let mut hook_cols = vec![];
|
|
let mut hook_vals = vec![];
|
|
if let Some(ref value) = config.hooks.pre_prompt {
|
|
hook_cols.push("pre_prompt".into());
|
|
hook_vals.push(value.clone());
|
|
}
|
|
if let Some(ref value) = config.hooks.pre_execution {
|
|
hook_cols.push("pre_execution".into());
|
|
hook_vals.push(value.clone());
|
|
}
|
|
if let Some(ref value) = config.hooks.env_change {
|
|
hook_cols.push("env_change".into());
|
|
hook_vals.push(value.clone());
|
|
}
|
|
if let Some(ref value) = config.hooks.display_output {
|
|
hook_cols.push("display_output".into());
|
|
hook_vals.push(value.clone());
|
|
}
|
|
vals.push(Value::Record {
|
|
cols: hook_cols,
|
|
vals: hook_vals,
|
|
span,
|
|
});
|
|
}
|
|
},
|
|
"datetime_format" => {
|
|
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
|
for index in (0..cols.len()).rev() {
|
|
let value = &vals[index];
|
|
let key2 = cols[index].as_str();
|
|
match key2 {
|
|
"normal" => {
|
|
if let Ok(v) = value.as_string() {
|
|
config.datetime_normal_format = Some(v);
|
|
} else {
|
|
invalid!(Some(*span), "should be a string");
|
|
}
|
|
}
|
|
"table" => {
|
|
if let Ok(v) = value.as_string() {
|
|
config.datetime_table_format = Some(v);
|
|
} else {
|
|
invalid!(Some(*span), "should be a string");
|
|
}
|
|
}
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{key}.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invalid!(vals[index].span().ok(), "should be a record");
|
|
// Reconstruct
|
|
vals[index] = Value::record(
|
|
vec!["metric".into(), "format".into()],
|
|
vec![
|
|
Value::bool(config.filesize_metric, span),
|
|
Value::string(config.filesize_format.clone(), span),
|
|
],
|
|
span,
|
|
);
|
|
}
|
|
}
|
|
// Catch all
|
|
x => {
|
|
invalid_key!(
|
|
cols,
|
|
vals,
|
|
index,
|
|
value.span().ok(),
|
|
"$env.config.{x} is an unknown config setting"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return (
|
|
config,
|
|
Some(ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
"$env.config is not a record".into(),
|
|
self.span().ok(),
|
|
None,
|
|
vec![],
|
|
)),
|
|
);
|
|
}
|
|
|
|
// Return the config and the vec of errors.
|
|
(
|
|
config,
|
|
if !errors.is_empty() {
|
|
// Because the config was iterated in reverse, these errors
|
|
// need to be reversed, too.
|
|
errors.reverse();
|
|
Some(ShellError::GenericError(
|
|
"Config record contains invalid values or unknown settings".into(),
|
|
// Without a span, this second string is ignored.
|
|
"".into(),
|
|
None,
|
|
None,
|
|
errors,
|
|
))
|
|
} else {
|
|
None
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
fn try_parse_trim_strategy(
|
|
value: &Value,
|
|
errors: &mut Vec<ShellError>,
|
|
) -> Result<TrimStrategy, ShellError> {
|
|
let map = create_map(value).map_err(|e| {
|
|
ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
"$env.config.table.trim is not a record".into(),
|
|
value.span().ok(),
|
|
Some("Please consult the documentation for configuring Nushell.".into()),
|
|
vec![e],
|
|
)
|
|
})?;
|
|
|
|
let mut methodology = match map.get("methodology") {
|
|
Some(value) => match try_parse_trim_methodology(value) {
|
|
Some(methodology) => methodology,
|
|
None => return Ok(TRIM_STRATEGY_DEFAULT),
|
|
},
|
|
None => {
|
|
errors.push(ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
"$env.config.table.trim.methodology was not provided".into(),
|
|
value.span().ok(),
|
|
Some("Please consult the documentation for configuring Nushell.".into()),
|
|
vec![],
|
|
));
|
|
return Ok(TRIM_STRATEGY_DEFAULT);
|
|
}
|
|
};
|
|
|
|
match &mut methodology {
|
|
TrimStrategy::Wrap { try_to_keep_words } => {
|
|
if let Some(value) = map.get("wrapping_try_keep_words") {
|
|
if let Ok(b) = value.as_bool() {
|
|
*try_to_keep_words = b;
|
|
} else {
|
|
errors.push(ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
"$env.config.table.trim.wrapping_try_keep_words is not a bool".into(),
|
|
value.span().ok(),
|
|
Some("Please consult the documentation for configuring Nushell.".into()),
|
|
vec![],
|
|
));
|
|
}
|
|
}
|
|
}
|
|
TrimStrategy::Truncate { suffix } => {
|
|
if let Some(value) = map.get("truncating_suffix") {
|
|
if let Ok(v) = value.as_string() {
|
|
*suffix = Some(v);
|
|
} else {
|
|
errors.push(ShellError::GenericError(
|
|
"Error while applying config changes".into(),
|
|
"$env.config.table.trim.truncating_suffix is not a string".into(),
|
|
value.span().ok(),
|
|
Some("Please consult the documentation for configuring Nushell.".into()),
|
|
vec![],
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(methodology)
|
|
}
|
|
|
|
fn try_parse_trim_methodology(value: &Value) -> Option<TrimStrategy> {
|
|
if let Ok(value) = value.as_string() {
|
|
match value.to_lowercase().as_str() {
|
|
"wrapping" => {
|
|
return Some(TrimStrategy::Wrap {
|
|
try_to_keep_words: false,
|
|
});
|
|
}
|
|
"truncating" => return Some(TrimStrategy::Truncate { suffix: None }),
|
|
_ => eprintln!("unrecognized $config.table.trim.methodology value; expected either 'truncating' or 'wrapping'"),
|
|
}
|
|
} else {
|
|
eprintln!("$env.config.table.trim.methodology is not a string")
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn create_map(value: &Value) -> Result<HashMap<String, Value>, ShellError> {
|
|
let (cols, inner_vals) = value.as_record()?;
|
|
let mut hm: HashMap<String, Value> = HashMap::new();
|
|
|
|
for (k, v) in cols.iter().zip(inner_vals) {
|
|
hm.insert(k.to_string(), v.clone());
|
|
}
|
|
|
|
Ok(hm)
|
|
}
|
|
|
|
// Parse the hooks to find the blocks to run when the hooks fire
|
|
fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
|
|
match value {
|
|
Value::Record { cols, vals, span } => {
|
|
let mut hooks = Hooks::new();
|
|
|
|
for idx in 0..cols.len() {
|
|
match cols[idx].as_str() {
|
|
"pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()),
|
|
"pre_execution" => hooks.pre_execution = Some(vals[idx].clone()),
|
|
"env_change" => hooks.env_change = Some(vals[idx].clone()),
|
|
"display_output" => hooks.display_output = Some(vals[idx].clone()),
|
|
"command_not_found" => hooks.command_not_found = Some(vals[idx].clone()),
|
|
x => {
|
|
return Err(ShellError::UnsupportedConfigValue(
|
|
"'pre_prompt', 'pre_execution', 'env_change', 'display_output', 'command_not_found'"
|
|
.to_string(),
|
|
x.to_string(),
|
|
*span,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(hooks)
|
|
}
|
|
v => Err(ShellError::UnsupportedConfigValue(
|
|
"record for 'hooks' config".into(),
|
|
"non-record value".into(),
|
|
v.span().unwrap_or_else(|_| Span::unknown()),
|
|
)),
|
|
}
|
|
}
|
|
|
|
// Parses the config object to extract the strings that will compose a keybinding for reedline
|
|
fn create_keybindings(value: &Value) -> Result<Vec<ParsedKeybinding>, ShellError> {
|
|
match value {
|
|
Value::Record { cols, vals, span } => {
|
|
let span = *span;
|
|
// Finding the modifier value in the record
|
|
let modifier = extract_value("modifier", cols, vals, span)?.clone();
|
|
let keycode = extract_value("keycode", cols, vals, span)?.clone();
|
|
let mode = extract_value("mode", cols, vals, span)?.clone();
|
|
let event = extract_value("event", cols, vals, span)?.clone();
|
|
|
|
let keybinding = ParsedKeybinding {
|
|
modifier,
|
|
keycode,
|
|
mode,
|
|
event,
|
|
};
|
|
|
|
// We return a menu to be able to do recursion on the same function
|
|
Ok(vec![keybinding])
|
|
}
|
|
Value::List { vals, .. } => {
|
|
let res = vals
|
|
.iter()
|
|
.map(create_keybindings)
|
|
.collect::<Result<Vec<Vec<ParsedKeybinding>>, ShellError>>();
|
|
|
|
let res = res?
|
|
.into_iter()
|
|
.flatten()
|
|
.collect::<Vec<ParsedKeybinding>>();
|
|
|
|
Ok(res)
|
|
}
|
|
_ => Ok(Vec::new()),
|
|
}
|
|
}
|
|
|
|
// Parses the config object to extract the strings that will compose a keybinding for reedline
|
|
pub fn create_menus(value: &Value) -> Result<Vec<ParsedMenu>, ShellError> {
|
|
match value {
|
|
Value::Record { cols, vals, span } => {
|
|
let span = *span;
|
|
// Finding the modifier value in the record
|
|
let name = extract_value("name", cols, vals, span)?.clone();
|
|
let marker = extract_value("marker", cols, vals, span)?.clone();
|
|
let only_buffer_difference =
|
|
extract_value("only_buffer_difference", cols, vals, span)?.clone();
|
|
let style = extract_value("style", cols, vals, span)?.clone();
|
|
let menu_type = extract_value("type", cols, vals, span)?.clone();
|
|
|
|
// Source is an optional value
|
|
let source = match extract_value("source", cols, vals, span) {
|
|
Ok(source) => source.clone(),
|
|
Err(_) => Value::Nothing { span },
|
|
};
|
|
|
|
let menu = ParsedMenu {
|
|
name,
|
|
only_buffer_difference,
|
|
marker,
|
|
style,
|
|
menu_type,
|
|
source,
|
|
};
|
|
|
|
Ok(vec![menu])
|
|
}
|
|
Value::List { vals, .. } => {
|
|
let res = vals
|
|
.iter()
|
|
.map(create_menus)
|
|
.collect::<Result<Vec<Vec<ParsedMenu>>, ShellError>>();
|
|
|
|
let res = res?.into_iter().flatten().collect::<Vec<ParsedMenu>>();
|
|
|
|
Ok(res)
|
|
}
|
|
_ => Ok(Vec::new()),
|
|
}
|
|
}
|
|
|
|
pub fn extract_value<'record>(
|
|
name: &str,
|
|
cols: &'record [String],
|
|
vals: &'record [Value],
|
|
span: Span,
|
|
) -> Result<&'record Value, ShellError> {
|
|
cols.iter()
|
|
.position(|col| col.as_str() == name)
|
|
.and_then(|index| vals.get(index))
|
|
.ok_or_else(|| ShellError::MissingConfigValue(name.to_string(), span))
|
|
}
|