xi_core_lib/
tabs.rs

1// Copyright 2016 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//! The main container for core state.
16//!
17//! All events from the frontend or from plugins are handled here first.
18//!
19//! This file is called 'tabs' for historical reasons, and should probably
20//! be renamed.
21
22use std::cell::{Cell, RefCell};
23use std::collections::{BTreeMap, HashSet};
24use std::fmt;
25use std::fs::File;
26use std::io;
27use std::mem;
28use std::path::{Path, PathBuf};
29
30use serde::de::{self, Deserialize, Deserializer, Unexpected};
31use serde::ser::{Serialize, Serializer};
32use serde_json::Value;
33
34use xi_rope::Rope;
35use xi_rpc::{self, ReadError, RemoteError, RpcCtx, RpcPeer};
36use xi_trace::{self, trace_block};
37
38use crate::client::Client;
39use crate::config::{self, ConfigDomain, ConfigDomainExternal, ConfigManager, Table};
40use crate::editor::Editor;
41use crate::event_context::EventContext;
42use crate::file::FileManager;
43use crate::line_ending::LineEnding;
44use crate::plugin_rpc::{PluginNotification, PluginRequest};
45use crate::plugins::rpc::ClientPluginInfo;
46use crate::plugins::{start_plugin_process, Plugin, PluginCatalog, PluginPid};
47use crate::recorder::Recorder;
48use crate::rpc::{
49    CoreNotification, CoreRequest, EditNotification, EditRequest,
50    PluginNotification as CorePluginNotification,
51};
52use crate::styles::{ThemeStyleMap, DEFAULT_THEME};
53use crate::syntax::LanguageId;
54use crate::view::View;
55use crate::whitespace::Indentation;
56use crate::width_cache::WidthCache;
57use crate::WeakXiCore;
58
59#[cfg(feature = "notify")]
60use crate::watcher::{FileWatcher, WatchToken};
61#[cfg(feature = "notify")]
62use notify::DebouncedEvent;
63#[cfg(feature = "notify")]
64use std::ffi::OsStr;
65
66/// ViewIds are the primary means of routing messages between
67/// xi-core and a client view.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct ViewId(pub(crate) usize);
70
71/// BufferIds uniquely identify open buffers.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
73pub struct BufferId(pub(crate) usize);
74
75pub type PluginId = crate::plugins::PluginPid;
76
77// old-style names; will be deprecated
78pub type BufferIdentifier = BufferId;
79
80/// Totally arbitrary; we reserve this space for `ViewId`s
81pub(crate) const RENDER_VIEW_IDLE_MASK: usize = 1 << 25;
82pub(crate) const REWRAP_VIEW_IDLE_MASK: usize = 1 << 26;
83pub(crate) const FIND_VIEW_IDLE_MASK: usize = 1 << 27;
84
85const NEW_VIEW_IDLE_TOKEN: usize = 1001;
86
87/// xi_rpc idle Token for watcher related idle scheduling.
88pub(crate) const WATCH_IDLE_TOKEN: usize = 1002;
89
90#[cfg(feature = "notify")]
91const CONFIG_EVENT_TOKEN: WatchToken = WatchToken(1);
92
93/// Token for file-change events in open files
94#[cfg(feature = "notify")]
95pub const OPEN_FILE_EVENT_TOKEN: WatchToken = WatchToken(2);
96
97#[cfg(feature = "notify")]
98const THEME_FILE_EVENT_TOKEN: WatchToken = WatchToken(3);
99
100#[cfg(feature = "notify")]
101const PLUGIN_EVENT_TOKEN: WatchToken = WatchToken(4);
102
103#[allow(dead_code)]
104pub struct CoreState {
105    editors: BTreeMap<BufferId, RefCell<Editor>>,
106    views: BTreeMap<ViewId, RefCell<View>>,
107    file_manager: FileManager,
108    /// A local pasteboard.
109    kill_ring: RefCell<Rope>,
110    /// Theme and style state.
111    style_map: RefCell<ThemeStyleMap>,
112    width_cache: RefCell<WidthCache>,
113    /// User and platform specific settings
114    config_manager: ConfigManager,
115    /// Recorded editor actions
116    recorder: RefCell<Recorder>,
117    /// A weak reference to the main state container, stashed so that
118    /// it can be passed to plugins.
119    self_ref: Option<WeakXiCore>,
120    /// Views which need to have setup finished.
121    pending_views: Vec<(ViewId, Table)>,
122    peer: Client,
123    id_counter: Counter,
124    plugins: PluginCatalog,
125    // for the time being we auto-start all plugins we find on launch.
126    running_plugins: Vec<Plugin>,
127}
128
129/// Initial setup and bookkeeping
130impl CoreState {
131    pub(crate) fn new(
132        peer: &RpcPeer,
133        config_dir: Option<PathBuf>,
134        extras_dir: Option<PathBuf>,
135    ) -> Self {
136        #[cfg(feature = "notify")]
137        let mut watcher = FileWatcher::new(peer.clone());
138
139        if let Some(p) = config_dir.as_ref() {
140            if !p.exists() {
141                if let Err(e) = config::init_config_dir(p) {
142                    //TODO: report this error?
143                    error!("error initing file based configs: {:?}", e);
144                }
145            }
146
147            #[cfg(feature = "notify")]
148            watcher.watch_filtered(p, true, CONFIG_EVENT_TOKEN, |p| {
149                p.extension().and_then(OsStr::to_str).unwrap_or("") == "xiconfig"
150            });
151        }
152
153        let config_manager = ConfigManager::new(config_dir, extras_dir);
154
155        let themes_dir = config_manager.get_themes_dir();
156        if let Some(p) = themes_dir.as_ref() {
157            #[cfg(feature = "notify")]
158            watcher.watch_filtered(p, true, THEME_FILE_EVENT_TOKEN, |p| {
159                p.extension().and_then(OsStr::to_str).unwrap_or("") == "tmTheme"
160            });
161        }
162
163        let plugins_dir = config_manager.get_plugins_dir();
164        if let Some(p) = plugins_dir.as_ref() {
165            #[cfg(feature = "notify")]
166            watcher.watch_filtered(p, true, PLUGIN_EVENT_TOKEN, |p| p.is_dir() || !p.exists());
167        }
168
169        CoreState {
170            views: BTreeMap::new(),
171            editors: BTreeMap::new(),
172            #[cfg(feature = "notify")]
173            file_manager: FileManager::new(watcher),
174            #[cfg(not(feature = "notify"))]
175            file_manager: FileManager::new(),
176            kill_ring: RefCell::new(Rope::from("")),
177            style_map: RefCell::new(ThemeStyleMap::new(themes_dir)),
178            width_cache: RefCell::new(WidthCache::new()),
179            config_manager,
180            recorder: RefCell::new(Recorder::new()),
181            self_ref: None,
182            pending_views: Vec::new(),
183            peer: Client::new(peer.clone()),
184            id_counter: Counter::default(),
185            plugins: PluginCatalog::default(),
186            running_plugins: Vec::new(),
187        }
188    }
189
190    fn next_view_id(&self) -> ViewId {
191        ViewId(self.id_counter.next())
192    }
193
194    fn next_buffer_id(&self) -> BufferId {
195        BufferId(self.id_counter.next())
196    }
197
198    fn next_plugin_id(&self) -> PluginId {
199        PluginPid(self.id_counter.next())
200    }
201
202    pub(crate) fn finish_setup(&mut self, self_ref: WeakXiCore) {
203        self.self_ref = Some(self_ref);
204
205        if let Some(path) = self.config_manager.base_config_file_path() {
206            self.load_file_based_config(&path);
207        }
208
209        // Load the custom theme files.
210        self.style_map.borrow_mut().load_theme_dir();
211
212        // instead of having to do this here, config should just own
213        // the plugin catalog and reload automatically
214        let plugin_paths = self.config_manager.get_plugin_paths();
215        self.plugins.reload_from_paths(&plugin_paths);
216        let languages = self.plugins.make_languages_map();
217        let languages_ids = languages.iter().map(|l| l.name.clone()).collect::<Vec<_>>();
218        self.peer.available_languages(languages_ids);
219        self.config_manager.set_languages(languages);
220        let theme_names = self.style_map.borrow().get_theme_names();
221        self.peer.available_themes(theme_names);
222
223        // FIXME: temporary: we just launch every plugin we find at startup
224        for manifest in self.plugins.iter() {
225            start_plugin_process(
226                manifest.clone(),
227                self.next_plugin_id(),
228                self.self_ref.as_ref().unwrap().clone(),
229            );
230        }
231    }
232
233    /// Attempt to load a config file.
234    fn load_file_based_config(&mut self, path: &Path) {
235        let _t = trace_block("CoreState::load_config_file", &["core"]);
236        if let Some(domain) = self.config_manager.domain_for_path(path) {
237            match config::try_load_from_file(&path) {
238                Ok(table) => self.set_config(domain, table),
239                Err(e) => self.peer.alert(e.to_string()),
240            }
241        } else {
242            self.peer.alert(format!("Unexpected config file {:?}", path));
243        }
244    }
245
246    /// Sets (overwriting) the config for a given domain.
247    fn set_config(&mut self, domain: ConfigDomain, table: Table) {
248        match self.config_manager.set_user_config(domain, table) {
249            Err(e) => self.peer.alert(format!("{}", &e)),
250            Ok(changes) => self.handle_config_changes(changes),
251        }
252    }
253
254    /// Notify editors/views/plugins of config changes.
255    fn handle_config_changes(&self, changes: Vec<(BufferId, Table)>) {
256        for (id, table) in changes {
257            let view_id = self
258                .views
259                .values()
260                .find(|v| v.borrow().get_buffer_id() == id)
261                .map(|v| v.borrow().get_view_id())
262                .unwrap();
263
264            self.make_context(view_id).unwrap().config_changed(&table)
265        }
266    }
267}
268
269/// Handling client events
270impl CoreState {
271    /// Creates an `EventContext` for the provided `ViewId`. This context
272    /// holds references to the `Editor` and `View` backing this `ViewId`,
273    /// as well as to sibling views, plugins, and other state necessary
274    /// for handling most events.
275    pub(crate) fn make_context(&self, view_id: ViewId) -> Option<EventContext> {
276        self.views.get(&view_id).map(|view| {
277            let buffer_id = view.borrow().get_buffer_id();
278
279            let editor = &self.editors[&buffer_id];
280            let info = self.file_manager.get_info(buffer_id);
281            let plugins = self.running_plugins.iter().collect::<Vec<_>>();
282            let config = self.config_manager.get_buffer_config(buffer_id);
283            let language = self.config_manager.get_buffer_language(buffer_id);
284
285            EventContext {
286                view_id,
287                buffer_id,
288                view,
289                editor,
290                config: &config.items,
291                recorder: &self.recorder,
292                language,
293                info,
294                siblings: Vec::new(),
295                plugins,
296                client: &self.peer,
297                style_map: &self.style_map,
298                width_cache: &self.width_cache,
299                kill_ring: &self.kill_ring,
300                weak_core: self.self_ref.as_ref().unwrap(),
301            }
302        })
303    }
304
305    /// Produces an iterator over all event contexts, with each view appearing
306    /// exactly once.
307    fn iter_groups<'a>(&'a self) -> Iter<'a, Box<dyn Iterator<Item = &ViewId> + 'a>> {
308        Iter { views: Box::new(self.views.keys()), seen: HashSet::new(), inner: self }
309    }
310
311    pub(crate) fn client_notification(&mut self, cmd: CoreNotification) {
312        use self::CoreNotification::*;
313        use self::CorePluginNotification as PN;
314        match cmd {
315            Edit(crate::rpc::EditCommand { view_id, cmd }) => self.do_edit(view_id, cmd),
316            Save { view_id, file_path } => self.do_save(view_id, file_path),
317            CloseView { view_id } => self.do_close_view(view_id),
318            ModifyUserConfig { domain, changes } => self.do_modify_user_config(domain, changes),
319            SetTheme { theme_name } => self.do_set_theme(&theme_name),
320            SaveTrace { destination, frontend_samples } => {
321                self.save_trace(&destination, frontend_samples)
322            }
323            Plugin(cmd) => match cmd {
324                PN::Start { view_id, plugin_name } => self.do_start_plugin(view_id, &plugin_name),
325                PN::Stop { view_id, plugin_name } => self.do_stop_plugin(view_id, &plugin_name),
326                PN::PluginRpc { view_id, receiver, rpc } => {
327                    self.do_plugin_rpc(view_id, &receiver, &rpc.method, &rpc.params)
328                }
329            },
330            TracingConfig { enabled } => self.toggle_tracing(enabled),
331            // handled at the top level
332            ClientStarted { .. } => (),
333            SetLanguage { view_id, language_id } => self.do_set_language(view_id, language_id),
334        }
335    }
336
337    pub(crate) fn client_request(&mut self, cmd: CoreRequest) -> Result<Value, RemoteError> {
338        use self::CoreRequest::*;
339        match cmd {
340            //TODO: make file_path be an Option<PathBuf>
341            //TODO: make this a notification
342            NewView { file_path } => self.do_new_view(file_path.map(PathBuf::from)),
343            Edit(crate::rpc::EditCommand { view_id, cmd }) => self.do_edit_sync(view_id, cmd),
344            //TODO: why is this a request?? make a notification?
345            GetConfig { view_id } => self.do_get_config(view_id).map(|c| json!(c)),
346            DebugGetContents { view_id } => self.do_get_contents(view_id).map(|c| json!(c)),
347        }
348    }
349
350    fn do_edit(&mut self, view_id: ViewId, cmd: EditNotification) {
351        if let Some(mut edit_ctx) = self.make_context(view_id) {
352            edit_ctx.do_edit(cmd);
353        }
354    }
355
356    fn do_edit_sync(&mut self, view_id: ViewId, cmd: EditRequest) -> Result<Value, RemoteError> {
357        if let Some(mut edit_ctx) = self.make_context(view_id) {
358            edit_ctx.do_edit_sync(cmd)
359        } else {
360            // TODO: some custom error tpye that can Into<RemoteError>
361            Err(RemoteError::custom(404, format!("missing view {:?}", view_id), None))
362        }
363    }
364
365    fn do_new_view(&mut self, path: Option<PathBuf>) -> Result<Value, RemoteError> {
366        let view_id = self.next_view_id();
367        let buffer_id = self.next_buffer_id();
368
369        let rope = match path.as_ref() {
370            Some(p) => self.file_manager.open(p, buffer_id)?,
371            None => Rope::from(""),
372        };
373
374        let editor = RefCell::new(Editor::with_text(rope));
375        let view = RefCell::new(View::new(view_id, buffer_id));
376
377        self.editors.insert(buffer_id, editor);
378        self.views.insert(view_id, view);
379
380        let config = self.config_manager.add_buffer(buffer_id, path.as_ref().map(|p| p.as_path()));
381
382        //NOTE: because this is a synchronous call, we have to return the
383        //view_id before we can send any events to this view. We mark the
384        // view as pending and schedule the idle handler so that we can finish
385        // setting up this view on the next runloop pass, in finalize_new_views.
386        self.pending_views.push((view_id, config));
387        self.peer.schedule_idle(NEW_VIEW_IDLE_TOKEN);
388
389        Ok(json!(view_id))
390    }
391
392    fn do_save<P>(&mut self, view_id: ViewId, path: P)
393    where
394        P: AsRef<Path>,
395    {
396        let _t = trace_block("CoreState::do_save", &["core"]);
397        let path = path.as_ref();
398        let buffer_id = self.views.get(&view_id).map(|v| v.borrow().get_buffer_id());
399        let buffer_id = match buffer_id {
400            Some(id) => id,
401            None => return,
402        };
403
404        let mut save_ctx = self.make_context(view_id).unwrap();
405        let fin_text = save_ctx.text_for_save();
406
407        if let Err(e) = self.file_manager.save(path, &fin_text, buffer_id) {
408            let error_message = e.to_string();
409            error!("File error: {:?}", error_message);
410            self.peer.alert(error_message);
411            return;
412        }
413
414        let changes = self.config_manager.update_buffer_path(buffer_id, path);
415        let language = self.config_manager.get_buffer_language(buffer_id);
416
417        self.make_context(view_id).unwrap().after_save(path);
418        self.make_context(view_id).unwrap().language_changed(&language);
419
420        // update the config _after_ sending save related events
421        if let Some(changes) = changes {
422            self.make_context(view_id).unwrap().config_changed(&changes);
423        }
424    }
425
426    fn do_close_view(&mut self, view_id: ViewId) {
427        let close_buffer = self.make_context(view_id).map(|ctx| ctx.close_view()).unwrap_or(true);
428
429        let buffer_id = self.views.remove(&view_id).map(|v| v.borrow().get_buffer_id());
430
431        if let Some(buffer_id) = buffer_id {
432            if close_buffer {
433                self.editors.remove(&buffer_id);
434                self.file_manager.close(buffer_id);
435                self.config_manager.remove_buffer(buffer_id);
436            }
437        }
438    }
439
440    fn do_set_theme(&self, theme_name: &str) {
441        //Set only if requested theme is different from the
442        //current one.
443        if theme_name != self.style_map.borrow().get_theme_name() {
444            if let Err(e) = self.style_map.borrow_mut().set_theme(&theme_name) {
445                error!("error setting theme: {:?}, {:?}", theme_name, e);
446                return;
447            }
448        }
449        self.notify_client_and_update_views();
450    }
451
452    fn notify_client_and_update_views(&self) {
453        {
454            let style_map = self.style_map.borrow();
455            self.peer.theme_changed(style_map.get_theme_name(), style_map.get_theme_settings());
456        }
457
458        self.iter_groups().for_each(|mut edit_ctx| {
459            edit_ctx.with_editor(|ed, view, _, _| {
460                ed.theme_changed(&self.style_map.borrow());
461                view.set_dirty(ed.get_buffer());
462            });
463            edit_ctx.render_if_needed();
464        });
465    }
466
467    /// Updates the config for a given domain.
468    fn do_modify_user_config(&mut self, domain: ConfigDomainExternal, changes: Table) {
469        // the client sends ViewId but we need BufferId so we do a dance
470        let domain: ConfigDomain = match domain {
471            ConfigDomainExternal::General => ConfigDomain::General,
472            ConfigDomainExternal::Syntax(id) => ConfigDomain::Language(id),
473            ConfigDomainExternal::Language(id) => ConfigDomain::Language(id),
474            ConfigDomainExternal::UserOverride(view_id) => match self.views.get(&view_id) {
475                Some(v) => ConfigDomain::UserOverride(v.borrow().get_buffer_id()),
476                None => return,
477            },
478        };
479        let new_config = self.config_manager.table_for_update(domain.clone(), changes);
480        self.set_config(domain, new_config);
481    }
482
483    fn do_get_config(&self, view_id: ViewId) -> Result<Table, RemoteError> {
484        let _t = trace_block("CoreState::get_config", &["core"]);
485        self.views
486            .get(&view_id)
487            .map(|v| v.borrow().get_buffer_id())
488            .map(|id| self.config_manager.get_buffer_config(id).to_table())
489            .ok_or(RemoteError::custom(404, format!("missing {}", view_id), None))
490    }
491
492    fn do_get_contents(&self, view_id: ViewId) -> Result<Rope, RemoteError> {
493        self.make_context(view_id)
494            .map(|ctx| ctx.editor.borrow().get_buffer().to_owned())
495            .ok_or_else(|| RemoteError::custom(404, format!("No view for id {}", view_id), None))
496    }
497
498    fn do_set_language(&mut self, view_id: ViewId, language_id: LanguageId) {
499        if let Some(view) = self.views.get(&view_id) {
500            let buffer_id = view.borrow().get_buffer_id();
501            let changes = self.config_manager.override_language(buffer_id, language_id.clone());
502
503            let mut context = self.make_context(view_id).unwrap();
504            context.language_changed(&language_id);
505            if let Some(changes) = changes {
506                context.config_changed(&changes);
507            }
508        }
509    }
510
511    fn do_start_plugin(&mut self, _view_id: ViewId, plugin: &str) {
512        if self.running_plugins.iter().any(|p| p.name == plugin) {
513            info!("plugin {} already running", plugin);
514            return;
515        }
516
517        if let Some(manifest) = self.plugins.get_named(plugin) {
518            //TODO: lots of races possible here, we need to keep track of
519            //pending launches.
520            start_plugin_process(
521                manifest.clone(),
522                self.next_plugin_id(),
523                self.self_ref.as_ref().unwrap().clone(),
524            );
525        } else {
526            warn!("no plugin found with name '{}'", plugin);
527        }
528    }
529
530    fn do_stop_plugin(&mut self, _view_id: ViewId, plugin: &str) {
531        if let Some(p) = self
532            .running_plugins
533            .iter()
534            .position(|p| p.name == plugin)
535            .map(|ix| self.running_plugins.remove(ix))
536        {
537            //TODO: verify shutdown; kill if necessary
538            p.shutdown();
539            self.after_stop_plugin(&p);
540        }
541    }
542
543    fn do_plugin_rpc(&self, view_id: ViewId, receiver: &str, method: &str, params: &Value) {
544        self.running_plugins
545            .iter()
546            .filter(|p| p.name == receiver)
547            .for_each(|p| p.dispatch_command(view_id, method, params))
548    }
549
550    fn after_stop_plugin(&mut self, plugin: &Plugin) {
551        self.iter_groups().for_each(|mut cx| cx.plugin_stopped(plugin));
552    }
553}
554
555/// Idle, tracing, and file event handling
556impl CoreState {
557    pub(crate) fn handle_idle(&mut self, token: usize) {
558        match token {
559            NEW_VIEW_IDLE_TOKEN => self.finalize_new_views(),
560            WATCH_IDLE_TOKEN => self.handle_fs_events(),
561            other if (other & RENDER_VIEW_IDLE_MASK) != 0 => {
562                self.handle_render_timer(other ^ RENDER_VIEW_IDLE_MASK)
563            }
564            other if (other & REWRAP_VIEW_IDLE_MASK) != 0 => {
565                self.handle_rewrap_callback(other ^ REWRAP_VIEW_IDLE_MASK)
566            }
567            other if (other & FIND_VIEW_IDLE_MASK) != 0 => {
568                self.handle_find_callback(other ^ FIND_VIEW_IDLE_MASK)
569            }
570            other => panic!("unexpected idle token {}", other),
571        };
572    }
573
574    fn finalize_new_views(&mut self) {
575        let to_start = mem::replace(&mut self.pending_views, Vec::new());
576        to_start.iter().for_each(|(id, config)| {
577            let modified = self.detect_whitespace(*id, config);
578            let config = modified.as_ref().unwrap_or(config);
579            let mut edit_ctx = self.make_context(*id).unwrap();
580            edit_ctx.finish_init(&config);
581        });
582    }
583
584    // Detects whitespace settings from the file and merges them with the config
585    fn detect_whitespace(&mut self, id: ViewId, config: &Table) -> Option<Table> {
586        let buffer_id = self.views.get(&id).map(|v| v.borrow().get_buffer_id())?;
587        let editor = self
588            .editors
589            .get(&buffer_id)
590            .expect("existing buffer_id must have corresponding editor");
591
592        if editor.borrow().get_buffer().is_empty() {
593            return None;
594        }
595
596        let autodetect_whitespace =
597            self.config_manager.get_buffer_config(buffer_id).items.autodetect_whitespace;
598        if !autodetect_whitespace {
599            return None;
600        }
601
602        let mut changes = Table::new();
603        let indentation = Indentation::parse(editor.borrow().get_buffer());
604        match indentation {
605            Ok(Some(Indentation::Tabs)) => {
606                changes.insert("translate_tabs_to_spaces".into(), false.into());
607            }
608            Ok(Some(Indentation::Spaces(n))) => {
609                changes.insert("translate_tabs_to_spaces".into(), true.into());
610                changes.insert("tab_size".into(), n.into());
611            }
612            Err(_) => info!("detected mixed indentation"),
613            Ok(None) => info!("file contains no indentation"),
614        }
615
616        let line_ending = LineEnding::parse(editor.borrow().get_buffer());
617        match line_ending {
618            Ok(Some(LineEnding::CrLf)) => {
619                changes.insert("line_ending".into(), "\r\n".into());
620            }
621            Ok(Some(LineEnding::Lf)) => {
622                changes.insert("line_ending".into(), "\n".into());
623            }
624            Err(_) => info!("detected mixed line endings"),
625            Ok(None) => info!("file contains no supported line endings"),
626        }
627
628        let config_delta =
629            self.config_manager.table_for_update(ConfigDomain::SysOverride(buffer_id), changes);
630        match self
631            .config_manager
632            .set_user_config(ConfigDomain::SysOverride(buffer_id), config_delta)
633        {
634            Ok(ref mut items) if !items.is_empty() => {
635                assert!(
636                    items.len() == 1,
637                    "whitespace overrides can only update a single buffer's config\n{:?}",
638                    items
639                );
640                let table = items.remove(0).1;
641                let mut config = config.clone();
642                config.extend(table);
643                Some(config)
644            }
645            Ok(_) => {
646                warn!("set_user_config failed to update config, no tables were returned");
647                None
648            }
649            Err(err) => {
650                warn!("detect_whitespace failed to update config: {:?}", err);
651                None
652            }
653        }
654    }
655
656    fn handle_render_timer(&mut self, token: usize) {
657        let id: ViewId = token.into();
658        if let Some(mut ctx) = self.make_context(id) {
659            ctx._finish_delayed_render();
660        }
661    }
662
663    /// Callback for doing word wrap on a view
664    fn handle_rewrap_callback(&mut self, token: usize) {
665        let id: ViewId = token.into();
666        if let Some(mut ctx) = self.make_context(id) {
667            ctx.do_rewrap_batch();
668        }
669    }
670
671    /// Callback for doing incremental find in a view
672    fn handle_find_callback(&mut self, token: usize) {
673        let id: ViewId = token.into();
674        if let Some(mut ctx) = self.make_context(id) {
675            ctx.do_incremental_find();
676        }
677    }
678
679    #[cfg(feature = "notify")]
680    fn handle_fs_events(&mut self) {
681        let _t = trace_block("CoreState::handle_fs_events", &["core"]);
682        let mut events = self.file_manager.watcher().take_events();
683
684        for (token, event) in events.drain(..) {
685            match token {
686                OPEN_FILE_EVENT_TOKEN => self.handle_open_file_fs_event(event),
687                CONFIG_EVENT_TOKEN => self.handle_config_fs_event(event),
688                THEME_FILE_EVENT_TOKEN => self.handle_themes_fs_event(event),
689                PLUGIN_EVENT_TOKEN => self.handle_plugin_fs_event(event),
690                _ => warn!("unexpected fs event token {:?}", token),
691            }
692        }
693    }
694
695    #[cfg(not(feature = "notify"))]
696    fn handle_fs_events(&mut self) {}
697
698    /// Handles a file system event related to a currently open file
699    #[cfg(feature = "notify")]
700    fn handle_open_file_fs_event(&mut self, event: DebouncedEvent) {
701        use notify::DebouncedEvent::*;
702        let path = match event {
703            NoticeWrite(ref path) | Create(ref path) | Write(ref path) | Chmod(ref path) => path,
704            other => {
705                debug!("Event in open file {:?}", other);
706                return;
707            }
708        };
709
710        let buffer_id = match self.file_manager.get_editor(path) {
711            Some(id) => id,
712            None => return,
713        };
714
715        let has_changes = self.file_manager.check_file(path, buffer_id);
716        let is_pristine = self.editors.get(&buffer_id).map(|ed| ed.borrow().is_pristine()).unwrap();
717        //TODO: currently we only use the file's modification time when
718        // determining if a file has been changed by another process.
719        // A more robust solution would also hash the file's contents.
720
721        if has_changes && is_pristine {
722            if let Ok(text) = self.file_manager.open(path, buffer_id) {
723                // this is ugly; we don't map buffer_id -> view_id anywhere
724                // but we know we must have a view.
725                let view_id = self
726                    .views
727                    .values()
728                    .find(|v| v.borrow().get_buffer_id() == buffer_id)
729                    .map(|v| v.borrow().get_view_id())
730                    .unwrap();
731                self.make_context(view_id).unwrap().reload(text);
732            }
733        }
734    }
735
736    /// Handles a config related file system event.
737    #[cfg(feature = "notify")]
738    fn handle_config_fs_event(&mut self, event: DebouncedEvent) {
739        use self::DebouncedEvent::*;
740        match event {
741            Create(ref path) | Write(ref path) | Chmod(ref path) => {
742                self.load_file_based_config(path)
743            }
744            Remove(ref path) if !path.exists() => self.remove_config_at_path(path),
745            Rename(ref old, ref new) => {
746                self.remove_config_at_path(old);
747                self.load_file_based_config(new);
748            }
749            _ => (),
750        }
751    }
752
753    fn remove_config_at_path(&mut self, path: &Path) {
754        if let Some(domain) = self.config_manager.domain_for_path(path) {
755            self.set_config(domain, Table::default());
756        }
757    }
758
759    /// Handles changes in plugin files.
760    #[cfg(feature = "notify")]
761    fn handle_plugin_fs_event(&mut self, event: DebouncedEvent) {
762        use self::DebouncedEvent::*;
763        match event {
764            Create(ref path) | Write(ref path) => {
765                self.plugins.load_from_paths(&[path.clone()]);
766                if let Some(plugin) = self.plugins.get_from_path(path) {
767                    self.do_start_plugin(ViewId(0), &plugin.name);
768                }
769            }
770            // the way FSEvents on macOS work, we want to verify that this path
771            // has actually be removed before we do anything.
772            NoticeRemove(ref path) | Remove(ref path) if !path.exists() => {
773                if let Some(plugin) = self.plugins.get_from_path(path) {
774                    self.do_stop_plugin(ViewId(0), &plugin.name);
775                    self.plugins.remove_named(&plugin.name);
776                }
777            }
778            Rename(ref old, ref new) => {
779                if let Some(old_plugin) = self.plugins.get_from_path(old) {
780                    self.do_stop_plugin(ViewId(0), &old_plugin.name);
781                    self.plugins.remove_named(&old_plugin.name);
782                }
783
784                self.plugins.load_from_paths(&[new.clone()]);
785                if let Some(new_plugin) = self.plugins.get_from_path(new) {
786                    self.do_start_plugin(ViewId(0), &new_plugin.name);
787                }
788            }
789            Chmod(ref path) | Remove(ref path) => {
790                if let Some(plugin) = self.plugins.get_from_path(path) {
791                    self.do_stop_plugin(ViewId(0), &plugin.name);
792                    self.do_start_plugin(ViewId(0), &plugin.name);
793                }
794            }
795            _ => (),
796        }
797
798        self.views.keys().for_each(|view_id| {
799            let available_plugins = self
800                .plugins
801                .iter()
802                .map(|plugin| ClientPluginInfo { name: plugin.name.clone(), running: true })
803                .collect::<Vec<_>>();
804            self.peer.available_plugins(view_id.clone(), &available_plugins);
805        });
806    }
807
808    /// Handles changes in theme files.
809    #[cfg(feature = "notify")]
810    fn handle_themes_fs_event(&mut self, event: DebouncedEvent) {
811        use self::DebouncedEvent::*;
812        match event {
813            Create(ref path) | Write(ref path) => self.load_theme_file(path),
814            // the way FSEvents on macOS work, we want to verify that this path
815            // has actually be removed before we do anything.
816            NoticeRemove(ref path) | Remove(ref path) if !path.exists() => self.remove_theme(path),
817            Rename(ref old, ref new) => {
818                self.remove_theme(old);
819                self.load_theme_file(new);
820            }
821            Chmod(ref path) | Remove(ref path) => {
822                self.style_map.borrow_mut().sync_dir(path.parent())
823            }
824            _ => (),
825        }
826        let theme_names = self.style_map.borrow().get_theme_names();
827        self.peer.available_themes(theme_names);
828    }
829
830    /// Load a single theme file. Updates if already present.
831    fn load_theme_file(&mut self, path: &Path) {
832        let _t = trace_block("CoreState::load_theme_file", &["core"]);
833
834        let result = self.style_map.borrow_mut().load_theme_info_from_path(path);
835        match result {
836            Ok(theme_name) => {
837                if theme_name == self.style_map.borrow().get_theme_name() {
838                    if self.style_map.borrow_mut().set_theme(&theme_name).is_ok() {
839                        self.notify_client_and_update_views();
840                    }
841                }
842            }
843            Err(e) => error!("Error loading theme file: {:?}, {:?}", path, e),
844        }
845    }
846
847    fn remove_theme(&mut self, path: &Path) {
848        let result = self.style_map.borrow_mut().remove_theme(path);
849
850        // Set default theme if the removed theme was the
851        // current one.
852        if let Some(theme_name) = result {
853            if theme_name == self.style_map.borrow().get_theme_name() {
854                self.do_set_theme(DEFAULT_THEME);
855            }
856        }
857    }
858
859    fn toggle_tracing(&self, enabled: bool) {
860        self.running_plugins.iter().for_each(|plugin| plugin.toggle_tracing(enabled))
861    }
862
863    fn save_trace<P>(&self, path: P, frontend_samples: Value)
864    where
865        P: AsRef<Path>,
866    {
867        use xi_trace::chrome_trace_dump;
868        let mut all_traces = xi_trace::samples_cloned_unsorted();
869        if let Ok(mut traces) = chrome_trace_dump::decode(frontend_samples) {
870            all_traces.append(&mut traces);
871        }
872
873        for plugin in &self.running_plugins {
874            match plugin.collect_trace() {
875                Ok(json) => {
876                    let mut trace = chrome_trace_dump::decode(json).unwrap();
877                    all_traces.append(&mut trace);
878                }
879                Err(e) => error!("trace error {:?}", e),
880            }
881        }
882
883        all_traces.sort_unstable();
884
885        let mut trace_file = match File::create(path.as_ref()) {
886            Ok(f) => f,
887            Err(e) => {
888                error!("error saving trace {:?}", e);
889                return;
890            }
891        };
892
893        if let Err(e) = chrome_trace_dump::serialize(&all_traces, &mut trace_file) {
894            error!("error saving trace {:?}", e);
895        }
896    }
897}
898
899/// plugin event handling
900impl CoreState {
901    /// Called from a plugin's thread after trying to start the plugin.
902    pub(crate) fn plugin_connect(&mut self, plugin: Result<Plugin, io::Error>) {
903        match plugin {
904            Ok(plugin) => {
905                let init_info =
906                    self.iter_groups().map(|mut ctx| ctx.plugin_info()).collect::<Vec<_>>();
907                plugin.initialize(init_info);
908                self.iter_groups().for_each(|mut cx| cx.plugin_started(&plugin));
909                self.running_plugins.push(plugin);
910            }
911            Err(e) => error!("failed to start plugin {:?}", e),
912        }
913    }
914
915    pub(crate) fn plugin_exit(&mut self, id: PluginId, error: Result<(), ReadError>) {
916        warn!("plugin {:?} exited with result {:?}", id, error);
917        let running_idx = self.running_plugins.iter().position(|p| p.id == id);
918        if let Some(idx) = running_idx {
919            let plugin = self.running_plugins.remove(idx);
920            self.after_stop_plugin(&plugin);
921        }
922    }
923
924    /// Handles the response to a sync update sent to a plugin.
925    pub(crate) fn plugin_update(
926        &mut self,
927        _plugin_id: PluginId,
928        view_id: ViewId,
929        response: Result<Value, xi_rpc::Error>,
930    ) {
931        if let Some(mut edit_ctx) = self.make_context(view_id) {
932            edit_ctx.do_plugin_update(response);
933        }
934    }
935
936    pub(crate) fn plugin_notification(
937        &mut self,
938        _ctx: &RpcCtx,
939        view_id: ViewId,
940        plugin_id: PluginId,
941        cmd: PluginNotification,
942    ) {
943        if let Some(mut edit_ctx) = self.make_context(view_id) {
944            edit_ctx.do_plugin_cmd(plugin_id, cmd)
945        }
946    }
947
948    pub(crate) fn plugin_request(
949        &mut self,
950        _ctx: &RpcCtx,
951        view_id: ViewId,
952        plugin_id: PluginId,
953        cmd: PluginRequest,
954    ) -> Result<Value, RemoteError> {
955        if let Some(mut edit_ctx) = self.make_context(view_id) {
956            Ok(edit_ctx.do_plugin_cmd_sync(plugin_id, cmd))
957        } else {
958            Err(RemoteError::custom(404, "missing view", None))
959        }
960    }
961}
962
963/// test helpers
964impl CoreState {
965    pub fn _test_open_editors(&self) -> Vec<BufferId> {
966        self.editors.keys().cloned().collect()
967    }
968
969    pub fn _test_open_views(&self) -> Vec<ViewId> {
970        self.views.keys().cloned().collect()
971    }
972}
973
974pub mod test_helpers {
975    use super::{BufferId, ViewId};
976
977    pub fn new_view_id(id: usize) -> ViewId {
978        ViewId(id)
979    }
980
981    pub fn new_buffer_id(id: usize) -> BufferId {
982        BufferId(id)
983    }
984}
985
986/// A multi-view aware iterator over `EventContext`s. A view which appears
987/// as a sibling will not appear again as a main view.
988pub struct Iter<'a, I> {
989    views: I,
990    seen: HashSet<ViewId>,
991    inner: &'a CoreState,
992}
993
994impl<'a, I> Iterator for Iter<'a, I>
995where
996    I: Iterator<Item = &'a ViewId>,
997{
998    type Item = EventContext<'a>;
999
1000    fn next(&mut self) -> Option<Self::Item> {
1001        let &mut Iter { ref mut views, ref mut seen, ref inner } = self;
1002        loop {
1003            let next_view = match views.next() {
1004                None => return None,
1005                Some(v) if seen.contains(v) => continue,
1006                Some(v) => v,
1007            };
1008            let context = inner.make_context(*next_view).unwrap();
1009            context.siblings.iter().for_each(|sibl| {
1010                let _ = seen.insert(sibl.borrow().get_view_id());
1011            });
1012            return Some(context);
1013        }
1014    }
1015}
1016
1017#[derive(Debug, Default)]
1018pub(crate) struct Counter(Cell<usize>);
1019
1020impl Counter {
1021    pub(crate) fn next(&self) -> usize {
1022        let n = self.0.get();
1023        self.0.set(n + 1);
1024        n + 1
1025    }
1026}
1027
1028// these two only exist so that we can use ViewIds as idle tokens
1029impl From<usize> for ViewId {
1030    fn from(src: usize) -> ViewId {
1031        ViewId(src)
1032    }
1033}
1034
1035impl From<ViewId> for usize {
1036    fn from(src: ViewId) -> usize {
1037        src.0
1038    }
1039}
1040
1041impl fmt::Display for ViewId {
1042    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1043        write!(f, "view-id-{}", self.0)
1044    }
1045}
1046
1047impl Serialize for ViewId {
1048    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1049    where
1050        S: Serializer,
1051    {
1052        serializer.serialize_str(&self.to_string())
1053    }
1054}
1055
1056impl<'de> Deserialize<'de> for ViewId {
1057    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1058    where
1059        D: Deserializer<'de>,
1060    {
1061        let s = String::deserialize(deserializer)?;
1062        let ord = s.trim_start_matches("view-id-");
1063        match usize::from_str_radix(ord, 10) {
1064            Ok(id) => Ok(ViewId(id)),
1065            Err(_) => Err(de::Error::invalid_value(Unexpected::Str(&s), &"view id")),
1066        }
1067    }
1068}
1069
1070impl fmt::Display for BufferId {
1071    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1072        write!(f, "buffer-id-{}", self.0)
1073    }
1074}
1075
1076impl BufferId {
1077    pub fn new(val: usize) -> Self {
1078        BufferId(val)
1079    }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084    use serde::Deserialize;
1085
1086    use super::ViewId;
1087
1088    #[test]
1089    fn test_deserialize_view_id() {
1090        let de = json!("view-id-1");
1091        assert_eq!(ViewId::deserialize(&de).unwrap(), ViewId(1));
1092
1093        let de = json!("not-a-view-id");
1094        assert!(ViewId::deserialize(&de).unwrap_err().is_data());
1095    }
1096}