use std::cell::{Cell, RefCell};
use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::fs::File;
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use serde::de::{self, Deserialize, Deserializer, Unexpected};
use serde::ser::{Serialize, Serializer};
use serde_json::Value;
use xi_rope::Rope;
use xi_rpc::{self, ReadError, RemoteError, RpcCtx, RpcPeer};
use xi_trace::{self, trace_block};
use crate::client::Client;
use crate::config::{self, ConfigDomain, ConfigDomainExternal, ConfigManager, Table};
use crate::editor::Editor;
use crate::event_context::EventContext;
use crate::file::FileManager;
use crate::line_ending::LineEnding;
use crate::plugin_rpc::{PluginNotification, PluginRequest};
use crate::plugins::rpc::ClientPluginInfo;
use crate::plugins::{start_plugin_process, Plugin, PluginCatalog, PluginPid};
use crate::recorder::Recorder;
use crate::rpc::{
CoreNotification, CoreRequest, EditNotification, EditRequest,
PluginNotification as CorePluginNotification,
};
use crate::styles::{ThemeStyleMap, DEFAULT_THEME};
use crate::syntax::LanguageId;
use crate::view::View;
use crate::whitespace::Indentation;
use crate::width_cache::WidthCache;
use crate::WeakXiCore;
#[cfg(feature = "notify")]
use crate::watcher::{FileWatcher, WatchToken};
#[cfg(feature = "notify")]
use notify::DebouncedEvent;
#[cfg(feature = "notify")]
use std::ffi::OsStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ViewId(pub(crate) usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct BufferId(pub(crate) usize);
pub type PluginId = crate::plugins::PluginPid;
pub type BufferIdentifier = BufferId;
pub(crate) const RENDER_VIEW_IDLE_MASK: usize = 1 << 25;
pub(crate) const REWRAP_VIEW_IDLE_MASK: usize = 1 << 26;
pub(crate) const FIND_VIEW_IDLE_MASK: usize = 1 << 27;
const NEW_VIEW_IDLE_TOKEN: usize = 1001;
pub(crate) const WATCH_IDLE_TOKEN: usize = 1002;
#[cfg(feature = "notify")]
const CONFIG_EVENT_TOKEN: WatchToken = WatchToken(1);
#[cfg(feature = "notify")]
pub const OPEN_FILE_EVENT_TOKEN: WatchToken = WatchToken(2);
#[cfg(feature = "notify")]
const THEME_FILE_EVENT_TOKEN: WatchToken = WatchToken(3);
#[cfg(feature = "notify")]
const PLUGIN_EVENT_TOKEN: WatchToken = WatchToken(4);
#[allow(dead_code)]
pub struct CoreState {
editors: BTreeMap<BufferId, RefCell<Editor>>,
views: BTreeMap<ViewId, RefCell<View>>,
file_manager: FileManager,
kill_ring: RefCell<Rope>,
style_map: RefCell<ThemeStyleMap>,
width_cache: RefCell<WidthCache>,
config_manager: ConfigManager,
recorder: RefCell<Recorder>,
self_ref: Option<WeakXiCore>,
pending_views: Vec<(ViewId, Table)>,
peer: Client,
id_counter: Counter,
plugins: PluginCatalog,
running_plugins: Vec<Plugin>,
}
impl CoreState {
pub(crate) fn new(
peer: &RpcPeer,
config_dir: Option<PathBuf>,
extras_dir: Option<PathBuf>,
) -> Self {
#[cfg(feature = "notify")]
let mut watcher = FileWatcher::new(peer.clone());
if let Some(p) = config_dir.as_ref() {
if !p.exists() {
if let Err(e) = config::init_config_dir(p) {
error!("error initing file based configs: {:?}", e);
}
}
#[cfg(feature = "notify")]
watcher.watch_filtered(p, true, CONFIG_EVENT_TOKEN, |p| {
p.extension().and_then(OsStr::to_str).unwrap_or("") == "xiconfig"
});
}
let config_manager = ConfigManager::new(config_dir, extras_dir);
let themes_dir = config_manager.get_themes_dir();
if let Some(p) = themes_dir.as_ref() {
#[cfg(feature = "notify")]
watcher.watch_filtered(p, true, THEME_FILE_EVENT_TOKEN, |p| {
p.extension().and_then(OsStr::to_str).unwrap_or("") == "tmTheme"
});
}
let plugins_dir = config_manager.get_plugins_dir();
if let Some(p) = plugins_dir.as_ref() {
#[cfg(feature = "notify")]
watcher.watch_filtered(p, true, PLUGIN_EVENT_TOKEN, |p| p.is_dir() || !p.exists());
}
CoreState {
views: BTreeMap::new(),
editors: BTreeMap::new(),
#[cfg(feature = "notify")]
file_manager: FileManager::new(watcher),
#[cfg(not(feature = "notify"))]
file_manager: FileManager::new(),
kill_ring: RefCell::new(Rope::from("")),
style_map: RefCell::new(ThemeStyleMap::new(themes_dir)),
width_cache: RefCell::new(WidthCache::new()),
config_manager,
recorder: RefCell::new(Recorder::new()),
self_ref: None,
pending_views: Vec::new(),
peer: Client::new(peer.clone()),
id_counter: Counter::default(),
plugins: PluginCatalog::default(),
running_plugins: Vec::new(),
}
}
fn next_view_id(&self) -> ViewId {
ViewId(self.id_counter.next())
}
fn next_buffer_id(&self) -> BufferId {
BufferId(self.id_counter.next())
}
fn next_plugin_id(&self) -> PluginId {
PluginPid(self.id_counter.next())
}
pub(crate) fn finish_setup(&mut self, self_ref: WeakXiCore) {
self.self_ref = Some(self_ref);
if let Some(path) = self.config_manager.base_config_file_path() {
self.load_file_based_config(&path);
}
self.style_map.borrow_mut().load_theme_dir();
let plugin_paths = self.config_manager.get_plugin_paths();
self.plugins.reload_from_paths(&plugin_paths);
let languages = self.plugins.make_languages_map();
let languages_ids = languages.iter().map(|l| l.name.clone()).collect::<Vec<_>>();
self.peer.available_languages(languages_ids);
self.config_manager.set_languages(languages);
let theme_names = self.style_map.borrow().get_theme_names();
self.peer.available_themes(theme_names);
for manifest in self.plugins.iter() {
start_plugin_process(
manifest.clone(),
self.next_plugin_id(),
self.self_ref.as_ref().unwrap().clone(),
);
}
}
fn load_file_based_config(&mut self, path: &Path) {
let _t = trace_block("CoreState::load_config_file", &["core"]);
if let Some(domain) = self.config_manager.domain_for_path(path) {
match config::try_load_from_file(&path) {
Ok(table) => self.set_config(domain, table),
Err(e) => self.peer.alert(e.to_string()),
}
} else {
self.peer.alert(format!("Unexpected config file {:?}", path));
}
}
fn set_config(&mut self, domain: ConfigDomain, table: Table) {
match self.config_manager.set_user_config(domain, table) {
Err(e) => self.peer.alert(format!("{}", &e)),
Ok(changes) => self.handle_config_changes(changes),
}
}
fn handle_config_changes(&self, changes: Vec<(BufferId, Table)>) {
for (id, table) in changes {
let view_id = self
.views
.values()
.find(|v| v.borrow().get_buffer_id() == id)
.map(|v| v.borrow().get_view_id())
.unwrap();
self.make_context(view_id).unwrap().config_changed(&table)
}
}
}
impl CoreState {
pub(crate) fn make_context(&self, view_id: ViewId) -> Option<EventContext> {
self.views.get(&view_id).map(|view| {
let buffer_id = view.borrow().get_buffer_id();
let editor = &self.editors[&buffer_id];
let info = self.file_manager.get_info(buffer_id);
let plugins = self.running_plugins.iter().collect::<Vec<_>>();
let config = self.config_manager.get_buffer_config(buffer_id);
let language = self.config_manager.get_buffer_language(buffer_id);
EventContext {
view_id,
buffer_id,
view,
editor,
config: &config.items,
recorder: &self.recorder,
language,
info,
siblings: Vec::new(),
plugins,
client: &self.peer,
style_map: &self.style_map,
width_cache: &self.width_cache,
kill_ring: &self.kill_ring,
weak_core: self.self_ref.as_ref().unwrap(),
}
})
}
fn iter_groups<'a>(&'a self) -> Iter<'a, Box<dyn Iterator<Item = &ViewId> + 'a>> {
Iter { views: Box::new(self.views.keys()), seen: HashSet::new(), inner: self }
}
pub(crate) fn client_notification(&mut self, cmd: CoreNotification) {
use self::CoreNotification::*;
use self::CorePluginNotification as PN;
match cmd {
Edit(crate::rpc::EditCommand { view_id, cmd }) => self.do_edit(view_id, cmd),
Save { view_id, file_path } => self.do_save(view_id, file_path),
CloseView { view_id } => self.do_close_view(view_id),
ModifyUserConfig { domain, changes } => self.do_modify_user_config(domain, changes),
SetTheme { theme_name } => self.do_set_theme(&theme_name),
SaveTrace { destination, frontend_samples } => {
self.save_trace(&destination, frontend_samples)
}
Plugin(cmd) => match cmd {
PN::Start { view_id, plugin_name } => self.do_start_plugin(view_id, &plugin_name),
PN::Stop { view_id, plugin_name } => self.do_stop_plugin(view_id, &plugin_name),
PN::PluginRpc { view_id, receiver, rpc } => {
self.do_plugin_rpc(view_id, &receiver, &rpc.method, &rpc.params)
}
},
TracingConfig { enabled } => self.toggle_tracing(enabled),
ClientStarted { .. } => (),
SetLanguage { view_id, language_id } => self.do_set_language(view_id, language_id),
}
}
pub(crate) fn client_request(&mut self, cmd: CoreRequest) -> Result<Value, RemoteError> {
use self::CoreRequest::*;
match cmd {
NewView { file_path } => self.do_new_view(file_path.map(PathBuf::from)),
Edit(crate::rpc::EditCommand { view_id, cmd }) => self.do_edit_sync(view_id, cmd),
GetConfig { view_id } => self.do_get_config(view_id).map(|c| json!(c)),
DebugGetContents { view_id } => self.do_get_contents(view_id).map(|c| json!(c)),
}
}
fn do_edit(&mut self, view_id: ViewId, cmd: EditNotification) {
if let Some(mut edit_ctx) = self.make_context(view_id) {
edit_ctx.do_edit(cmd);
}
}
fn do_edit_sync(&mut self, view_id: ViewId, cmd: EditRequest) -> Result<Value, RemoteError> {
if let Some(mut edit_ctx) = self.make_context(view_id) {
edit_ctx.do_edit_sync(cmd)
} else {
Err(RemoteError::custom(404, format!("missing view {:?}", view_id), None))
}
}
fn do_new_view(&mut self, path: Option<PathBuf>) -> Result<Value, RemoteError> {
let view_id = self.next_view_id();
let buffer_id = self.next_buffer_id();
let rope = match path.as_ref() {
Some(p) => self.file_manager.open(p, buffer_id)?,
None => Rope::from(""),
};
let editor = RefCell::new(Editor::with_text(rope));
let view = RefCell::new(View::new(view_id, buffer_id));
self.editors.insert(buffer_id, editor);
self.views.insert(view_id, view);
let config = self.config_manager.add_buffer(buffer_id, path.as_ref().map(|p| p.as_path()));
self.pending_views.push((view_id, config));
self.peer.schedule_idle(NEW_VIEW_IDLE_TOKEN);
Ok(json!(view_id))
}
fn do_save<P>(&mut self, view_id: ViewId, path: P)
where
P: AsRef<Path>,
{
let _t = trace_block("CoreState::do_save", &["core"]);
let path = path.as_ref();
let buffer_id = self.views.get(&view_id).map(|v| v.borrow().get_buffer_id());
let buffer_id = match buffer_id {
Some(id) => id,
None => return,
};
let mut save_ctx = self.make_context(view_id).unwrap();
let fin_text = save_ctx.text_for_save();
if let Err(e) = self.file_manager.save(path, &fin_text, buffer_id) {
let error_message = e.to_string();
error!("File error: {:?}", error_message);
self.peer.alert(error_message);
return;
}
let changes = self.config_manager.update_buffer_path(buffer_id, path);
let language = self.config_manager.get_buffer_language(buffer_id);
self.make_context(view_id).unwrap().after_save(path);
self.make_context(view_id).unwrap().language_changed(&language);
if let Some(changes) = changes {
self.make_context(view_id).unwrap().config_changed(&changes);
}
}
fn do_close_view(&mut self, view_id: ViewId) {
let close_buffer = self.make_context(view_id).map(|ctx| ctx.close_view()).unwrap_or(true);
let buffer_id = self.views.remove(&view_id).map(|v| v.borrow().get_buffer_id());
if let Some(buffer_id) = buffer_id {
if close_buffer {
self.editors.remove(&buffer_id);
self.file_manager.close(buffer_id);
self.config_manager.remove_buffer(buffer_id);
}
}
}
fn do_set_theme(&self, theme_name: &str) {
if theme_name != self.style_map.borrow().get_theme_name() {
if let Err(e) = self.style_map.borrow_mut().set_theme(&theme_name) {
error!("error setting theme: {:?}, {:?}", theme_name, e);
return;
}
}
self.notify_client_and_update_views();
}
fn notify_client_and_update_views(&self) {
{
let style_map = self.style_map.borrow();
self.peer.theme_changed(style_map.get_theme_name(), style_map.get_theme_settings());
}
self.iter_groups().for_each(|mut edit_ctx| {
edit_ctx.with_editor(|ed, view, _, _| {
ed.theme_changed(&self.style_map.borrow());
view.set_dirty(ed.get_buffer());
});
edit_ctx.render_if_needed();
});
}
fn do_modify_user_config(&mut self, domain: ConfigDomainExternal, changes: Table) {
let domain: ConfigDomain = match domain {
ConfigDomainExternal::General => ConfigDomain::General,
ConfigDomainExternal::Syntax(id) => ConfigDomain::Language(id),
ConfigDomainExternal::Language(id) => ConfigDomain::Language(id),
ConfigDomainExternal::UserOverride(view_id) => match self.views.get(&view_id) {
Some(v) => ConfigDomain::UserOverride(v.borrow().get_buffer_id()),
None => return,
},
};
let new_config = self.config_manager.table_for_update(domain.clone(), changes);
self.set_config(domain, new_config);
}
fn do_get_config(&self, view_id: ViewId) -> Result<Table, RemoteError> {
let _t = trace_block("CoreState::get_config", &["core"]);
self.views
.get(&view_id)
.map(|v| v.borrow().get_buffer_id())
.map(|id| self.config_manager.get_buffer_config(id).to_table())
.ok_or(RemoteError::custom(404, format!("missing {}", view_id), None))
}
fn do_get_contents(&self, view_id: ViewId) -> Result<Rope, RemoteError> {
self.make_context(view_id)
.map(|ctx| ctx.editor.borrow().get_buffer().to_owned())
.ok_or_else(|| RemoteError::custom(404, format!("No view for id {}", view_id), None))
}
fn do_set_language(&mut self, view_id: ViewId, language_id: LanguageId) {
if let Some(view) = self.views.get(&view_id) {
let buffer_id = view.borrow().get_buffer_id();
let changes = self.config_manager.override_language(buffer_id, language_id.clone());
let mut context = self.make_context(view_id).unwrap();
context.language_changed(&language_id);
if let Some(changes) = changes {
context.config_changed(&changes);
}
}
}
fn do_start_plugin(&mut self, _view_id: ViewId, plugin: &str) {
if self.running_plugins.iter().any(|p| p.name == plugin) {
info!("plugin {} already running", plugin);
return;
}
if let Some(manifest) = self.plugins.get_named(plugin) {
start_plugin_process(
manifest.clone(),
self.next_plugin_id(),
self.self_ref.as_ref().unwrap().clone(),
);
} else {
warn!("no plugin found with name '{}'", plugin);
}
}
fn do_stop_plugin(&mut self, _view_id: ViewId, plugin: &str) {
if let Some(p) = self
.running_plugins
.iter()
.position(|p| p.name == plugin)
.map(|ix| self.running_plugins.remove(ix))
{
p.shutdown();
self.after_stop_plugin(&p);
}
}
fn do_plugin_rpc(&self, view_id: ViewId, receiver: &str, method: &str, params: &Value) {
self.running_plugins
.iter()
.filter(|p| p.name == receiver)
.for_each(|p| p.dispatch_command(view_id, method, params))
}
fn after_stop_plugin(&mut self, plugin: &Plugin) {
self.iter_groups().for_each(|mut cx| cx.plugin_stopped(plugin));
}
}
impl CoreState {
pub(crate) fn handle_idle(&mut self, token: usize) {
match token {
NEW_VIEW_IDLE_TOKEN => self.finalize_new_views(),
WATCH_IDLE_TOKEN => self.handle_fs_events(),
other if (other & RENDER_VIEW_IDLE_MASK) != 0 => {
self.handle_render_timer(other ^ RENDER_VIEW_IDLE_MASK)
}
other if (other & REWRAP_VIEW_IDLE_MASK) != 0 => {
self.handle_rewrap_callback(other ^ REWRAP_VIEW_IDLE_MASK)
}
other if (other & FIND_VIEW_IDLE_MASK) != 0 => {
self.handle_find_callback(other ^ FIND_VIEW_IDLE_MASK)
}
other => panic!("unexpected idle token {}", other),
};
}
fn finalize_new_views(&mut self) {
let to_start = mem::replace(&mut self.pending_views, Vec::new());
to_start.iter().for_each(|(id, config)| {
let modified = self.detect_whitespace(*id, config);
let config = modified.as_ref().unwrap_or(config);
let mut edit_ctx = self.make_context(*id).unwrap();
edit_ctx.finish_init(&config);
});
}
fn detect_whitespace(&mut self, id: ViewId, config: &Table) -> Option<Table> {
let buffer_id = self.views.get(&id).map(|v| v.borrow().get_buffer_id())?;
let editor = self
.editors
.get(&buffer_id)
.expect("existing buffer_id must have corresponding editor");
if editor.borrow().get_buffer().is_empty() {
return None;
}
let autodetect_whitespace =
self.config_manager.get_buffer_config(buffer_id).items.autodetect_whitespace;
if !autodetect_whitespace {
return None;
}
let mut changes = Table::new();
let indentation = Indentation::parse(editor.borrow().get_buffer());
match indentation {
Ok(Some(Indentation::Tabs)) => {
changes.insert("translate_tabs_to_spaces".into(), false.into());
}
Ok(Some(Indentation::Spaces(n))) => {
changes.insert("translate_tabs_to_spaces".into(), true.into());
changes.insert("tab_size".into(), n.into());
}
Err(_) => info!("detected mixed indentation"),
Ok(None) => info!("file contains no indentation"),
}
let line_ending = LineEnding::parse(editor.borrow().get_buffer());
match line_ending {
Ok(Some(LineEnding::CrLf)) => {
changes.insert("line_ending".into(), "\r\n".into());
}
Ok(Some(LineEnding::Lf)) => {
changes.insert("line_ending".into(), "\n".into());
}
Err(_) => info!("detected mixed line endings"),
Ok(None) => info!("file contains no supported line endings"),
}
let config_delta =
self.config_manager.table_for_update(ConfigDomain::SysOverride(buffer_id), changes);
match self
.config_manager
.set_user_config(ConfigDomain::SysOverride(buffer_id), config_delta)
{
Ok(ref mut items) if !items.is_empty() => {
assert!(
items.len() == 1,
"whitespace overrides can only update a single buffer's config\n{:?}",
items
);
let table = items.remove(0).1;
let mut config = config.clone();
config.extend(table);
Some(config)
}
Ok(_) => {
warn!("set_user_config failed to update config, no tables were returned");
None
}
Err(err) => {
warn!("detect_whitespace failed to update config: {:?}", err);
None
}
}
}
fn handle_render_timer(&mut self, token: usize) {
let id: ViewId = token.into();
if let Some(mut ctx) = self.make_context(id) {
ctx._finish_delayed_render();
}
}
fn handle_rewrap_callback(&mut self, token: usize) {
let id: ViewId = token.into();
if let Some(mut ctx) = self.make_context(id) {
ctx.do_rewrap_batch();
}
}
fn handle_find_callback(&mut self, token: usize) {
let id: ViewId = token.into();
if let Some(mut ctx) = self.make_context(id) {
ctx.do_incremental_find();
}
}
#[cfg(feature = "notify")]
fn handle_fs_events(&mut self) {
let _t = trace_block("CoreState::handle_fs_events", &["core"]);
let mut events = self.file_manager.watcher().take_events();
for (token, event) in events.drain(..) {
match token {
OPEN_FILE_EVENT_TOKEN => self.handle_open_file_fs_event(event),
CONFIG_EVENT_TOKEN => self.handle_config_fs_event(event),
THEME_FILE_EVENT_TOKEN => self.handle_themes_fs_event(event),
PLUGIN_EVENT_TOKEN => self.handle_plugin_fs_event(event),
_ => warn!("unexpected fs event token {:?}", token),
}
}
}
#[cfg(not(feature = "notify"))]
fn handle_fs_events(&mut self) {}
#[cfg(feature = "notify")]
fn handle_open_file_fs_event(&mut self, event: DebouncedEvent) {
use notify::DebouncedEvent::*;
let path = match event {
NoticeWrite(ref path) | Create(ref path) | Write(ref path) | Chmod(ref path) => path,
other => {
debug!("Event in open file {:?}", other);
return;
}
};
let buffer_id = match self.file_manager.get_editor(path) {
Some(id) => id,
None => return,
};
let has_changes = self.file_manager.check_file(path, buffer_id);
let is_pristine = self.editors.get(&buffer_id).map(|ed| ed.borrow().is_pristine()).unwrap();
if has_changes && is_pristine {
if let Ok(text) = self.file_manager.open(path, buffer_id) {
let view_id = self
.views
.values()
.find(|v| v.borrow().get_buffer_id() == buffer_id)
.map(|v| v.borrow().get_view_id())
.unwrap();
self.make_context(view_id).unwrap().reload(text);
}
}
}
#[cfg(feature = "notify")]
fn handle_config_fs_event(&mut self, event: DebouncedEvent) {
use self::DebouncedEvent::*;
match event {
Create(ref path) | Write(ref path) | Chmod(ref path) => {
self.load_file_based_config(path)
}
Remove(ref path) if !path.exists() => self.remove_config_at_path(path),
Rename(ref old, ref new) => {
self.remove_config_at_path(old);
self.load_file_based_config(new);
}
_ => (),
}
}
fn remove_config_at_path(&mut self, path: &Path) {
if let Some(domain) = self.config_manager.domain_for_path(path) {
self.set_config(domain, Table::default());
}
}
#[cfg(feature = "notify")]
fn handle_plugin_fs_event(&mut self, event: DebouncedEvent) {
use self::DebouncedEvent::*;
match event {
Create(ref path) | Write(ref path) => {
self.plugins.load_from_paths(&[path.clone()]);
if let Some(plugin) = self.plugins.get_from_path(path) {
self.do_start_plugin(ViewId(0), &plugin.name);
}
}
NoticeRemove(ref path) | Remove(ref path) if !path.exists() => {
if let Some(plugin) = self.plugins.get_from_path(path) {
self.do_stop_plugin(ViewId(0), &plugin.name);
self.plugins.remove_named(&plugin.name);
}
}
Rename(ref old, ref new) => {
if let Some(old_plugin) = self.plugins.get_from_path(old) {
self.do_stop_plugin(ViewId(0), &old_plugin.name);
self.plugins.remove_named(&old_plugin.name);
}
self.plugins.load_from_paths(&[new.clone()]);
if let Some(new_plugin) = self.plugins.get_from_path(new) {
self.do_start_plugin(ViewId(0), &new_plugin.name);
}
}
Chmod(ref path) | Remove(ref path) => {
if let Some(plugin) = self.plugins.get_from_path(path) {
self.do_stop_plugin(ViewId(0), &plugin.name);
self.do_start_plugin(ViewId(0), &plugin.name);
}
}
_ => (),
}
self.views.keys().for_each(|view_id| {
let available_plugins = self
.plugins
.iter()
.map(|plugin| ClientPluginInfo { name: plugin.name.clone(), running: true })
.collect::<Vec<_>>();
self.peer.available_plugins(view_id.clone(), &available_plugins);
});
}
#[cfg(feature = "notify")]
fn handle_themes_fs_event(&mut self, event: DebouncedEvent) {
use self::DebouncedEvent::*;
match event {
Create(ref path) | Write(ref path) => self.load_theme_file(path),
NoticeRemove(ref path) | Remove(ref path) if !path.exists() => self.remove_theme(path),
Rename(ref old, ref new) => {
self.remove_theme(old);
self.load_theme_file(new);
}
Chmod(ref path) | Remove(ref path) => {
self.style_map.borrow_mut().sync_dir(path.parent())
}
_ => (),
}
let theme_names = self.style_map.borrow().get_theme_names();
self.peer.available_themes(theme_names);
}
fn load_theme_file(&mut self, path: &Path) {
let _t = trace_block("CoreState::load_theme_file", &["core"]);
let result = self.style_map.borrow_mut().load_theme_info_from_path(path);
match result {
Ok(theme_name) => {
if theme_name == self.style_map.borrow().get_theme_name() {
if self.style_map.borrow_mut().set_theme(&theme_name).is_ok() {
self.notify_client_and_update_views();
}
}
}
Err(e) => error!("Error loading theme file: {:?}, {:?}", path, e),
}
}
fn remove_theme(&mut self, path: &Path) {
let result = self.style_map.borrow_mut().remove_theme(path);
if let Some(theme_name) = result {
if theme_name == self.style_map.borrow().get_theme_name() {
self.do_set_theme(DEFAULT_THEME);
}
}
}
fn toggle_tracing(&self, enabled: bool) {
self.running_plugins.iter().for_each(|plugin| plugin.toggle_tracing(enabled))
}
fn save_trace<P>(&self, path: P, frontend_samples: Value)
where
P: AsRef<Path>,
{
use xi_trace::chrome_trace_dump;
let mut all_traces = xi_trace::samples_cloned_unsorted();
if let Ok(mut traces) = chrome_trace_dump::decode(frontend_samples) {
all_traces.append(&mut traces);
}
for plugin in &self.running_plugins {
match plugin.collect_trace() {
Ok(json) => {
let mut trace = chrome_trace_dump::decode(json).unwrap();
all_traces.append(&mut trace);
}
Err(e) => error!("trace error {:?}", e),
}
}
all_traces.sort_unstable();
let mut trace_file = match File::create(path.as_ref()) {
Ok(f) => f,
Err(e) => {
error!("error saving trace {:?}", e);
return;
}
};
if let Err(e) = chrome_trace_dump::serialize(&all_traces, &mut trace_file) {
error!("error saving trace {:?}", e);
}
}
}
impl CoreState {
pub(crate) fn plugin_connect(&mut self, plugin: Result<Plugin, io::Error>) {
match plugin {
Ok(plugin) => {
let init_info =
self.iter_groups().map(|mut ctx| ctx.plugin_info()).collect::<Vec<_>>();
plugin.initialize(init_info);
self.iter_groups().for_each(|mut cx| cx.plugin_started(&plugin));
self.running_plugins.push(plugin);
}
Err(e) => error!("failed to start plugin {:?}", e),
}
}
pub(crate) fn plugin_exit(&mut self, id: PluginId, error: Result<(), ReadError>) {
warn!("plugin {:?} exited with result {:?}", id, error);
let running_idx = self.running_plugins.iter().position(|p| p.id == id);
if let Some(idx) = running_idx {
let plugin = self.running_plugins.remove(idx);
self.after_stop_plugin(&plugin);
}
}
pub(crate) fn plugin_update(
&mut self,
_plugin_id: PluginId,
view_id: ViewId,
response: Result<Value, xi_rpc::Error>,
) {
if let Some(mut edit_ctx) = self.make_context(view_id) {
edit_ctx.do_plugin_update(response);
}
}
pub(crate) fn plugin_notification(
&mut self,
_ctx: &RpcCtx,
view_id: ViewId,
plugin_id: PluginId,
cmd: PluginNotification,
) {
if let Some(mut edit_ctx) = self.make_context(view_id) {
edit_ctx.do_plugin_cmd(plugin_id, cmd)
}
}
pub(crate) fn plugin_request(
&mut self,
_ctx: &RpcCtx,
view_id: ViewId,
plugin_id: PluginId,
cmd: PluginRequest,
) -> Result<Value, RemoteError> {
if let Some(mut edit_ctx) = self.make_context(view_id) {
Ok(edit_ctx.do_plugin_cmd_sync(plugin_id, cmd))
} else {
Err(RemoteError::custom(404, "missing view", None))
}
}
}
impl CoreState {
pub fn _test_open_editors(&self) -> Vec<BufferId> {
self.editors.keys().cloned().collect()
}
pub fn _test_open_views(&self) -> Vec<ViewId> {
self.views.keys().cloned().collect()
}
}
pub mod test_helpers {
use super::{BufferId, ViewId};
pub fn new_view_id(id: usize) -> ViewId {
ViewId(id)
}
pub fn new_buffer_id(id: usize) -> BufferId {
BufferId(id)
}
}
pub struct Iter<'a, I> {
views: I,
seen: HashSet<ViewId>,
inner: &'a CoreState,
}
impl<'a, I> Iterator for Iter<'a, I>
where
I: Iterator<Item = &'a ViewId>,
{
type Item = EventContext<'a>;
fn next(&mut self) -> Option<Self::Item> {
let &mut Iter { ref mut views, ref mut seen, ref inner } = self;
loop {
let next_view = match views.next() {
None => return None,
Some(v) if seen.contains(v) => continue,
Some(v) => v,
};
let context = inner.make_context(*next_view).unwrap();
context.siblings.iter().for_each(|sibl| {
let _ = seen.insert(sibl.borrow().get_view_id());
});
return Some(context);
}
}
}
#[derive(Debug, Default)]
pub(crate) struct Counter(Cell<usize>);
impl Counter {
pub(crate) fn next(&self) -> usize {
let n = self.0.get();
self.0.set(n + 1);
n + 1
}
}
impl From<usize> for ViewId {
fn from(src: usize) -> ViewId {
ViewId(src)
}
}
impl From<ViewId> for usize {
fn from(src: ViewId) -> usize {
src.0
}
}
impl fmt::Display for ViewId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "view-id-{}", self.0)
}
}
impl Serialize for ViewId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for ViewId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let ord = s.trim_start_matches("view-id-");
match usize::from_str_radix(ord, 10) {
Ok(id) => Ok(ViewId(id)),
Err(_) => Err(de::Error::invalid_value(Unexpected::Str(&s), &"view id")),
}
}
}
impl fmt::Display for BufferId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "buffer-id-{}", self.0)
}
}
impl BufferId {
pub fn new(val: usize) -> Self {
BufferId(val)
}
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use super::ViewId;
#[test]
fn test_deserialize_view_id() {
let de = json!("view-id-1");
assert_eq!(ViewId::deserialize(&de).unwrap(), ViewId(1));
let de = json!("not-a-view-id");
assert!(ViewId::deserialize(&de).unwrap_err().is_data());
}
}