xi_core_lib/plugins/
mod.rs

1// Copyright 2017 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//! Plugins and related functionality.
16
17mod catalog;
18pub mod manifest;
19pub mod rpc;
20
21use std::fmt;
22use std::io::BufReader;
23use std::path::Path;
24use std::process::{Child, Command as ProcCommand, Stdio};
25use std::sync::Arc;
26use std::thread;
27
28use serde_json::Value;
29
30use xi_rpc::{self, RpcLoop, RpcPeer};
31use xi_trace;
32
33use crate::config::Table;
34use crate::syntax::LanguageId;
35use crate::tabs::ViewId;
36use crate::WeakXiCore;
37
38use self::rpc::{PluginBufferInfo, PluginUpdate};
39
40pub(crate) use self::catalog::PluginCatalog;
41pub use self::manifest::{Command, PlaceholderRpc, PluginDescription};
42
43pub type PluginName = String;
44
45/// A process-unique identifier for a running plugin.
46///
47/// Note: two instances of the same executable will have different identifiers.
48/// Note: this identifier is distinct from the OS's process id.
49#[derive(
50    Serialize, Deserialize, Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord,
51)]
52pub struct PluginPid(pub(crate) usize);
53
54pub type PluginId = PluginPid;
55
56impl fmt::Display for PluginPid {
57    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
58        write!(f, "plugin-{}", self.0)
59    }
60}
61
62pub struct Plugin {
63    peer: RpcPeer,
64    pub(crate) id: PluginId,
65    pub(crate) name: String,
66    #[allow(dead_code)]
67    process: Child,
68}
69
70impl Plugin {
71    //TODO: initialize should be sent automatically during launch,
72    //and should only send the plugin_id. We can just use the existing 'new_buffer'
73    // RPC for adding views
74    pub fn initialize(&self, info: Vec<PluginBufferInfo>) {
75        self.peer.send_rpc_notification(
76            "initialize",
77            &json!({
78                "plugin_id": self.id,
79                "buffer_info": info,
80            }),
81        )
82    }
83
84    pub fn shutdown(&self) {
85        self.peer.send_rpc_notification("shutdown", &json!({}));
86    }
87
88    // TODO: rethink naming, does this need to be a vec?
89    pub fn new_buffer(&self, info: &PluginBufferInfo) {
90        self.peer.send_rpc_notification("new_buffer", &json!({ "buffer_info": [info] }))
91    }
92
93    pub fn close_view(&self, view_id: ViewId) {
94        self.peer.send_rpc_notification("did_close", &json!({ "view_id": view_id }))
95    }
96
97    pub fn did_save(&self, view_id: ViewId, path: &Path) {
98        self.peer.send_rpc_notification(
99            "did_save",
100            &json!({
101                "view_id": view_id,
102                "path": path,
103            }),
104        )
105    }
106
107    pub fn update<F>(&self, update: &PluginUpdate, callback: F)
108    where
109        F: FnOnce(Result<Value, xi_rpc::Error>) + Send + 'static,
110    {
111        self.peer.send_rpc_request_async("update", &json!(update), Box::new(callback))
112    }
113
114    pub fn toggle_tracing(&self, enabled: bool) {
115        self.peer.send_rpc_notification("tracing_config", &json!({ "enabled": enabled }))
116    }
117
118    pub fn collect_trace(&self) -> Result<Value, xi_rpc::Error> {
119        self.peer.send_rpc_request("collect_trace", &json!({}))
120    }
121
122    pub fn config_changed(&self, view_id: ViewId, changes: &Table) {
123        self.peer.send_rpc_notification(
124            "config_changed",
125            &json!({
126                "view_id": view_id,
127                "changes": changes,
128            }),
129        )
130    }
131
132    pub fn language_changed(&self, view_id: ViewId, new_lang: &LanguageId) {
133        self.peer.send_rpc_notification(
134            "language_changed",
135            &json!({
136                "view_id": view_id,
137                "new_lang": new_lang,
138            }),
139        )
140    }
141
142    pub fn get_hover(&self, view_id: ViewId, request_id: usize, position: usize) {
143        self.peer.send_rpc_notification(
144            "get_hover",
145            &json!({
146                "view_id": view_id,
147                "request_id": request_id,
148                "position": position,
149            }),
150        )
151    }
152
153    pub fn dispatch_command(&self, view_id: ViewId, method: &str, params: &Value) {
154        self.peer.send_rpc_notification(
155            "custom_command",
156            &json!({
157                "view_id": view_id,
158                "method": method,
159                "params": params,
160            }),
161        )
162    }
163}
164
165pub(crate) fn start_plugin_process(
166    plugin_desc: Arc<PluginDescription>,
167    id: PluginId,
168    core: WeakXiCore,
169) {
170    let spawn_result = thread::Builder::new()
171        .name(format!("<{}> core host thread", &plugin_desc.name))
172        .spawn(move || {
173            info!("starting plugin {}", &plugin_desc.name);
174            let child = ProcCommand::new(&plugin_desc.exec_path)
175                .stdin(Stdio::piped())
176                .stdout(Stdio::piped())
177                .spawn();
178
179            match child {
180                Ok(mut child) => {
181                    let child_stdin = child.stdin.take().unwrap();
182                    let child_stdout = child.stdout.take().unwrap();
183                    let mut looper = RpcLoop::new(child_stdin);
184                    let peer: RpcPeer = Box::new(looper.get_raw_peer());
185                    let name = plugin_desc.name.clone();
186                    peer.send_rpc_notification("ping", &Value::Array(Vec::new()));
187                    let plugin = Plugin { peer, process: child, name, id };
188
189                    // set tracing immediately
190                    if xi_trace::is_enabled() {
191                        plugin.toggle_tracing(true);
192                    }
193
194                    core.plugin_connect(Ok(plugin));
195                    let mut core = core;
196                    let err = looper.mainloop(|| BufReader::new(child_stdout), &mut core);
197                    core.plugin_exit(id, err);
198                }
199                Err(err) => core.plugin_connect(Err(err)),
200            }
201        });
202
203    if let Err(err) = spawn_result {
204        error!("thread spawn failed for {}, {:?}", id, err);
205    }
206}