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