xi_core_lib/
config.rs

1// Copyright 2017 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16use std::error::Error;
17use std::fmt;
18use std::fs;
19use std::io::{self, Read};
20use std::path::{Path, PathBuf};
21use std::sync::Arc;
22
23use serde::de::{self, Deserialize};
24use serde_json::{self, Value};
25use toml;
26
27use crate::syntax::{LanguageId, Languages};
28use crate::tabs::{BufferId, ViewId};
29
30/// Loads the included base config settings.
31fn load_base_config() -> Table {
32    fn load(default: &str) -> Table {
33        table_from_toml_str(default).expect("default configs must load")
34    }
35
36    fn platform_overrides() -> Option<Table> {
37        if cfg!(test) {
38            // Exit early if we are in tests and never have platform overrides.
39            // This makes sure we have a stable test environment.
40            None
41        } else if cfg!(windows) {
42            let toml = include_str!("../assets/windows.toml");
43            Some(load(toml))
44        } else {
45            // All other platorms
46            None
47        }
48    }
49
50    let base_toml: &str = include_str!("../assets/defaults.toml");
51    let mut base = load(base_toml);
52    if let Some(overrides) = platform_overrides() {
53        for (k, v) in overrides.iter() {
54            base.insert(k.to_owned(), v.to_owned());
55        }
56    }
57    base
58}
59
60/// A map of config keys to settings
61pub type Table = serde_json::Map<String, Value>;
62
63/// A `ConfigDomain` describes a level or category of user settings.
64#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum ConfigDomain {
67    /// The general user preferences
68    General,
69    /// The overrides for a particular syntax.
70    Language(LanguageId),
71    /// The user overrides for a particular buffer
72    UserOverride(BufferId),
73    /// The system's overrides for a particular buffer. Only used internally.
74    #[serde(skip_deserializing)]
75    SysOverride(BufferId),
76}
77
78/// The external RPC sends `ViewId`s, which we convert to `BufferId`s
79/// internally.
80#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
81#[serde(rename_all = "snake_case")]
82pub enum ConfigDomainExternal {
83    General,
84    //TODO: remove this old name
85    Syntax(LanguageId),
86    Language(LanguageId),
87    UserOverride(ViewId),
88}
89
90/// The errors that can occur when managing configs.
91#[derive(Debug)]
92pub enum ConfigError {
93    /// The config domain was not recognized.
94    UnknownDomain(String),
95    /// A file-based config could not be loaded or parsed.
96    Parse(PathBuf, toml::de::Error),
97    /// The config table contained unexpected values
98    UnexpectedItem(serde_json::Error),
99    /// An Io Error
100    Io(io::Error),
101}
102
103/// Represents the common pattern of default settings masked by
104/// user settings.
105#[derive(Debug)]
106pub struct ConfigPair {
107    /// A static default configuration, which will never change.
108    base: Option<Arc<Table>>,
109    /// A variable, user provided configuration. Items here take
110    /// precedence over items in `base`.
111    user: Option<Arc<Table>>,
112    /// A snapshot of base + user.
113    cache: Arc<Table>,
114}
115
116/// The language associated with a given buffer; this is always detected
117/// but can also be manually set by the user.
118#[derive(Debug, Clone)]
119struct LanguageTag {
120    detected: LanguageId,
121    user: Option<LanguageId>,
122}
123
124#[derive(Debug)]
125pub struct ConfigManager {
126    /// A map of `ConfigPairs` (defaults + overrides) for all in-use domains.
127    configs: HashMap<ConfigDomain, ConfigPair>,
128    /// The currently loaded `Languages`.
129    languages: Languages,
130    /// The language assigned to each buffer.
131    buffer_tags: HashMap<BufferId, LanguageTag>,
132    /// The configs for any open buffers
133    buffer_configs: HashMap<BufferId, BufferConfig>,
134    /// If using file-based config, this is the base config directory
135    /// (perhaps `$HOME/.config/xi`, by default).
136    config_dir: Option<PathBuf>,
137    /// An optional client-provided path for bundled resources, such
138    /// as plugins and themes.
139    extras_dir: Option<PathBuf>,
140}
141
142/// A collection of config tables representing a hierarchy, with each
143/// table's keys superseding keys in preceding tables.
144#[derive(Debug, Clone, Default)]
145struct TableStack(Vec<Arc<Table>>);
146
147/// A frozen collection of settings, and their sources.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct Config<T> {
150    /// The underlying set of config tables that contributed to this
151    /// `Config` instance. Used for diffing.
152    #[serde(skip)]
153    source: TableStack,
154    /// The settings themselves, deserialized into some concrete type.
155    pub items: T,
156}
157
158fn deserialize_tab_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
159where
160    D: serde::Deserializer<'de>,
161{
162    let tab_size = usize::deserialize(deserializer)?;
163    if tab_size == 0 {
164        Err(de::Error::invalid_value(
165            de::Unexpected::Unsigned(tab_size as u64),
166            &"tab_size must be at least 1",
167        ))
168    } else {
169        Ok(tab_size)
170    }
171}
172
173/// The concrete type for buffer-related settings.
174#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
175pub struct BufferItems {
176    pub line_ending: String,
177    #[serde(deserialize_with = "deserialize_tab_size")]
178    pub tab_size: usize,
179    pub translate_tabs_to_spaces: bool,
180    pub use_tab_stops: bool,
181    pub font_face: String,
182    pub font_size: f32,
183    pub auto_indent: bool,
184    pub scroll_past_end: bool,
185    pub wrap_width: usize,
186    pub word_wrap: bool,
187    pub autodetect_whitespace: bool,
188    pub surrounding_pairs: Vec<(String, String)>,
189    pub save_with_newline: bool,
190}
191
192pub type BufferConfig = Config<BufferItems>;
193
194impl ConfigPair {
195    /// Creates a new `ConfigPair` with the provided base config.
196    fn with_base<T: Into<Option<Table>>>(table: T) -> Self {
197        let base = table.into().map(Arc::new);
198        let cache = base.clone().unwrap_or_default();
199        ConfigPair { base, cache, user: None }
200    }
201
202    /// Returns a new `ConfigPair` with the provided base and the current
203    /// user config.
204    fn new_with_base<T: Into<Option<Table>>>(&self, table: T) -> Self {
205        let mut new_self = ConfigPair::with_base(table);
206        new_self.user = self.user.clone();
207        new_self.rebuild();
208        new_self
209    }
210
211    fn set_table(&mut self, user: Table) {
212        self.user = Some(Arc::new(user));
213        self.rebuild();
214    }
215
216    /// Returns the `Table` produced by updating `self.user` with the contents
217    /// of `user`, deleting null entries.
218    fn table_for_update(&self, user: Table) -> Table {
219        let mut new_user: Table =
220            self.user.as_ref().map(|arc| arc.as_ref().clone()).unwrap_or_default();
221        for (k, v) in user {
222            if v.is_null() {
223                new_user.remove(&k);
224            } else {
225                new_user.insert(k, v);
226            }
227        }
228        new_user
229    }
230
231    fn rebuild(&mut self) {
232        let mut cache = self.base.clone().unwrap_or_default();
233        if let Some(ref user) = self.user {
234            for (k, v) in user.iter() {
235                Arc::make_mut(&mut cache).insert(k.to_owned(), v.clone());
236            }
237        }
238        self.cache = cache;
239    }
240}
241
242impl ConfigManager {
243    pub fn new(config_dir: Option<PathBuf>, extras_dir: Option<PathBuf>) -> Self {
244        let base = load_base_config();
245        let mut defaults = HashMap::new();
246        defaults.insert(ConfigDomain::General, ConfigPair::with_base(base));
247        ConfigManager {
248            configs: defaults,
249            buffer_tags: HashMap::new(),
250            buffer_configs: HashMap::new(),
251            languages: Languages::default(),
252            config_dir,
253            extras_dir,
254        }
255    }
256
257    /// The path of the user's config file, if present.
258    pub(crate) fn base_config_file_path(&self) -> Option<PathBuf> {
259        let config_file = self.config_dir.as_ref().map(|p| p.join("preferences.xiconfig"));
260        let exists = config_file.as_ref().map(|p| p.exists()).unwrap_or(false);
261        if exists {
262            config_file
263        } else {
264            None
265        }
266    }
267
268    pub(crate) fn get_plugin_paths(&self) -> Vec<PathBuf> {
269        let config_dir = self.config_dir.as_ref().map(|p| p.join("plugins"));
270        [self.extras_dir.as_ref(), config_dir.as_ref()]
271            .iter()
272            .flat_map(|p| p.map(|p| p.to_owned()))
273            .filter(|p| p.exists())
274            .collect()
275    }
276
277    /// Adds a new buffer to the config manager, and returns the initial config
278    /// `Table` for that buffer. The `path` argument is used to determine
279    /// the buffer's default language.
280    ///
281    /// # Note: The caller is responsible for ensuring the config manager is
282    /// notified every time a buffer is added or removed.
283    ///
284    /// # Panics:
285    ///
286    /// Panics if `id` already exists.
287    pub(crate) fn add_buffer(&mut self, id: BufferId, path: Option<&Path>) -> Table {
288        let lang = path.and_then(|p| self.language_for_path(p)).unwrap_or_default();
289        let lang_tag = LanguageTag::new(lang);
290        assert!(self.buffer_tags.insert(id, lang_tag).is_none());
291        self.update_buffer_config(id).expect("new buffer must always have config")
292    }
293
294    /// Updates the default language for the given buffer.
295    ///
296    /// # Panics:
297    ///
298    /// Panics if `id` does not exist.
299    pub(crate) fn update_buffer_path(&mut self, id: BufferId, path: &Path) -> Option<Table> {
300        assert!(self.buffer_tags.contains_key(&id));
301        let lang = self.language_for_path(path).unwrap_or_default();
302        let has_changed = self.buffer_tags.get_mut(&id).map(|tag| tag.set_detected(lang)).unwrap();
303
304        if has_changed {
305            self.update_buffer_config(id)
306        } else {
307            None
308        }
309    }
310
311    /// Instructs the `ConfigManager` to stop tracking a given buffer.
312    ///
313    /// # Panics:
314    ///
315    /// Panics if `id` does not exist.
316    pub(crate) fn remove_buffer(&mut self, id: BufferId) {
317        self.buffer_tags.remove(&id).expect("remove key must exist");
318        self.buffer_configs.remove(&id);
319        // TODO: remove any overrides
320    }
321
322    /// Sets a specific language for the given buffer. This is used if the
323    /// user selects a specific language in the frontend, for instance.
324    pub(crate) fn override_language(
325        &mut self,
326        id: BufferId,
327        new_lang: LanguageId,
328    ) -> Option<Table> {
329        let has_changed = self
330            .buffer_tags
331            .get_mut(&id)
332            .map(|tag| tag.set_user(Some(new_lang)))
333            .expect("buffer must exist");
334        if has_changed {
335            self.update_buffer_config(id)
336        } else {
337            None
338        }
339    }
340
341    fn update_buffer_config(&mut self, id: BufferId) -> Option<Table> {
342        let new_config = self.generate_buffer_config(id);
343        let changes = new_config.changes_from(self.buffer_configs.get(&id));
344        self.buffer_configs.insert(id, new_config);
345        changes
346    }
347
348    fn update_all_buffer_configs(&mut self) -> Vec<(BufferId, Table)> {
349        self.buffer_configs
350            .keys()
351            .cloned()
352            .collect::<Vec<_>>()
353            .into_iter()
354            .flat_map(|k| self.update_buffer_config(k).map(|c| (k, c)))
355            .collect::<Vec<_>>()
356    }
357
358    fn generate_buffer_config(&mut self, id: BufferId) -> BufferConfig {
359        // it's possible for a buffer to be tagged with since-removed language
360        let lang = self
361            .buffer_tags
362            .get(&id)
363            .map(LanguageTag::resolve)
364            .and_then(|name| self.languages.language_for_name(name))
365            .map(|l| l.name.clone());
366        let mut configs = Vec::new();
367
368        configs.push(self.configs.get(&ConfigDomain::General));
369        if let Some(s) = lang {
370            configs.push(self.configs.get(&s.into()))
371        };
372        configs.push(self.configs.get(&ConfigDomain::SysOverride(id)));
373        configs.push(self.configs.get(&ConfigDomain::UserOverride(id)));
374
375        let configs = configs
376            .iter()
377            .flat_map(Option::iter)
378            .map(|c| c.cache.clone())
379            .rev()
380            .collect::<Vec<_>>();
381
382        let stack = TableStack(configs);
383        stack.into_config()
384    }
385
386    /// Returns a reference to the `BufferConfig` for this buffer.
387    ///
388    /// # Panics:
389    ///
390    /// Panics if `id` does not exist. The caller is responsible for ensuring
391    /// that the `ConfigManager` is kept up to date as buffers are added/removed.
392    pub(crate) fn get_buffer_config(&self, id: BufferId) -> &BufferConfig {
393        self.buffer_configs.get(&id).unwrap()
394    }
395
396    /// Returns the language associated with this buffer.
397    ///
398    /// # Panics:
399    ///
400    /// Panics if `id` does not exist.
401    pub(crate) fn get_buffer_language(&self, id: BufferId) -> LanguageId {
402        self.buffer_tags.get(&id).map(LanguageTag::resolve).unwrap()
403    }
404
405    /// Set the available `LanguageDefinition`s. Overrides any previous values.
406    pub fn set_languages(&mut self, languages: Languages) {
407        // remove base configs for any removed languages
408        self.languages.difference(&languages).iter().for_each(|lang| {
409            let domain: ConfigDomain = lang.name.clone().into();
410            if let Some(pair) = self.configs.get_mut(&domain) {
411                *pair = pair.new_with_base(None);
412            }
413        });
414
415        for language in languages.iter() {
416            let lang_id = language.name.clone();
417            let domain: ConfigDomain = lang_id.into();
418            let default_config = language.default_config.clone();
419            self.configs
420                .entry(domain.clone())
421                .and_modify(|c| *c = c.new_with_base(default_config.clone()))
422                .or_insert_with(|| ConfigPair::with_base(default_config));
423            if let Some(table) = self.load_user_config_file(&domain) {
424                // we can't report this error because we don't have a
425                // handle to the peer :|
426                let _ = self.set_user_config(domain, table);
427            }
428        }
429        //FIXME these changes are happening silently, which won't work once
430        //languages can by dynamically changed
431        self.languages = languages;
432        self.update_all_buffer_configs();
433    }
434
435    fn load_user_config_file(&self, domain: &ConfigDomain) -> Option<Table> {
436        let path = self
437            .config_dir
438            .as_ref()
439            .map(|p| p.join(domain.file_stem()).with_extension("xiconfig"))?;
440
441        if !path.exists() {
442            return None;
443        }
444
445        match try_load_from_file(&path) {
446            Ok(t) => Some(t),
447            Err(e) => {
448                error!("Error loading config: {:?}", e);
449                None
450            }
451        }
452    }
453
454    pub fn language_for_path(&self, path: &Path) -> Option<LanguageId> {
455        self.languages.language_for_path(path).map(|lang| lang.name.clone())
456    }
457
458    /// Sets the config for the given domain, removing any existing config.
459    /// Returns a `Vec` of individual buffer config changes that result from
460    /// this update, or a `ConfigError` if `config` is poorly formed.
461    pub fn set_user_config(
462        &mut self,
463        domain: ConfigDomain,
464        config: Table,
465    ) -> Result<Vec<(BufferId, Table)>, ConfigError> {
466        self.check_table(&config)?;
467        self.configs
468            .entry(domain.clone())
469            .or_insert_with(|| ConfigPair::with_base(None))
470            .set_table(config);
471        Ok(self.update_all_buffer_configs())
472    }
473
474    /// Returns the `Table` produced by applying `changes` to the current user
475    /// config for the given `ConfigDomain`.
476    ///
477    /// # Note:
478    ///
479    /// When the user modifys a config _file_, the whole file is read,
480    /// and we can just overwrite any existing user config with the newly
481    /// loaded one.
482    ///
483    /// When the client modifies a config via the RPC mechanism, however,
484    /// this isn't the case. Instead of sending all config settings with
485    /// each update, the client just sends the keys/values they would like
486    /// to change. When they would like to remove a previously set key,
487    /// they send `Null` as the value for that key.
488    ///
489    /// This function creates a new table which is the product of updating
490    /// any existing table by applying the client's changes. This new table can
491    /// then be passed to `Self::set_user_config(..)`, as if it were loaded
492    /// from disk.
493    pub(crate) fn table_for_update(&mut self, domain: ConfigDomain, changes: Table) -> Table {
494        self.configs
495            .entry(domain.clone())
496            .or_insert_with(|| ConfigPair::with_base(None))
497            .table_for_update(changes)
498    }
499
500    /// Returns the `ConfigDomain` relevant to a given file, if one exists.
501    pub fn domain_for_path(&self, path: &Path) -> Option<ConfigDomain> {
502        if path.extension().map(|e| e != "xiconfig").unwrap_or(true) {
503            return None;
504        }
505        match path.file_stem().and_then(|s| s.to_str()) {
506            Some("preferences") => Some(ConfigDomain::General),
507            Some(name) if self.languages.language_for_name(&name).is_some() => {
508                let lang =
509                    self.languages.language_for_name(&name).map(|lang| lang.name.clone()).unwrap();
510                Some(ConfigDomain::Language(lang))
511            }
512            //TODO: plugin configs
513            _ => None,
514        }
515    }
516
517    fn check_table(&self, table: &Table) -> Result<(), ConfigError> {
518        let defaults = self
519            .configs
520            .get(&ConfigDomain::General)
521            .and_then(|pair| pair.base.clone())
522            .expect("general domain must have defaults");
523        let mut defaults: Table = defaults.as_ref().clone();
524        for (k, v) in table.iter() {
525            // changes can include 'null', which means clear field
526            if v.is_null() {
527                continue;
528            }
529            defaults.insert(k.to_owned(), v.to_owned());
530        }
531        let _: BufferItems = serde_json::from_value(defaults.into())?;
532        Ok(())
533    }
534
535    /// Path to themes sub directory inside config directory.
536    /// Creates one if not present.
537    pub(crate) fn get_themes_dir(&self) -> Option<PathBuf> {
538        let themes_dir = self.config_dir.as_ref().map(|p| p.join("themes"));
539
540        if let Some(p) = themes_dir {
541            if p.exists() {
542                return Some(p);
543            }
544            if fs::DirBuilder::new().create(&p).is_ok() {
545                return Some(p);
546            }
547        }
548        None
549    }
550
551    /// Path to plugins sub directory inside config directory.
552    /// Creates one if not present.
553    pub(crate) fn get_plugins_dir(&self) -> Option<PathBuf> {
554        let plugins_dir = self.config_dir.as_ref().map(|p| p.join("plugins"));
555
556        if let Some(p) = plugins_dir {
557            if p.exists() {
558                return Some(p);
559            }
560            if fs::DirBuilder::new().create(&p).is_ok() {
561                return Some(p);
562            }
563        }
564        None
565    }
566}
567
568impl TableStack {
569    /// Create a single table representing the final config values.
570    fn collate(&self) -> Table {
571        // NOTE: This is fairly expensive; a future optimization would borrow
572        // from the underlying collections.
573        let mut out = Table::new();
574        for table in &self.0 {
575            for (k, v) in table.iter() {
576                if !out.contains_key(k) {
577                    // cloning these objects feels a bit gross, we could
578                    // improve this by implementing Deserialize for TableStack.
579                    out.insert(k.to_owned(), v.to_owned());
580                }
581            }
582        }
583        out
584    }
585
586    /// Converts the underlying tables into a static `Config` instance.
587    fn into_config<T>(self) -> Config<T>
588    where
589        for<'de> T: Deserialize<'de>,
590    {
591        let out = self.collate();
592        let items: T = serde_json::from_value(out.into()).unwrap();
593        let source = self;
594        Config { source, items }
595    }
596
597    /// Walks the tables in priority order, returning the first
598    /// occurance of `key`.
599    fn get<S: AsRef<str>>(&self, key: S) -> Option<&Value> {
600        for table in &self.0 {
601            if let Some(v) = table.get(key.as_ref()) {
602                return Some(v);
603            }
604        }
605        None
606    }
607
608    /// Returns a new `Table` containing only those keys and values in `self`
609    /// which have changed from `other`.
610    fn diff(&self, other: &TableStack) -> Option<Table> {
611        let mut out: Option<Table> = None;
612        let this = self.collate();
613        for (k, v) in this.iter() {
614            if other.get(k) != Some(v) {
615                let out: &mut Table = out.get_or_insert(Table::new());
616                out.insert(k.to_owned(), v.to_owned());
617            }
618        }
619        out
620    }
621}
622
623impl<T> Config<T> {
624    pub fn to_table(&self) -> Table {
625        self.source.collate()
626    }
627}
628
629impl<'de, T: Deserialize<'de>> Config<T> {
630    /// Returns a `Table` of all the items in `self` which have different
631    /// values than in `other`.
632    pub fn changes_from(&self, other: Option<&Config<T>>) -> Option<Table> {
633        match other {
634            Some(other) => self.source.diff(&other.source),
635            None => self.source.collate().into(),
636        }
637    }
638}
639
640impl ConfigDomain {
641    fn file_stem(&self) -> &str {
642        match self {
643            ConfigDomain::General => "preferences",
644            ConfigDomain::Language(lang) => lang.as_ref(),
645            ConfigDomain::UserOverride(_) | ConfigDomain::SysOverride(_) => "we don't have files",
646        }
647    }
648}
649
650impl LanguageTag {
651    fn new(detected: LanguageId) -> Self {
652        LanguageTag { detected, user: None }
653    }
654
655    fn resolve(&self) -> LanguageId {
656        self.user.as_ref().unwrap_or(&self.detected).clone()
657    }
658
659    /// Set the detected language. Returns `true` if this changes the resolved
660    /// language.
661    fn set_detected(&mut self, detected: LanguageId) -> bool {
662        let before = self.resolve();
663        self.detected = detected;
664        before != self.resolve()
665    }
666
667    /// Set the user-specified language. Returns `true` if this changes
668    /// the resolved language.
669    #[allow(dead_code)]
670    fn set_user(&mut self, new_lang: Option<LanguageId>) -> bool {
671        let has_changed = self.user != new_lang;
672        self.user = new_lang;
673        has_changed
674    }
675}
676
677impl<T: PartialEq> PartialEq for Config<T> {
678    fn eq(&self, other: &Config<T>) -> bool {
679        self.items == other.items
680    }
681}
682
683impl From<LanguageId> for ConfigDomain {
684    fn from(src: LanguageId) -> ConfigDomain {
685        ConfigDomain::Language(src)
686    }
687}
688
689impl From<BufferId> for ConfigDomain {
690    fn from(src: BufferId) -> ConfigDomain {
691        ConfigDomain::UserOverride(src)
692    }
693}
694
695impl fmt::Display for ConfigError {
696    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
697        use self::ConfigError::*;
698        match *self {
699            UnknownDomain(ref s) => write!(f, "{}: {}", self.description(), s),
700            Parse(ref p, ref e) => write!(f, "{} ({:?}), {:?}", self.description(), p, e),
701            Io(ref e) => write!(f, "error loading config: {:?}", e),
702            UnexpectedItem(ref e) => write!(f, "{}", e),
703        }
704    }
705}
706
707impl Error for ConfigError {
708    fn description(&self) -> &str {
709        use self::ConfigError::*;
710        match *self {
711            UnknownDomain(..) => "unknown domain",
712            Parse(_, ref e) => e.description(),
713            Io(ref e) => e.description(),
714            UnexpectedItem(ref e) => e.description(),
715        }
716    }
717}
718
719impl From<io::Error> for ConfigError {
720    fn from(src: io::Error) -> ConfigError {
721        ConfigError::Io(src)
722    }
723}
724
725impl From<serde_json::Error> for ConfigError {
726    fn from(src: serde_json::Error) -> ConfigError {
727        ConfigError::UnexpectedItem(src)
728    }
729}
730
731/// Creates initial config directory structure
732pub(crate) fn init_config_dir(dir: &Path) -> io::Result<()> {
733    let builder = fs::DirBuilder::new();
734    builder.create(dir)?;
735    builder.create(dir.join("plugins"))?;
736    Ok(())
737}
738
739/// Attempts to load a config from a file. The config's domain is determined
740/// by the file name.
741pub(crate) fn try_load_from_file(path: &Path) -> Result<Table, ConfigError> {
742    let mut file = fs::File::open(&path)?;
743    let mut contents = String::new();
744    file.read_to_string(&mut contents)?;
745    table_from_toml_str(&contents).map_err(|e| ConfigError::Parse(path.to_owned(), e))
746}
747
748pub(crate) fn table_from_toml_str(s: &str) -> Result<Table, toml::de::Error> {
749    let table = toml::from_str(&s)?;
750    let table = from_toml_value(table).as_object().unwrap().to_owned();
751    Ok(table)
752}
753
754//adapted from https://docs.rs/crate/config/0.7.0/source/src/file/format/toml.rs
755/// Converts between toml (used to write config files) and json
756/// (used to store config values internally).
757fn from_toml_value(value: toml::Value) -> Value {
758    match value {
759        toml::Value::String(value) => value.to_owned().into(),
760        toml::Value::Float(value) => value.into(),
761        toml::Value::Integer(value) => value.into(),
762        toml::Value::Boolean(value) => value.into(),
763        toml::Value::Datetime(value) => value.to_string().into(),
764
765        toml::Value::Table(table) => {
766            let mut m = Table::new();
767            for (key, value) in table {
768                m.insert(key.clone(), from_toml_value(value));
769            }
770            m.into()
771        }
772
773        toml::Value::Array(array) => {
774            let mut l = Vec::new();
775            for value in array {
776                l.push(from_toml_value(value));
777            }
778            l.into()
779        }
780    }
781}
782
783#[cfg(test)]
784mod tests {
785    use super::*;
786    use crate::syntax::LanguageDefinition;
787
788    #[test]
789    fn test_overrides() {
790        let user_config = table_from_toml_str(r#"tab_size = 42"#).unwrap();
791        let rust_config = table_from_toml_str(r#"tab_size = 31"#).unwrap();
792
793        let lang_def = rust_lang_def(None);
794        let rust_id: LanguageId = "Rust".into();
795
796        let buf_id_1 = BufferId(1); // no language
797        let buf_id_2 = BufferId(2); // just rust
798        let buf_id_3 = BufferId(3); // rust, + system overrides
799
800        let mut manager = ConfigManager::new(None, None);
801        manager.set_languages(Languages::new(&[lang_def]));
802        manager.set_user_config(rust_id.clone().into(), rust_config).unwrap();
803        manager.set_user_config(ConfigDomain::General, user_config).unwrap();
804
805        let changes = json!({"tab_size": 67}).as_object().unwrap().to_owned();
806        manager.set_user_config(ConfigDomain::SysOverride(buf_id_3), changes).unwrap();
807
808        manager.add_buffer(buf_id_1, None);
809        manager.add_buffer(buf_id_2, Some(Path::new("file.rs")));
810        manager.add_buffer(buf_id_3, Some(Path::new("file2.rs")));
811
812        // system override
813        let config = manager.get_buffer_config(buf_id_1).to_owned();
814        assert_eq!(config.source.0.len(), 1);
815        assert_eq!(config.items.tab_size, 42);
816        let config = manager.get_buffer_config(buf_id_2).to_owned();
817        assert_eq!(config.items.tab_size, 31);
818        let config = manager.get_buffer_config(buf_id_3).to_owned();
819        assert_eq!(config.items.tab_size, 67);
820
821        // user override trumps everything
822        let changes = json!({"tab_size": 85}).as_object().unwrap().to_owned();
823        manager.set_user_config(ConfigDomain::UserOverride(buf_id_3), changes).unwrap();
824        let config = manager.get_buffer_config(buf_id_3);
825        assert_eq!(config.items.tab_size, 85);
826    }
827
828    #[test]
829    fn test_config_domain_serde() {
830        assert_eq!(serde_json::to_string(&ConfigDomain::General).unwrap(), "\"general\"");
831        let d = ConfigDomainExternal::UserOverride(ViewId(1));
832        assert_eq!(serde_json::to_string(&d).unwrap(), "{\"user_override\":\"view-id-1\"}");
833        let d = ConfigDomain::Language("Swift".into());
834        assert_eq!(serde_json::to_string(&d).unwrap(), "{\"language\":\"Swift\"}");
835    }
836
837    #[test]
838    fn test_diff() {
839        let conf1 = r#"
840tab_size = 42
841translate_tabs_to_spaces = true
842"#;
843        let conf1 = table_from_toml_str(conf1).unwrap();
844
845        let conf2 = r#"
846tab_size = 6
847translate_tabs_to_spaces = true
848"#;
849        let conf2 = table_from_toml_str(conf2).unwrap();
850
851        let stack1 = TableStack(vec![Arc::new(conf1)]);
852        let stack2 = TableStack(vec![Arc::new(conf2)]);
853        let diff = stack1.diff(&stack2).unwrap();
854        assert!(diff.len() == 1);
855        assert_eq!(diff.get("tab_size"), Some(&42.into()));
856    }
857
858    #[test]
859    fn test_updating_in_place() {
860        let mut manager = ConfigManager::new(None, None);
861        let buf_id = BufferId(1);
862        manager.add_buffer(buf_id, None);
863        assert_eq!(manager.get_buffer_config(buf_id).items.font_size, 14.);
864        let changes = json!({"font_size": 69, "font_face": "nice"}).as_object().unwrap().to_owned();
865        let table = manager.table_for_update(ConfigDomain::General, changes);
866        manager.set_user_config(ConfigDomain::General, table).unwrap();
867        assert_eq!(manager.get_buffer_config(buf_id).items.font_size, 69.);
868
869        // null values in updates removes keys
870        let changes = json!({ "font_size": Value::Null }).as_object().unwrap().to_owned();
871        let table = manager.table_for_update(ConfigDomain::General, changes);
872        manager.set_user_config(ConfigDomain::General, table).unwrap();
873        assert_eq!(manager.get_buffer_config(buf_id).items.font_size, 14.);
874        assert_eq!(manager.get_buffer_config(buf_id).items.font_face, "nice");
875    }
876
877    #[test]
878    fn lang_overrides() {
879        let mut manager = ConfigManager::new(None, None);
880        let lang_defaults = json!({"font_size": 69, "font_face": "nice"});
881        let lang_overrides = json!({"font_size": 420, "font_face": "cool"});
882        let lang_def = rust_lang_def(lang_defaults.as_object().map(Table::clone));
883        let lang_id: LanguageId = "Rust".into();
884        let domain: ConfigDomain = lang_id.clone().into();
885
886        manager.set_languages(Languages::new(&[lang_def.clone()]));
887        assert_eq!(manager.languages.iter().count(), 1);
888
889        let buf_id = BufferId(1);
890        manager.add_buffer(buf_id, Some(Path::new("file.rs")));
891
892        let config = manager.get_buffer_config(buf_id).to_owned();
893        assert_eq!(config.source.0.len(), 2);
894        assert_eq!(config.items.font_size, 69.);
895
896        // removing language should remove default configs
897        manager.set_languages(Languages::new(&[]));
898        assert_eq!(manager.languages.iter().count(), 0);
899
900        let config = manager.get_buffer_config(buf_id).to_owned();
901        assert_eq!(config.source.0.len(), 1);
902        assert_eq!(config.items.font_size, 14.);
903
904        manager
905            .set_user_config(domain.clone(), lang_overrides.as_object().map(Table::clone).unwrap())
906            .unwrap();
907
908        // user config for unknown language is ignored
909        let config = manager.get_buffer_config(buf_id).to_owned();
910        assert_eq!(config.items.font_size, 14.);
911
912        // user config trumps defaults when language exists
913        manager.set_languages(Languages::new(&[lang_def.clone()]));
914        let config = manager.get_buffer_config(buf_id).to_owned();
915        assert_eq!(config.items.font_size, 420.);
916
917        let changes = json!({ "font_size": Value::Null }).as_object().unwrap().to_owned();
918
919        // null key should void user setting, leave language default
920        let table = manager.table_for_update(domain.clone(), changes);
921        manager.set_user_config(domain.clone(), table).unwrap();
922        let config = manager.get_buffer_config(buf_id).to_owned();
923        assert_eq!(config.items.font_size, 69.);
924
925        manager.set_languages(Languages::new(&[]));
926        let config = manager.get_buffer_config(buf_id);
927        assert_eq!(config.items.font_size, 14.);
928    }
929
930    fn rust_lang_def<T: Into<Option<Table>>>(defaults: T) -> LanguageDefinition {
931        LanguageDefinition::simple("Rust", &["rs"], "source.rust", defaults.into())
932    }
933}