use serde::{Deserialize, Serialize};
use std::default::Default;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SheetsConfig {
pub theme: ThemeConfig,
pub display: DisplayConfig,
pub behavior: BehaviorConfig,
pub columns: ColumnConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThemeConfig {
pub background: String,
pub text: String,
pub header_background: String,
pub header_text: String,
pub selected_background: String,
pub selected_text: String,
pub column_header_background: String,
pub column_header_text: String,
pub accent_colors: AccentColors,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccentColors {
pub number: String,
pub string: String,
pub boolean: String,
pub date: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DisplayConfig {
pub preview_rows: usize,
pub show_column_numbers: bool,
pub show_row_numbers: bool,
pub truncate_long_values: bool,
pub max_cell_length: usize,
pub show_data_types: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehaviorConfig {
pub auto_refresh: bool,
pub auto_refresh_interval: u64,
pub scroll_speed: ScrollSpeed,
pub page_size: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScrollSpeed {
pub normal: f32,
pub fast: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnConfig {
pub auto_width: bool,
pub fixed_widths: Vec<usize>,
pub min_column_width: usize,
pub max_column_width: usize,
pub width_mode: ColumnWidthMode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ColumnWidthMode {
Auto,
Fixed,
Mixed,
}
impl Default for SheetsConfig {
fn default() -> Self {
Self {
theme: ThemeConfig::default(),
display: DisplayConfig::default(),
behavior: BehaviorConfig::default(),
columns: ColumnConfig::default(),
}
}
}
impl Default for ThemeConfig {
fn default() -> Self {
Self {
background: "#000000".to_string(),
text: "#FFFFFF".to_string(),
header_background: "#0055AA".to_string(),
header_text: "#FFFFFF".to_string(),
selected_background: "#00AAFF".to_string(),
selected_text: "#000000".to_string(),
column_header_background: "#004488".to_string(),
column_header_text: "#00FFFF".to_string(),
accent_colors: AccentColors::default(),
}
}
}
impl Default for AccentColors {
fn default() -> Self {
Self {
number: "#00FF00".to_string(),
string: "#FFFF00".to_string(),
boolean: "#FF00FF".to_string(),
date: "#FF8800".to_string(),
}
}
}
impl Default for DisplayConfig {
fn default() -> Self {
Self {
preview_rows: 20,
show_column_numbers: true,
show_row_numbers: false,
truncate_long_values: true,
max_cell_length: 50,
show_data_types: false,
}
}
}
impl Default for BehaviorConfig {
fn default() -> Self {
Self {
auto_refresh: false,
auto_refresh_interval: 5,
scroll_speed: ScrollSpeed::default(),
page_size: 10,
}
}
}
impl Default for ScrollSpeed {
fn default() -> Self {
Self {
normal: 1.0,
fast: 3.0,
}
}
}
impl Default for ColumnConfig {
fn default() -> Self {
Self {
auto_width: true,
fixed_widths: Vec::new(),
min_column_width: 8,
max_column_width: 40,
width_mode: ColumnWidthMode::Auto,
}
}
}
pub fn load_config(path: &str) -> Result<SheetsConfig, ConfigError> {
let content = std::fs::read_to_string(path)?;
let config: SheetsConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn save_config(config: &SheetsConfig, path: &str) -> Result<(), ConfigError> {
let content = toml::to_string_pretty(config)?;
std::fs::write(path, content)?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("TOML parsing error: {0}")]
TomlError(#[from] toml::de::Error),
#[error("TOML serialization error: {0}")]
TomlSerializeError(#[from] toml::ser::Error),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
}
pub fn default_config_path() -> Option<String> {
let home = std::env::var("HOME").ok()?;
let config_dir = format!("{}/.config/zellij-sheets", home);
std::fs::create_dir_all(&config_dir).ok()?;
Some(format!("{}/config.toml", config_dir))
}
pub fn validate_config(config: &SheetsConfig) -> Result<(), ConfigError> {
validate_color(&config.theme.background)?;
validate_color(&config.theme.text)?;
validate_color(&config.theme.header_background)?;
validate_color(&config.theme.header_text)?;
validate_color(&config.theme.selected_background)?;
validate_color(&config.theme.selected_text)?;
validate_color(&config.theme.column_header_background)?;
validate_color(&config.theme.column_header_text)?;
validate_color(&config.theme.accent_colors.number)?;
validate_color(&config.theme.accent_colors.string)?;
validate_color(&config.theme.accent_colors.boolean)?;
validate_color(&config.theme.accent_colors.date)?;
if config.display.preview_rows == 0 {
return Err(ConfigError::InvalidConfig(
"preview_rows must be greater than 0".to_string(),
));
}
if config.display.max_cell_length == 0 {
return Err(ConfigError::InvalidConfig(
"max_cell_length must be greater than 0".to_string(),
));
}
if config.behavior.auto_refresh_interval == 0 {
return Err(ConfigError::InvalidConfig(
"auto_refresh_interval must be greater than 0".to_string(),
));
}
if config.behavior.page_size == 0 {
return Err(ConfigError::InvalidConfig(
"page_size must be greater than 0".to_string(),
));
}
if config.columns.min_column_width == 0 {
return Err(ConfigError::InvalidConfig(
"min_column_width must be greater than 0".to_string(),
));
}
if config.columns.max_column_width == 0 {
return Err(ConfigError::InvalidConfig(
"max_column_width must be greater than 0".to_string(),
));
}
if config.columns.min_column_width > config.columns.max_column_width {
return Err(ConfigError::InvalidConfig(
"min_column_width must not exceed max_column_width".to_string(),
));
}
for &width in &config.columns.fixed_widths {
if width == 0 {
return Err(ConfigError::InvalidConfig(
"fixed_widths must be greater than 0".to_string(),
));
}
}
Ok(())
}
fn validate_color(color: &str) -> Result<(), ConfigError> {
if color.starts_with('#') {
let hex = color.trim_start_matches('#');
if hex.len() != 6 && hex.len() != 3 {
return Err(ConfigError::InvalidConfig(format!(
"Invalid hex color format: {}",
color
)));
}
}
Ok(())
}