tinymist_query/
prepare_rename.rs1use tinymist_world::vfs::WorkspaceResolver;
2
3use crate::{
4 analysis::Definition,
5 prelude::*,
6 syntax::{Decl, SyntaxClass},
7};
8
9#[derive(Debug, Clone)]
25pub struct PrepareRenameRequest {
26 pub path: PathBuf,
28 pub position: LspPosition,
30}
31
32impl SemanticRequest for PrepareRenameRequest {
35 type Response = PrepareRenameResponse;
36
37 fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
38 let source = ctx.source_by_path(&self.path).ok()?;
39 let syntax = ctx.classify_for_decl(&source, self.position)?;
40 if matches!(syntax.node().kind(), SyntaxKind::FieldAccess) {
41 log::info!("prepare_rename: field access is not a definition site");
43 return None;
44 }
45
46 let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
47 let def = ctx.def_of_syntax(&source, syntax.clone())?;
48
49 let (name, range) = prepare_renaming(&syntax, &def)?;
50
51 Some(PrepareRenameResponse::RangeWithPlaceholder {
52 range: range.unwrap_or(origin_selection_range),
53 placeholder: name,
54 })
55 }
56}
57
58pub(crate) fn prepare_renaming(
59 deref_target: &SyntaxClass,
60 def: &Definition,
61) -> Option<(String, Option<LspRange>)> {
62 let name = def.name().clone();
63 let def_fid = def.file_id()?;
64
65 if WorkspaceResolver::is_package_file(def_fid) {
66 crate::log_debug_ct!(
67 "prepare_rename: {name} is in a package {pkg:?}",
68 pkg = def_fid.package(),
69 );
70 return None;
71 }
72
73 let var_rename = || Some((name.to_string(), None));
74
75 crate::log_debug_ct!("prepare_rename: {name}");
76 use Decl::*;
77 match def.decl.as_ref() {
78 Var(..) => var_rename(),
85 Func(..) | Closure(..) => validate_fn_renaming(def).map(|_| (name.to_string(), None)),
86 Module(..) | ModuleAlias(..) | PathStem(..) | ImportPath(..) | IncludePath(..)
87 | ModuleImport(..) => {
88 let node = deref_target.node().get().clone();
89 let path = node.cast::<ast::Str>()?;
90 let name = path.get().to_string();
91 Some((name, None))
92 }
93 BibEntry(..) | Label(..) | ContentRef(..) => None,
95 ImportAlias(..) | Constant(..) | IdentRef(..) | Import(..) | StrName(..) | Spread(..) => {
96 None
97 }
98 Pattern(..) | Content(..) | Generated(..) | Docs(..) => None,
99 }
100}
101
102fn validate_fn_renaming(def: &Definition) -> Option<()> {
103 use typst::foundations::func::Repr;
104 let value = def.value();
105 let mut func = match &value {
106 None => return Some(()),
107 Some(Value::Func(func)) => func,
108 Some(..) => {
109 log::info!(
110 "prepare_rename: not a function on function definition site: {:?}",
111 def.term
112 );
113 return None;
114 }
115 };
116 loop {
117 match func.inner() {
118 Repr::With(w) => func = &w.0,
120 Repr::Closure(..) | Repr::Plugin(..) => return Some(()),
121 Repr::Native(..) | Repr::Element(..) => return None,
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::tests::*;
131
132 #[test]
133 fn prepare() {
134 snapshot_testing("rename", &|ctx, path| {
135 let source = ctx.source_by_path(&path).unwrap();
136
137 let request = PrepareRenameRequest {
138 path: path.clone(),
139 position: find_test_position(&source),
140 };
141
142 let result = request.request(ctx);
143 assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
144 });
145 }
146}