/
githubmirror
/
lapce
Обзор
Документация
Войти
/
githubmirror
/
lapce
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
lapce-proxy/src/plugin/lsp.rs
557 строк
16 KB
Jakub Panek
chore: were spellcecked nauw (#3887)
03 апр 2026, 20:56
Не верифицирован
03 апр 2026, 20:56
c9e4c33
Код
Авторство
О чём код?
#[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; use std::{ io::{BufRead, BufReader, BufWriter, Write}, path::{Path, PathBuf}, process::{self, Child, Command, Stdio}, sync::Arc, thread, }; use anyhow::{Result, anyhow}; use jsonrpc_lite::{Id, Params}; use lapce_core::meta; use lapce_rpc::{ RpcError, plugin::{PluginId, VoltID}, style::LineStyle, }; use lapce_xi_rope::Rope; use lsp_types::{ notification::{Initialized, Notification}, request::{Initialize, Request}, *, }; use parking_lot::Mutex; use serde_json::Value; use super::{ client_capabilities, psp::{ PluginHandlerNotification, PluginHostHandler, PluginServerHandler, PluginServerRpcHandler, ResponseSender, RpcCallback, handle_plugin_server_message, }, }; use crate::{buffer::Buffer, plugin::PluginCatalogRpcHandler}; const HEADER_CONTENT_LENGTH: &str = "content-length"; const HEADER_CONTENT_TYPE: &str = "content-type"; pub enum LspRpc { Request { id: u64, method: String, params: Params, }, Notification { method: String, params: Params, }, Response { id: u64, result: Value, }, Error { id: u64, error: RpcError, }, } pub struct LspClient { plugin_rpc: PluginCatalogRpcHandler, server_rpc: PluginServerRpcHandler, process: Child, workspace: Option<PathBuf>, host: PluginHostHandler, options: Option<Value>, } impl PluginServerHandler for LspClient { fn method_registered(&mut self, method: &str) -> bool { self.host.method_registered(method) } fn document_supported( &mut self, language_id: Option<&str>, path: Option<&Path>, ) -> bool { self.host.document_supported(language_id, path) } fn handle_handler_notification( &mut self, notification: PluginHandlerNotification, ) { use PluginHandlerNotification::*; match notification { Initialize => { self.initialize(); } InitializeResult(result) => { self.host.server_capabilities = result.capabilities; } Shutdown => { self.shutdown(); } SpawnedPluginLoaded { .. } => {} } } fn handle_host_request( &mut self, id: Id, method: String, params: Params, resp: ResponseSender, ) { self.host.handle_request(id, method, params, resp); } fn handle_host_notification( &mut self, method: String, params: Params, from: String, ) { if let Err(err) = self.host.handle_notification(method, params, from) { tracing::error!("{:?}", err); } } fn handle_did_save_text_document( &self, language_id: String, path: PathBuf, text_document: TextDocumentIdentifier, text: lapce_xi_rope::Rope, ) { self.host.handle_did_save_text_document( language_id, path, text_document, text, ); } fn handle_did_change_text_document( &mut self, language_id: String, document: lsp_types::VersionedTextDocumentIdentifier, delta: lapce_xi_rope::RopeDelta, text: lapce_xi_rope::Rope, new_text: lapce_xi_rope::Rope, change: Arc< Mutex<( Option<TextDocumentContentChangeEvent>, Option<TextDocumentContentChangeEvent>, )>, >, ) { self.host.handle_did_change_text_document( language_id, document, delta, text, new_text, change, ); } fn format_semantic_tokens( &self, tokens: SemanticTokens, text: Rope, f: Box<dyn RpcCallback<Vec<LineStyle>, RpcError>>, ) { self.host.format_semantic_tokens(tokens, text, f); } } impl LspClient { #[allow(clippy::too_many_arguments)] fn new( plugin_rpc: PluginCatalogRpcHandler, document_selector: DocumentSelector, workspace: Option<PathBuf>, volt_id: VoltID, volt_display_name: String, spawned_by: Option<PluginId>, plugin_id: Option<PluginId>, pwd: Option<PathBuf>, server_uri: Url, args: Vec<String>, options: Option<Value>, ) -> Result<Self> { let server = match server_uri.scheme() { "file" => { let path = server_uri.to_file_path().map_err(|_| anyhow!(""))?; #[cfg(unix)] if let Err(err) = std::process::Command::new("chmod") .arg("+x") .arg(&path) .output() { tracing::error!("{:?}", err); } path.to_str().ok_or_else(|| anyhow!(""))?.to_string() } "urn" => server_uri.path().to_string(), _ => return Err(anyhow!("uri not supported")), }; let mut process = Self::process(workspace.as_ref(), &server, &args)?; let stdin = process.stdin.take().unwrap(); let stdout = process.stdout.take().unwrap(); let stderr = process.stderr.take().unwrap(); let mut writer = Box::new(BufWriter::new(stdin)); let (io_tx, io_rx) = crossbeam_channel::unbounded(); let server_rpc = PluginServerRpcHandler::new( volt_id.clone(), spawned_by, plugin_id, io_tx.clone(), ); thread::spawn(move || { for msg in io_rx { if msg .get_method() .map(|x| x == lsp_types::request::Shutdown::METHOD) .unwrap_or_default() { break; } if let Ok(msg) = serde_json::to_string(&msg) { tracing::debug!("write to lsp: {}", msg); let msg = format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg); if let Err(err) = writer.write(msg.as_bytes()) { tracing::error!("{:?}", err); } if let Err(err) = writer.flush() { tracing::error!("{:?}", err); } } } }); let local_server_rpc = server_rpc.clone(); let core_rpc = plugin_rpc.core_rpc.clone(); let volt_id_closure = volt_id.clone(); let name = volt_display_name.clone(); thread::spawn(move || { let mut reader = Box::new(BufReader::new(stdout)); loop { match read_message(&mut reader) { Ok(message_str) => { if !message_str.contains("$/progress") { tracing::debug!("read from lsp: {}", message_str); } if let Some(resp) = handle_plugin_server_message( &local_server_rpc, &message_str, &name, ) { if let Err(err) = io_tx.send(resp) { tracing::error!("{:?}", err); } } } Err(_err) => { core_rpc.log( lapce_rpc::core::LogLevel::Error, format!("lsp server {server} stopped!"), Some(format!( "lapce_proxy::plugin::lsp::{}::{}::stopped", volt_id_closure.author, volt_id_closure.name )), ); return; } }; } }); let core_rpc = plugin_rpc.core_rpc.clone(); let volt_id_closure = volt_id.clone(); thread::spawn(move || { let mut reader = Box::new(BufReader::new(stderr)); loop { let mut line = String::new(); match reader.read_line(&mut line) { Ok(n) => { if n == 0 { return; } core_rpc.log( lapce_rpc::core::LogLevel::Trace, line.trim_end().to_string(), Some(format!( "lapce_proxy::plugin::lsp::{}::{}::stderr", volt_id_closure.author, volt_id_closure.name )), ); } Err(_) => { return; } } } }); let host = PluginHostHandler::new( workspace.clone(), pwd, volt_id, volt_display_name, document_selector, plugin_rpc.core_rpc.clone(), server_rpc.clone(), plugin_rpc.clone(), ); Ok(Self { plugin_rpc, server_rpc, process, workspace, host, options, }) } #[allow(clippy::too_many_arguments)] pub fn start( plugin_rpc: PluginCatalogRpcHandler, document_selector: DocumentSelector, workspace: Option<PathBuf>, volt_id: VoltID, volt_display_name: String, spawned_by: Option<PluginId>, plugin_id: Option<PluginId>, pwd: Option<PathBuf>, server_uri: Url, args: Vec<String>, options: Option<Value>, ) -> Result<PluginId> { let mut lsp = Self::new( plugin_rpc, document_selector, workspace, volt_id, volt_display_name, spawned_by, plugin_id, pwd, server_uri, args, options, )?; let plugin_id = lsp.server_rpc.plugin_id; let rpc = lsp.server_rpc.clone(); thread::spawn(move || { rpc.mainloop(&mut lsp); }); Ok(plugin_id) } fn initialize(&mut self) { let root_uri = self .workspace .clone() .map(|p| Url::from_directory_path(p).unwrap()); tracing::debug!("initialization_options {:?}", self.options); #[allow(deprecated)] let params = InitializeParams { process_id: Some(process::id()), root_uri: root_uri.clone(), initialization_options: self.options.clone(), capabilities: client_capabilities(), trace: Some(TraceValue::Verbose), workspace_folders: root_uri.map(|uri| { vec![WorkspaceFolder { name: uri.as_str().to_string(), uri, }] }), client_info: Some(ClientInfo { name: meta::NAME.to_owned(), version: Some(meta::VERSION.to_owned()), }), locale: None, root_path: None, work_done_progress_params: WorkDoneProgressParams::default(), }; match self.server_rpc.server_request( Initialize::METHOD, params, None, None, false, ) { Ok(value) => { let result: InitializeResult = serde_json::from_value(value).unwrap(); self.host.server_capabilities = result.capabilities; self.server_rpc.server_notification( Initialized::METHOD, InitializedParams {}, None, None, false, ); if self .plugin_rpc .plugin_server_loaded(self.server_rpc.clone()) .is_err() { self.server_rpc.shutdown(); self.shutdown(); } } Err(err) => { tracing::error!("{:?}", err); } } // move |result| { // if let Ok(value) = result { // let result: InitializeResult = // serde_json::from_value(value).unwrap(); // server_rpc.handle_rpc(PluginServerRpc::Handler( // PluginHandlerNotification::InitializeDone(result), // )); // } // }, // ); } fn shutdown(&mut self) { if let Err(err) = self.process.kill() { tracing::error!("{:?}", err); } if let Err(err) = self.process.wait() { tracing::error!("{:?}", err); } } fn process( workspace: Option<&PathBuf>, server: &str, args: &[String], ) -> Result<Child> { let mut process = Command::new(server); if let Some(workspace) = workspace { process.current_dir(workspace); } process.args(args); #[cfg(target_os = "windows")] let process = process.creation_flags(0x08000000); let child = process .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; Ok(child) } } pub struct DocumentFilter { /// The document must have this language id, if it exists pub language_id: Option<String>, /// The document's path must match this glob, if it exists pub pattern: Option<globset::GlobMatcher>, // TODO: URI Scheme from lsp-types document filter } impl DocumentFilter { /// Constructs a document filter from the LSP version /// This ignores any fields that are badly constructed pub(crate) fn from_lsp_filter_loose( filter: &lsp_types::DocumentFilter, ) -> DocumentFilter { DocumentFilter { language_id: filter.language.clone(), // TODO: clean this up pattern: filter .pattern .as_deref() .map(globset::Glob::new) .and_then(Result::ok) .map(|x| globset::Glob::compile_matcher(&x)), } } } pub enum LspHeader { ContentType, ContentLength(usize), } fn parse_header(s: &str) -> Result<LspHeader> { let split: Vec<String> = s.splitn(2, ": ").map(|s| s.trim().to_lowercase()).collect(); if split.len() != 2 { return Err(anyhow!("Malformed")); }; match split[0].as_ref() { HEADER_CONTENT_TYPE => Ok(LspHeader::ContentType), HEADER_CONTENT_LENGTH => { Ok(LspHeader::ContentLength(split[1].parse::<usize>()?)) } _ => Err(anyhow!("Unknown parse error occurred")), } } pub fn read_message<T: BufRead>(reader: &mut T) -> Result<String> { let mut buffer = String::new(); let mut content_length: Option<usize> = None; loop { buffer.clear(); let _ = reader.read_line(&mut buffer)?; // eprin match &buffer { s if s.trim().is_empty() => break, s => { match parse_header(s)? { LspHeader::ContentLength(len) => content_length = Some(len), LspHeader::ContentType => (), }; } }; } let content_length = content_length .ok_or_else(|| anyhow!("missing content-length header: {}", buffer))?; let mut body_buffer = vec![0; content_length]; reader.read_exact(&mut body_buffer)?; let body = String::from_utf8(body_buffer)?; Ok(body) } pub fn get_change_for_sync_kind( sync_kind: TextDocumentSyncKind, buffer: &Buffer, content_change: &TextDocumentContentChangeEvent, ) -> Option<Vec<TextDocumentContentChangeEvent>> { match sync_kind { TextDocumentSyncKind::NONE => None, TextDocumentSyncKind::FULL => { let text_document_content_change_event = TextDocumentContentChangeEvent { range: None, range_length: None, text: buffer.get_document(), }; Some(vec![text_document_content_change_event]) } TextDocumentSyncKind::INCREMENTAL => Some(vec![content_change.clone()]), _ => None, } }