1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct ViewId(pub(crate) usize);
70
71#[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
77pub type BufferIdentifier = BufferId;
79
80pub(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
87pub(crate) const WATCH_IDLE_TOKEN: usize = 1002;
89
90#[cfg(feature = "notify")]
91const CONFIG_EVENT_TOKEN: WatchToken = WatchToken(1);
92
93#[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 kill_ring: RefCell<Rope>,
110 style_map: RefCell<ThemeStyleMap>,
112 width_cache: RefCell<WidthCache>,
113 config_manager: ConfigManager,
115 recorder: RefCell<Recorder>,
117 self_ref: Option<WeakXiCore>,
120 pending_views: Vec<(ViewId, Table)>,
122 peer: Client,
123 id_counter: Counter,
124 plugins: PluginCatalog,
125 running_plugins: Vec<Plugin>,
127}
128
129impl 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 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 self.style_map.borrow_mut().load_theme_dir();
211
212 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 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 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 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 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
269impl CoreState {
271 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 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 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 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 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 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 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 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 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 fn do_modify_user_config(&mut self, domain: ConfigDomainExternal, changes: Table) {
469 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 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 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
555impl 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 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 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 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 #[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 if has_changes && is_pristine {
722 if let Ok(text) = self.file_manager.open(path, buffer_id) {
723 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 #[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 #[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 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 #[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 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 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 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
899impl CoreState {
901 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 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
963impl 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
986pub 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
1028impl 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}