mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: issue import changes request during willRenameFiles
(#648)
* feat: issue import changes request during `willRenameFiles` * test: update snapshot * fix: snapshot
This commit is contained in:
parent
c9846b1d0d
commit
7b0fb6036d
10 changed files with 193 additions and 63 deletions
|
@ -5,11 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path.typ
|
|||
---
|
||||
{
|
||||
"documentChanges": [
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
},
|
||||
{
|
||||
"edits": [
|
||||
{
|
||||
|
@ -21,6 +16,11 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path.typ
|
|||
"uri": "s1.typ",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,11 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path_alias.typ
|
|||
---
|
||||
{
|
||||
"documentChanges": [
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
},
|
||||
{
|
||||
"edits": [
|
||||
{
|
||||
|
@ -21,6 +16,11 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path_alias.typ
|
|||
"uri": "s1.typ",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,11 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path_non_cano.typ
|
|||
---
|
||||
{
|
||||
"documentChanges": [
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
},
|
||||
{
|
||||
"edits": [
|
||||
{
|
||||
|
@ -21,6 +16,11 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path_non_cano.typ
|
|||
"uri": "s1.typ",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,11 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path_star.typ
|
|||
---
|
||||
{
|
||||
"documentChanges": [
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
},
|
||||
{
|
||||
"edits": [
|
||||
{
|
||||
|
@ -21,6 +16,11 @@ input_file: crates/tinymist-query/src/fixtures/rename/module_path_star.typ
|
|||
"uri": "s1.typ",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "rename",
|
||||
"newUri": "new_name.typ",
|
||||
"oldUri": "variable.typ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ mod inlay_hint;
|
|||
pub use inlay_hint::*;
|
||||
mod jump;
|
||||
pub use jump::*;
|
||||
mod will_rename_files;
|
||||
pub use will_rename_files::*;
|
||||
mod rename;
|
||||
pub use rename::*;
|
||||
mod selection_range;
|
||||
|
@ -251,6 +253,7 @@ mod polymorphic {
|
|||
Completion(CompletionRequest),
|
||||
SignatureHelp(SignatureHelpRequest),
|
||||
Rename(RenameRequest),
|
||||
WillRenameFiles(WillRenameFilesRequest),
|
||||
PrepareRename(PrepareRenameRequest),
|
||||
DocumentSymbol(DocumentSymbolRequest),
|
||||
Symbol(SymbolRequest),
|
||||
|
@ -286,6 +289,7 @@ mod polymorphic {
|
|||
Self::Completion(..) => Mergeable,
|
||||
Self::SignatureHelp(..) => PinnedFirst,
|
||||
Self::Rename(..) => Mergeable,
|
||||
Self::WillRenameFiles(..) => Mergeable,
|
||||
Self::PrepareRename(..) => Mergeable,
|
||||
Self::DocumentSymbol(..) => ContextFreeUnique,
|
||||
Self::WorkspaceLabel(..) => Mergeable,
|
||||
|
@ -320,6 +324,7 @@ mod polymorphic {
|
|||
Self::Completion(req) => &req.path,
|
||||
Self::SignatureHelp(req) => &req.path,
|
||||
Self::Rename(req) => &req.path,
|
||||
Self::WillRenameFiles(..) => return None,
|
||||
Self::PrepareRename(req) => &req.path,
|
||||
Self::DocumentSymbol(req) => &req.path,
|
||||
Self::Symbol(..) => return None,
|
||||
|
@ -356,6 +361,7 @@ mod polymorphic {
|
|||
SignatureHelp(Option<SignatureHelp>),
|
||||
PrepareRename(Option<PrepareRenameResponse>),
|
||||
Rename(Option<WorkspaceEdit>),
|
||||
WillRenameFiles(Option<WorkspaceEdit>),
|
||||
DocumentSymbol(Option<DocumentSymbolResponse>),
|
||||
Symbol(Option<Vec<SymbolInformation>>),
|
||||
WorkspaceLabel(Option<Vec<SymbolInformation>>),
|
||||
|
@ -390,6 +396,7 @@ mod polymorphic {
|
|||
Self::SignatureHelp(res) => serde_json::to_value(res),
|
||||
Self::PrepareRename(res) => serde_json::to_value(res),
|
||||
Self::Rename(res) => serde_json::to_value(res),
|
||||
Self::WillRenameFiles(res) => serde_json::to_value(res),
|
||||
Self::DocumentSymbol(res) => serde_json::to_value(res),
|
||||
Self::Symbol(res) => serde_json::to_value(res),
|
||||
Self::WorkspaceLabel(res) => serde_json::to_value(res),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use lsp_types::{
|
||||
DocumentChanges, OneOf, OptionalVersionedTextDocumentIdentifier, RenameFile, TextDocumentEdit,
|
||||
DocumentChangeOperation, DocumentChanges, OneOf, OptionalVersionedTextDocumentIdentifier,
|
||||
RenameFile, TextDocumentEdit,
|
||||
};
|
||||
use reflexo::path::{unix_slash, PathClean};
|
||||
use typst::foundations::{Repr, Str};
|
||||
|
@ -56,9 +57,6 @@ impl StatefulRequest for RenameRequest {
|
|||
self.new_name
|
||||
};
|
||||
|
||||
let mut editions: HashMap<Url, Vec<TextEdit>> = HashMap::new();
|
||||
let mut document_changes = vec![];
|
||||
|
||||
let def_fid = lnk.def_at?.0;
|
||||
let old_path = ctx.path_for_id(def_fid).ok()?;
|
||||
|
||||
|
@ -74,6 +72,11 @@ impl StatefulRequest for RenameRequest {
|
|||
let old_uri = path_to_url(&old_path).ok()?;
|
||||
let new_uri = path_to_url(&new_path).ok()?;
|
||||
|
||||
let mut edits: HashMap<Url, Vec<TextEdit>> = HashMap::new();
|
||||
do_rename_file(ctx, def_fid, diff, &mut edits)?;
|
||||
|
||||
let mut document_changes = edits_to_document_changes(edits);
|
||||
|
||||
document_changes.push(lsp_types::DocumentChangeOperation::Op(
|
||||
lsp_types::ResourceOp::Rename(RenameFile {
|
||||
old_uri,
|
||||
|
@ -83,40 +86,7 @@ impl StatefulRequest for RenameRequest {
|
|||
}),
|
||||
));
|
||||
|
||||
let dep = ctx.module_dependencies().get(&def_fid)?.clone();
|
||||
|
||||
for ref_fid in dep.dependents.iter() {
|
||||
let ref_src = ctx.source_by_id(*ref_fid).ok()?;
|
||||
let uri = ctx.uri_for_id(*ref_fid).ok()?;
|
||||
|
||||
let Some(import_info) = ctx.import_info(ref_src.clone()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let edits = editions.entry(uri).or_default();
|
||||
for (rng, importing_src) in &import_info.imports {
|
||||
let importing = importing_src.as_ref().map(|s| s.id());
|
||||
if importing.map_or(true, |i| i != def_fid) {
|
||||
continue;
|
||||
}
|
||||
log::debug!("import: {rng:?} -> {importing:?} v.s. {def_fid:?}");
|
||||
rename_importer(ctx, &ref_src, rng.clone(), &diff, edits);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: validate: workspace.workspaceEdit.resourceOperations
|
||||
for edition in editions.into_iter() {
|
||||
document_changes.push(lsp_types::DocumentChangeOperation::Edit(
|
||||
TextDocumentEdit {
|
||||
text_document: OptionalVersionedTextDocumentIdentifier {
|
||||
uri: edition.0,
|
||||
version: None,
|
||||
},
|
||||
edits: edition.1.into_iter().map(OneOf::Left).collect(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Some(WorkspaceEdit {
|
||||
document_changes: Some(DocumentChanges::Operations(document_changes)),
|
||||
..Default::default()
|
||||
|
@ -125,7 +95,7 @@ impl StatefulRequest for RenameRequest {
|
|||
_ => {
|
||||
let references = find_references(ctx, source.clone(), doc.as_ref(), deref_target)?;
|
||||
|
||||
let mut editions = HashMap::new();
|
||||
let mut edits = HashMap::new();
|
||||
|
||||
let (def_fid, _def_range) = lnk.def_at?;
|
||||
let def_loc = {
|
||||
|
@ -147,17 +117,17 @@ impl StatefulRequest for RenameRequest {
|
|||
for i in (Some(def_loc).into_iter()).chain(references) {
|
||||
let uri = i.uri;
|
||||
let range = i.range;
|
||||
let edits = editions.entry(uri).or_insert_with(Vec::new);
|
||||
let edits = edits.entry(uri).or_insert_with(Vec::new);
|
||||
edits.push(TextEdit {
|
||||
range,
|
||||
new_text: self.new_name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
log::info!("rename editions: {editions:?}");
|
||||
log::info!("rename edits: {edits:?}");
|
||||
|
||||
Some(WorkspaceEdit {
|
||||
changes: Some(editions),
|
||||
changes: Some(edits),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
@ -165,6 +135,51 @@ impl StatefulRequest for RenameRequest {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn do_rename_file(
|
||||
ctx: &mut AnalysisContext,
|
||||
def_fid: TypstFileId,
|
||||
diff: PathBuf,
|
||||
edits: &mut HashMap<Url, Vec<TextEdit>>,
|
||||
) -> Option<()> {
|
||||
let dep = ctx.module_dependencies().get(&def_fid)?.clone();
|
||||
|
||||
for ref_fid in dep.dependents.iter() {
|
||||
let ref_src = ctx.source_by_id(*ref_fid).ok()?;
|
||||
let uri = ctx.uri_for_id(*ref_fid).ok()?;
|
||||
|
||||
let Some(import_info) = ctx.import_info(ref_src.clone()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let edits = edits.entry(uri).or_default();
|
||||
for (rng, importing_src) in &import_info.imports {
|
||||
let importing = importing_src.as_ref().map(|s| s.id());
|
||||
if importing.map_or(true, |i| i != def_fid) {
|
||||
continue;
|
||||
}
|
||||
log::debug!("import: {rng:?} -> {importing:?} v.s. {def_fid:?}");
|
||||
rename_importer(ctx, &ref_src, rng.clone(), &diff, edits);
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(crate) fn edits_to_document_changes(
|
||||
edits: HashMap<Url, Vec<TextEdit>>,
|
||||
) -> Vec<DocumentChangeOperation> {
|
||||
let mut document_changes = vec![];
|
||||
|
||||
for (uri, edits) in edits {
|
||||
document_changes.push(lsp_types::DocumentChangeOperation::Edit(TextDocumentEdit {
|
||||
text_document: OptionalVersionedTextDocumentIdentifier { uri, version: None },
|
||||
edits: edits.into_iter().map(OneOf::Left).collect(),
|
||||
}));
|
||||
}
|
||||
|
||||
document_changes
|
||||
}
|
||||
|
||||
fn rename_importer(
|
||||
ctx: &AnalysisContext,
|
||||
src: &Source,
|
||||
|
|
65
crates/tinymist-query/src/will_rename_files.rs
Normal file
65
crates/tinymist-query/src/will_rename_files.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use lsp_types::ChangeAnnotation;
|
||||
|
||||
use crate::{do_rename_file, edits_to_document_changes, prelude::*};
|
||||
|
||||
/// Handle [`workspace/willRenameFiles`] request is sent from the client to the
|
||||
/// server.
|
||||
///
|
||||
/// [`workspace/willRenameFiles`]: https://microsoft.github.io/language-server-protocol/specification#workspace_willRenameFiles
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WillRenameFilesRequest {
|
||||
/// rename paths from `left` to `right`
|
||||
pub paths: Vec<(PathBuf, PathBuf)>,
|
||||
}
|
||||
|
||||
impl StatefulRequest for WillRenameFilesRequest {
|
||||
type Response = WorkspaceEdit;
|
||||
|
||||
fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
_doc: Option<VersionedDocument>,
|
||||
) -> Option<Self::Response> {
|
||||
let mut edits: HashMap<Url, Vec<TextEdit>> = HashMap::new();
|
||||
|
||||
self.paths
|
||||
.into_iter()
|
||||
.map(|(left, right)| {
|
||||
let diff = pathdiff::diff_paths(&right, &left)?;
|
||||
log::info!("did rename diff: {diff:?}");
|
||||
if diff.is_absolute() {
|
||||
log::info!(
|
||||
"bad rename: absolute path, base: {left:?}, new: {right:?}, diff: {diff:?}"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let def_fid = ctx.file_id_by_path(&left).ok()?;
|
||||
log::info!("did rename def_fid: {def_fid:?}");
|
||||
|
||||
do_rename_file(ctx, def_fid, diff, &mut edits)
|
||||
})
|
||||
.collect::<Option<Vec<()>>>()?;
|
||||
log::info!("did rename edits: {edits:?}");
|
||||
let document_changes = edits_to_document_changes(edits);
|
||||
if document_changes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut change_annotations = HashMap::new();
|
||||
change_annotations.insert(
|
||||
"Typst Rename Files".to_string(),
|
||||
ChangeAnnotation {
|
||||
label: "Typst Rename Files".to_string(),
|
||||
needs_confirmation: Some(true),
|
||||
description: Some("Rename files should update imports".to_string()),
|
||||
},
|
||||
);
|
||||
|
||||
Some(WorkspaceEdit {
|
||||
changes: None,
|
||||
document_changes: Some(lsp_types::DocumentChanges::Operations(document_changes)),
|
||||
change_annotations: Some(change_annotations),
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue