use nu_ansi_term::Color;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
#[derive(Default)]
pub struct ExperimentalConfig {
#[serde(default)]
pub history_forget: HistoryForgetConfig,
#[serde(default)]
pub completion_min_chars: Option<usize>,
#[serde(default)]
pub prompt_spinner: SpinnerConfig,
#[serde(default)]
pub prompt_duration: PromptDurationConfig,
#[serde(default)]
pub r_completion: RCompletionConfig,
#[serde(default)]
pub shell_completion: ShellCompletionConfig,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct ShellCompletionConfig {
pub command_names: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct RCompletionConfig {
pub fuzzy: bool,
#[serde(default = "default_package_functions")]
pub package_functions: Vec<String>,
}
fn default_package_functions() -> Vec<String> {
vec!["library".to_string(), "require".to_string()]
}
impl Default for RCompletionConfig {
fn default() -> Self {
Self {
fuzzy: false,
package_functions: default_package_functions(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
struct SpinnerConfigSchema {
pub frames: String,
pub color: String,
}
impl Default for SpinnerConfigSchema {
fn default() -> Self {
SpinnerConfigSchema {
frames: String::new(),
color: "Cyan".to_string(),
}
}
}
impl JsonSchema for SpinnerConfigSchema {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("SpinnerConfigSchema")
}
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use super::colors::color_prop;
schemars::json_schema!({
"type": "object",
"description": "Spinner configuration for busy indicator during R execution.",
"properties": {
"frames": {
"type": "string",
"description": "Spinner animation frames as a string where each character is one frame. Empty string disables the spinner. Example: \"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\" (braille dots), \"|/-\\\\\" (ASCII spinner).",
"default": ""
},
"color": color_prop!("Color for the spinner.", default = "Cyan")
}
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
#[derive(Default)]
struct ExperimentalConfigSchema {
pub history_forget: HistoryForgetConfig,
pub completion_min_chars: Option<usize>,
pub prompt_spinner: SpinnerConfigSchema,
pub prompt_duration: PromptDurationConfig,
pub r_completion: RCompletionConfig,
pub shell_completion: ShellCompletionConfig,
}
impl JsonSchema for ExperimentalConfig {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("ExperimentalConfig")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
generator.subschema_for::<ExperimentalConfigSchema>()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct HistoryForgetConfig {
pub enabled: bool,
pub delay: usize,
pub on_exit_only: bool,
}
impl Default for HistoryForgetConfig {
fn default() -> Self {
HistoryForgetConfig {
enabled: false,
delay: 2,
on_exit_only: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct PromptDurationConfig {
#[serde(default = "default_duration_format")]
pub format: String,
#[serde(default = "default_duration_threshold_ms")]
pub threshold_ms: u64,
}
fn default_duration_format() -> String {
"{value} ".to_string()
}
fn default_duration_threshold_ms() -> u64 {
2000
}
impl Default for PromptDurationConfig {
fn default() -> Self {
Self {
format: default_duration_format(),
threshold_ms: default_duration_threshold_ms(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct SpinnerConfig {
#[serde(default = "default_spinner_frames")]
pub frames: String,
#[serde(default = "default_spinner_color")]
pub color: Color,
}
fn default_spinner_frames() -> String {
String::new() }
fn default_spinner_color() -> Color {
Color::Cyan
}
impl Default for SpinnerConfig {
fn default() -> Self {
Self {
frames: default_spinner_frames(),
color: default_spinner_color(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spinner_config_default() {
let config = SpinnerConfig::default();
assert!(config.frames.is_empty()); assert_eq!(config.color, Color::Cyan);
}
#[test]
fn test_spinner_config_disabled() {
let config = SpinnerConfig {
frames: String::new(),
color: Color::Default,
};
assert!(config.frames.is_empty());
}
#[test]
fn test_spinner_config_custom_frames() {
let config = SpinnerConfig {
frames: "|/-\\".to_string(),
color: Color::Green,
};
assert_eq!(config.frames, "|/-\\");
assert_eq!(config.color, Color::Green);
}
#[test]
fn test_experimental_config_default() {
let config = ExperimentalConfig::default();
assert!(!config.history_forget.enabled);
assert!(config.completion_min_chars.is_none());
assert!(config.prompt_spinner.frames.is_empty()); assert!(!config.r_completion.fuzzy); assert_eq!(
config.r_completion.package_functions,
vec!["library".to_string(), "require".to_string()]
);
assert!(!config.shell_completion.command_names); }
#[test]
fn test_parse_spinner_config() {
let toml_str = r#"
[experimental.prompt_spinner]
frames = "abc"
color = "Red"
"#;
let config: crate::config::Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.experimental.prompt_spinner.frames, "abc");
assert_eq!(config.experimental.prompt_spinner.color, Color::Red);
}
#[test]
fn test_parse_spinner_disabled() {
let toml_str = r#"
[experimental.prompt_spinner]
frames = ""
"#;
let config: crate::config::Config = toml::from_str(toml_str).unwrap();
assert!(config.experimental.prompt_spinner.frames.is_empty());
}
#[test]
fn test_r_completion_default() {
let config = RCompletionConfig::default();
assert!(!config.fuzzy);
assert_eq!(
config.package_functions,
vec!["library".to_string(), "require".to_string()]
);
}
#[test]
fn test_parse_r_completion() {
let toml_str = r#"
[experimental.r_completion]
fuzzy = true
package_functions = ["library", "require", "box::use"]
"#;
let config: crate::config::Config = toml::from_str(toml_str).unwrap();
assert!(config.experimental.r_completion.fuzzy);
assert_eq!(
config.experimental.r_completion.package_functions,
vec![
"library".to_string(),
"require".to_string(),
"box::use".to_string()
]
);
}
#[test]
fn test_parse_r_completion_fuzzy_only() {
let toml_str = r#"
[experimental.r_completion]
fuzzy = true
"#;
let config: crate::config::Config = toml::from_str(toml_str).unwrap();
assert!(config.experimental.r_completion.fuzzy);
assert_eq!(
config.experimental.r_completion.package_functions,
vec!["library".to_string(), "require".to_string()]
);
}
#[test]
fn test_shell_completion_default() {
let config = ShellCompletionConfig::default();
assert!(!config.command_names);
}
#[test]
fn test_parse_shell_completion() {
let toml_str = r#"
[experimental.shell_completion]
command_names = true
"#;
let config: crate::config::Config = toml::from_str(toml_str).unwrap();
assert!(config.experimental.shell_completion.command_names);
}
}