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