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