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