tinymist_query/analysis/completion/
typst_specific.rs1use typst::foundations::Symbol;
5
6use super::*;
7impl CompletionPair<'_, '_, '_> {
8 pub fn font_completions(&mut self) {
10 let equation = self.cursor.before_window(25).contains("equation");
11 for (family, iter) in self.worker.world().clone().book().families() {
12 let detail = summarize_font_family(iter);
13 if !equation || family.contains("Math") {
14 self.value_completion(
15 None,
16 &Value::Str(family.into()),
17 false,
18 Some(detail.as_str()),
19 );
20 }
21 }
22 }
23
24 pub fn font_feature_completions(&mut self) {
26 }
28
29 pub fn package_completions(&mut self, all_versions: bool) {
31 let w = self.worker.world().clone();
32 let mut packages: Vec<_> = w
33 .packages()
34 .iter()
35 .map(|(spec, desc)| (spec, desc.clone()))
36 .collect();
37 #[cfg(feature = "http-registry")]
38 {
39 let local_packages_refs = self.worker.ctx.local_packages();
41 packages.extend(
42 local_packages_refs
43 .iter()
44 .map(|spec| (spec, Some(eco_format!("{} v{}", spec.name, spec.version)))),
45 );
46 }
47
48 packages.sort_by_key(|(spec, _)| (&spec.namespace, &spec.name, Reverse(spec.version)));
49 if !all_versions {
50 packages.dedup_by_key(|(spec, _)| (&spec.namespace, &spec.name));
51 }
52 for (package, description) in packages {
53 self.value_completion(
54 None,
55 &Value::Str(format_str!("{package}")),
56 false,
57 description.as_deref(),
58 );
59 }
60 }
61
62 pub fn raw_completions(&mut self) {
64 for (name, mut tags) in RawElem::languages() {
65 let lower = name.to_lowercase();
66 if !tags.contains(&lower.as_str()) {
67 tags.push(lower.as_str());
68 }
69
70 tags.retain(|tag| is_ident(tag));
71 if tags.is_empty() {
72 continue;
73 }
74
75 self.push_completion(Completion {
76 kind: CompletionKind::Constant,
77 label: name.into(),
78 apply: Some(tags[0].into()),
79 detail: Some(repr::separated_list(&tags, " or ").into()),
80 ..Completion::default()
81 });
82 }
83 }
84
85 pub fn ref_completions(&mut self) {
87 self.label_completions_(false, true);
88 }
89
90 pub fn label_completions(&mut self, only_citation: bool) {
92 self.label_completions_(only_citation, false);
93 }
94
95 pub fn label_completions_(&mut self, only_citation: bool, ref_label: bool) {
97 let Some(document) = self.worker.document else {
98 return;
99 };
100 let (labels, split) = analyze_labels(document);
101
102 let head = &self.cursor.text[..self.cursor.from];
103 let at = head.ends_with('@');
104 let open = !at && !head.ends_with('<');
105 let close = !at && !self.cursor.after.starts_with('>');
106 let citation = !at && only_citation;
107
108 let (skip, take) = if at || ref_label {
109 (0, usize::MAX)
110 } else if citation {
111 (split, usize::MAX)
112 } else {
113 (0, split)
114 };
115
116 for DynLabel {
117 label,
118 label_desc,
119 detail,
120 bib_title,
121 } in labels.into_iter().skip(skip).take(take)
122 {
123 if !self.worker.seen_casts.insert(hash128(&label)) {
124 continue;
125 }
126 let label: EcoString = label.resolve().as_str().into();
127 let completion = Completion {
128 kind: CompletionKind::Reference,
129 apply: Some(eco_format!(
130 "{}{}{}",
131 if open { "<" } else { "" },
132 label.as_str(),
133 if close { ">" } else { "" }
134 )),
135 label: label.clone(),
136 label_details: label_desc.clone(),
137 filter_text: Some(label.clone()),
138 detail: detail.clone(),
139 ..Completion::default()
140 };
141
142 if let Some(bib_title) = bib_title {
143 self.push_completion(Completion {
146 kind: CompletionKind::Constant,
147 label: bib_title.clone(),
148 label_details: Some(label),
149 filter_text: Some(bib_title),
150 detail,
151 ..completion.clone()
152 });
153 }
154
155 self.push_completion(completion);
156 }
157 }
158
159 pub fn value_completion(
161 &mut self,
162 label: Option<EcoString>,
163 value: &Value,
164 parens: bool,
165 docs: Option<&str>,
166 ) {
167 self.value_completion_(
168 value,
169 ValueCompletionInfo {
170 label,
171 parens,
172 label_details: None,
173 docs,
174 bound_self: false,
175 },
176 );
177 }
178
179 pub fn value_completion_(&mut self, value: &Value, extras: ValueCompletionInfo) {
181 let ValueCompletionInfo {
182 label,
183 parens,
184 label_details,
185 docs,
186 bound_self,
187 } = extras;
188
189 if !self.worker.seen_casts.insert(hash128(&(&label, &value))) {
191 return;
192 }
193
194 let at = label.as_deref().is_some_and(|field| !is_ident(field));
195 let label = label.unwrap_or_else(|| value.repr());
196
197 let detail = docs.map(Into::into).or_else(|| match value {
198 Value::Symbol(symbol) => Some(symbol_detail(symbol.get())),
199 Value::Func(func) => func.docs().map(plain_docs_sentence),
200 Value::Type(ty) => Some(plain_docs_sentence(ty.docs())),
201 v => {
202 let repr = v.repr();
203 (repr.as_str() != label).then_some(repr)
204 }
205 });
206 let label_details = label_details.or_else(|| match value {
207 Value::Symbol(s) => Some(symbol_label_detail(s.get())),
208 _ => None,
209 });
210
211 let mut apply = None;
212 if parens && matches!(value, Value::Func(_)) {
213 let mode = self.cursor.leaf_mode();
214 let ty = Ty::Value(InsTy::new(value.clone()));
215 let kind_checker = CompletionKindChecker {
216 symbols: HashSet::default(),
217 functions: HashSet::from_iter([&ty]),
218 };
219 let mut fn_feat = FnCompletionFeat::default();
220 fn_feat.bound_self = bound_self;
222 let fn_feat = fn_feat.check(kind_checker.functions.iter().copied());
223 self.func_completion(mode, fn_feat, label, label_details, detail, parens);
224 return;
225 } else if at {
226 apply = Some(eco_format!("at(\"{label}\")"));
227 } else {
228 let apply_label = &mut label.as_str();
229 if apply_label.ends_with('"')
230 && self.cursor.after.starts_with('"')
231 && let Some(trimmed) = apply_label.strip_suffix('"')
232 {
233 *apply_label = trimmed;
234 }
235 let from_before = slice_at(self.cursor.text, 0..self.cursor.from);
236 if apply_label.starts_with('"')
237 && from_before.ends_with('"')
238 && let Some(trimmed) = apply_label.strip_prefix('"')
239 {
240 *apply_label = trimmed;
241 }
242
243 if apply_label.len() != label.len() {
244 apply = Some((*apply_label).into());
245 }
246 }
247
248 self.push_completion(Completion {
249 kind: value_to_completion_kind(value),
250 label,
251 apply,
252 detail,
253 label_details,
254 ..Completion::default()
255 });
256 }
257
258 pub fn symbol_completions(&mut self, label: EcoString, symbol: &Symbol) {
259 let ch = symbol.get();
260 let kind = CompletionKind::Symbol(ch);
261 self.push_completion(Completion {
262 kind,
263 label: label.clone(),
264 label_details: Some(symbol_label_detail(ch)),
265 detail: Some(symbol_detail(ch)),
266 ..Completion::default()
267 });
268
269 let is_stepless = self.cursor.ctx.analysis.completion_feat.is_stepless();
270 if is_stepless {
271 self.symbol_var_completions(symbol, Some(&label));
272 }
273 }
274
275 pub fn symbol_var_completions(&mut self, symbol: &Symbol, prefix: Option<&str>) {
276 for modifier in symbol.modifiers() {
277 if let Ok(modified) = symbol.clone().modified(modifier) {
278 let label = match &prefix {
279 Some(prefix) => eco_format!("{prefix}.{modifier}"),
280 None => modifier.into(),
281 };
282
283 self.symbol_completions(label, &modified);
284 }
285 }
286 }
287}
288
289#[derive(Debug, Clone, Default)]
290pub struct ValueCompletionInfo<'a> {
291 pub label: Option<EcoString>,
292 pub parens: bool,
293 pub label_details: Option<EcoString>,
294 pub docs: Option<&'a str>,
295 pub bound_self: bool,
296}