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
15 use std::io;
16 use std::sync::{Arc, Mutex, MutexGuard, Weak};
17
18 use serde_json::Value;
19
20 use xi_rpc::{Error as RpcError, Handler, ReadError, RemoteError, RpcCtx};
21 use xi_trace;
22
23 use crate::plugin_rpc::{PluginCommand, PluginNotification, PluginRequest};
24 use crate::plugins::{Plugin, PluginId};
25 use crate::rpc::*;
26 use 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.
35 pub 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)]
44 pub struct WeakXiCore(Weak<Mutex<CoreState>>);
45
46 #[allow(dead_code)]
47 impl XiCore {
new() -> Self48 pub fn new() -> Self {
49 XiCore::Waiting
50 }
51
52 /// Returns `true` if the `client_started` has not been received.
is_waiting(&self) -> bool53 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.
inner(&self) -> MutexGuard<CoreState>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.
weak_self(&self) -> Option<WeakXiCore>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.
85 impl Handler for XiCore {
86 type Notification = CoreNotification;
87 type Request = CoreRequest;
88
handle_notification(&mut self, ctx: &RpcCtx, rpc: Self::Notification)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
handle_request(&mut self, _ctx: &RpcCtx, rpc: Self::Request) -> Result<Value, RemoteError>118 fn handle_request(&mut self, _ctx: &RpcCtx, rpc: Self::Request) -> Result<Value, RemoteError> {
119 self.inner().client_request(rpc)
120 }
121
idle(&mut self, _ctx: &RpcCtx, token: usize)122 fn idle(&mut self, _ctx: &RpcCtx, token: usize) {
123 self.inner().handle_idle(token);
124 }
125 }
126
127 impl WeakXiCore {
128 /// Attempts to upgrade the weak reference. Essentially a wrapper
129 /// for `Arc::upgrade`.
upgrade(&self) -> Option<XiCore>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.
plugin_connect(&self, plugin: Result<Plugin, io::Error>)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.
plugin_exit(&self, plugin: PluginId, error: Result<(), ReadError>)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.
handle_plugin_update( &self, plugin: PluginId, view: ViewId, response: Result<Value, RpcError>, )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.
169 impl Handler for WeakXiCore {
170 type Notification = PluginCommand<PluginNotification>;
171 type Request = PluginCommand<PluginRequest>;
172
handle_notification(&mut self, ctx: &RpcCtx, rpc: Self::Notification)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
handle_request(&mut self, ctx: &RpcCtx, rpc: Self::Request) -> Result<Value, RemoteError>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.
dummy_weak_core() -> WeakXiCore192 pub 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 }
200