2022-01-19 14:28:08 +01:00
|
|
|
use crate::{BlockId, ShellError, Span, Value};
|
2021-11-20 14:12:35 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::collections::HashMap;
|
2021-11-14 20:25:57 +01:00
|
|
|
|
2021-12-31 23:41:29 +01:00
|
|
|
const ANIMATE_PROMPT_DEFAULT: bool = true;
|
2021-12-02 08:10:40 +01:00
|
|
|
|
2021-12-17 02:04:54 +01:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
|
|
pub struct EnvConversion {
|
2021-12-21 23:32:38 +01:00
|
|
|
pub from_string: Option<(BlockId, Span)>,
|
|
|
|
pub to_string: Option<(BlockId, Span)>,
|
2021-12-17 02:04:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EnvConversion {
|
|
|
|
pub fn from_record(value: &Value) -> Result<Self, ShellError> {
|
|
|
|
let record = value.as_record()?;
|
|
|
|
|
|
|
|
let mut conv_map = HashMap::new();
|
|
|
|
|
|
|
|
for (k, v) in record.0.iter().zip(record.1) {
|
|
|
|
if (k == "from_string") || (k == "to_string") {
|
|
|
|
conv_map.insert(k.as_str(), (v.as_block()?, v.span()?));
|
|
|
|
} else {
|
|
|
|
return Err(ShellError::UnsupportedConfigValue(
|
|
|
|
"'from_string' and 'to_string' fields".into(),
|
|
|
|
k.into(),
|
|
|
|
value.span()?,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 23:32:38 +01:00
|
|
|
let from_string = conv_map.get("from_string").cloned();
|
|
|
|
let to_string = conv_map.get("to_string").cloned();
|
|
|
|
|
|
|
|
Ok(EnvConversion {
|
|
|
|
from_string,
|
|
|
|
to_string,
|
|
|
|
})
|
2021-12-17 02:04:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 09:48:28 +01:00
|
|
|
/// Definition of a parsed keybinding from the config object
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
|
|
pub struct ParsedKeybinding {
|
2022-01-19 14:28:08 +01:00
|
|
|
pub modifier: Value,
|
|
|
|
pub keycode: Value,
|
|
|
|
pub event: Value,
|
|
|
|
pub mode: Value,
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 20:25:57 +01:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
|
|
pub struct Config {
|
|
|
|
pub filesize_metric: bool,
|
|
|
|
pub table_mode: String,
|
2021-11-15 21:09:17 +01:00
|
|
|
pub use_ls_colors: bool,
|
2022-01-15 18:01:44 +01:00
|
|
|
pub color_config: HashMap<String, Value>,
|
2021-11-29 21:37:09 +01:00
|
|
|
pub use_grid_icons: bool,
|
2021-12-01 20:20:23 +01:00
|
|
|
pub footer_mode: FooterMode,
|
2021-12-02 08:10:40 +01:00
|
|
|
pub animate_prompt: bool,
|
2021-12-07 21:06:14 +01:00
|
|
|
pub float_precision: i64,
|
2021-12-09 20:19:36 +01:00
|
|
|
pub filesize_format: String,
|
2021-12-09 23:06:26 +01:00
|
|
|
pub use_ansi_coloring: bool,
|
2021-12-17 02:04:54 +01:00
|
|
|
pub env_conversions: HashMap<String, EnvConversion>,
|
2021-12-23 10:31:16 +01:00
|
|
|
pub edit_mode: String,
|
2021-12-23 20:59:00 +01:00
|
|
|
pub max_history_size: i64,
|
2022-01-01 22:42:50 +01:00
|
|
|
pub log_level: String,
|
2022-01-15 18:01:44 +01:00
|
|
|
pub menu_config: HashMap<String, Value>,
|
2022-01-18 09:48:28 +01:00
|
|
|
pub keybindings: Vec<ParsedKeybinding>,
|
2021-11-14 20:25:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Config {
|
|
|
|
Config {
|
|
|
|
filesize_metric: false,
|
|
|
|
table_mode: "rounded".into(),
|
2021-11-15 21:09:17 +01:00
|
|
|
use_ls_colors: true,
|
2021-11-20 14:12:35 +01:00
|
|
|
color_config: HashMap::new(),
|
2021-11-29 21:37:09 +01:00
|
|
|
use_grid_icons: false,
|
2021-12-01 20:20:23 +01:00
|
|
|
footer_mode: FooterMode::Never,
|
2021-12-02 08:10:40 +01:00
|
|
|
animate_prompt: ANIMATE_PROMPT_DEFAULT,
|
2021-12-07 21:06:14 +01:00
|
|
|
float_precision: 4,
|
2021-12-09 20:19:36 +01:00
|
|
|
filesize_format: "auto".into(),
|
2021-12-09 23:06:26 +01:00
|
|
|
use_ansi_coloring: true,
|
2021-12-17 02:04:54 +01:00
|
|
|
env_conversions: HashMap::new(), // TODO: Add default conversoins
|
2021-12-23 10:31:16 +01:00
|
|
|
edit_mode: "emacs".into(),
|
2021-12-23 20:59:00 +01:00
|
|
|
max_history_size: 1000,
|
2022-01-01 22:42:50 +01:00
|
|
|
log_level: String::new(),
|
2022-01-15 18:01:44 +01:00
|
|
|
menu_config: HashMap::new(),
|
2022-01-18 09:48:28 +01:00
|
|
|
keybindings: Vec::new(),
|
2021-11-14 20:25:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:20:23 +01:00
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
2021-11-14 20:25:57 +01:00
|
|
|
impl Value {
|
|
|
|
pub fn into_config(self) -> Result<Config, ShellError> {
|
2022-01-19 17:42:12 +01:00
|
|
|
let v = self.as_record();
|
2021-11-14 20:25:57 +01:00
|
|
|
|
|
|
|
let mut config = Config::default();
|
|
|
|
|
2022-01-19 17:42:12 +01:00
|
|
|
if let Ok(v) = v {
|
|
|
|
for (key, value) in v.0.iter().zip(v.1) {
|
|
|
|
match key.as_str() {
|
|
|
|
"filesize_metric" => {
|
|
|
|
if let Ok(b) = value.as_bool() {
|
|
|
|
config.filesize_metric = b;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.filesize_metric is not a bool")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"table_mode" => {
|
|
|
|
if let Ok(v) = value.as_string() {
|
|
|
|
config.table_mode = v;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.table_mode is not a string")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"use_ls_colors" => {
|
|
|
|
if let Ok(b) = value.as_bool() {
|
|
|
|
config.use_ls_colors = b;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.use_ls_colors is not a bool")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"color_config" => {
|
|
|
|
if let Ok(map) = create_map(value, &config) {
|
|
|
|
config.color_config = map;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.color_config is not a record")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"use_grid_icons" => {
|
|
|
|
if let Ok(b) = value.as_bool() {
|
|
|
|
config.use_grid_icons = b;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.use_grid_icons is not a bool")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"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 {
|
|
|
|
eprintln!("$config.footer_mode is not a string")
|
|
|
|
}
|
2021-12-17 02:04:54 +01:00
|
|
|
}
|
2022-01-19 17:42:12 +01:00
|
|
|
"animate_prompt" => {
|
|
|
|
if let Ok(b) = value.as_bool() {
|
|
|
|
config.animate_prompt = b;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.animate_prompt is not a bool")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"float_precision" => {
|
|
|
|
if let Ok(i) = value.as_integer() {
|
|
|
|
config.float_precision = i;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.float_precision is not an integer")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"use_ansi_coloring" => {
|
|
|
|
if let Ok(b) = value.as_bool() {
|
|
|
|
config.use_ansi_coloring = b;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.use_ansi_coloring is not a bool")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"filesize_format" => {
|
|
|
|
if let Ok(v) = value.as_string() {
|
|
|
|
config.filesize_format = v.to_lowercase();
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.filesize_format is not a string")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"env_conversions" => {
|
|
|
|
if let Ok((env_vars, conversions)) = value.as_record() {
|
|
|
|
let mut env_conversions = HashMap::new();
|
2021-12-17 02:04:54 +01:00
|
|
|
|
2022-01-19 17:42:12 +01:00
|
|
|
for (env_var, record) in env_vars.iter().zip(conversions) {
|
|
|
|
// println!("{}: {:?}", env_var, record);
|
|
|
|
if let Ok(conversion) = EnvConversion::from_record(record) {
|
|
|
|
env_conversions.insert(env_var.into(), conversion);
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.env_conversions has incorrect conversion")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config.env_conversions = env_conversions;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.env_conversions is not a record")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"edit_mode" => {
|
|
|
|
if let Ok(v) = value.as_string() {
|
|
|
|
config.edit_mode = v.to_lowercase();
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.edit_mode is not a string")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"max_history_size" => {
|
|
|
|
if let Ok(i) = value.as_i64() {
|
|
|
|
config.max_history_size = i;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.max_history_size is not an integer")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"log_level" => {
|
|
|
|
if let Ok(v) = value.as_string() {
|
|
|
|
config.log_level = v.to_lowercase();
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.log_level is not a string")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"menu_config" => {
|
|
|
|
if let Ok(map) = create_map(value, &config) {
|
|
|
|
config.menu_config = map;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.menu_config is not a record")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"keybindings" => {
|
|
|
|
if let Ok(keybindings) = create_keybindings(value, &config) {
|
|
|
|
config.keybindings = keybindings;
|
|
|
|
} else {
|
|
|
|
eprintln!("$config.keybindings is not a valid keybindings list")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
x => {
|
|
|
|
eprintln!("$config.{} is an unknown config setting", x)
|
|
|
|
}
|
2022-01-15 18:01:44 +01:00
|
|
|
}
|
2021-11-14 20:25:57 +01:00
|
|
|
}
|
2022-01-19 17:42:12 +01:00
|
|
|
} else {
|
|
|
|
eprintln!("$config is not a record");
|
2021-11-14 20:25:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
}
|
2022-01-15 18:01:44 +01:00
|
|
|
|
|
|
|
fn create_map(value: &Value, config: &Config) -> 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) {
|
|
|
|
match &v {
|
|
|
|
Value::Record {
|
|
|
|
cols: inner_cols,
|
|
|
|
vals: inner_vals,
|
|
|
|
span,
|
|
|
|
} => {
|
2022-01-18 09:48:28 +01:00
|
|
|
let val = color_value_string(span, inner_cols, inner_vals, config);
|
|
|
|
hm.insert(k.to_string(), val);
|
2022-01-15 18:01:44 +01:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
hm.insert(k.to_string(), v.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(hm)
|
|
|
|
}
|
2022-01-18 09:48:28 +01:00
|
|
|
|
|
|
|
fn color_value_string(
|
|
|
|
span: &Span,
|
|
|
|
inner_cols: &[String],
|
|
|
|
inner_vals: &[Value],
|
|
|
|
config: &Config,
|
|
|
|
) -> Value {
|
|
|
|
// make a string from our config.color_config section that
|
|
|
|
// looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", }
|
|
|
|
// the real key here was to have quotes around the values but not
|
|
|
|
// require them around the keys.
|
|
|
|
|
|
|
|
// maybe there's a better way to generate this but i'm not sure
|
|
|
|
// what it is.
|
|
|
|
let val: String = inner_cols
|
|
|
|
.iter()
|
|
|
|
.zip(inner_vals)
|
2022-01-19 14:28:08 +01:00
|
|
|
.map(|(x, y)| format!("{}: \"{}\" ", x, y.into_string(", ", config)))
|
2022-01-18 09:48:28 +01:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
// now insert the braces at the front and the back to fake the json string
|
|
|
|
Value::String {
|
|
|
|
val: format!("{{{}}}", val),
|
|
|
|
span: *span,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parses the config object to extract the strings that will compose a keybinding for reedline
|
|
|
|
fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybinding>, ShellError> {
|
|
|
|
match value {
|
2022-01-19 14:28:08 +01:00
|
|
|
Value::Record { cols, vals, span } => {
|
|
|
|
// Finding the modifier value in the record
|
|
|
|
let modifier = extract_value("modifier", cols, vals, span)?;
|
|
|
|
let keycode = extract_value("keycode", cols, vals, span)?;
|
|
|
|
let mode = extract_value("mode", cols, vals, span)?;
|
|
|
|
let event = extract_value("event", cols, vals, span)?;
|
|
|
|
|
|
|
|
let keybinding = ParsedKeybinding {
|
|
|
|
modifier: modifier.clone(),
|
|
|
|
keycode: keycode.clone(),
|
|
|
|
mode: mode.clone(),
|
|
|
|
event: event.clone(),
|
|
|
|
};
|
2022-01-18 09:48:28 +01:00
|
|
|
|
|
|
|
Ok(vec![keybinding])
|
|
|
|
}
|
|
|
|
Value::List { vals, .. } => {
|
|
|
|
let res = vals
|
|
|
|
.iter()
|
|
|
|
.map(|inner_value| create_keybindings(inner_value, config))
|
|
|
|
.collect::<Result<Vec<Vec<ParsedKeybinding>>, ShellError>>();
|
|
|
|
|
|
|
|
let res = res?
|
|
|
|
.into_iter()
|
|
|
|
.flatten()
|
|
|
|
.collect::<Vec<ParsedKeybinding>>();
|
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
_ => Ok(Vec::new()),
|
|
|
|
}
|
|
|
|
}
|
2022-01-19 14:28:08 +01:00
|
|
|
|
|
|
|
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))
|
|
|
|
}
|