mod catalog;
pub mod manifest;
pub mod rpc;
use std::fmt;
use std::io::BufReader;
use std::path::Path;
use std::process::{Child, Command as ProcCommand, Stdio};
use std::sync::Arc;
use std::thread;
use serde_json::Value;
use xi_rpc::{self, RpcLoop, RpcPeer};
use xi_trace;
use crate::config::Table;
use crate::syntax::LanguageId;
use crate::tabs::ViewId;
use crate::WeakXiCore;
use self::rpc::{PluginBufferInfo, PluginUpdate};
pub(crate) use self::catalog::PluginCatalog;
pub use self::manifest::{Command, PlaceholderRpc, PluginDescription};
pub type PluginName = String;
#[derive(
Serialize, Deserialize, Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord,
)]
pub struct PluginPid(pub(crate) usize);
pub type PluginId = PluginPid;
impl fmt::Display for PluginPid {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "plugin-{}", self.0)
}
}
pub struct Plugin {
peer: RpcPeer,
pub(crate) id: PluginId,
pub(crate) name: String,
#[allow(dead_code)]
process: Child,
}
impl Plugin {
pub fn initialize(&self, info: Vec<PluginBufferInfo>) {
self.peer.send_rpc_notification(
"initialize",
&json!({
"plugin_id": self.id,
"buffer_info": info,
}),
)
}
pub fn shutdown(&self) {
self.peer.send_rpc_notification("shutdown", &json!({}));
}
pub fn new_buffer(&self, info: &PluginBufferInfo) {
self.peer.send_rpc_notification("new_buffer", &json!({ "buffer_info": [info] }))
}
pub fn close_view(&self, view_id: ViewId) {
self.peer.send_rpc_notification("did_close", &json!({ "view_id": view_id }))
}
pub fn did_save(&self, view_id: ViewId, path: &Path) {
self.peer.send_rpc_notification(
"did_save",
&json!({
"view_id": view_id,
"path": path,
}),
)
}
pub fn update<F>(&self, update: &PluginUpdate, callback: F)
where
F: FnOnce(Result<Value, xi_rpc::Error>) + Send + 'static,
{
self.peer.send_rpc_request_async("update", &json!(update), Box::new(callback))
}
pub fn toggle_tracing(&self, enabled: bool) {
self.peer.send_rpc_notification("tracing_config", &json!({ "enabled": enabled }))
}
pub fn collect_trace(&self) -> Result<Value, xi_rpc::Error> {
self.peer.send_rpc_request("collect_trace", &json!({}))
}
pub fn config_changed(&self, view_id: ViewId, changes: &Table) {
self.peer.send_rpc_notification(
"config_changed",
&json!({
"view_id": view_id,
"changes": changes,
}),
)
}
pub fn language_changed(&self, view_id: ViewId, new_lang: &LanguageId) {
self.peer.send_rpc_notification(
"language_changed",
&json!({
"view_id": view_id,
"new_lang": new_lang,
}),
)
}
pub fn get_hover(&self, view_id: ViewId, request_id: usize, position: usize) {
self.peer.send_rpc_notification(
"get_hover",
&json!({
"view_id": view_id,
"request_id": request_id,
"position": position,
}),
)
}
pub fn dispatch_command(&self, view_id: ViewId, method: &str, params: &Value) {
self.peer.send_rpc_notification(
"custom_command",
&json!({
"view_id": view_id,
"method": method,
"params": params,
}),
)
}
}
pub(crate) fn start_plugin_process(
plugin_desc: Arc<PluginDescription>,
id: PluginId,
core: WeakXiCore,
) {
let spawn_result = thread::Builder::new()
.name(format!("<{}> core host thread", &plugin_desc.name))
.spawn(move || {
info!("starting plugin {}", &plugin_desc.name);
let child = ProcCommand::new(&plugin_desc.exec_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
match child {
Ok(mut child) => {
let child_stdin = child.stdin.take().unwrap();
let child_stdout = child.stdout.take().unwrap();
let mut looper = RpcLoop::new(child_stdin);
let peer: RpcPeer = Box::new(looper.get_raw_peer());
let name = plugin_desc.name.clone();
peer.send_rpc_notification("ping", &Value::Array(Vec::new()));
let plugin = Plugin { peer, process: child, name, id };
if xi_trace::is_enabled() {
plugin.toggle_tracing(true);
}
core.plugin_connect(Ok(plugin));
let mut core = core;
let err = looper.mainloop(|| BufReader::new(child_stdout), &mut core);
core.plugin_exit(id, err);
}
Err(err) => core.plugin_connect(Err(err)),
}
});
if let Err(err) = spawn_result {
error!("thread spawn failed for {}, {:?}", id, err);
}
}