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.rs254
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);
}
}