1use super::*;
4use typst::foundations::Label;
5use typst::visualize::Color;
6
7pub(crate) struct TypeCompletionWorker<'a, 'b, 'c, 'd> {
8 pub base: &'d mut CompletionPair<'a, 'b, 'c>,
9 pub filter: &'d dyn Fn(&Ty) -> bool,
10}
11
12impl TypeCompletionWorker<'_, '_, '_, '_> {
13 fn snippet_completion(&mut self, label: &str, apply: &str, detail: &str) {
14 if !(self.filter)(&Ty::Any) {
15 return;
16 }
17
18 self.base.snippet_completion(label, apply, detail);
19 }
20
21 pub fn type_completion(&mut self, infer_type: &Ty, docs: Option<&str>) -> Option<()> {
22 if !self.base.worker.seen_types.insert(infer_type.clone()) {
24 return Some(());
25 }
26
27 crate::log_debug_ct!("type_completion: {infer_type:?}");
28
29 match infer_type {
30 Ty::Any => return None,
31 Ty::Pattern(_) => return None,
32 Ty::Args(_) => return None,
33 Ty::Func(_) => return None,
34 Ty::With(_) => return None,
35 Ty::Select(_) => return None,
36 Ty::Var(_) => return None,
37 Ty::Unary(_) => return None,
38 Ty::Binary(_) => return None,
39 Ty::If(_) => return None,
40 Ty::Union(u) => {
41 for info in u.as_ref() {
42 self.type_completion(info, docs);
43 }
44 }
45 Ty::Let(bounds) => {
46 for ut in bounds.ubs.iter() {
47 self.type_completion(ut, docs);
48 }
49 for lt in bounds.lbs.iter() {
50 self.type_completion(lt, docs);
51 }
52 }
53 Ty::Tuple(..) | Ty::Array(..) => {
54 if !(self.filter)(infer_type) {
55 return None;
56 }
57 self.snippet_completion("()", "(${})", "An array.");
58 }
59 Ty::Dict(..) => {
60 if !(self.filter)(infer_type) {
61 return None;
62 }
63 self.snippet_completion("()", "(${})", "A dictionary.");
64 }
65 Ty::Boolean(_b) => {
66 if !(self.filter)(infer_type) {
67 return None;
68 }
69 self.snippet_completion("false", "false", "No / Disabled.");
70 self.snippet_completion("true", "true", "Yes / Enabled.");
71 }
72 Ty::Builtin(v) => {
73 if !(self.filter)(infer_type) {
74 return None;
75 }
76 self.builtin_type_completion(v, docs);
77 }
78 Ty::Value(v) => {
79 if !(self.filter)(infer_type) {
80 return None;
81 }
82 let docs = v.syntax.as_ref().map(|s| s.doc.as_ref()).or(docs);
83
84 if let Value::Type(ty) = &v.val {
85 self.type_completion(&Ty::Builtin(BuiltinTy::Type(*ty)), docs);
86 } else if v.val.ty() == Type::of::<NoneValue>() {
87 self.type_completion(&Ty::Builtin(BuiltinTy::None), docs);
88 } else if v.val.ty() == Type::of::<AutoValue>() {
89 self.type_completion(&Ty::Builtin(BuiltinTy::Auto), docs);
90 } else {
91 self.base.value_completion(None, &v.val, true, docs);
92 }
93 }
94 Ty::Param(param) => {
95 let resolved_docs;
98 let docs = if docs.is_some() {
99 docs
100 } else {
101 resolved_docs = param.docs.as_ref().map(|docs| {
102 crate::docs::resolve_doc_text(self.base.worker.ctx.shared(), docs)
103 });
104 resolved_docs.as_deref()
105 };
106 if param.attrs.positional {
107 self.type_completion(¶m.ty, docs);
108 }
109 if !param.attrs.named {
110 return Some(());
111 }
112
113 let field = ¶m.name;
114 if self.base.worker.seen_field(field.clone()) {
115 return Some(());
116 }
117 if !(self.filter)(infer_type) {
118 return None;
119 }
120
121 let mut rev_stream = self.base.cursor.before.chars().rev();
122 let ch = rev_stream.find(|ch| !typst::syntax::is_id_continue(*ch));
123 if matches!(ch, Some('<' | '@')) {
126 return Some(());
127 }
128
129 self.base.push_completion(Completion {
130 kind: CompletionKind::Field,
131 label: field.into(),
132 apply: Some(eco_format!("{}: ${{}}", field)),
133 label_details: param.ty.describe(),
134 detail: docs.map(Into::into),
135 command: self
136 .base
137 .worker
138 .ctx
139 .analysis
140 .trigger_on_snippet_with_param_hint(true)
141 .map(From::from),
142 ..Completion::default()
143 });
144 }
145 };
146
147 Some(())
148 }
149
150 pub fn builtin_type_completion(&mut self, v: &BuiltinTy, docs: Option<&str>) -> Option<()> {
151 match v {
152 BuiltinTy::None => self.snippet_completion("none", "none", "Nothing."),
153 BuiltinTy::Auto => {
154 self.snippet_completion("auto", "auto", "A smart default.");
155 }
156 BuiltinTy::Clause => return None,
157 BuiltinTy::Undef => return None,
158 BuiltinTy::Space => return None,
159 BuiltinTy::Break => return None,
160 BuiltinTy::Continue => return None,
161 BuiltinTy::Never => return None,
162 BuiltinTy::Content(..) => return None,
163 BuiltinTy::Infer => return None,
164 BuiltinTy::FlowNone => return None,
165 BuiltinTy::Tag(..) => return None,
166 BuiltinTy::Module(..) => return None,
167
168 BuiltinTy::Path(preference) => {
169 let items = self.base.complete_path(preference);
170 self.base
171 .worker
172 .completions
173 .extend(items.into_iter().flatten());
174 }
175 BuiltinTy::Args => return None,
176 BuiltinTy::Stroke => {
177 self.snippet_completion("stroke()", "stroke(${})", "Stroke type.");
178 self.snippet_completion("()", "(${})", "Stroke dictionary.");
179 self.type_completion(&Ty::Builtin(BuiltinTy::Color), docs);
180 self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
181 }
182 BuiltinTy::Color => {
183 self.snippet_completion("luma()", "luma(${v})", "A custom grayscale color.");
184 self.snippet_completion(
185 "rgb()",
186 "rgb(${r}, ${g}, ${b}, ${a})",
187 "A custom RGBA color.",
188 );
189 self.snippet_completion(
190 "cmyk()",
191 "cmyk(${c}, ${m}, ${y}, ${k})",
192 "A custom CMYK color.",
193 );
194 self.snippet_completion(
195 "oklab()",
196 "oklab(${l}, ${a}, ${b}, ${alpha})",
197 "A custom Oklab color.",
198 );
199 self.snippet_completion(
200 "oklch()",
201 "oklch(${l}, ${chroma}, ${hue}, ${alpha})",
202 "A custom Oklch color.",
203 );
204 self.snippet_completion(
205 "color.linear-rgb()",
206 "color.linear-rgb(${r}, ${g}, ${b}, ${a})",
207 "A custom linear RGBA color.",
208 );
209 self.snippet_completion(
210 "color.hsv()",
211 "color.hsv(${h}, ${s}, ${v}, ${a})",
212 "A custom HSVA color.",
213 );
214 self.snippet_completion(
215 "color.hsl()",
216 "color.hsl(${h}, ${s}, ${l}, ${a})",
217 "A custom HSLA color.",
218 );
219 }
220 BuiltinTy::TextSize => return None,
221 #[cfg(not(feature = "lsp"))]
222 BuiltinTy::TextLang => return None,
223 #[cfg(feature = "lsp")]
224 BuiltinTy::TextLang => {
225 for (&key, desc) in rust_iso639::ALL_MAP.entries() {
226 let detail =
227 eco_format!("An ISO 639-1/2/3 language code, {}.", desc.get_name());
228 self.base.push_completion(Completion {
229 kind: CompletionKind::Syntax,
230 label: key.to_lowercase().into(),
231 apply: Some(eco_format!("\"{}\"", key.to_lowercase())),
232 detail: Some(detail),
233 label_details: Some(desc.get_name()),
234 ..Completion::default()
235 });
236 }
237 }
238 #[cfg(not(feature = "lsp"))]
239 BuiltinTy::TextRegion => return None,
240 #[cfg(feature = "lsp")]
241 BuiltinTy::TextRegion => {
242 for (&key, desc) in rust_iso3166::ALPHA2_MAP.entries() {
243 let detail =
244 eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.get_name());
245 self.base.push_completion(Completion {
246 kind: CompletionKind::Syntax,
247 label: key.to_lowercase().into(),
248 apply: Some(eco_format!("\"{}\"", key.to_lowercase())),
249 detail: Some(detail),
250 label_details: Some(desc.get_name()),
251 ..Completion::default()
252 });
253 }
254 }
255 BuiltinTy::Dir => {}
256 BuiltinTy::TextFont => {
257 self.base.font_completions();
258 }
259 BuiltinTy::TextFeature => {
260 self.base.font_feature_completions();
261 }
262 BuiltinTy::Margin => {
263 self.snippet_completion("()", "(${})", "Margin dictionary.");
264 self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
265 }
266 BuiltinTy::Inset => {
267 self.snippet_completion("()", "(${})", "Inset dictionary.");
268 self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
269 }
270 BuiltinTy::Outset => {
271 self.snippet_completion("()", "(${})", "Outset dictionary.");
272 self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
273 }
274 BuiltinTy::Radius => {
275 self.snippet_completion("()", "(${})", "Radius dictionary.");
276 self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
277 }
278 BuiltinTy::Length => {
279 self.snippet_completion("pt", "${1}pt", "Point length unit.");
280 self.snippet_completion("mm", "${1}mm", "Millimeter length unit.");
281 self.snippet_completion("cm", "${1}cm", "Centimeter length unit.");
282 self.snippet_completion("in", "${1}in", "Inch length unit.");
283 self.snippet_completion("em", "${1}em", "Em length unit.");
284 self.type_completion(&Ty::Builtin(BuiltinTy::Auto), docs);
285 }
286 BuiltinTy::Float => {
287 self.snippet_completion(
288 "exponential notation",
289 "${1}e${0}",
290 "Exponential notation",
291 );
292 }
293 BuiltinTy::Label => {
294 self.base.label_completions(false);
295 }
296 BuiltinTy::CiteLabel => {
297 self.base.label_completions(true);
298 }
299 BuiltinTy::RefLabel => {
300 self.base.ref_completions();
301 }
302 BuiltinTy::TypeType(ty) | BuiltinTy::Type(ty) => {
303 if *ty == Type::of::<NoneValue>() {
304 let docs = docs.or(Some("Nothing."));
305 self.type_completion(&Ty::Builtin(BuiltinTy::None), docs);
306 } else if *ty == Type::of::<AutoValue>() {
307 let docs = docs.or(Some("A smart default."));
308 self.type_completion(&Ty::Builtin(BuiltinTy::Auto), docs);
309 } else if *ty == Type::of::<bool>() {
310 self.snippet_completion("false", "false", "No / Disabled.");
311 self.snippet_completion("true", "true", "Yes / Enabled.");
312 } else if *ty == Type::of::<Color>() {
313 self.type_completion(&Ty::Builtin(BuiltinTy::Color), docs);
314 } else if *ty == Type::of::<Label>() {
315 self.base.label_completions(false)
316 } else if *ty == Type::of::<Func>() {
317 self.snippet_completion(
318 "function",
319 "(${params}) => ${output}",
320 "A custom function.",
321 );
322 } else if let Ok(cons) = ty.constructor() {
323 let docs = docs.or(cons.docs()).unwrap_or(ty.docs());
324 self.base.value_completion(
325 Some(ty.short_name().into()),
326 &Value::Func(cons),
327 true,
328 Some(docs),
329 );
330 } else if ty.scope().iter().any(|(_, b)| {
331 if let Value::Func(f) = b.read() {
332 let pos = f.params().find(|s| s.required());
333 pos.is_none_or(|pos| pos.name() != Some("self"))
334 } else {
335 true
336 }
337 }) {
338 let docs = docs.unwrap_or(ty.docs());
339 self.base.push_completion(Completion {
340 kind: CompletionKind::Syntax,
341 label: ty.short_name().into(),
342 apply: Some(eco_format!("${{{ty}}}")),
343 detail: Some(docs.into()),
344 ..Completion::default()
345 });
346 }
347 }
353 BuiltinTy::Element(elem) => {
354 self.base.value_completion(
355 Some(elem.name().into()),
356 &Value::Func((*elem).into()),
357 true,
358 docs,
359 );
360 }
361 };
362
363 Some(())
364 }
365}
366
367#[cfg(feature = "lsp")]
368use iso::*;
369#[cfg(feature = "lsp")]
370mod iso {
371 use ecow::EcoString;
372
373 pub trait GetName {
374 fn get_name(&self) -> EcoString;
375 }
376
377 #[cfg(not(target_arch = "wasm32"))]
378 impl GetName for rust_iso639::LanguageCode<'_> {
379 fn get_name(&self) -> EcoString {
380 self.name.into()
381 }
382 }
383
384 #[cfg(not(all(direct_wasm, target_arch = "wasm32")))]
385 impl GetName for rust_iso3166::CountryCode {
386 fn get_name(&self) -> EcoString {
387 self.name.into()
388 }
389 }
390
391 #[cfg(target_arch = "wasm32")]
392 impl GetName for rust_iso639::LanguageCode {
393 fn get_name(&self) -> EcoString {
394 self.name().into()
395 }
396 }
397
398 #[cfg(all(direct_wasm, target_arch = "wasm32"))]
399 impl GetName for rust_iso3166::CountryCode {
400 fn get_name(&self) -> EcoString {
401 self.name().into()
402 }
403 }
404}