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 
15 use std::collections::HashMap;
16 use std::error::Error;
17 use std::fmt;
18 use std::fs;
19 use std::io::{self, Read};
20 use std::path::{Path, PathBuf};
21 use std::sync::Arc;
22 
23 use serde::de::{self, Deserialize};
24 use serde_json::{self, Value};
25 use toml;
26 
27 use crate::syntax::{LanguageId, Languages};
28 use crate::tabs::{BufferId, ViewId};
29 
30 /// Loads the included base config settings.
load_base_config() -> Table31 fn 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
61 pub 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")]
66 pub 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")]
82 pub 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)]
92 pub 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)]
106 pub 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)]
119 struct LanguageTag {
120     detected: LanguageId,
121     user: Option<LanguageId>,
122 }
123 
124 #[derive(Debug)]
125 pub 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)]
145 struct TableStack(Vec<Arc<Table>>);
146 
147 /// A frozen collection of settings, and their sources.
148 #[derive(Debug, Clone, Serialize, Deserialize)]
149 pub 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 
deserialize_tab_size<'de, D>(deserializer: D) -> Result<usize, D::Error> where D: serde::Deserializer<'de>,158 fn deserialize_tab_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
159 where
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)]
175 pub 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 
192 pub type BufferConfig = Config<BufferItems>;
193 
194 impl ConfigPair {
195     /// Creates a new `ConfigPair` with the provided base config.
with_base<T: Into<Option<Table>>>(table: T) -> Self196     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.
new_with_base<T: Into<Option<Table>>>(&self, table: T) -> Self204     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 
set_table(&mut self, user: Table)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.
table_for_update(&self, user: Table) -> Table218     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 
rebuild(&mut self)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 
242 impl ConfigManager {
new(config_dir: Option<PathBuf>, extras_dir: Option<PathBuf>) -> Self243     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.
base_config_file_path(&self) -> Option<PathBuf>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 
get_plugin_paths(&self) -> Vec<PathBuf>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.
add_buffer(&mut self, id: BufferId, path: Option<&Path>) -> Table287     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.
update_buffer_path(&mut self, id: BufferId, path: &Path) -> Option<Table>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.
remove_buffer(&mut self, id: BufferId)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.
override_language( &mut self, id: BufferId, new_lang: LanguageId, ) -> Option<Table>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 
update_buffer_config(&mut self, id: BufferId) -> Option<Table>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 
update_all_buffer_configs(&mut self) -> Vec<(BufferId, Table)>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 
generate_buffer_config(&mut self, id: BufferId) -> BufferConfig358     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.
get_buffer_config(&self, id: BufferId) -> &BufferConfig392     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.
get_buffer_language(&self, id: BufferId) -> LanguageId401     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.
set_languages(&mut self, languages: Languages)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 
load_user_config_file(&self, domain: &ConfigDomain) -> Option<Table>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 
language_for_path(&self, path: &Path) -> Option<LanguageId>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.
set_user_config( &mut self, domain: ConfigDomain, config: Table, ) -> Result<Vec<(BufferId, Table)>, ConfigError>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.
table_for_update(&mut self, domain: ConfigDomain, changes: Table) -> Table493     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.
domain_for_path(&self, path: &Path) -> Option<ConfigDomain>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 
check_table(&self, table: &Table) -> Result<(), ConfigError>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.
get_themes_dir(&self) -> Option<PathBuf>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.
get_plugins_dir(&self) -> Option<PathBuf>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 
568 impl TableStack {
569     /// Create a single table representing the final config values.
collate(&self) -> Table570     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.
into_config<T>(self) -> Config<T> where for<'de> T: Deserialize<'de>,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`.
get<S: AsRef<str>>(&self, key: S) -> Option<&Value>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`.
diff(&self, other: &TableStack) -> Option<Table>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 
623 impl<T> Config<T> {
to_table(&self) -> Table624     pub fn to_table(&self) -> Table {
625         self.source.collate()
626     }
627 }
628 
629 impl<'de, T: Deserialize<'de>> Config<T> {
630     /// Returns a `Table` of all the items in `self` which have different
631     /// values than in `other`.
changes_from(&self, other: Option<&Config<T>>) -> Option<Table>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 
640 impl ConfigDomain {
file_stem(&self) -> &str641     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 
650 impl LanguageTag {
new(detected: LanguageId) -> Self651     fn new(detected: LanguageId) -> Self {
652         LanguageTag { detected, user: None }
653     }
654 
resolve(&self) -> LanguageId655     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.
set_detected(&mut self, detected: LanguageId) -> bool661     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)]
set_user(&mut self, new_lang: Option<LanguageId>) -> bool670     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 
677 impl<T: PartialEq> PartialEq for Config<T> {
eq(&self, other: &Config<T>) -> bool678     fn eq(&self, other: &Config<T>) -> bool {
679         self.items == other.items
680     }
681 }
682 
683 impl From<LanguageId> for ConfigDomain {
from(src: LanguageId) -> ConfigDomain684     fn from(src: LanguageId) -> ConfigDomain {
685         ConfigDomain::Language(src)
686     }
687 }
688 
689 impl From<BufferId> for ConfigDomain {
from(src: BufferId) -> ConfigDomain690     fn from(src: BufferId) -> ConfigDomain {
691         ConfigDomain::UserOverride(src)
692     }
693 }
694 
695 impl fmt::Display for ConfigError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result696     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 
707 impl Error for ConfigError {
description(&self) -> &str708     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 
719 impl From<io::Error> for ConfigError {
from(src: io::Error) -> ConfigError720     fn from(src: io::Error) -> ConfigError {
721         ConfigError::Io(src)
722     }
723 }
724 
725 impl From<serde_json::Error> for ConfigError {
from(src: serde_json::Error) -> ConfigError726     fn from(src: serde_json::Error) -> ConfigError {
727         ConfigError::UnexpectedItem(src)
728     }
729 }
730 
731 /// Creates initial config directory structure
init_config_dir(dir: &Path) -> io::Result<()>732 pub(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.
try_load_from_file(path: &Path) -> Result<Table, ConfigError>741 pub(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 
table_from_toml_str(s: &str) -> Result<Table, toml::de::Error>748 pub(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).
from_toml_value(value: toml::Value) -> Value757 fn 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)]
784 mod tests {
785     use super::*;
786     use crate::syntax::LanguageDefinition;
787 
788     #[test]
test_overrides()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]
test_config_domain_serde()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]
test_diff()838     fn test_diff() {
839         let conf1 = r#"
840 tab_size = 42
841 translate_tabs_to_spaces = true
842 "#;
843         let conf1 = table_from_toml_str(conf1).unwrap();
844 
845         let conf2 = r#"
846 tab_size = 6
847 translate_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]
test_updating_in_place()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]
lang_overrides()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 
rust_lang_def<T: Into<Option<Table>>>(defaults: T) -> LanguageDefinition930     fn rust_lang_def<T: Into<Option<Table>>>(defaults: T) -> LanguageDefinition {
931         LanguageDefinition::simple("Rust", &["rs"], "source.rust", defaults.into())
932     }
933 }
934