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