1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
//! Switches [alacritty](https://github.com/alacritty/alacritty) color schemes
//!
//! Since alacritty is configured via [yaml](https://yaml.org/), using anchors and aliases is the
//! simplest way of managing color schemes.
//!
//! ## Usage
//! In your `alacritty.yml`, define your colors
//!
//! ```yaml
//! # define your color themes:
//!
//! solarized: &solarized_dark
//! # ^^^^^^^^^^^^^^ - use this name in thcon.toml
//! primary:
//! background: '0x002b36'
//! foreground: '0x839496'
//! # ... the normal contents of a `colors` object
//!
//! light_solarized: &solarized_light:
//! # ^^^^^^^^^^^^^^^ - use this name in thcon.toml
//! primary:
//! background: '0xfdf6e3'
//! foreground: '0x586e75'
//!
//! # then choose your color scheme one last time:
//! colors: *solarized_light # thcon:replace-line
//!
//! # thcon will manage the line ending in `thcon:replace-line`
//! # to swap alacritty color schemes
//! ```
//!
//! In your `thcon.toml`, define light and dark themes based on the `&anchor`s defined above:
//!
//! ```toml
//! [alacritty]
//! dark = "solarized_dark"
//! light = "solarized_light"
//!
//! # optionally, tell thcon where your alacritty config is stored
//! config = "/path/to/alacritty.yml"
//! ```
//!
//! ## `thcon.toml` Schema
//! Section: `alacritty`
//!
//! | Key | Type | Description | Default |
//! | --- | ---- | ----------- | -------- |
//! | `disabled` | boolean | `true` to disable theming of this app, otherwise `false` | `false` |
//! | `dark` | string | The YAML anchor (declared in `alacritty.yml`) used for dark mode | (none) |
//! | `light` | string | The YAML anchor (declared in `alacritty.yml`) used for light mode | (none) |
//! | `config` | string | Absolute path to your `alacritty.yml` file | (see below) |
//!
//! ### Default value for `config`
//! Thcon checks all default locations that `alacritty` [defines for alacritty.yml](https://github.com/alacritty/alacritty#configuration):
//!
//! * Windows: `%APPDATA%\alacritty\alacritty.yml`
//! * Other platforms:
//! 1. `$XDG_CONFIG_HOME/alacritty/alacritty.yml`
//! 2. `$XDG_CONFIG_HOME/alacritty.yml`
//! 3. `$HOME/.config/alacritty/alacritty.yml`
//! 4. `$HOME/.alacritty.yml`
use std::fs;
use std::path::PathBuf;
use crate::config::Config as ThconConfig;
use crate::operation::Operation;
use crate::themeable::{ConfigError, ConfigState, Themeable};
use crate::AppConfig;
use crate::Disableable;
use anyhow::{Context, Result};
use log::{debug, error};
use regex::{Captures, Regex};
use serde::Deserialize;
#[derive(Debug, Deserialize, Disableable, AppConfig)]
pub struct _Config {
light: String,
dark: String,
config: Option<String>,
#[serde(default)]
disabled: bool,
}
#[derive(Debug, Deserialize)]
pub struct Alacritty {}
impl Themeable for Alacritty {
fn config_state(&self, config: &ThconConfig) -> ConfigState {
ConfigState::with_manual_config(config.alacritty.as_ref().map(|c| c.inner.as_ref()))
}
fn switch(&self, config: &ThconConfig, operation: &Operation) -> Result<()> {
let config = match self.config_state(config) {
ConfigState::NoDefault => {
return Err(ConfigError::RequiresManualConfig("alacritty").into())
}
ConfigState::Default => unreachable!(),
ConfigState::Disabled => return Ok(()),
ConfigState::Enabled => config.alacritty.as_ref().unwrap().unwrap_inner_left(),
};
let theme = match operation {
Operation::Darken => &config.dark,
Operation::Lighten => &config.light,
};
let alacritty_yaml = match alacritty_config() {
Some(path) => path,
None => {
let couldnt_find = String::from("Couldn't find alacritty.yml");
let message = match &config.config {
Some(_path) => couldnt_find,
None => format!("{}; consider adding `config` property to `[alacritty]` section of `thcon.toml`", couldnt_find),
};
error!("{}", message);
return Ok(());
}
};
debug!(
"Reading/writing alacritty.yml at {}",
alacritty_yaml.display()
);
match fs::read_to_string(&alacritty_yaml)
.with_context(|| format!("Unable to read settings from {}", &alacritty_yaml.display()))
{
Ok(settings) => {
let theme_regex = Regex::new(
r#"^(?P<prefix>"?colors"?\s*:\s*"?)(?P<v>[^\s]+)(?P<suffix>"?,?\s*#\s*thcon:replace-line)"#,
)?;
let modified_lines: Vec<String> = settings
.lines()
.map(|line| {
theme_regex
.replace(line, |caps: &Captures| {
format!("{}*{}{}", &caps["prefix"], theme, &caps["suffix"])
})
.into_owned()
})
.collect();
let settings = modified_lines.join("\n");
fs::write(&alacritty_yaml, settings).with_context(|| {
format!("Unable to write settings to {}", &alacritty_yaml.display())
})
}
Err(e) => {
error!("Unable to read settings: {}", e);
Err(e)
}
}
}
}
// copied from upstream `alacritty`:
// https://github.com/alacritty/alacritty/blob/5a3bf69e3fd771271921f62219cdb8f920db39ee/alacritty/src/config/mod.rs#L236-L274
/// Get the location of the first found default config file paths
/// according to the following order:
///
/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml
/// 2. $XDG_CONFIG_HOME/alacritty.yml
/// 3. $HOME/.config/alacritty/alacritty.yml
/// 4. $HOME/.alacritty.yml
#[cfg(not(windows))]
fn alacritty_config() -> Option<PathBuf> {
use std::env;
// Try using XDG location by default.
xdg::BaseDirectories::with_prefix("alacritty")
.ok()
.and_then(|xdg| xdg.find_config_file("alacritty.yml"))
.or_else(|| {
xdg::BaseDirectories::new()
.ok()
.and_then(|fallback| fallback.find_config_file("alacritty.yml"))
})
.or_else(|| {
if let Ok(home) = env::var("HOME") {
// Fallback path: $HOME/.config/alacritty/alacritty.yml.
let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
if fallback.exists() {
return Some(fallback);
}
// Fallback path: $HOME/.alacritty.yml.
let fallback = PathBuf::from(&home).join(".alacritty.yml");
if fallback.exists() {
return Some(fallback);
}
}
None
})
}
// copied from upstream `alacritty`:
// https://github.com/alacritty/alacritty/blob/5a3bf69e3fd771271921f62219cdb8f920db39ee/alacritty/src/config/mod.rs#L236-L274
#[cfg(windows)]
fn alacritty_config() -> Option<PathBuf> {
dirs::config_dir()
.map(|path| path.join("alacritty\\alacritty.yml"))
.filter(|new| new.exists())
}