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