tinymist_query/analysis/completion/
typst_specific.rs1use tinymist_std::time::yyyy_mm_dd;
5use typst::foundations::Symbol;
6use typst::syntax::is_valid_label_literal_id;
7
8use super::*;
9impl CompletionPair<'_, '_, '_> {
10 pub fn font_completions(&mut self) {
12 let equation = self.cursor.before_window(25).contains("equation");
13 for (family, iter) in self.worker.world().clone().book().families() {
14 let detail = summarize_font_family(iter);
15 if !equation || family.contains("Math") {
16 self.value_completion(
17 None,
18 &Value::Str(family.into()),
19 false,
20 Some(detail.as_str()),
21 );
22 }
23 }
24 }
25
26 pub fn font_feature_completions(&mut self) {
28 }
30
31 pub fn package_completions(&mut self, all_versions: bool) {
33 let w = self.worker.world().clone();
34 let mut packages = w.packages().iter().collect_vec();
36
37 #[cfg(feature = "local-registry")]
39 let other_packages_refs = self.worker.ctx.non_preview_packages();
40 #[cfg(feature = "local-registry")]
41 packages.extend(other_packages_refs.iter());
42
43 packages.sort_by_key(|entry| {
44 (
45 &entry.namespace,
46 &entry.package.name,
47 Reverse(entry.package.version),
48 )
49 });
50 if !all_versions {
51 packages.dedup_by_key(|entry| (&entry.namespace, &entry.package.name));
52 }
53 let completion_info = packages
54 .iter()
55 .map(|entry| {
56 let mut docs = String::new();
57 if let Some(desc) = &entry.package.description {
58 docs.push_str(desc);
59 docs.push_str("\n\n");
60 }
61 docs.push_str(&format!("Authors: {}\n", entry.package.authors.join(", ")));
62 docs.push_str(&format!("Version: {}\n", entry.package.version));
63 if let Some(updated_at) = &entry.updated_at
64 && let Ok(formatted) = updated_at.format(&yyyy_mm_dd())
65 {
66 docs.push_str(&format!("Updated: {formatted}\n"));
67 }
68 (format_str!("{}", entry.spec()), docs)
69 })
70 .collect_vec(); for (value, docs) in completion_info {
72 self.value_completion(None, &Value::Str(value), false, Some(&docs));
73 }
74 }
75
76 pub fn raw_completions(&mut self) {
78 for (name, mut tags) in RawElem::languages() {
79 let lower = name.to_lowercase();
80 if !tags.contains(&lower.as_str()) {
81 tags.push(lower.as_str());
82 }
83
84 tags.retain(|tag| is_ident(tag));
85 if tags.is_empty() {
86 continue;
87 }
88
89 self.push_completion(Completion {
90 kind: CompletionKind::Constant,
91 label: name.into(),
92 apply: Some(tags[0].into()),
93 detail: Some(repr::separated_list(&tags, " or ").into()),
94 ..Completion::default()
95 });
96 }
97 }
98
99 pub fn ref_completions(&mut self) {
101 self.label_completions_(false, true);
102 }
103
104 pub fn label_completions(&mut self, only_citation: bool) {
106 self.label_completions_(only_citation, false);
107 }
108
109 fn label_completion_apply(
110 &mut self,
111 label: &EcoString,
112 open: bool,
113 close: bool,
114 ) -> (EcoString, Option<Vec<EcoTextEdit>>) {
115 if is_valid_label_literal_id(label.as_str()) {
116 return (
117 eco_format!(
118 "{}{}{}",
119 if open { "<" } else { "" },
120 label.as_str(),
121 if close { ">" } else { "" }
122 ),
123 None,
124 );
125 }
126 let mut edits = vec![];
127
128 let (remove_open, remove_close) = match self.cursor.selected_node() {
129 Some(SelectedNode::Label(node)) => {
130 let range = node.range();
131 let remove_open = node
132 .text()
133 .starts_with('<')
134 .then_some(range.start..range.start + 1);
135 let remove_close = node
136 .text()
137 .ends_with('>')
138 .then_some(range.end - 1..range.end);
139 (remove_open, remove_close)
140 }
141 _ => (
142 (self.cursor.from > 0 && self.cursor.text[..self.cursor.from].ends_with('<'))
143 .then_some(self.cursor.from - 1..self.cursor.from),
144 self.cursor
145 .after
146 .starts_with('>')
147 .then_some(self.cursor.cursor..self.cursor.cursor + 1),
148 ),
149 };
150
151 if let Some(range) = remove_open {
152 edits.push(EcoTextEdit::new(
153 self.cursor.lsp_range_of(range),
154 EcoString::new(),
155 ));
156 }
157 if let Some(range) = remove_close {
158 edits.push(EcoTextEdit::new(
159 self.cursor.lsp_range_of(range),
160 EcoString::new(),
161 ));
162 }
163
164 (
165 eco_format!("label({})", label.as_str().repr()),
166 (!edits.is_empty()).then_some(edits),
167 )
168 }
169
170 pub fn label_completions_(&mut self, only_citation: bool, ref_label: bool) {
172 let Some(document) = self.worker.document else {
173 return;
174 };
175 let (labels, split) = analyze_labels(document);
176
177 let head = &self.cursor.text[..self.cursor.from];
178 let at = head.ends_with('@');
179 let open = !at && !head.ends_with('<');
180 let close = !at && !self.cursor.after.starts_with('>');
181 let citation = !at && only_citation;
182
183 let (skip, take) = if at || ref_label {
184 (0, usize::MAX)
185 } else if citation {
186 (split, usize::MAX)
187 } else {
188 (0, split)
189 };
190
191 for DynLabel {
192 label,
193 label_desc,
194 detail,
195 bib_title,
196 } in labels.into_iter().skip(skip).take(take)
197 {
198 if !self.worker.seen_casts.insert(hash128(&label)) {
199 continue;
200 }
201 let label: EcoString = label.resolve().as_str().into();
202 let (apply, additional_text_edits) = self.label_completion_apply(&label, open, close);
203 let completion = Completion {
204 kind: CompletionKind::Reference,
205 apply: Some(apply),
206 additional_text_edits,
207 label: label.clone(),
208 label_details: label_desc.clone(),
209 filter_text: Some(label.clone()),
210 detail: detail.clone(),
211 ..Completion::default()
212 };
213
214 if let Some(bib_title) = bib_title {
215 self.push_completion(Completion {
218 kind: CompletionKind::Constant,
219 label: bib_title.clone(),
220 label_details: Some(label),
221 filter_text: Some(bib_title),
222 detail,
223 ..completion.clone()
224 });
225 }
226
227 self.push_completion(completion);
228 }
229 }
230
231 pub fn value_completion(
233 &mut self,
234 label: Option<EcoString>,
235 value: &Value,
236 parens: bool,
237 docs: Option<&str>,
238 ) {
239 self.value_completion_(
240 value,
241 ValueCompletionInfo {
242 label,
243 parens,
244 label_details: None,
245 docs,
246 bound_self: false,
247 },
248 );
249 }
250
251 pub fn value_completion_(&mut self, value: &Value, extras: ValueCompletionInfo) {
253 let ValueCompletionInfo {
254 label,
255 parens,
256 label_details,
257 docs,
258 bound_self,
259 } = extras;
260
261 if !self.worker.seen_casts.insert(hash128(&(&label, &value))) {
263 return;
264 }
265
266 let at = label.as_deref().is_some_and(|field| !is_ident(field));
267 let label = label.unwrap_or_else(|| value.repr());
268
269 let detail = docs.map(Into::into).or_else(|| match value {
270 Value::Symbol(symbol) => Some(symbol_detail(symbol.get())),
271 Value::Func(func) => func.docs().map(plain_docs_sentence),
272 Value::Type(ty) => Some(plain_docs_sentence(ty.docs())),
273 v => {
274 let repr = v.repr();
275 (repr.as_str() != label).then_some(repr)
276 }
277 });
278 let label_details = label_details.or_else(|| match value {
279 Value::Symbol(s) => Some(symbol_label_detail(s.get())),
280 _ => None,
281 });
282
283 let mut apply = None;
284 if parens && matches!(value, Value::Func(_)) {
285 let mode = self.cursor.leaf_mode();
286 let ty = Ty::Value(InsTy::new(value.clone()));
287 let kind_checker = CompletionKindChecker {
288 symbols: HashSet::default(),
289 functions: HashSet::from_iter([&ty]),
290 };
291 let mut fn_feat = FnCompletionFeat::default();
292 fn_feat.bound_self = bound_self;
294 let fn_feat = fn_feat.check(kind_checker.functions.iter().copied());
295 self.func_completion(mode, fn_feat, label, label_details, detail, parens);
296 return;
297 } else if at {
298 apply = Some(eco_format!("at(\"{label}\")"));
299 } else {
300 let apply_label = &mut label.as_str();
301 if apply_label.ends_with('"')
302 && self.cursor.after.starts_with('"')
303 && let Some(trimmed) = apply_label.strip_suffix('"')
304 {
305 *apply_label = trimmed;
306 }
307 let from_before = slice_at(self.cursor.text, 0..self.cursor.from);
308 if apply_label.starts_with('"')
309 && from_before.ends_with('"')
310 && let Some(trimmed) = apply_label.strip_prefix('"')
311 {
312 *apply_label = trimmed;
313 }
314
315 if apply_label.len() != label.len() {
316 apply = Some((*apply_label).into());
317 }
318 }
319
320 self.push_completion(Completion {
321 kind: value_to_completion_kind(value),
322 label,
323 apply,
324 detail,
325 label_details,
326 ..Completion::default()
327 });
328 }
329
330 pub fn symbol_completions(&mut self, label: EcoString, symbol: &Symbol) {
331 let sym_val = symbol.get();
332 let kind = CompletionKind::Symbol(sym_val.into());
333 self.push_completion(Completion {
334 kind,
335 label: label.clone(),
336 label_details: Some(symbol_label_detail(sym_val)),
337 detail: Some(symbol_detail(sym_val)),
338 ..Completion::default()
339 });
340
341 let is_stepless = self.cursor.ctx.analysis.completion_feat.is_stepless();
342 if is_stepless {
343 self.symbol_var_completions(symbol, Some(&label));
344 }
345 }
346
347 pub fn symbol_var_completions(&mut self, symbol: &Symbol, prefix: Option<&str>) {
348 for modifier in symbol.modifiers() {
349 if let Ok(modified) = symbol.clone().modified((), modifier) {
350 let label = match &prefix {
351 Some(prefix) => eco_format!("{prefix}.{modifier}"),
352 None => modifier.into(),
353 };
354
355 self.symbol_completions(label, &modified);
356 }
357 }
358 }
359}
360
361#[derive(Debug, Clone, Default)]
362pub struct ValueCompletionInfo<'a> {
363 pub label: Option<EcoString>,
364 pub parens: bool,
365 pub label_details: Option<EcoString>,
366 pub docs: Option<&'a str>,
367 pub bound_self: bool,
368}