use std::path::PathBuf;
use std::{fs, io};
use anyhow::{Context, Result};
use log::debug;
use regex::{Captures, Regex};
use serde::Deserialize;
use crate::config::Config as ThconConfig;
use crate::operation::Operation;
use crate::themeable::{ConfigState, Themeable};
use crate::AppConfig;
use crate::Disableable;
#[derive(Debug, Deserialize, Disableable, AppConfig)]
pub struct _Config {
light: String,
dark: String,
config: Option<String>,
#[serde(default)]
disabled: bool,
}
impl Default for _Config {
fn default() -> Self {
Self {
light: "Default Light+".to_string(),
dark: "Default Dark+".to_string(),
config: None,
disabled: false,
}
}
}
pub struct VSCode;
impl VSCode {
fn settings_json_path(&self) -> PathBuf {
[
dirs::config_dir().unwrap().to_str().unwrap(),
"Code",
"User",
"settings.json",
]
.iter()
.collect()
}
}
impl Themeable for VSCode {
fn config_state(&self, config: &ThconConfig) -> ConfigState {
ConfigState::with_default_config(config.vscode.as_ref().map(|c| c.inner.as_ref()))
}
fn switch(&self, config: &ThconConfig, operation: &Operation) -> Result<()> {
let default_config = _Config::default();
let config = match self.config_state(config) {
ConfigState::NoDefault => unreachable!(),
ConfigState::Disabled => return Ok(()),
ConfigState::Default => &default_config,
ConfigState::Enabled => config.vscode.as_ref().unwrap().unwrap_inner_left(),
};
let theme = match operation {
Operation::Darken => &config.dark,
Operation::Lighten => &config.light,
};
let settings_path = self.settings_json_path();
debug!(
"Reading/writing settings.json at {}",
&settings_path.display()
);
match fs::read_to_string(self.settings_json_path()) {
Ok(settings) => {
let settings = replace_color_theme(&settings, theme);
fs::write(self.settings_json_path(), settings).with_context(|| {
format!("Unable to write settings to {}", &settings_path.display())
})
}
Err(e) => {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(anyhow::Error::new(e).context(format!(
"Unable to read settings from {}",
&settings_path.display()
)))
}
}
}
}
}
fn replace_color_theme(settings_json: &str, new_theme: &str) -> String {
let theme_regex = Regex::new(
r#"^(?P<prefix>\s*"workbench.colorTheme"\s*:\s*)"(?P<v>.+)"(?P<suffix>,?\s*//\s*thcon:replace-line)"#,
);
match theme_regex {
Err(_) => settings_json.to_owned(),
Ok(theme_regex) => {
let modified_lines: Vec<String> = settings_json
.lines()
.map(|line| {
theme_regex
.replace(line, |caps: &Captures| {
format!(r#"{}"{}"{}"#, &caps["prefix"], new_theme, &caps["suffix"])
})
.into_owned()
})
.collect();
modified_lines.join("\n")
}
}
}
#[test]
fn replace_color_trailing_comma() {
let settings_json = r#"
{
"workbench.colorTheme": "Default Dark+", // thcon:replace-line
"editor.minimap.enabled": false,
}
"#;
let res = replace_color_theme(settings_json, "Default Light+");
assert_eq!(
res,
r#"
{
"workbench.colorTheme": "Default Light+", // thcon:replace-line
"editor.minimap.enabled": false,
}
"#
);
}
#[test]
fn replace_color_no_trailing_comma() {
let settings_json = r#"
{
"editor.minimap.enabled": false,
"workbench.colorTheme" :"Default Dark+" // thcon:replace-line
}
"#;
let res = replace_color_theme(settings_json, "Default Light+");
assert_eq!(
res,
r#"
{
"editor.minimap.enabled": false,
"workbench.colorTheme" :"Default Light+" // thcon:replace-line
}
"#
);
}