Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/handlers/lsp.rs')
-rw-r--r--helix-view/src/handlers/lsp.rs446
1 files changed, 0 insertions, 446 deletions
diff --git a/helix-view/src/handlers/lsp.rs b/helix-view/src/handlers/lsp.rs
deleted file mode 100644
index 96ab4626..00000000
--- a/helix-view/src/handlers/lsp.rs
+++ /dev/null
@@ -1,446 +0,0 @@
-use std::collections::btree_map::Entry;
-use std::collections::HashSet;
-use std::fmt::Display;
-
-use crate::editor::Action;
-use crate::events::{
- DiagnosticsDidChange, DocumentDidChange, DocumentDidClose, LanguageServerInitialized,
-};
-use crate::{DocumentId, Editor};
-use helix_core::diagnostic::DiagnosticProvider;
-use helix_core::Uri;
-use helix_event::register_hook;
-use helix_lsp::util::generate_transaction_from_edits;
-use helix_lsp::{lsp, LanguageServerId, OffsetEncoding};
-
-use super::Handlers;
-
-pub struct DocumentColorsEvent(pub DocumentId);
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum SignatureHelpInvoked {
- Automatic,
- Manual,
-}
-
-pub enum SignatureHelpEvent {
- Invoked,
- Trigger,
- ReTrigger,
- Cancel,
- RequestComplete { open: bool },
-}
-
-pub struct PullDiagnosticsEvent {
- pub document_id: DocumentId,
-}
-
-pub struct PullAllDocumentsDiagnosticsEvent {
- pub language_servers: HashSet<LanguageServerId>,
-}
-
-#[derive(Debug)]
-pub struct ApplyEditError {
- pub kind: ApplyEditErrorKind,
- pub failed_change_idx: usize,
-}
-
-#[derive(Debug)]
-pub enum ApplyEditErrorKind {
- DocumentChanged,
- FileNotFound,
- InvalidUrl(helix_core::uri::UrlConversionError),
- IoError(std::io::Error),
- // TODO: check edits before applying and propagate failure
- // InvalidEdit,
-}
-
-impl From<std::io::Error> for ApplyEditErrorKind {
- fn from(err: std::io::Error) -> Self {
- ApplyEditErrorKind::IoError(err)
- }
-}
-
-impl From<helix_core::uri::UrlConversionError> for ApplyEditErrorKind {
- fn from(err: helix_core::uri::UrlConversionError) -> Self {
- ApplyEditErrorKind::InvalidUrl(err)
- }
-}
-
-impl Display for ApplyEditErrorKind {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- ApplyEditErrorKind::DocumentChanged => f.write_str("document has changed"),
- ApplyEditErrorKind::FileNotFound => f.write_str("file not found"),
- ApplyEditErrorKind::InvalidUrl(err) => f.write_str(&format!("{err}")),
- ApplyEditErrorKind::IoError(err) => f.write_str(&format!("{err}")),
- }
- }
-}
-
-impl Editor {
- fn apply_text_edits(
- &mut self,
- url: &helix_lsp::Url,
- version: Option<i32>,
- text_edits: Vec<lsp::TextEdit>,
- offset_encoding: OffsetEncoding,
- ) -> Result<(), ApplyEditErrorKind> {
- let uri = match Uri::try_from(url) {
- Ok(uri) => uri,
- Err(err) => {
- log::error!("{err}");
- return Err(err.into());
- }
- };
- let path = uri.as_path().expect("URIs are valid paths");
-
- let doc_id = match self.open(path, Action::Load) {
- Ok(doc_id) => doc_id,
- Err(err) => {
- let err = format!(
- "failed to open document: {}: {}",
- path.to_string_lossy(),
- err
- );
- log::error!("{}", err);
- self.set_error(err);
- return Err(ApplyEditErrorKind::FileNotFound);
- }
- };
-
- let doc = doc_mut!(self, &doc_id);
- if let Some(version) = version {
- if version != doc.version() {
- let err = format!("outdated workspace edit for {path:?}");
- log::error!("{err}, expected {} but got {version}", doc.version());
- self.set_error(err);
- return Err(ApplyEditErrorKind::DocumentChanged);
- }
- }
-
- // Need to determine a view for apply/append_changes_to_history
- let view_id = self.get_synced_view_id(doc_id);
- let doc = doc_mut!(self, &doc_id);
-
- let transaction = generate_transaction_from_edits(doc.text(), text_edits, offset_encoding);
- let view = view_mut!(self, view_id);
- doc.apply(&transaction, view.id);
- doc.append_changes_to_history(view);
- Ok(())
- }
-
- // TODO make this transactional (and set failureMode to transactional)
- pub fn apply_workspace_edit(
- &mut self,
- offset_encoding: OffsetEncoding,
- workspace_edit: &lsp::WorkspaceEdit,
- ) -> Result<(), ApplyEditError> {
- if let Some(ref document_changes) = workspace_edit.document_changes {
- match document_changes {
- lsp::DocumentChanges::Edits(document_edits) => {
- for (i, document_edit) in document_edits.iter().enumerate() {
- let edits = document_edit
- .edits
- .iter()
- .map(|edit| match edit {
- lsp::OneOf::Left(text_edit) => text_edit,
- lsp::OneOf::Right(annotated_text_edit) => {
- &annotated_text_edit.text_edit
- }
- })
- .cloned()
- .collect();
- self.apply_text_edits(
- &document_edit.text_document.uri,
- document_edit.text_document.version,
- edits,
- offset_encoding,
- )
- .map_err(|kind| ApplyEditError {
- kind,
- failed_change_idx: i,
- })?;
- }
- }
- lsp::DocumentChanges::Operations(operations) => {
- log::debug!("document changes - operations: {:?}", operations);
- for (i, operation) in operations.iter().enumerate() {
- match operation {
- lsp::DocumentChangeOperation::Op(op) => {
- self.apply_document_resource_op(op).map_err(|err| {
- ApplyEditError {
- kind: err,
- failed_change_idx: i,
- }
- })?;
- }
-
- lsp::DocumentChangeOperation::Edit(document_edit) => {
- let edits = document_edit
- .edits
- .iter()
- .map(|edit| match edit {
- lsp::OneOf::Left(text_edit) => text_edit,
- lsp::OneOf::Right(annotated_text_edit) => {
- &annotated_text_edit.text_edit
- }
- })
- .cloned()
- .collect();
- self.apply_text_edits(
- &document_edit.text_document.uri,
- document_edit.text_document.version,
- edits,
- offset_encoding,
- )
- .map_err(|kind| {
- ApplyEditError {
- kind,
- failed_change_idx: i,
- }
- })?;
- }
- }
- }
- }
- }
-
- return Ok(());
- }
-
- if let Some(ref changes) = workspace_edit.changes {
- log::debug!("workspace changes: {:?}", changes);
- for (i, (uri, text_edits)) in changes.iter().enumerate() {
- let text_edits = text_edits.to_vec();
- self.apply_text_edits(uri, None, text_edits, offset_encoding)
- .map_err(|kind| ApplyEditError {
- kind,
- failed_change_idx: i,
- })?;
- }
- }
-
- Ok(())
- }
-
- fn apply_document_resource_op(
- &mut self,
- op: &lsp::ResourceOp,
- ) -> Result<(), ApplyEditErrorKind> {
- use lsp::ResourceOp;
- use std::fs;
- // NOTE: If `Uri` gets another variant than `Path`, the below `expect`s
- // may no longer be valid.
- match op {
- ResourceOp::Create(op) => {
- let uri = Uri::try_from(&op.uri)?;
- let path = uri.as_path().expect("URIs are valid paths");
- let ignore_if_exists = op.options.as_ref().is_some_and(|options| {
- !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
- });
- if !ignore_if_exists || !path.exists() {
- // Create directory if it does not exist
- if let Some(dir) = path.parent() {
- if !dir.is_dir() {
- fs::create_dir_all(dir)?;
- }
- }
-
- fs::write(path, [])?;
- self.language_servers
- .file_event_handler
- .file_changed(path.to_path_buf());
- }
- }
- ResourceOp::Delete(op) => {
- let uri = Uri::try_from(&op.uri)?;
- let path = uri.as_path().expect("URIs are valid paths");
- if path.is_dir() {
- let recursive = op
- .options
- .as_ref()
- .and_then(|options| options.recursive)
- .unwrap_or(false);
-
- if recursive {
- fs::remove_dir_all(path)?
- } else {
- fs::remove_dir(path)?
- }
- self.language_servers
- .file_event_handler
- .file_changed(path.to_path_buf());
- } else if path.is_file() {
- fs::remove_file(path)?;
- }
- }
- ResourceOp::Rename(op) => {
- let from_uri = Uri::try_from(&op.old_uri)?;
- let from = from_uri.as_path().expect("URIs are valid paths");
- let to_uri = Uri::try_from(&op.new_uri)?;
- let to = to_uri.as_path().expect("URIs are valid paths");
- let ignore_if_exists = op.options.as_ref().is_some_and(|options| {
- !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
- });
- if !ignore_if_exists || !to.exists() {
- self.move_path(from, to)?;
- }
- }
- }
- Ok(())
- }
-
- pub fn handle_lsp_diagnostics(
- &mut self,
- provider: &DiagnosticProvider,
- uri: Uri,
- version: Option<i32>,
- mut diagnostics: Vec<lsp::Diagnostic>,
- ) {
- let doc = self
- .documents
- .values_mut()
- .find(|doc| doc.uri().is_some_and(|u| u == uri));
-
- if let Some((version, doc)) = version.zip(doc.as_ref()) {
- if version != doc.version() {
- log::info!("Version ({version}) is out of date for {uri:?} (expected ({})), dropping PublishDiagnostic notification", doc.version());
- return;
- }
- }
-
- let mut unchanged_diag_sources = Vec::new();
- if let Some((lang_conf, old_diagnostics)) = doc
- .as_ref()
- .and_then(|doc| Some((doc.language_config()?, self.diagnostics.get(&uri)?)))
- {
- if !lang_conf.persistent_diagnostic_sources.is_empty() {
- // Sort diagnostics first by severity and then by line numbers.
- // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
- diagnostics.sort_by_key(|d| (d.severity, d.range.start));
- }
- for source in &lang_conf.persistent_diagnostic_sources {
- let new_diagnostics = diagnostics
- .iter()
- .filter(|d| d.source.as_ref() == Some(source));
- let old_diagnostics = old_diagnostics
- .iter()
- .filter(|(d, d_provider)| {
- d_provider == provider && d.source.as_ref() == Some(source)
- })
- .map(|(d, _)| d);
- if new_diagnostics.eq(old_diagnostics) {
- unchanged_diag_sources.push(source.clone())
- }
- }
- }
-
- let diagnostics = diagnostics.into_iter().map(|d| (d, provider.clone()));
-
- // Insert the original lsp::Diagnostics here because we may have no open document
- // for diagnostic message and so we can't calculate the exact position.
- // When using them later in the diagnostics picker, we calculate them on-demand.
- let diagnostics = match self.diagnostics.entry(uri) {
- Entry::Occupied(o) => {
- let current_diagnostics = o.into_mut();
- // there may entries of other language servers, which is why we can't overwrite the whole entry
- current_diagnostics.retain(|(_, d_provider)| d_provider != provider);
- current_diagnostics.extend(diagnostics);
- current_diagnostics
- // Sort diagnostics first by severity and then by line numbers.
- }
- Entry::Vacant(v) => v.insert(diagnostics.collect()),
- };
-
- // Sort diagnostics first by severity and then by line numbers.
- // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
- diagnostics.sort_by_key(|(d, provider)| (d.severity, d.range.start, provider.clone()));
-
- if let Some(doc) = doc {
- let diagnostic_of_language_server_and_not_in_unchanged_sources =
- |diagnostic: &lsp::Diagnostic, d_provider: &DiagnosticProvider| {
- d_provider == provider
- && diagnostic
- .source
- .as_ref()
- .is_none_or(|source| !unchanged_diag_sources.contains(source))
- };
- let diagnostics = Self::doc_diagnostics_with_filter(
- &self.language_servers,
- &self.diagnostics,
- doc,
- diagnostic_of_language_server_and_not_in_unchanged_sources,
- );
- doc.replace_diagnostics(diagnostics, &unchanged_diag_sources, Some(provider));
-
- let doc = doc.id();
- helix_event::dispatch(DiagnosticsDidChange { editor: self, doc });
- }
- }
-
- pub fn execute_lsp_command(&mut self, command: lsp::Command, server_id: LanguageServerId) {
- // the command is executed on the server and communicated back
- // to the client asynchronously using workspace edits
- let Some(future) = self
- .language_server_by_id(server_id)
- .and_then(|server| server.command(command))
- else {
- self.set_error("Language server does not support executing commands");
- return;
- };
-
- tokio::spawn(async move {
- if let Err(err) = future.await {
- log::error!("Error executing LSP command: {err}");
- }
- });
- }
-}
-
-pub fn register_hooks(_handlers: &Handlers) {
- register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
- let language_server = event.editor.language_server_by_id(event.server_id).unwrap();
-
- for doc in event
- .editor
- .documents()
- .filter(|doc| doc.supports_language_server(event.server_id))
- {
- let Some(url) = doc.url() else {
- continue;
- };
-
- let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
-
- language_server.text_document_did_open(url, doc.version(), doc.text(), language_id);
- }
-
- Ok(())
- });
-
- register_hook!(move |event: &mut DocumentDidChange<'_>| {
- // Send textDocument/didChange notifications.
- if !event.ghost_transaction {
- for language_server in event.doc.language_servers() {
- language_server.text_document_did_change(
- event.doc.versioned_identifier(),
- event.old_text,
- event.doc.text(),
- event.changes,
- );
- }
- }
-
- Ok(())
- });
-
- register_hook!(move |event: &mut DocumentDidClose<'_>| {
- // Send textDocument/didClose notifications.
- for language_server in event.doc.language_servers() {
- language_server.text_document_did_close(event.doc.identifier());
- }
-
- Ok(())
- });
-}