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 bad_syntax(&syntax) {
41 return None;
42 }
43
44 let mut node = syntax.node().clone();
46 if matches!(node.kind(), SyntaxKind::Ref) {
47 let marker = node
48 .children()
49 .find(|child| child.kind() == SyntaxKind::RefMarker)?;
50 node = marker;
51 }
52 let mut range = node.range();
53 if matches!(node.kind(), SyntaxKind::RefMarker) {
54 range.start += 1;
55 }
56
57 let origin_selection_range = ctx.to_lsp_range(range, &source);
58 let def = ctx.def_of_syntax(&source, syntax.clone())?;
59
60 let name = prepare_renaming(&syntax, &def)?;
61
62 Some(PrepareRenameResponse::RangeWithPlaceholder {
63 range: origin_selection_range,
64 placeholder: name,
65 })
66 }
67}
68
69fn bad_syntax(syntax: &SyntaxClass) -> bool {
70 if matches!(syntax.node().kind(), SyntaxKind::FieldAccess) {
71 log::info!("prepare_rename: field access is not a definition site");
73 return true;
74 }
75
76 if syntax.erroneous() {
77 return true;
78 }
79
80 false
81}
82
83pub(crate) fn prepare_renaming(syntax: &SyntaxClass, def: &Definition) -> Option<String> {
84 if bad_syntax(syntax) {
85 return None;
86 }
87
88 let def_fid = def.file_id()?;
89
90 if WorkspaceResolver::is_package_file(def_fid) {
91 crate::log_debug_ct!(
92 "prepare_rename: is in a package {pkg:?}, def: {def:?}",
93 pkg = def_fid.package(),
94 );
95 return None;
96 }
97
98 let decl_name = || def.name().clone().to_string();
99
100 use Decl::*;
101 match def.decl.as_ref() {
102 Var(..) | Label(..) | ContentRef(..) => Some(decl_name()),
109 Func(..) | Closure(..) => validate_fn_renaming(def).map(|_| decl_name()),
110 Module(..) | ModuleAlias(..) | PathStem(..) | ImportPath(..) | IncludePath(..)
111 | ModuleImport(..) => {
112 let node = syntax.node().get().clone();
113 let path = node.cast::<ast::Str>()?;
114 let name = path.get().to_string();
115 Some(name)
116 }
117 BibEntry(..) => None,
119 ImportAlias(..) | Constant(..) | IdentRef(..) | Import(..) | StrName(..) | Spread(..) => {
120 None
121 }
122 Pattern(..) | Content(..) | Generated(..) | Docs(..) => None,
123 }
124}
125
126fn validate_fn_renaming(def: &Definition) -> Option<()> {
127 use typst::foundations::func::Repr;
128 let value = def.value();
129 let mut func = match &value {
130 None => return Some(()),
131 Some(Value::Func(func)) => func,
132 Some(..) => {
133 log::info!(
134 "prepare_rename: not a function on function definition site: {:?}",
135 def.term
136 );
137 return None;
138 }
139 };
140 loop {
141 match func.inner() {
142 Repr::With(w) => func = &w.0,
144 Repr::Closure(..) | Repr::Plugin(..) => return Some(()),
145 Repr::Native(..) | Repr::Element(..) => return None,
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::tests::*;
155
156 #[test]
157 fn prepare() {
158 snapshot_testing("rename", &|ctx, path| {
159 let source = ctx.source_by_path(&path).unwrap();
160
161 let request = PrepareRenameRequest {
162 path: path.clone(),
163 position: find_test_position(&source),
164 };
165
166 let result = request.request(ctx);
167 assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
168 });
169 }
170}