use tinymist_world::vfs::WorkspaceResolver;
use crate::{
analysis::Definition,
prelude::*,
syntax::{Decl, SyntaxClass},
};
#[derive(Debug, Clone)]
pub struct PrepareRenameRequest {
pub path: PathBuf,
pub position: LspPosition,
}
impl SemanticRequest for PrepareRenameRequest {
type Response = PrepareRenameResponse;
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
if bad_syntax(&syntax) {
return None;
}
let mut node = syntax.node().clone();
if matches!(node.kind(), SyntaxKind::Ref) {
let marker = node
.children()
.find(|child| child.kind() == SyntaxKind::RefMarker)?;
node = marker;
}
let mut range = node.range();
if matches!(node.kind(), SyntaxKind::RefMarker) {
range.start += 1;
}
let origin_selection_range = ctx.to_lsp_range(range, &source);
let def = ctx.def_of_syntax(&source, syntax.clone())?;
let name = prepare_renaming(&syntax, &def)?;
Some(PrepareRenameResponse::RangeWithPlaceholder {
range: origin_selection_range,
placeholder: name,
})
}
}
fn bad_syntax(syntax: &SyntaxClass) -> bool {
if matches!(syntax.node().kind(), SyntaxKind::FieldAccess) {
log::info!("prepare_rename: field access is not a definition site");
return true;
}
if syntax.erroneous() {
return true;
}
false
}
pub(crate) fn prepare_renaming(syntax: &SyntaxClass, def: &Definition) -> Option<String> {
if bad_syntax(syntax) {
return None;
}
let def_fid = def.file_id()?;
if WorkspaceResolver::is_package_file(def_fid) {
crate::log_debug_ct!(
"prepare_rename: is in a package {pkg:?}, def: {def:?}",
pkg = def_fid.package(),
);
return None;
}
let decl_name = || def.name().clone().to_string();
use Decl::*;
match def.decl.as_ref() {
Var(..) | Label(..) | ContentRef(..) => Some(decl_name()),
Func(..) | Closure(..) => validate_fn_renaming(def).map(|_| decl_name()),
Module(..) | ModuleAlias(..) | PathStem(..) | ImportPath(..) | IncludePath(..)
| ModuleImport(..) => {
let node = syntax.node().get().clone();
let path = node.cast::<ast::Str>()?;
let name = path.get().to_string();
Some(name)
}
BibEntry(..) => None,
ImportAlias(..) | Constant(..) | IdentRef(..) | Import(..) | StrName(..) | Spread(..) => {
None
}
Pattern(..) | Content(..) | Generated(..) | Docs(..) => None,
}
}
fn validate_fn_renaming(def: &Definition) -> Option<()> {
use typst::foundations::func::Repr;
let value = def.value();
let mut func = match &value {
None => return Some(()),
Some(Value::Func(func)) => func,
Some(..) => {
log::info!(
"prepare_rename: not a function on function definition site: {:?}",
def.term
);
return None;
}
};
loop {
match func.inner() {
Repr::With(w) => func = &w.0,
Repr::Closure(..) | Repr::Plugin(..) => return Some(()),
Repr::Native(..) | Repr::Element(..) => return None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
#[test]
fn prepare() {
snapshot_testing("rename", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = PrepareRenameRequest {
path: path.clone(),
position: find_test_position(&source),
};
let result = request.request(ctx);
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
});
}
}