Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-lsp/src/lib.rs')
| -rw-r--r-- | helix-lsp/src/lib.rs | 254 |
1 files changed, 60 insertions, 194 deletions
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 259980dd..134cb74f 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -12,7 +12,7 @@ pub use jsonrpc::Call; pub use lsp::{Position, Url}; use futures_util::stream::select_all::SelectAll; -use helix_core::syntax::config::{ +use helix_core::syntax::{ LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures, }; use helix_stdx::path; @@ -37,7 +37,7 @@ pub enum Error { #[error("protocol error: {0}")] Rpc(#[from] jsonrpc::Error), #[error("failed to parse: {0}")] - Parse(Box<dyn std::error::Error + Send + Sync>), + Parse(#[from] serde_json::Error), #[error("IO Error: {0}")] IO(#[from] std::io::Error), #[error("request {0} timed out")] @@ -52,18 +52,6 @@ pub enum Error { Other(#[from] anyhow::Error), } -impl From<serde_json::Error> for Error { - fn from(value: serde_json::Error) -> Self { - Self::Parse(Box::new(value)) - } -} - -impl From<sonic_rs::Error> for Error { - fn from(value: sonic_rs::Error) -> Self { - Self::Parse(Box::new(value)) - } -} - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum OffsetEncoding { /// UTF-8 code units aka bytes @@ -475,7 +463,6 @@ pub enum MethodCall { RegisterCapability(lsp::RegistrationParams), UnregisterCapability(lsp::UnregistrationParams), ShowDocument(lsp::ShowDocumentParams), - WorkspaceDiagnosticRefresh, } impl MethodCall { @@ -507,7 +494,6 @@ impl MethodCall { let params: lsp::ShowDocumentParams = params.parse()?; Self::ShowDocument(params) } - lsp::request::WorkspaceDiagnosticRefresh::METHOD => Self::WorkspaceDiagnosticRefresh, _ => { return Err(Error::Unhandled); } @@ -632,45 +618,51 @@ impl Registry { Ok(self.inner[id].clone()) } - /// If this method is called, all documents that have a reference to the language server have to refresh their language servers, + /// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers, + /// as it could be that language servers of these documents were stopped by this method. /// See helix_view::editor::Editor::refresh_language_servers - pub fn restart_server( + pub fn restart( &mut self, - name: &str, language_config: &LanguageConfiguration, doc_path: Option<&std::path::PathBuf>, root_dirs: &[PathBuf], enable_snippets: bool, - ) -> Option<Result<Arc<Client>>> { - if let Some(old_clients) = self.inner_by_name.remove(name) { - if old_clients.is_empty() { - log::info!("restarting client for '{name}' which was manually stopped"); - } else { - log::info!("stopping existing clients for '{name}'"); - } - for old_client in old_clients { - self.file_event_handler.remove_client(old_client.id()); - self.inner.remove(old_client.id()); - tokio::spawn(async move { - let _ = old_client.force_shutdown().await; - }); - } - } - let client = match self.start_client( - name.to_string(), - language_config, - doc_path, - root_dirs, - enable_snippets, - ) { - Ok(client) => client, - Err(StartupError::NoRequiredRootFound) => return None, - Err(StartupError::Error(err)) => return Some(Err(err)), - }; - self.inner_by_name - .insert(name.to_owned(), vec![client.clone()]); + ) -> Result<Vec<Arc<Client>>> { + language_config + .language_servers + .iter() + .filter_map(|LanguageServerFeatures { name, .. }| { + if let Some(old_clients) = self.inner_by_name.remove(name) { + if old_clients.is_empty() { + log::info!("restarting client for '{name}' which was manually stopped"); + } else { + log::info!("stopping existing clients for '{name}'"); + } + for old_client in old_clients { + self.file_event_handler.remove_client(old_client.id()); + self.inner.remove(old_client.id()); + tokio::spawn(async move { + let _ = old_client.force_shutdown().await; + }); + } + } + let client = match self.start_client( + name.clone(), + language_config, + doc_path, + root_dirs, + enable_snippets, + ) { + Ok(client) => client, + Err(StartupError::NoRequiredRootFound) => return None, + Err(StartupError::Error(err)) => return Some(Err(err)), + }; + self.inner_by_name + .insert(name.to_owned(), vec![client.clone()]); - Some(Ok(client)) + Some(Ok(client)) + }) + .collect() } pub fn stop(&mut self, name: &str) { @@ -709,11 +701,7 @@ impl Registry { } if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| { - let manual_roots = language_config - .workspace_lsp_roots - .as_deref() - .unwrap_or(root_dirs); - client.try_add_doc(&language_config.roots, manual_roots, doc_path, *i == 0) + client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0) }) { return Some((name.to_owned(), Ok(client.clone()))); } @@ -747,17 +735,14 @@ impl Registry { #[derive(Debug)] pub enum ProgressStatus { Created, - Started { - title: String, - progress: lsp::WorkDoneProgress, - }, + Started(lsp::WorkDoneProgress), } impl ProgressStatus { pub fn progress(&self) -> Option<&lsp::WorkDoneProgress> { match &self { ProgressStatus::Created => None, - ProgressStatus::Started { title: _, progress } => Some(progress), + ProgressStatus::Started(progress) => Some(progress), } } } @@ -794,13 +779,6 @@ impl LspProgressMap { self.0.get(&id).and_then(|values| values.get(token)) } - pub fn title(&self, id: LanguageServerId, token: &lsp::ProgressToken) -> Option<&String> { - self.progress(id, token).and_then(|p| match p { - ProgressStatus::Created => None, - ProgressStatus::Started { title, .. } => Some(title), - }) - } - /// Checks if progress `token` for server with `id` is created. pub fn is_created(&mut self, id: LanguageServerId, token: &lsp::ProgressToken) -> bool { self.0 @@ -825,39 +803,17 @@ impl LspProgressMap { self.0.get_mut(&id).and_then(|vals| vals.remove(token)) } - /// Updates the progress of `token` for server with `id` to begin state `status` - pub fn begin( - &mut self, - id: LanguageServerId, - token: lsp::ProgressToken, - status: lsp::WorkDoneProgressBegin, - ) { - self.0.entry(id).or_default().insert( - token, - ProgressStatus::Started { - title: status.title.clone(), - progress: lsp::WorkDoneProgress::Begin(status), - }, - ); - } - - /// Updates the progress of `token` for server with `id` to report state `status`. + /// Updates the progress of `token` for server with `id` to `status`, returns the value replaced or `None`. pub fn update( &mut self, id: LanguageServerId, token: lsp::ProgressToken, - status: lsp::WorkDoneProgressReport, - ) { + status: lsp::WorkDoneProgress, + ) -> Option<ProgressStatus> { self.0 .entry(id) .or_default() - .entry(token) - .and_modify(|e| match e { - ProgressStatus::Created => (), - ProgressStatus::Started { progress, .. } => { - *progress = lsp::WorkDoneProgress::Report(status) - } - }); + .insert(token, ProgressStatus::Started(status)) } } @@ -917,7 +873,7 @@ fn start_client( &ls_config.command, &ls_config.args, ls_config.config.clone(), - &ls_config.environment, + ls_config.environment.clone(), root_path, root_uri, id, @@ -946,7 +902,17 @@ fn start_client( } // next up, notify<initialized> - _client.notify::<lsp::notification::Initialized>(lsp::InitializedParams {}); + let notification_result = _client + .notify::<lsp::notification::Initialized>(lsp::InitializedParams {}) + .await; + + if let Err(e) = notification_result { + log::error!( + "failed to notify language server of its initialization: {}", + e + ); + return; + } initialize_notify.notify_one(); }); @@ -1078,107 +1044,7 @@ mod tests { let mut source = Rope::from_str("[\n\"πΊπΈ\",\n\"π\",\n]"); - let transaction = generate_transaction_from_edits(&source, edits, OffsetEncoding::Utf16); - assert!(transaction.apply(&mut source)); - assert_eq!(source, "[\n \"πΊπΈ\",\n \"π\",\n]"); - } - #[test] - fn rahh() { - use helix_lsp_types::*; - let th = [ - TextEdit { - range: Range { - start: Position { - line: 1, - character: 0, - }, - end: Position { - line: 1, - character: 4, - }, - }, - new_text: "".into(), - }, - TextEdit { - range: Range { - start: Position { - line: 2, - character: 0, - }, - end: Position { - line: 3, - character: 1, - }, - }, - new_text: "".into(), - }, - TextEdit { - range: Range { - start: Position { - line: 3, - character: 9, - }, - end: Position { - line: 3, - character: 9, - }, - }, - new_text: "let new =\n".into(), - }, - TextEdit { - range: Range { - start: Position { - line: 3, - character: 20, - }, - end: Position { - line: 3, - character: 29, - }, - }, - new_text: "".into(), - }, - TextEdit { - range: Range { - start: Position { - line: 3, - character: 56, - }, - end: Position { - line: 4, - character: 24, - }, - }, - new_text: "".into(), - }, - TextEdit { - range: Range { - start: Position { - line: 6, - character: 1, - }, - end: Position { - line: 6, - character: 1, - }, - }, - new_text: "\n".into(), - }, - ]; - let mut source = Rope::from_str( - "impl Editor { // 0 - pub fn open(f: &Path) { // 1 -// 2 - let new = std::fs::read_to_string(f) // 3 - .map_err(anyhow::Error::from)?; // 4 - } -}", - ); - println!("{}", source); - - let transaction = - generate_transaction_from_edits(&source, th.to_vec(), OffsetEncoding::Utf8); + let transaction = generate_transaction_from_edits(&source, edits, OffsetEncoding::Utf8); assert!(transaction.apply(&mut source)); - println!("{}", source); } } |