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:
Myriad-Dreamin 2024-10-09 14:53:19 +08:00 committed by GitHub
parent c9846b1d0d
commit 7b0fb6036d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 193 additions and 63 deletions

View file

@ -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"
}
]
}

View file

@ -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"
}
]
}

View file

@ -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"
}
]
}

View file

@ -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"
}
]
}

View file

@ -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),

View file

@ -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,

View 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),
})
}
}