Add command to move files with LSP support (#8584)
* Added rename command * Added an error if the new path already exists * Fixed wrong command name being used * fixed clippy suggestions * removed didRenameFiles call, fixed early return due to path Err * added ':rnm' alias to ':rename' * code cleanup * formatting * removed debug line * cargo fmt * Improved new buffer error message Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de> * Removed unnecessary path normalizing Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de> * Update helix-term/src/commands/typed.rs Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de> * Update helix-term/src/commands/typed.rs Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de> * Update helix-term/src/commands/typed.rs Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de> * Update helix-term/src/commands/typed.rs Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de> * feat: change `rename` command to `move` * feat: add multi lsp support when moving files * feat: allow lsp calls with a custom timeout * feat: sending lsp file_changed event once file has moved --------- Co-authored-by: ontley <theontley@gmail.com> Co-authored-by: ontley <67148677+ontley@users.noreply.github.com> Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de>
This commit is contained in:
parent
7bc564d3dc
commit
e868678139
3 changed files with 159 additions and 2 deletions
|
@ -85,3 +85,4 @@
|
||||||
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
|
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
|
||||||
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
|
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
|
||||||
| `:redraw` | Clear and re-render the whole UI |
|
| `:redraw` | Clear and re-render the whole UI |
|
||||||
|
| `:move` | Move the current buffer and its corresponding file to a different path |
|
||||||
|
|
|
@ -401,12 +401,22 @@ impl Client {
|
||||||
&self,
|
&self,
|
||||||
params: R::Params,
|
params: R::Params,
|
||||||
) -> impl Future<Output = Result<Value>>
|
) -> impl Future<Output = Result<Value>>
|
||||||
|
where
|
||||||
|
R::Params: serde::Serialize,
|
||||||
|
{
|
||||||
|
self.call_with_timeout::<R>(params, self.req_timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_with_timeout<R: lsp::request::Request>(
|
||||||
|
&self,
|
||||||
|
params: R::Params,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> impl Future<Output = Result<Value>>
|
||||||
where
|
where
|
||||||
R::Params: serde::Serialize,
|
R::Params: serde::Serialize,
|
||||||
{
|
{
|
||||||
let server_tx = self.server_tx.clone();
|
let server_tx = self.server_tx.clone();
|
||||||
let id = self.next_request_id();
|
let id = self.next_request_id();
|
||||||
let timeout_secs = self.req_timeout;
|
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -548,6 +558,11 @@ impl Client {
|
||||||
dynamic_registration: Some(true),
|
dynamic_registration: Some(true),
|
||||||
relative_pattern_support: Some(false),
|
relative_pattern_support: Some(false),
|
||||||
}),
|
}),
|
||||||
|
file_operations: Some(lsp::WorkspaceFileOperationsClientCapabilities {
|
||||||
|
will_rename: Some(true),
|
||||||
|
did_rename: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
text_document: Some(lsp::TextDocumentClientCapabilities {
|
text_document: Some(lsp::TextDocumentClientCapabilities {
|
||||||
|
@ -700,6 +715,65 @@ impl Client {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prepare_file_rename(
|
||||||
|
&self,
|
||||||
|
old_uri: &lsp::Url,
|
||||||
|
new_uri: &lsp::Url,
|
||||||
|
) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
|
||||||
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
|
// Return early if the server does not support willRename feature
|
||||||
|
match &capabilities.workspace {
|
||||||
|
Some(workspace) => match &workspace.file_operations {
|
||||||
|
Some(op) => {
|
||||||
|
op.will_rename.as_ref()?;
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = vec![lsp::FileRename {
|
||||||
|
old_uri: old_uri.to_string(),
|
||||||
|
new_uri: new_uri.to_string(),
|
||||||
|
}];
|
||||||
|
let request = self.call_with_timeout::<lsp::request::WillRenameFiles>(
|
||||||
|
lsp::RenameFilesParams { files },
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(async move {
|
||||||
|
let json = request.await?;
|
||||||
|
let response: Option<lsp::WorkspaceEdit> = serde_json::from_value(json)?;
|
||||||
|
Ok(response.unwrap_or_default())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn did_file_rename(
|
||||||
|
&self,
|
||||||
|
old_uri: &lsp::Url,
|
||||||
|
new_uri: &lsp::Url,
|
||||||
|
) -> Option<impl Future<Output = std::result::Result<(), Error>>> {
|
||||||
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
|
// Return early if the server does not support DidRename feature
|
||||||
|
match &capabilities.workspace {
|
||||||
|
Some(workspace) => match &workspace.file_operations {
|
||||||
|
Some(op) => {
|
||||||
|
op.did_rename.as_ref()?;
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = vec![lsp::FileRename {
|
||||||
|
old_uri: old_uri.to_string(),
|
||||||
|
new_uri: new_uri.to_string(),
|
||||||
|
}];
|
||||||
|
Some(self.notify::<lsp::notification::DidRenameFiles>(lsp::RenameFilesParams { files }))
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------
|
||||||
// Text document
|
// Text document
|
||||||
// -------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -6,7 +6,8 @@ use crate::job::Job;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use helix_core::fuzzy::fuzzy_match;
|
use helix_core::fuzzy::fuzzy_match;
|
||||||
use helix_core::{encoding, line_ending, shellwords::Shellwords};
|
use helix_core::{encoding, line_ending, path::get_canonicalized_path, shellwords::Shellwords};
|
||||||
|
use helix_lsp::{OffsetEncoding, Url};
|
||||||
use helix_view::document::DEFAULT_LANGUAGE_NAME;
|
use helix_view::document::DEFAULT_LANGUAGE_NAME;
|
||||||
use helix_view::editor::{Action, CloseError, ConfigEvent};
|
use helix_view::editor::{Action, CloseError, ConfigEvent};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
@ -2408,6 +2409,80 @@ fn redraw(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_buffer(
|
||||||
|
cx: &mut compositor::Context,
|
||||||
|
args: &[Cow<str>],
|
||||||
|
event: PromptEvent,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if event != PromptEvent::Validate {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure!(args.len() == 1, format!(":move takes one argument"));
|
||||||
|
let doc = doc!(cx.editor);
|
||||||
|
|
||||||
|
let new_path = get_canonicalized_path(&PathBuf::from(args.first().unwrap().to_string()));
|
||||||
|
let old_path = doc
|
||||||
|
.path()
|
||||||
|
.ok_or_else(|| anyhow!("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 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(())
|
||||||
|
}
|
||||||
|
|
||||||
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
||||||
TypableCommand {
|
TypableCommand {
|
||||||
name: "quit",
|
name: "quit",
|
||||||
|
@ -3008,6 +3083,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
||||||
fun: redraw,
|
fun: redraw,
|
||||||
signature: CommandSignature::none(),
|
signature: CommandSignature::none(),
|
||||||
},
|
},
|
||||||
|
TypableCommand {
|
||||||
|
name: "move",
|
||||||
|
aliases: &[],
|
||||||
|
doc: "Move the current buffer and its corresponding file to a different path",
|
||||||
|
fun: move_buffer,
|
||||||
|
signature: CommandSignature::positional(&[completers::filename]),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
|
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
|
||||||
|
|
Loading…
Add table
Reference in a new issue