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