1 mod acronym_ref;
2 mod argument;
3 mod begin_command;
4 mod citation;
5 mod color;
6 mod color_model;
7 mod component_command;
8 mod component_environment;
9 mod entry_type;
10 mod field;
11 mod glossary_ref;
12 mod import;
13 mod include;
14 mod label;
15 mod theorem;
16 mod tikz_library;
17 mod types;
18 mod user_command;
19 mod user_environment;
20 mod util;
21 
22 use std::borrow::Cow;
23 
24 use cancellation::CancellationToken;
25 use cstree::TextSize;
26 use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
27 use lsp_types::{
28     CompletionItem, CompletionList, CompletionParams, CompletionTextEdit, Documentation,
29     InsertTextFormat, MarkupContent, MarkupKind, TextEdit,
30 };
31 use rustc_hash::FxHashSet;
32 
33 use crate::{
34     syntax::{bibtex, latex, CstNode},
35     LineIndexExt,
36 };
37 
38 use self::{
39     acronym_ref::complete_acronyms,
40     argument::complete_arguments,
41     begin_command::complete_begin_command,
42     citation::complete_citations,
43     color::complete_colors,
44     color_model::complete_color_models,
45     component_command::complete_component_commands,
46     component_environment::complete_component_environments,
47     entry_type::complete_entry_types,
48     field::complete_fields,
49     glossary_ref::complete_glossary_entries,
50     import::complete_imports,
51     include::complete_includes,
52     label::complete_labels,
53     theorem::complete_theorem_environments,
54     tikz_library::complete_tikz_libraries,
55     types::{InternalCompletionItem, InternalCompletionItemData},
56     user_command::complete_user_commands,
57     user_environment::complete_user_environments,
58     util::{adjust_kind, component_detail, image_documentation},
59 };
60 
61 pub use self::types::CompletionItemData;
62 
63 use super::{
64     cursor::{Cursor, CursorContext},
65     lsp_kinds::Structure,
66     FeatureRequest,
67 };
68 
69 pub const COMPLETION_LIMIT: usize = 50;
70 
complete( request: FeatureRequest<CompletionParams>, cancellation_token: &CancellationToken, ) -> Option<CompletionList>71 pub fn complete(
72     request: FeatureRequest<CompletionParams>,
73     cancellation_token: &CancellationToken,
74 ) -> Option<CompletionList> {
75     let mut items = Vec::new();
76     let context = CursorContext::new(request);
77     log::debug!("[Completion] Cursor: {:?}", context.cursor);
78     complete_entry_types(&context, &mut items, cancellation_token);
79     complete_fields(&context, &mut items, cancellation_token);
80     complete_arguments(&context, &mut items, cancellation_token);
81     complete_citations(&context, &mut items, cancellation_token);
82     complete_imports(&context, &mut items, cancellation_token);
83     complete_colors(&context, &mut items, cancellation_token);
84     complete_color_models(&context, &mut items, cancellation_token);
85     complete_acronyms(&context, &mut items, cancellation_token);
86     complete_glossary_entries(&context, &mut items, cancellation_token);
87     complete_includes(&context, &mut items, cancellation_token);
88     complete_labels(&context, &mut items, cancellation_token);
89     complete_tikz_libraries(&context, &mut items, cancellation_token);
90     complete_component_environments(&context, &mut items, cancellation_token);
91     complete_theorem_environments(&context, &mut items, cancellation_token);
92     complete_user_environments(&context, &mut items, cancellation_token);
93     complete_begin_command(&context, &mut items, cancellation_token);
94     complete_component_commands(&context, &mut items, cancellation_token);
95     complete_user_commands(&context, &mut items, cancellation_token);
96 
97     cancellation_token.result().ok()?;
98 
99     let mut items = dedup(items);
100     preselect(&context, &mut items);
101     score(&context, &mut items);
102 
103     items.sort_by_key(|item| (!item.preselect, -item.score.unwrap_or(std::i64::MIN + 1)));
104     let items: Vec<_> = items
105         .into_iter()
106         .take(COMPLETION_LIMIT)
107         .filter(|item| item.score.is_some())
108         .map(|item| convert_internal_items(&context, item))
109         .enumerate()
110         .map(|(i, item)| append_sort_text(item, i))
111         .collect();
112 
113     let is_incomplete = if context
114         .request
115         .context
116         .client_info
117         .lock()
118         .unwrap()
119         .as_ref()
120         .map(|info| info.name.as_str())
121         .unwrap_or_default()
122         == "Visual Studio Code"
123     {
124         true
125     } else {
126         items.len() >= COMPLETION_LIMIT
127     };
128 
129     Some(CompletionList {
130         is_incomplete,
131         items,
132     })
133 }
134 
dedup(items: Vec<InternalCompletionItem>) -> Vec<InternalCompletionItem>135 fn dedup(items: Vec<InternalCompletionItem>) -> Vec<InternalCompletionItem> {
136     let mut labels = FxHashSet::default();
137     let mut insert = vec![false; items.len()];
138     for (i, item) in items.iter().enumerate() {
139         insert[i] = labels.insert(item.data.label());
140     }
141     items
142         .into_iter()
143         .enumerate()
144         .filter(|(i, _)| insert[*i])
145         .map(|(_, item)| item)
146         .collect()
147 }
148 
score(context: &CursorContext<CompletionParams>, items: &mut Vec<InternalCompletionItem>)149 fn score(context: &CursorContext<CompletionParams>, items: &mut Vec<InternalCompletionItem>) {
150     let pattern: Cow<str> = match &context.cursor {
151         Cursor::Latex(token) if token.kind().is_command_name() => {
152             if token.text_range().start() + TextSize::from(1) == context.offset {
153                 // Handle cases similar to this one correctly:
154                 // $\|$ % (| is the cursor)
155                 "\\".into()
156             } else {
157                 token.text().trim_end().into()
158             }
159         }
160         Cursor::Latex(token) if token.kind() == latex::WORD => {
161             if let Some(key) = latex::Key::cast(token.parent()) {
162                 key.to_string().into()
163             } else {
164                 token.text().into()
165             }
166         }
167         Cursor::Latex(_) => "".into(),
168         Cursor::Bibtex(token) if token.kind().is_type() => token.text().into(),
169         Cursor::Bibtex(token) if token.kind() == bibtex::WORD => {
170             if let Some(key) = bibtex::Key::cast(token.parent()) {
171                 key.to_string().into()
172             } else {
173                 token.text().into()
174             }
175         }
176         Cursor::Bibtex(token) if token.kind() == bibtex::COMMAND_NAME => {
177             token.text().trim_end().into()
178         }
179         Cursor::Bibtex(_) => "".into(),
180         Cursor::Nothing => "".into(),
181     };
182 
183     let file_pattern = pattern.split('/').last().unwrap();
184     let matcher = SkimMatcherV2::default().ignore_case();
185     for item in items {
186         item.score = match &item.data {
187             InternalCompletionItemData::EntryType { ty } => {
188                 matcher.fuzzy_match(&ty.name, &pattern[1..])
189             }
190             InternalCompletionItemData::Field { field } => {
191                 matcher.fuzzy_match(&field.name, &pattern)
192             }
193             InternalCompletionItemData::Argument { name, .. } => {
194                 matcher.fuzzy_match(name, &pattern)
195             }
196             InternalCompletionItemData::BeginCommand => matcher.fuzzy_match("begin", &pattern[1..]),
197             InternalCompletionItemData::Citation { key, .. } => matcher.fuzzy_match(&key, &pattern),
198             InternalCompletionItemData::ComponentCommand { name, .. } => {
199                 matcher.fuzzy_match(name, &pattern[1..])
200             }
201             InternalCompletionItemData::ComponentEnvironment { name, .. } => {
202                 matcher.fuzzy_match(name, &pattern)
203             }
204             InternalCompletionItemData::Class { name } => matcher.fuzzy_match(&name, &pattern),
205             InternalCompletionItemData::Package { name } => matcher.fuzzy_match(&name, &pattern),
206             InternalCompletionItemData::Color { name } => matcher.fuzzy_match(&name, &pattern),
207             InternalCompletionItemData::ColorModel { name } => matcher.fuzzy_match(&name, &pattern),
208             InternalCompletionItemData::Acronym { name } => matcher.fuzzy_match(&name, &pattern),
209             InternalCompletionItemData::GlossaryEntry { name } => {
210                 matcher.fuzzy_match(&name, &pattern)
211             }
212             InternalCompletionItemData::File { name } => matcher.fuzzy_match(&name, file_pattern),
213             InternalCompletionItemData::Directory { name } => {
214                 matcher.fuzzy_match(&name, file_pattern)
215             }
216             InternalCompletionItemData::Label { name, .. } => matcher.fuzzy_match(&name, &pattern),
217             InternalCompletionItemData::UserCommand { name } => {
218                 matcher.fuzzy_match(&name, &pattern[1..])
219             }
220             InternalCompletionItemData::UserEnvironment { name } => {
221                 matcher.fuzzy_match(&name, &pattern)
222             }
223             InternalCompletionItemData::PgfLibrary { name } => matcher.fuzzy_match(&name, &pattern),
224             InternalCompletionItemData::TikzLibrary { name } => {
225                 matcher.fuzzy_match(&name, &pattern)
226             }
227         };
228     }
229 }
230 
231 fn preselect(
232     context: &CursorContext<CompletionParams>,
233     items: &mut [InternalCompletionItem],
234 ) -> Option<()> {
235     let name = context.cursor.as_latex()?;
236     let group = latex::CurlyGroupWord::cast(name.parent())?;
237     let end = latex::End::cast(group.syntax().parent()?)?;
238     let environment = latex::Environment::cast(end.syntax().parent()?)?;
239     let name = environment.begin()?.name()?.key()?.to_string();
240 
241     for item in items {
242         if item.data.label() == name {
243             item.preselect = true;
244         }
245     }
246     Some(())
247 }
248 
249 fn convert_internal_items(
250     context: &CursorContext<CompletionParams>,
251     item: InternalCompletionItem,
252 ) -> CompletionItem {
253     let range = context
254         .request
255         .main_document()
256         .line_index
257         .line_col_lsp_range(item.range);
258 
259     let mut new_item = match item.data {
260         InternalCompletionItemData::EntryType { ty } => {
261             let text_edit = TextEdit::new(range, (&ty.name).into());
262             let kind = Structure::Entry(ty.category).completion_kind();
263             CompletionItem {
264                 label: (&ty.name).into(),
265                 kind: Some(adjust_kind(&context.request, kind)),
266                 documentation: ty.documentation.as_ref().map(|doc| {
267                     Documentation::MarkupContent(MarkupContent {
268                         kind: MarkupKind::Markdown,
269                         value: doc.into(),
270                     })
271                 }),
272                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
273                 data: Some(serde_json::to_value(CompletionItemData::EntryType).unwrap()),
274                 ..CompletionItem::default()
275             }
276         }
277         InternalCompletionItemData::Field { field } => {
278             let text_edit = TextEdit::new(range, (&field.name).into());
279             CompletionItem {
280                 label: (&field.name).into(),
281                 kind: Some(adjust_kind(
282                     &context.request,
283                     Structure::Field.completion_kind(),
284                 )),
285                 documentation: Some(Documentation::MarkupContent(MarkupContent {
286                     kind: MarkupKind::Markdown,
287                     value: (&field.documentation).into(),
288                 })),
289                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
290                 data: Some(serde_json::to_value(CompletionItemData::FieldName).unwrap()),
291                 ..CompletionItem::default()
292             }
293         }
294         InternalCompletionItemData::Argument { name, image } => {
295             let text_edit = TextEdit::new(range, name.into());
296             CompletionItem {
297                 label: name.into(),
298                 kind: Some(adjust_kind(
299                     &context.request,
300                     Structure::Argument.completion_kind(),
301                 )),
302                 data: Some(serde_json::to_value(CompletionItemData::Argument).unwrap()),
303                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
304                 documentation: image
305                     .and_then(|image| image_documentation(&context.request, &name, image)),
306                 ..CompletionItem::default()
307             }
308         }
309         InternalCompletionItemData::BeginCommand => {
310             if context
311                 .request
312                 .context
313                 .client_capabilities
314                 .lock()
315                 .unwrap()
316                 .text_document
317                 .as_ref()
318                 .and_then(|cap| cap.completion.as_ref())
319                 .and_then(|cap| cap.completion_item.as_ref())
320                 .and_then(|cap| cap.snippet_support)
321                 == Some(true)
322             {
323                 CompletionItem {
324                     kind: Some(adjust_kind(
325                         &context.request,
326                         Structure::Snippet.completion_kind(),
327                     )),
328                     data: Some(serde_json::to_value(CompletionItemData::CommandSnippet).unwrap()),
329                     insert_text: Some("begin{$1}\n\t$0\n\\end{$1}".into()),
330                     insert_text_format: Some(InsertTextFormat::Snippet),
331                     ..CompletionItem::new_simple("begin".into(), component_detail(&[]))
332                 }
333             } else {
334                 let text_edit = TextEdit::new(range, "begin".to_string());
335                 CompletionItem {
336                     kind: Some(adjust_kind(
337                         &context.request,
338                         Structure::Command.completion_kind(),
339                     )),
340                     data: Some(serde_json::to_value(CompletionItemData::Command).unwrap()),
341                     text_edit: Some(CompletionTextEdit::Edit(text_edit)),
342                     ..CompletionItem::new_simple("begin".to_string(), component_detail(&[]))
343                 }
344             }
345         }
346         InternalCompletionItemData::Citation { uri, key, text, ty } => {
347             let text_edit = TextEdit::new(range, key.to_string());
348             CompletionItem {
349                 label: key.to_string(),
350                 kind: Some(adjust_kind(&context.request, ty.completion_kind())),
351                 filter_text: Some(text.clone()),
352                 sort_text: Some(text),
353                 data: Some(
354                     serde_json::to_value(CompletionItemData::Citation {
355                         uri: uri.as_ref().clone(),
356                         key: key.into(),
357                     })
358                     .unwrap(),
359                 ),
360                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
361                 ..CompletionItem::default()
362             }
363         }
364         InternalCompletionItemData::ComponentCommand {
365             name,
366             image,
367             glyph,
368             file_names,
369         } => {
370             let detail = glyph.map_or_else(
371                 || component_detail(file_names),
372                 |glyph| format!("{}, {}", glyph, component_detail(file_names)),
373             );
374             let documentation =
375                 image.and_then(|img| image_documentation(&context.request, &name, img));
376             let text_edit = TextEdit::new(range, name.to_string());
377             CompletionItem {
378                 kind: Some(adjust_kind(
379                     &context.request,
380                     Structure::Command.completion_kind(),
381                 )),
382                 data: Some(serde_json::to_value(CompletionItemData::Command).unwrap()),
383                 documentation,
384                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
385                 ..CompletionItem::new_simple(name.to_string(), detail)
386             }
387         }
388         InternalCompletionItemData::ComponentEnvironment { name, file_names } => {
389             let text_edit = TextEdit::new(range, name.to_string());
390             CompletionItem {
391                 kind: Some(adjust_kind(
392                     &context.request,
393                     Structure::Environment.completion_kind(),
394                 )),
395                 data: Some(serde_json::to_value(CompletionItemData::Environment).unwrap()),
396                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
397                 ..CompletionItem::new_simple(name.to_string(), component_detail(file_names))
398             }
399         }
400         InternalCompletionItemData::Class { name } => {
401             let text_edit = TextEdit::new(range, name.to_string());
402             CompletionItem {
403                 label: name.into(),
404                 kind: Some(adjust_kind(
405                     &context.request,
406                     Structure::Class.completion_kind(),
407                 )),
408                 data: Some(serde_json::to_value(CompletionItemData::Class).unwrap()),
409                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
410                 ..CompletionItem::default()
411             }
412         }
413         InternalCompletionItemData::Package { name } => {
414             let text_edit = TextEdit::new(range, name.to_string());
415             CompletionItem {
416                 label: name.into(),
417                 kind: Some(adjust_kind(
418                     &context.request,
419                     Structure::Package.completion_kind(),
420                 )),
421                 data: Some(serde_json::to_value(CompletionItemData::Package).unwrap()),
422                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
423                 ..CompletionItem::default()
424             }
425         }
426         InternalCompletionItemData::Color { name } => {
427             let text_edit = TextEdit::new(range, name.into());
428             CompletionItem {
429                 label: name.into(),
430                 kind: Some(adjust_kind(
431                     &context.request,
432                     Structure::Color.completion_kind(),
433                 )),
434                 data: Some(serde_json::to_value(CompletionItemData::Color).unwrap()),
435                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
436                 ..CompletionItem::default()
437             }
438         }
439         InternalCompletionItemData::ColorModel { name } => {
440             let text_edit = TextEdit::new(range, name.into());
441             CompletionItem {
442                 label: name.into(),
443                 kind: Some(adjust_kind(
444                     &context.request,
445                     Structure::ColorModel.completion_kind(),
446                 )),
447                 data: Some(serde_json::to_value(CompletionItemData::ColorModel).unwrap()),
448                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
449                 ..CompletionItem::default()
450             }
451         }
452         InternalCompletionItemData::Acronym { name } => {
453             let text_edit = TextEdit::new(range, name.to_string());
454             CompletionItem {
455                 label: name.into(),
456                 kind: Some(adjust_kind(
457                     &context.request,
458                     Structure::GlossaryEntry.completion_kind(),
459                 )),
460                 data: Some(serde_json::to_value(CompletionItemData::Acronym).unwrap()),
461                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
462                 ..CompletionItem::default()
463             }
464         }
465         InternalCompletionItemData::GlossaryEntry { name } => {
466             let text_edit = TextEdit::new(range, name.to_string());
467             CompletionItem {
468                 label: name.into(),
469                 kind: Some(adjust_kind(
470                     &context.request,
471                     Structure::GlossaryEntry.completion_kind(),
472                 )),
473                 data: Some(serde_json::to_value(CompletionItemData::GlossaryEntry).unwrap()),
474                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
475                 ..CompletionItem::default()
476             }
477         }
478         InternalCompletionItemData::File { name } => {
479             let text_edit = TextEdit::new(range, name.to_string());
480             CompletionItem {
481                 label: name.into(),
482                 kind: Some(adjust_kind(
483                     &context.request,
484                     Structure::File.completion_kind(),
485                 )),
486                 data: Some(serde_json::to_value(CompletionItemData::File).unwrap()),
487                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
488                 ..CompletionItem::default()
489             }
490         }
491         InternalCompletionItemData::Directory { name } => {
492             let text_edit = TextEdit::new(range, name.to_string());
493             CompletionItem {
494                 label: name.into(),
495                 kind: Some(adjust_kind(
496                     &context.request,
497                     Structure::Folder.completion_kind(),
498                 )),
499                 data: Some(serde_json::to_value(CompletionItemData::Folder).unwrap()),
500                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
501                 ..CompletionItem::default()
502             }
503         }
504         InternalCompletionItemData::Label {
505             name,
506             kind,
507             header,
508             footer,
509             text,
510         } => {
511             let text_edit = TextEdit::new(range, name.to_string());
512             CompletionItem {
513                 label: name.into(),
514                 kind: Some(adjust_kind(&context.request, kind.completion_kind())),
515                 detail: header,
516                 documentation: footer.map(Documentation::String),
517                 sort_text: Some(text.clone()),
518                 filter_text: Some(text),
519                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
520                 data: Some(serde_json::to_value(CompletionItemData::Label).unwrap()),
521                 ..CompletionItem::default()
522             }
523         }
524         InternalCompletionItemData::UserCommand { name } => {
525             let detail = "user-defined".into();
526             let text_edit = TextEdit::new(range, name.to_string());
527             CompletionItem {
528                 kind: Some(adjust_kind(
529                     &context.request,
530                     Structure::Command.completion_kind(),
531                 )),
532                 data: Some(serde_json::to_value(CompletionItemData::Command).unwrap()),
533                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
534                 ..CompletionItem::new_simple(name.into(), detail)
535             }
536         }
537         InternalCompletionItemData::UserEnvironment { name } => {
538             let detail = "user-defined".into();
539             let text_edit = TextEdit::new(range, name.to_string());
540             CompletionItem {
541                 kind: Some(adjust_kind(
542                     &context.request,
543                     Structure::Environment.completion_kind(),
544                 )),
545                 data: Some(serde_json::to_value(CompletionItemData::Environment).unwrap()),
546                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
547                 ..CompletionItem::new_simple(name.into(), detail)
548             }
549         }
550         InternalCompletionItemData::PgfLibrary { name } => {
551             let text_edit = TextEdit::new(range, name.into());
552             CompletionItem {
553                 label: name.into(),
554                 kind: Some(adjust_kind(
555                     &context.request,
556                     Structure::PgfLibrary.completion_kind(),
557                 )),
558                 data: Some(serde_json::to_value(CompletionItemData::PgfLibrary).unwrap()),
559                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
560                 ..CompletionItem::default()
561             }
562         }
563         InternalCompletionItemData::TikzLibrary { name } => {
564             let text_edit = TextEdit::new(range, name.into());
565             CompletionItem {
566                 label: name.into(),
567                 kind: Some(adjust_kind(
568                     &context.request,
569                     Structure::TikzLibrary.completion_kind(),
570                 )),
571                 data: Some(serde_json::to_value(CompletionItemData::TikzLibrary).unwrap()),
572                 text_edit: Some(CompletionTextEdit::Edit(text_edit)),
573                 ..CompletionItem::default()
574             }
575         }
576     };
577     new_item.preselect = Some(item.preselect);
578     new_item
579 }
580 
581 fn append_sort_text(mut item: CompletionItem, index: usize) -> CompletionItem {
582     let sort_prefix = format!("{:0>2}", index);
583     match &item.sort_text {
584         Some(sort_text) => {
585             item.sort_text = Some(format!("{} {}", sort_prefix, sort_text));
586         }
587         None => {
588             item.sort_text = Some(sort_prefix);
589         }
590     };
591     item
592 }
593