diff options
author | Pascal Kuthe | 2024-01-28 16:34:45 +0000 |
---|---|---|
committer | GitHub | 2024-01-28 16:34:45 +0000 |
commit | 87a720c3a13ccc7245f5b0befc008db5bd039032 (patch) | |
tree | e0e3f91c516a10d154cd01861e96ae0f50ea3cad /helix-term/src | |
parent | f5b67d9acb89ea54e7226111e3e4d8c3a008144b (diff) |
make path changes LSP spec conform (#8949)
Currently, helix implements operations which change the paths of files
incorrectly and inconsistently. This PR ensures that we do the following
whenever a buffer is renamed (`:move` and workspace edits)
* always send did_open/did_close notifications
* send will_rename/did_rename requests correctly
* send them to all LSP servers not just those that are active for a
buffer
* also send these requests for paths that are not yet open in a buffer (if
triggered from workspace edit).
* only send these if the server registered interests in the path
* autodetect language, indent, line ending, ..
This PR also centralizes the infrastructure for path setting and
therefore `:w <path>` benefits from similar fixed (but without didRename)
Diffstat (limited to 'helix-term/src')
-rw-r--r-- | helix-term/src/application.rs | 31 | ||||
-rw-r--r-- | helix-term/src/commands/lsp.rs | 198 | ||||
-rw-r--r-- | helix-term/src/commands/typed.rs | 62 |
3 files changed, 11 insertions, 280 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 3f3e59c6..b5150a13 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -21,7 +21,6 @@ use tui::backend::Backend; use crate::{ args::Args, - commands::apply_workspace_edit, compositor::{Compositor, Event}, config::Config, handlers, @@ -573,26 +572,8 @@ impl Application { let lines = doc_save_event.text.len_lines(); let bytes = doc_save_event.text.len_bytes(); - if doc.path() != Some(&doc_save_event.path) { - doc.set_path(Some(&doc_save_event.path)); - - let loader = self.editor.syn_loader.clone(); - - // borrowing the same doc again to get around the borrow checker - let doc = doc_mut!(self.editor, &doc_save_event.doc_id); - let id = doc.id(); - doc.detect_language(loader); - self.editor.refresh_language_servers(id); - // and again a borrow checker workaround... - let doc = doc_mut!(self.editor, &doc_save_event.doc_id); - let diagnostics = Editor::doc_diagnostics( - &self.editor.language_servers, - &self.editor.diagnostics, - doc, - ); - doc.replace_diagnostics(diagnostics, &[], None); - } - + self.editor + .set_doc_path(doc_save_event.doc_id, &doc_save_event.path); // TODO: fix being overwritten by lsp self.editor.set_status(format!( "'{}' written, {}L {}B", @@ -1011,11 +992,9 @@ impl Application { let language_server = language_server!(); if language_server.is_initialized() { let offset_encoding = language_server.offset_encoding(); - let res = apply_workspace_edit( - &mut self.editor, - offset_encoding, - ¶ms.edit, - ); + let res = self + .editor + .apply_workspace_edit(offset_encoding, ¶ms.edit); Ok(json!(lsp::ApplyWorkspaceEditResponse { applied: res.is_ok(), diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index c694ba25..a1f7bf17 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -726,8 +726,7 @@ pub fn code_action(cx: &mut Context) { resolved_code_action.as_ref().unwrap_or(code_action); if let Some(ref workspace_edit) = resolved_code_action.edit { - log::debug!("edit: {:?}", workspace_edit); - let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit); + let _ = editor.apply_workspace_edit(offset_encoding, workspace_edit); } // if code action provides both edit and command first the edit @@ -787,63 +786,6 @@ pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd: }); } -pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { - use lsp::ResourceOp; - use std::fs; - match op { - ResourceOp::Create(op) => { - let path = op.uri.to_file_path().unwrap(); - let ignore_if_exists = op.options.as_ref().map_or(false, |options| { - !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) - }); - if ignore_if_exists && path.exists() { - Ok(()) - } else { - // 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, []) - } - } - ResourceOp::Delete(op) => { - let path = op.uri.to_file_path().unwrap(); - 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) - } - } else if path.is_file() { - fs::remove_file(&path) - } else { - Ok(()) - } - } - ResourceOp::Rename(op) => { - let from = op.old_uri.to_file_path().unwrap(); - let to = op.new_uri.to_file_path().unwrap(); - let ignore_if_exists = op.options.as_ref().map_or(false, |options| { - !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) - }); - if ignore_if_exists && to.exists() { - Ok(()) - } else { - fs::rename(from, &to) - } - } - } -} - #[derive(Debug)] pub struct ApplyEditError { pub kind: ApplyEditErrorKind, @@ -871,142 +813,6 @@ impl ToString for ApplyEditErrorKind { } } -///TODO make this transactional (and set failureMode to transactional) -pub fn apply_workspace_edit( - editor: &mut Editor, - offset_encoding: OffsetEncoding, - workspace_edit: &lsp::WorkspaceEdit, -) -> Result<(), ApplyEditError> { - let mut apply_edits = |uri: &helix_lsp::Url, - version: Option<i32>, - text_edits: Vec<lsp::TextEdit>| - -> Result<(), ApplyEditErrorKind> { - let path = match uri.to_file_path() { - Ok(path) => path, - Err(_) => { - let err = format!("unable to convert URI to filepath: {}", uri); - log::error!("{}", err); - editor.set_error(err); - return Err(ApplyEditErrorKind::UnknownURISchema); - } - }; - - let doc_id = match editor.open(&path, Action::Load) { - Ok(doc_id) => doc_id, - Err(err) => { - let err = format!("failed to open document: {}: {}", uri, err); - log::error!("{}", err); - editor.set_error(err); - return Err(ApplyEditErrorKind::FileNotFound); - } - }; - - let doc = doc!(editor, &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()); - editor.set_error(err); - return Err(ApplyEditErrorKind::DocumentChanged); - } - } - - // Need to determine a view for apply/append_changes_to_history - let view_id = editor.get_synced_view_id(doc_id); - let doc = doc_mut!(editor, &doc_id); - - let transaction = helix_lsp::util::generate_transaction_from_edits( - doc.text(), - text_edits, - offset_encoding, - ); - let view = view_mut!(editor, view_id); - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view); - Ok(()) - }; - - 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(); - apply_edits( - &document_edit.text_document.uri, - document_edit.text_document.version, - edits, - ) - .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) => { - apply_document_resource_op(op).map_err(|io| ApplyEditError { - kind: ApplyEditErrorKind::IoError(io), - 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(); - apply_edits( - &document_edit.text_document.uri, - document_edit.text_document.version, - edits, - ) - .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(); - apply_edits(uri, None, text_edits).map_err(|kind| ApplyEditError { - kind, - failed_change_idx: i, - })?; - } - } - - Ok(()) -} - /// Precondition: `locations` should be non-empty. fn goto_impl( editor: &mut Editor, @@ -1263,7 +1069,7 @@ pub fn rename_symbol(cx: &mut Context) { match block_on(future) { Ok(edits) => { - let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits); + let _ = cx.editor.apply_workspace_edit(offset_encoding, &edits); } Err(err) => cx.editor.set_error(err.to_string()), } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 81ffdf87..b7ceeba5 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -8,7 +8,6 @@ use super::*; use helix_core::fuzzy::fuzzy_match; use helix_core::indent::MAX_INDENT; use helix_core::{encoding, line_ending, shellwords::Shellwords}; -use helix_lsp::{OffsetEncoding, Url}; use helix_view::document::DEFAULT_LANGUAGE_NAME; use helix_view::editor::{Action, CloseError, ConfigEvent}; use serde_json::Value; @@ -2404,67 +2403,14 @@ fn move_buffer( ensure!(args.len() == 1, format!(":move takes one argument")); let doc = doc!(cx.editor); - - let new_path = - helix_stdx::path::canonicalize(&PathBuf::from(args.first().unwrap().to_string())); let old_path = doc .path() - .ok_or_else(|| anyhow!("Scratch buffer cannot be moved. Use :write instead"))? + .context("Scratch buffer cannot be moved. Use :write instead")? .clone(); - let old_path_as_url = doc.url().unwrap(); - let new_path_as_url = Url::from_file_path(&new_path).unwrap(); - - let edits: Vec<( - helix_lsp::Result<helix_lsp::lsp::WorkspaceEdit>, - OffsetEncoding, - String, - )> = doc - .language_servers() - .map(|lsp| { - ( - lsp.prepare_file_rename(&old_path_as_url, &new_path_as_url), - lsp.offset_encoding(), - lsp.name().to_owned(), - ) - }) - .filter(|(f, _, _)| f.is_some()) - .map(|(f, encoding, name)| (helix_lsp::block_on(f.unwrap()), encoding, name)) - .collect(); - - for (lsp_reply, encoding, name) in edits { - match lsp_reply { - Ok(edit) => { - if let Err(e) = apply_workspace_edit(cx.editor, encoding, &edit) { - log::error!( - ":move command failed to apply edits from lsp {}: {:?}", - name, - e - ); - }; - } - Err(e) => { - log::error!("LSP {} failed to treat willRename request: {:?}", name, e); - } - }; + let new_path = args.first().unwrap().to_string(); + if let Err(err) = cx.editor.move_path(&old_path, new_path.as_ref()) { + bail!("Could not move file: {err}"); } - - let doc = doc_mut!(cx.editor); - - doc.set_path(Some(new_path.as_path())); - if let Err(e) = std::fs::rename(&old_path, &new_path) { - doc.set_path(Some(old_path.as_path())); - bail!("Could not move file: {}", e); - }; - - doc.language_servers().for_each(|lsp| { - lsp.did_file_rename(&old_path_as_url, &new_path_as_url); - }); - - cx.editor - .language_servers - .file_event_handler - .file_changed(new_path); - Ok(()) } |