diff --git a/lapce-proxy/src/lsp.rs b/lapce-proxy/src/lsp.rs index 41f74f7a..4d97f6db 100644 --- a/lapce-proxy/src/lsp.rs +++ b/lapce-proxy/src/lsp.rs @@ -57,6 +57,7 @@ pub struct LspState { #[derive(Clone)] pub struct LspClient { exec_path: String, + binary_args: Vec, options: Option, state: Arc>, dispatcher: Dispatcher, @@ -84,15 +85,51 @@ pub fn start_server( language_id: &str, options: Option, ) { + let binary_args = self.get_plugin_binary_args(options.clone()); let client = LspClient::new( language_id.to_string(), exec_path, options, + binary_args.to_owned(), self.dispatcher.clone().unwrap(), ); self.clients.insert(language_id.to_string(), client); } + fn get_plugin_binary_args(&mut self, option: Option) -> Vec { + let mut vals = Vec::new(); + + let option = match option { + Some(val) => val, + None => { + return vals; + } + }; + + let binary = match option["binary"].as_object() { + Some(binary) => binary, + None => return vals, + }; + + let option = match binary.get("binary_args") { + Some(binary_args) => binary_args, + None => return vals, + }; + + let option = if let Some(option) = option.as_array() { + option + } else { + println!("binary_args value should be of type [String]."); + return vals; + }; + + for val in option { + vals.push(String::from(val.as_str().unwrap())); + } + + return vals; + } + pub fn new_buffer( &self, buffer_id: &BufferId, @@ -368,15 +405,18 @@ pub fn new( _language_id: String, exec_path: &str, options: Option, + binary_args: Vec, dispatcher: Dispatcher, ) -> Arc { - let mut process = Self::process(exec_path); + //TODO: better handling of binary args in plugin + let mut process = Self::process(exec_path, binary_args.clone()); let writer = Box::new(BufWriter::new(process.stdin.take().unwrap())); let stdout = process.stdout.take().unwrap(); let lsp_client = Arc::new(LspClient { dispatcher, exec_path: exec_path.to_string(), + binary_args: binary_args, options, state: Arc::new(Mutex::new(LspState { next_id: 0, @@ -414,8 +454,12 @@ fn handle_stdout(&self, stdout: ChildStdout) { }); } - fn process(exec_path: &str) -> Child { + fn process(exec_path: &str, binary_args: Vec) -> Child { let mut process = Command::new(exec_path); + + for arg in binary_args { + process.arg(arg); + } #[cfg(target_os = "windows")] let process = process.creation_flags(0x08000000); process @@ -426,7 +470,8 @@ fn process(exec_path: &str) -> Child { } fn reload(&self) { - let mut process = Self::process(&self.exec_path); + //TODO: avoid clone using a &[String] ? + let mut process = Self::process(&self.exec_path, self.binary_args.clone()); let writer = Box::new(BufWriter::new(process.stdin.take().unwrap())); let stdout = process.stdout.take().unwrap(); @@ -473,9 +518,15 @@ pub fn get_uri(&self, buffer: &Buffer) -> Url { } pub fn handle_message(&self, message: &str) { + println!("Received message! {}", message); match JsonRpc::parse(message) { - Ok(JsonRpc::Request(_obj)) => { - // trace!("client received unexpected request: {:?}", obj) + Ok(value @ JsonRpc::Request(_)) => { + let id = value.get_id().unwrap(); + self.handle_request( + value.get_method().unwrap(), + id, + value.get_params().unwrap(), + ) } Ok(value @ JsonRpc::Notification(_)) => { self.handle_notification( @@ -497,6 +548,20 @@ pub fn handle_message(&self, message: &str) { } } + pub fn handle_request(&self, method: &str, id: Id, _params: Params) { + match method { + "window/workDoneProgress/create" => { + // Token is ignored as the workProgress Widget is always working + // In the future, for multiple workProgress Handling we should + // probably store the token + self.send_success_response(id, &json!({})); + } + method => { + println!("Received unhandled request {method}"); + } + } + } + pub fn handle_notification(&self, method: &str, params: Params) { match method { "textDocument/publishDiagnostics" => { @@ -515,7 +580,19 @@ pub fn handle_notification(&self, method: &str, params: Params) { }), ); } - _ => (), + "window/showMessage" => { + // TODO: send message to display + } + "window/logMessage" => { + // TODO: We should log the message here. Waiting for + // the discussion about handling plugins logs before doing anything + } + "experimental/serverStatus" => { + //TODO: Logging of server status + } + method => { + println!("Received unhandled notification {}", method); + } } } @@ -564,6 +641,22 @@ pub fn send_request(&self, method: &str, params: Params, completion: Callback) { self.send_rpc(&to_value(&request).unwrap()); } + pub fn send_success_response(&self, id: Id, result: &Value) { + let response = JsonRpc::success(id, result); + + self.send_rpc(&to_value(&response).unwrap()); + } + + pub fn send_error_response( + &self, + id: jsonrpc_lite::Id, + error: jsonrpc_lite::Error, + ) { + let response = JsonRpc::error(id, error); + + self.send_rpc(&to_value(&response).unwrap()); + } + fn initialize(&self) { if let Some(workspace) = self.dispatcher.workspace.lock().clone() { let root_url = Url::from_directory_path(workspace).unwrap(); diff --git a/lapce-proxy/src/plugin.rs b/lapce-proxy/src/plugin.rs index 3a089183..74dee07a 100644 --- a/lapce-proxy/src/plugin.rs +++ b/lapce-proxy/src/plugin.rs @@ -171,10 +171,15 @@ fn start_plugin( let output = Pipe::new(); let input = Pipe::new(); + let env = match plugin_desc.get_plugin_env() { + Ok(env) => env, + Err(err) => return Err(err), + }; let mut wasi_env = WasiState::new("Lapce") .map_dir("/", plugin_desc.dir.clone().unwrap())? .stdin(Box::new(input)) .stdout(Box::new(output)) + .envs(env) .finalize()?; let wasi = wasi_env.import_object(&module)?; diff --git a/lapce-rpc/src/plugin.rs b/lapce-rpc/src/plugin.rs index 918da51b..82198724 100644 --- a/lapce-rpc/src/plugin.rs +++ b/lapce-rpc/src/plugin.rs @@ -1,5 +1,6 @@ -use std::path::PathBuf; +use std::{path::PathBuf, process::Command}; +use anyhow::{format_err, Error}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -27,3 +28,81 @@ pub struct PluginInfo { pub os: String, pub configuration: Option, } + +impl PluginDescription { + pub fn get_plugin_env(&self) -> Result, Error> { + let mut vars = Vec::new(); + + let conf = match &self.configuration { + Some(val) => val, + None => { + return Err(format_err!( + "Empty configuration for plugin {}", + self.display_name + )) + } + }; + + let conf = match conf.as_object() { + Some(val) => val, + None => { + return Err(format_err!( + "Empty configuration for plugin {}", + self.display_name + )) + } + }; + + let env = match conf.get("env_command") { + Some(env) => env, + // We do not print any error as no env is allowed. + None => return Ok(vars), + }; + + let args = match env.as_str() { + Some(arg) => arg, + None => { + return Err(format_err!( + "Plugin {}: env_command is not a string", + self.display_name + )) + } + }; + + let output = if cfg!(target_os = "windows") { + Command::new("cmd").arg("/c").arg(args).output() + } else { + Command::new("sh").arg("-c").arg(args).output() + }; + + let output = match output { + Ok(val) => val.stdout, + Err(err) => { + return Err(format_err!( + "Error during env command execution for plugin {}: {}", + self.name, + err + )) + } + }; + + let data = match String::from_utf8(output) { + Ok(val) => val, + Err(err) => { + return Err(format_err!( + "Error during UTF-8 conversion for plugin {}: {}", + self.display_name, + err + )) + } + }; + + for l in data.lines() { + if let Some((key, value)) = l.split_once('=') { + vars.push((String::from(key), String::from(value))); + }; + } + + return Ok(vars); + } +}