use std::{fs, io::Write, path::PathBuf};
use super::theme_manager::{get_config_dir, load_theme, ThemeColors};
#[derive(Debug, Clone)]
pub struct EditorPreferences {
pub first_launch: bool,
pub tab_size: usize,
pub use_spaces: bool,
pub auto_indent_enabled: bool,
pub vim_mode_enabled: bool,
pub discord_rpc_enabled: bool,
pub autosave_enabled: bool,
pub autosave_interval_ms: u64,
pub theme_name: String,
pub window_width: f32,
pub window_height: f32,
pub line_number_width: f32,
pub tab_drag_floating: bool,
#[cfg(feature = "unstable-comet")]
pub developer_mode: bool,
}
impl Default for EditorPreferences {
fn default() -> Self {
Self {
first_launch: true,
tab_size: 4,
use_spaces: true,
auto_indent_enabled: true,
vim_mode_enabled: false,
discord_rpc_enabled: false,
autosave_enabled: true,
autosave_interval_ms: 300,
theme_name: "Pinel Blueberry Dark".to_string(),
window_width: 1200.0,
window_height: 800.0,
line_number_width: 40.0,
tab_drag_floating: true,
#[cfg(feature = "unstable-comet")]
developer_mode: false,
}
}
}
#[allow(dead_code)]
impl EditorPreferences {
pub fn indent_unit(&self) -> String {
if self.use_spaces {
" ".repeat(self.tab_size)
} else {
"\t".to_string()
}
}
}
pub fn get_preferences_path() -> PathBuf {
get_config_dir().join("preferences.lua")
}
#[allow(dead_code)]
pub fn get_themes_dir() -> PathBuf {
get_config_dir().join("themes")
}
pub fn load_preferences() -> EditorPreferences {
let primary = get_preferences_path();
let legacy = legacy_preferences_path();
let primary_prefs = read_preferences_from(&primary);
let legacy_prefs = legacy.as_ref().and_then(read_preferences_from);
match (primary_prefs, legacy_prefs) {
(Some(prefs), None) => prefs,
(None, Some(prefs)) => {
let _ = save_preferences_to_path(&prefs, &primary);
prefs
},
(Some(primary_prefs), Some(legacy_prefs)) => {
if legacy_is_newer_than_primary(legacy.as_ref(), &primary) {
let _ = save_preferences_to_path(&legacy_prefs, &primary);
legacy_prefs
} else {
primary_prefs
}
},
(None, None) => {
let prefs = EditorPreferences::default();
let _ = save_preferences_to_path(&prefs, &primary);
prefs
},
}
}
fn legacy_preferences_path() -> Option<PathBuf> {
let home = std::env::var("HOME").ok()?;
Some(PathBuf::from(home).join(".config").join("pinel").join("preferences.lua"))
}
fn parse_preferences(content: &str) -> EditorPreferences {
let mut prefs = EditorPreferences::default();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("--") || line == "return {" || line == "}" {
continue;
}
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim().trim_end_matches(',').trim_matches('"').trim_matches('\'');
match key {
"tab_size" => {
if let Ok(size) = value.parse::<usize>() {
prefs.tab_size = size.clamp(1, 16);
}
},
"use_spaces" => {
prefs.use_spaces = value == "true";
},
"auto_indent_enabled" => {
prefs.auto_indent_enabled = value == "true";
},
"vim_mode_enabled" => {
prefs.vim_mode_enabled = value == "true";
},
"discord_rpc_enabled" => {
prefs.discord_rpc_enabled = value == "true";
},
"autosave_enabled" => {
prefs.autosave_enabled = value == "true";
},
"autosave_interval_ms" => {
if let Ok(interval) = value.parse::<u64>() {
prefs.autosave_interval_ms = interval.clamp(30, 1000);
}
},
"theme_name" => {
prefs.theme_name = value.to_string();
},
"window_width" => {
if let Ok(width) = value.parse::<f32>() {
prefs.window_width = width.clamp(640.0, 10000.0);
}
},
"window_height" => {
if let Ok(height) = value.parse::<f32>() {
prefs.window_height = height.clamp(480.0, 10000.0);
}
},
"line_number_width" => {
if let Ok(w) = value.parse::<f32>() {
prefs.line_number_width = w.clamp(20.0, 120.0);
}
},
"first_launch" => {
prefs.first_launch = value == "true";
},
"tab_drag_floating" => {
prefs.tab_drag_floating = value == "true";
},
#[cfg(feature = "unstable-comet")]
"developer_mode" => {
prefs.developer_mode = value == "true";
},
_ => {},
}
}
}
prefs
}
pub fn save_preferences(prefs: &EditorPreferences) -> Result<(), std::io::Error> {
let path = get_preferences_path();
save_preferences_to_path(prefs, &path)
}
#[allow(dead_code)]
pub fn list_available_themes() -> Vec<String> {
let mut themes = vec!["default".to_string()];
let themes_dir = get_themes_dir();
if let Ok(entries) = fs::read_dir(&themes_dir) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".lua") {
themes.push(name.trim_end_matches(".lua").to_string());
}
}
}
}
themes
}
#[allow(dead_code)]
pub fn load_theme_by_name(name: &str) -> ThemeColors {
if name == "default" {
return ThemeColors::default();
}
let theme_path = get_themes_dir().join(format!("{}.lua", name));
if let Ok(content) = fs::read_to_string(&theme_path) {
if let Ok(theme) = ThemeColors::from_lua(&content) {
return theme;
}
}
load_theme()
}
fn read_preferences_from(path: &PathBuf) -> Option<EditorPreferences> {
let content = fs::read_to_string(path).ok()?;
Some(parse_preferences(&content))
}
fn legacy_is_newer_than_primary(legacy: Option<&PathBuf>, primary: &PathBuf) -> bool {
let Some(legacy) = legacy else {
return false;
};
let legacy_meta = fs::metadata(legacy).ok();
let primary_meta = fs::metadata(primary).ok();
let legacy_mtime = legacy_meta.and_then(|m| m.modified().ok());
let primary_mtime = primary_meta.and_then(|m| m.modified().ok());
match (legacy_mtime, primary_mtime) {
(Some(legacy_mtime), Some(primary_mtime)) => legacy_mtime > primary_mtime,
(Some(_), None) => true,
_ => false,
}
}
fn save_preferences_to_path(
prefs: &EditorPreferences,
path: &PathBuf,
) -> Result<(), std::io::Error> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
#[cfg(feature = "unstable-comet")]
let developer_mode_config = format!(
" -- Enable developer mode with debug logging (WARNING: Logs may contain sensitive data)\n developer_mode = {},\n",
prefs.developer_mode
);
#[cfg(not(feature = "unstable-comet"))]
let developer_mode_config = String::new();
let content = format!(
r#"-- Pinel Editor Preferences
-- Edit these values to customize your editor
return {{
first_launch = {},
tab_size = {},
use_spaces = {},
auto_indent_enabled = {},
vim_mode_enabled = {},
discord_rpc_enabled = {},
autosave_enabled = {},
-- Autosave interval in milliseconds (30–1000)
autosave_interval_ms = {},
theme_name = "{}",
window_width = {},
window_height = {},
-- Width of the line-number gutter in logical pixels (20–120)
line_number_width = {},
-- Tab drag style: true = floating ghost (default), false = static shift
tab_drag_floating = {},
{}}}
"#,
prefs.first_launch,
prefs.tab_size,
prefs.use_spaces,
prefs.auto_indent_enabled,
prefs.vim_mode_enabled,
prefs.discord_rpc_enabled,
prefs.autosave_enabled,
prefs.autosave_interval_ms,
prefs.theme_name,
prefs.window_width,
prefs.window_height,
prefs.line_number_width,
prefs.tab_drag_floating,
developer_mode_config,
);
let mut file = fs::File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}