1use crate::{
3 buffer::Buffer,
4 editor::{Action, Actions},
5 key::Input,
6 mode::normal_mode,
7 term::{Color, Styles},
8 trie::Trie,
9 util::parent_dir_containing,
10};
11use serde::{de, Deserialize, Deserializer};
12use std::{collections::HashMap, env, fs, io, iter::successors, path::Path};
13use tracing::{error, warn};
14
15pub const DEFAULT_CONFIG: &str = include_str!("../data/config.toml");
16
17pub const TK_DEFAULT: &str = "default";
18pub const TK_DOT: &str = "dot";
19pub const TK_LOAD: &str = "load";
20pub const TK_EXEC: &str = "exec";
21
22pub(crate) fn config_path() -> String {
23 let home = env::var("HOME").unwrap();
24 format!("{home}/.ad/config.toml")
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
29pub struct Config {
30 pub tabstop: usize,
31 pub expand_tab: bool,
32 pub auto_mount: bool,
33 pub match_indent: bool,
34 pub status_timeout: u64,
35 pub double_click_ms: u64,
36 pub minibuffer_lines: usize,
37 pub find_command: String,
38
39 #[serde(default)]
40 pub colorscheme: ColorScheme,
41 #[serde(default)]
42 pub tree_sitter: TsConfig,
43 #[serde(default)]
44 pub languages: Vec<LangConfig>,
45 #[serde(default)]
46 pub keys: KeyBindings,
47}
48
49impl Default for Config {
50 fn default() -> Self {
51 toml::from_str(DEFAULT_CONFIG).unwrap()
52 }
53}
54
55impl Config {
56 pub fn try_load() -> Result<Self, String> {
58 let home = env::var("HOME").unwrap();
59 let path = config_path();
60
61 let mut cfg = match fs::read_to_string(&path) {
62 Ok(s) => match toml::from_str(&s) {
63 Ok(cfg) => cfg,
64 Err(e) => return Err(format!("Invalid config file: {e}")),
65 },
66
67 Err(e) if e.kind() == io::ErrorKind::NotFound => {
68 if fs::create_dir_all(format!("{home}/.ad")).is_ok() {
69 if let Err(e) = fs::write(path, DEFAULT_CONFIG) {
70 error!("unable to write default config file: {e}");
71 }
72 }
73
74 Config::default()
75 }
76
77 Err(e) => return Err(format!("Unable to load config file: {e}")),
78 };
79
80 for style in cfg.colorscheme.syntax.values_mut() {
82 style.bg = style.bg.or(Some(cfg.colorscheme.bg));
83 }
84
85 for s in [
87 &mut cfg.tree_sitter.parser_dir,
88 &mut cfg.tree_sitter.syntax_query_dir,
89 ] {
90 if s.starts_with("~/") {
91 *s = s.replacen("~", &home, 1);
92 }
93 }
94
95 Ok(cfg)
96 }
97
98 pub fn ts_lang_for_buffer(&self, b: &Buffer) -> Option<&str> {
100 let os_ext = b.path()?.extension().unwrap_or_default();
101 let ext = os_ext.to_str().unwrap_or_default();
102 let first_line = b.line(0).map(|l| l.to_string()).unwrap_or_default();
103
104 self.languages
105 .iter()
106 .find(|c| {
107 c.extensions.iter().any(|e| e == ext)
108 || c.first_lines.iter().any(|l| first_line.starts_with(l))
109 })
110 .map(|c| c.name.as_str())
111 }
112
113 pub(crate) fn update_from(&mut self, input: &str) -> Result<(), String> {
114 warn!("ignoring runtime config update: {input}");
115
116 Err("runtime config updates are not currently supported".to_owned())
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
125pub struct ColorScheme {
126 pub bg: Color,
127 pub fg: Color,
128 pub bar_bg: Color,
129 pub signcol_fg: Color,
130 pub minibuffer_hl: Color,
131 pub syntax: HashMap<String, Styles>,
132}
133
134impl Default for ColorScheme {
135 fn default() -> Self {
136 let bg: Color = "#1B1720".try_into().unwrap();
137 let fg: Color = "#E6D29E".try_into().unwrap();
138 let dot_bg: Color = "#336677".try_into().unwrap();
139 let load_bg: Color = "#957FB8".try_into().unwrap();
140 let exec_bg: Color = "#Bf616A".try_into().unwrap();
141 let comment: Color = "#624354".try_into().unwrap();
142 let constant: Color = "#FF9E3B".try_into().unwrap();
143 let function: Color = "#957FB8".try_into().unwrap();
144 let keyword: Color = "#Bf616A".try_into().unwrap();
145 let module: Color = "#2D4F67".try_into().unwrap();
146 let punctuation: Color = "#9CABCA".try_into().unwrap();
147 let string: Color = "#61DCA5".try_into().unwrap();
148 let type_: Color = "#7E9CD8".try_into().unwrap();
149 let variable: Color = "#DCA561".try_into().unwrap();
150
151 #[rustfmt::skip]
152 let syntax = [
153 (TK_DEFAULT, Styles { fg: Some(fg), bg: Some(bg), ..Default::default() }),
154 (TK_DOT, Styles { fg: Some(fg), bg: Some(dot_bg), ..Default::default() }),
155 (TK_LOAD, Styles { fg: Some(fg), bg: Some(load_bg), ..Default::default() }),
156 (TK_EXEC, Styles { fg: Some(fg), bg: Some(exec_bg), ..Default::default() }),
157 ("character", Styles { fg: Some(string), bold: true, ..Default::default() }),
158 ("comment", Styles { fg: Some(comment), italic: true, ..Default::default() }),
159 ("constant", Styles { fg: Some(constant), ..Default::default() }),
160 ("function", Styles { fg: Some(function), ..Default::default() }),
161 ("keyword", Styles { fg: Some(keyword), ..Default::default() }),
162 ("module", Styles { fg: Some(module), ..Default::default() }),
163 ("punctuation", Styles { fg: Some(punctuation), ..Default::default() }),
164 ("string", Styles { fg: Some(string), ..Default::default() }),
165 ("type", Styles { fg: Some(type_), ..Default::default() }),
166 ("variable", Styles { fg: Some(variable), ..Default::default() }),
167 ]
168 .map(|(s, v)| (s.to_string(), v))
169 .into_iter()
170 .collect();
171
172 Self {
173 bg,
174 fg,
175 bar_bg: "#4E415C".try_into().unwrap(),
176 signcol_fg: "#544863".try_into().unwrap(),
177 minibuffer_hl: "#3E3549".try_into().unwrap(),
178 syntax,
179 }
180 }
181}
182
183impl ColorScheme {
184 pub fn styles_for(&self, tag: &str) -> &Styles {
195 successors(Some(tag), |s| Some(s.rsplit_once('.')?.0))
196 .find_map(|k| self.syntax.get(k))
197 .or(self.syntax.get(TK_DEFAULT))
198 .expect("to have default styles")
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
203pub struct TsConfig {
204 pub parser_dir: String,
205 pub syntax_query_dir: String,
206}
207
208impl Default for TsConfig {
209 fn default() -> Self {
210 let home = env::var("HOME").unwrap();
211
212 TsConfig {
213 parser_dir: format!("{home}/.ad/tree-sitter/parsers"),
214 syntax_query_dir: format!("{home}/.ad/tree-sitter/queries"),
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
220pub struct LangConfig {
221 pub name: String,
222 pub extensions: Vec<String>,
223 #[serde(default)]
224 pub first_lines: Vec<String>,
225 #[serde(default)]
226 pub lsp: Option<LspConfig>,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
231pub struct LspConfig {
232 pub command: String,
234 #[serde(default)]
236 pub args: Vec<String>,
237 pub roots: Vec<String>,
239}
240
241impl LspConfig {
242 pub fn root_for_buffer<'a>(&self, b: &'a Buffer) -> Option<&'a Path> {
243 let d = b.dir()?;
244 for root in self.roots.iter() {
245 if let Some(p) = parent_dir_containing(d, root) {
246 return Some(p);
247 }
248 }
249
250 None
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
255pub struct KeyBindings {
256 #[serde(deserialize_with = "de_serde_trie")]
257 pub normal: Trie<Input, KeyAction>,
258}
259
260impl Default for KeyBindings {
261 fn default() -> Self {
262 KeyBindings {
263 normal: Trie::from_pairs(Vec::new()).unwrap(),
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
269#[serde(untagged)]
270pub enum KeyAction {
271 External { run: String },
272}
273
274impl KeyAction {
275 pub fn as_actions(&self) -> Actions {
276 match self {
277 Self::External { run } => Actions::Single(Action::ExecuteString { s: run.clone() }),
278 }
279 }
280}
281
282pub fn de_serde_trie<'de, D>(deserializer: D) -> Result<Trie<Input, KeyAction>, D::Error>
283where
284 D: Deserializer<'de>,
285{
286 let raw_map: HashMap<String, KeyAction> = Deserialize::deserialize(deserializer)?;
287 let mut raw = Vec::with_capacity(raw_map.len());
288
289 for (k, action) in raw_map.into_iter() {
290 let keys: Vec<Input> = k
291 .split_whitespace()
292 .filter_map(|s| {
293 if s.len() == 1 {
294 let c = s.chars().next().unwrap();
295 if c.is_whitespace() {
296 None
297 } else {
298 Some(Input::Char(c))
299 }
300 } else {
301 match s {
302 "<space>" => Some(Input::Char(' ')),
303 _ => None,
304 }
305 }
306 })
307 .collect();
308
309 raw.push((keys, action));
310 }
311
312 let nm = normal_mode();
315 for (keys, _) in raw.iter() {
316 if nm.keymap.contains_key_or_prefix(keys) {
317 let mut s = String::new();
318 for k in keys {
319 if let Input::Char(c) = k {
320 s.push(*c);
321 }
322 }
323
324 return Err(de::Error::custom(format!(
325 "mapping '{s}' collides with a Normal mode mapping"
326 )));
327 }
328 }
329
330 Trie::from_pairs(raw).map_err(de::Error::custom)
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn default_loads() {
339 Config::default(); }
341}