1mod 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#[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 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 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 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}