aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
authorPascal Kuthe2024-01-28 16:34:45 +0000
committerGitHub2024-01-28 16:34:45 +0000
commit87a720c3a13ccc7245f5b0befc008db5bd039032 (patch)
treee0e3f91c516a10d154cd01861e96ae0f50ea3cad /helix-term
parentf5b67d9acb89ea54e7226111e3e4d8c3a008144b (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')
-rw-r--r--helix-term/src/application.rs31
-rw-r--r--helix-term/src/commands/lsp.rs198
-rw-r--r--helix-term/src/commands/typed.rs62
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,
- &params.edit,
- );
+ let res = self
+ .editor
+ .apply_workspace_edit(offset_encoding, &params.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(())
}