xi_core_lib/
core.rs

1// Copyright 2018 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
15use std::io;
16use std::sync::{Arc, Mutex, MutexGuard, Weak};
17
18use serde_json::Value;
19
20use xi_rpc::{Error as RpcError, Handler, ReadError, RemoteError, RpcCtx};
21use xi_trace;
22
23use crate::plugin_rpc::{PluginCommand, PluginNotification, PluginRequest};
24use crate::plugins::{Plugin, PluginId};
25use crate::rpc::*;
26use crate::tabs::{CoreState, ViewId};
27
28/// A reference to the main core state.
29///
30/// # Note
31///
32/// Various items of initial setup are dependent on how the client
33/// is configured, so we defer instantiating state until we have that
34/// information.
35pub enum XiCore {
36    // TODO: profile startup, and determine what things (such as theme loading)
37    // we should be doing before client_init.
38    Waiting,
39    Running(Arc<Mutex<CoreState>>),
40}
41
42/// A weak reference to the main state. This is passed to plugin threads.
43#[derive(Clone)]
44pub struct WeakXiCore(Weak<Mutex<CoreState>>);
45
46#[allow(dead_code)]
47impl XiCore {
48    pub fn new() -> Self {
49        XiCore::Waiting
50    }
51
52    /// Returns `true` if the `client_started` has not been received.
53    fn is_waiting(&self) -> bool {
54        match *self {
55            XiCore::Waiting => true,
56            _ => false,
57        }
58    }
59
60    /// Returns a guard to the core state. A convenience around `Mutex::lock`.
61    ///
62    /// # Panics
63    ///
64    /// Panics if core has not yet received the `client_started` message.
65    pub fn inner(&self) -> MutexGuard<CoreState> {
66        match self {
67            XiCore::Running(ref inner) => inner.lock().unwrap(),
68            XiCore::Waiting => panic!(
69                "core does not start until client_started \
70                 RPC is received"
71            ),
72        }
73    }
74
75    /// Returns a new reference to the core state, if core is running.
76    fn weak_self(&self) -> Option<WeakXiCore> {
77        match self {
78            XiCore::Running(ref inner) => Some(WeakXiCore(Arc::downgrade(inner))),
79            XiCore::Waiting => None,
80        }
81    }
82}
83
84/// Handler for messages originating with the frontend.
85impl Handler for XiCore {
86    type Notification = CoreNotification;
87    type Request = CoreRequest;
88
89    fn handle_notification(&mut self, ctx: &RpcCtx, rpc: Self::Notification) {
90        use self::CoreNotification::*;
91
92        // We allow tracing to be enabled before event `client_started`
93        if let TracingConfig { enabled } = rpc {
94            match enabled {
95                true => xi_trace::enable_tracing(),
96                false => xi_trace::disable_tracing(),
97            }
98            info!("tracing in core = {:?}", enabled);
99            if self.is_waiting() {
100                return;
101            }
102        }
103
104        // wait for client_started before setting up inner
105        if let ClientStarted { ref config_dir, ref client_extras_dir } = rpc {
106            assert!(self.is_waiting(), "client_started can only be sent once");
107            let state =
108                CoreState::new(ctx.get_peer(), config_dir.clone(), client_extras_dir.clone());
109            let state = Arc::new(Mutex::new(state));
110            *self = XiCore::Running(state);
111            let weak_self = self.weak_self().unwrap();
112            self.inner().finish_setup(weak_self);
113        }
114
115        self.inner().client_notification(rpc);
116    }
117
118    fn handle_request(&mut self, _ctx: &RpcCtx, rpc: Self::Request) -> Result<Value, RemoteError> {
119        self.inner().client_request(rpc)
120    }
121
122    fn idle(&mut self, _ctx: &RpcCtx, token: usize) {
123        self.inner().handle_idle(token);
124    }
125}
126
127impl WeakXiCore {
128    /// Attempts to upgrade the weak reference. Essentially a wrapper
129    /// for `Arc::upgrade`.
130    fn upgrade(&self) -> Option<XiCore> {
131        self.0.upgrade().map(XiCore::Running)
132    }
133
134    /// Called immediately after attempting to start a plugin,
135    /// from the plugin's thread.
136    pub fn plugin_connect(&self, plugin: Result<Plugin, io::Error>) {
137        if let Some(core) = self.upgrade() {
138            core.inner().plugin_connect(plugin)
139        }
140    }
141
142    /// Called from a plugin runloop thread when the runloop exits.
143    pub fn plugin_exit(&self, plugin: PluginId, error: Result<(), ReadError>) {
144        if let Some(core) = self.upgrade() {
145            core.inner().plugin_exit(plugin, error)
146        }
147    }
148
149    /// Handles the result of an update sent to a plugin.
150    ///
151    /// All plugins must acknowledge when they are sent a new update, so that
152    /// core can track which revisions are still 'live', that is can still
153    /// be the base revision for a delta. Once a plugin has acknowledged a new
154    /// revision, it can no longer send deltas against any older revision.
155    pub fn handle_plugin_update(
156        &self,
157        plugin: PluginId,
158        view: ViewId,
159        response: Result<Value, RpcError>,
160    ) {
161        if let Some(core) = self.upgrade() {
162            let _t = xi_trace::trace_block("WeakXiCore::plugin_update", &["core"]);
163            core.inner().plugin_update(plugin, view, response);
164        }
165    }
166}
167
168/// Handler for messages originating from plugins.
169impl Handler for WeakXiCore {
170    type Notification = PluginCommand<PluginNotification>;
171    type Request = PluginCommand<PluginRequest>;
172
173    fn handle_notification(&mut self, ctx: &RpcCtx, rpc: Self::Notification) {
174        let PluginCommand { view_id, plugin_id, cmd } = rpc;
175        if let Some(core) = self.upgrade() {
176            core.inner().plugin_notification(ctx, view_id, plugin_id, cmd)
177        }
178    }
179
180    fn handle_request(&mut self, ctx: &RpcCtx, rpc: Self::Request) -> Result<Value, RemoteError> {
181        let PluginCommand { view_id, plugin_id, cmd } = rpc;
182        if let Some(core) = self.upgrade() {
183            core.inner().plugin_request(ctx, view_id, plugin_id, cmd)
184        } else {
185            Err(RemoteError::custom(0, "core is missing", None))
186        }
187    }
188}
189
190#[cfg(test)]
191/// Returns a non-functional `WeakXiRef`, needed to mock other types.
192pub fn dummy_weak_core() -> WeakXiCore {
193    use xi_rpc::test_utils::DummyPeer;
194    use xi_rpc::Peer;
195    let peer = Box::new(DummyPeer);
196    let state = CoreState::new(&peer.box_clone(), None, None);
197    let core = Arc::new(Mutex::new(state));
198    WeakXiCore(Arc::downgrade(&core))
199}