summaryrefslogtreecommitdiff
path: root/support/texlab/src
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src')
-rw-r--r--support/texlab/src/citation/driver.rs2
-rw-r--r--support/texlab/src/citation/field/date.rs2
-rw-r--r--support/texlab/src/citation/tests.rs4
-rw-r--r--support/texlab/src/client.rs144
-rw-r--r--support/texlab/src/component_db.rs151
-rw-r--r--support/texlab/src/db.rs14
-rw-r--r--support/texlab/src/db/analysis.rs199
-rw-r--r--support/texlab/src/db/analysis/label.rs122
-rw-r--r--support/texlab/src/db/diagnostics.rs187
-rw-r--r--support/texlab/src/db/diagnostics/bib.rs112
-rw-r--r--support/texlab/src/db/diagnostics/log.rs100
-rw-r--r--support/texlab/src/db/diagnostics/tex.rs125
-rw-r--r--support/texlab/src/db/discovery.rs251
-rw-r--r--support/texlab/src/db/document.rs193
-rw-r--r--support/texlab/src/db/parse.rs71
-rw-r--r--support/texlab/src/db/workspace.rs245
-rw-r--r--support/texlab/src/debouncer.rs55
-rw-r--r--support/texlab/src/diagnostics.rs167
-rw-r--r--support/texlab/src/diagnostics/bibtex.rs139
-rw-r--r--support/texlab/src/diagnostics/build.rs68
-rw-r--r--support/texlab/src/diagnostics/chktex.rs103
-rw-r--r--support/texlab/src/diagnostics/latex.rs126
-rw-r--r--support/texlab/src/distro.rs65
-rw-r--r--support/texlab/src/distro/file_name_db.rs80
-rw-r--r--support/texlab/src/distro/kpsewhich.rs52
-rw-r--r--support/texlab/src/distro/miktex.rs30
-rw-r--r--support/texlab/src/distro/texlive.rs10
-rw-r--r--support/texlab/src/document.rs135
-rw-r--r--support/texlab/src/environment.rs33
-rw-r--r--support/texlab/src/features.rs66
-rw-r--r--support/texlab/src/features/build.rs335
-rw-r--r--support/texlab/src/features/build/progress.rs54
-rw-r--r--support/texlab/src/features/completion.rs599
-rw-r--r--support/texlab/src/features/completion/acronym_ref.rs25
-rw-r--r--support/texlab/src/features/completion/argument.rs27
-rw-r--r--support/texlab/src/features/completion/begin_command.rs18
-rw-r--r--support/texlab/src/features/completion/begin_snippet.rs9
-rw-r--r--support/texlab/src/features/completion/builder.rs784
-rw-r--r--support/texlab/src/features/completion/citation.rs82
-rw-r--r--support/texlab/src/features/completion/color.rs19
-rw-r--r--support/texlab/src/features/completion/color_model.rs20
-rw-r--r--support/texlab/src/features/completion/component_command.rs28
-rw-r--r--support/texlab/src/features/completion/component_environment.rs22
-rw-r--r--support/texlab/src/features/completion/entry_type.rs22
-rw-r--r--support/texlab/src/features/completion/field.rs19
-rw-r--r--support/texlab/src/features/completion/glossary_ref.rs27
-rw-r--r--support/texlab/src/features/completion/import.rs57
-rw-r--r--support/texlab/src/features/completion/include.rs78
-rw-r--r--support/texlab/src/features/completion/label.rs112
-rw-r--r--support/texlab/src/features/completion/theorem.rs26
-rw-r--r--support/texlab/src/features/completion/tikz_library.rs24
-rw-r--r--support/texlab/src/features/completion/types.rs151
-rw-r--r--support/texlab/src/features/completion/user_command.rs34
-rw-r--r--support/texlab/src/features/completion/user_environment.rs26
-rw-r--r--support/texlab/src/features/completion/util.rs64
-rw-r--r--support/texlab/src/features/definition.rs54
-rw-r--r--support/texlab/src/features/definition/command.rs20
-rw-r--r--support/texlab/src/features/definition/document.rs50
-rw-r--r--support/texlab/src/features/definition/entry.rs24
-rw-r--r--support/texlab/src/features/definition/label.rs37
-rw-r--r--support/texlab/src/features/definition/string.rs33
-rw-r--r--support/texlab/src/features/execute_command.rs103
-rw-r--r--support/texlab/src/features/folding.rs59
-rw-r--r--support/texlab/src/features/formatting.rs35
-rw-r--r--support/texlab/src/features/formatting/bibtex_internal.rs56
-rw-r--r--support/texlab/src/features/formatting/latexindent.rs112
-rw-r--r--support/texlab/src/features/forward_search.rs184
-rw-r--r--support/texlab/src/features/highlight.rs14
-rw-r--r--support/texlab/src/features/highlight/label.rs93
-rw-r--r--support/texlab/src/features/hover.rs39
-rw-r--r--support/texlab/src/features/hover/citation.rs25
-rw-r--r--support/texlab/src/features/hover/component.rs39
-rw-r--r--support/texlab/src/features/hover/entry_type.rs11
-rw-r--r--support/texlab/src/features/hover/field.rs11
-rw-r--r--support/texlab/src/features/hover/label.rs24
-rw-r--r--support/texlab/src/features/hover/string_ref.rs15
-rw-r--r--support/texlab/src/features/inlay_hint.rs45
-rw-r--r--support/texlab/src/features/inlay_hint/label.rs31
-rw-r--r--support/texlab/src/features/link.rs54
-rw-r--r--support/texlab/src/features/link/include.rs42
-rw-r--r--support/texlab/src/features/reference.rs45
-rw-r--r--support/texlab/src/features/reference/entry.rs35
-rw-r--r--support/texlab/src/features/reference/label.rs31
-rw-r--r--support/texlab/src/features/reference/string.rs24
-rw-r--r--support/texlab/src/features/rename.rs58
-rw-r--r--support/texlab/src/features/rename/command.rs30
-rw-r--r--support/texlab/src/features/rename/entry.rs40
-rw-r--r--support/texlab/src/features/rename/label.rs34
-rw-r--r--support/texlab/src/features/symbol.rs82
-rw-r--r--support/texlab/src/features/symbol/bibtex.rs137
-rw-r--r--support/texlab/src/features/symbol/latex.rs236
-rw-r--r--support/texlab/src/features/symbol/project_order.rs307
-rw-r--r--support/texlab/src/features/symbol/types.rs15
-rw-r--r--support/texlab/src/features/workspace_command.rs103
-rw-r--r--support/texlab/src/language.rs33
-rw-r--r--support/texlab/src/lib.rs147
-rw-r--r--support/texlab/src/main.rs8
-rw-r--r--support/texlab/src/options.rs103
-rw-r--r--support/texlab/src/parser.rs5
-rw-r--r--support/texlab/src/parser/bibtex.rs (renamed from support/texlab/src/syntax/bibtex/parser.rs)206
-rw-r--r--support/texlab/src/parser/build_log.rs184
-rw-r--r--support/texlab/src/parser/latex.rs (renamed from support/texlab/src/syntax/latex/parser.rs)700
-rw-r--r--support/texlab/src/parser/latex/lexer.rs (renamed from support/texlab/src/syntax/latex/lexer.rs)115
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@comment.txt.snap10
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@issue_809.txt.snap31
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@preamble.txt.snap (renamed from support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__preamble.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aho_2006.txt.snap132
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aksin_2006.txt.snap (renamed from support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_002.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__almendro_1998.txt.snap246
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__averroes_1998.txt.snap351
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__betram_1996.txt.snap208
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__blom_2021.txt.snap223
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__combi_2004.txt.snap251
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__erwin_2007.txt.snap239
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__jain_1999.txt.snap267
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__kastenholz_2006.txt.snap302
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__knuth_1984.txt.snap320
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__matuz_1990.txt.snap176
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__nietzsche_1998.txt.snap368
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__rivest_1978.txt.snap232
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@001.txt.snap27
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@002.txt.snap20
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@003.txt.snap23
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@004.txt.snap31
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@005.txt.snap30
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@006.txt.snap69
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@007.txt.snap19
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@block_comments.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__block_comments.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_default.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_default_error.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_figure.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_minimal.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_minimal_error.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_empty.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_missing_brace.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_multiple_keys.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_prenote.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_prenote_postnote.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_redundant_comma.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_star.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_definition_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_reference_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error1.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error2.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error3.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error4.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_argc.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_impl.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_impl_error.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_optional.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_optional.snap)7
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_with_begin.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_with_begin.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__math_operator_no_impl.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__math_operator_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_asymptote.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__asymptote.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_definition.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_definition_optional_arg.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition_optional_arg.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_nested.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_nested_missing_braces.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@equation.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@equation_missing_begin.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_args.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_empty.txt.snap10
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_escape.txt.snap10
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_declaration.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_definition_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_definition_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_package.txt.snap30
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_reference_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_reference_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_definition_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_reference_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_reference_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path.txt.snap18
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_command.txt.snap23
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_options.txt.snap23
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__curly_group_missing_end.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__curly_group_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__equation_missing_end.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__escaped_brackets.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_braces.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_brackets.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_brackets_with_group.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@hello_world.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__biblatex_include_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__biblatex_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__bibtex_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_empty.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_command.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_command.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_complicated_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_complicated_options.snap)7
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__import_incomplete.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__import_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__inkscape_include_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__inkscape_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_include_equality_sign.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_equality_sign.snap)7
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_input_path_brackets.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_path_with_brackets.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_empty.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_multiple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__pgf_library_import_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__svg_include_options.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__svg_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__tikz_library_import_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__verbatim_include_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@inline.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@inline_double_dollar.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_745.txt.snap120
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_789.txt.snap15
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_definition_line_break.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_line_break.snap)7
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_definition_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_number.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_equation.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_incomplete.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_incomplete.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_multiple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_error.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_incomplete.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_simple.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter.txt.snap10
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter_error.txt.snap10
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_enum_item.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_invalid_nesting.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_nested.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_siblings.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_full.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_counter.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_description.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_description_and_counter.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap)6
-rw-r--r--support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_only_name.txt.snap (renamed from support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap)6
-rw-r--r--support/texlab/src/parser/test_data/bibtex/comment.txt6
-rw-r--r--support/texlab/src/parser/test_data/bibtex/issue_809.txt3
-rw-r--r--support/texlab/src/parser/test_data/bibtex/preamble.txt1
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/aho_2006.txt8
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/aksin_2006.txt16
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/almendro_1998.txt14
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/averroes_1998.txt21
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/betram_1996.txt16
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/blom_2021.txt20
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/combi_2004.txt16
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/erwin_2007.txt14
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/jain_1999.txt18
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/kastenholz_2006.txt20
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/knuth_1984.txt22
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/matuz_1990.txt14
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/nietzsche_1998.txt24
-rw-r--r--support/texlab/src/parser/test_data/bibtex/samples/rivest_1978.txt17
-rw-r--r--support/texlab/src/parser/test_data/build_log/001.txt83
-rw-r--r--support/texlab/src/parser/test_data/build_log/002.txt64
-rw-r--r--support/texlab/src/parser/test_data/build_log/003.txt65
-rw-r--r--support/texlab/src/parser/test_data/build_log/004.txt83
-rw-r--r--support/texlab/src/parser/test_data/build_log/005.txt385
-rw-r--r--support/texlab/src/parser/test_data/build_log/006.txt122
-rw-r--r--support/texlab/src/parser/test_data/build_log/007.txt176
-rw-r--r--support/texlab/src/parser/test_data/latex/block_comments.txt9
-rw-r--r--support/texlab/src/parser/test_data/latex/caption/caption_default.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/caption/caption_default_error.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/caption/caption_figure.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/caption/caption_minimal.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/caption/caption_minimal_error.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_empty.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_missing_brace.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_multiple_keys.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_prenote.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_prenote_postnote.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_redundant_comma.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/citation/citation_star.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_definition_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_reference_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_set_definition_error1.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_set_definition_error2.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_set_definition_error3.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_set_definition_error4.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/color/color_set_definition_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_argc.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl_error.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/command_definition_optional.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/command_definition_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/command_definition_with_begin.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/math_operator_no_impl.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/command_definition/math_operator_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/environment/environment_asymptote.txt3
-rw-r--r--support/texlab/src/parser/test_data/latex/environment/environment_definition.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/environment/environment_definition_optional_arg.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/environment/environment_nested.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/environment/environment_nested_missing_braces.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/environment/environment_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/equation.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/equation_missing_begin.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/generic_command_args.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/generic_command_empty.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/generic_command_escape.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/acronym_declaration.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/acronym_definition_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/acronym_definition_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/acronym_package.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/acronym_reference_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/acronym_reference_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/glossary_entry_definition_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/graphics_path.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/graphics_path_command.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/graphics_path_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/curly_group_missing_end.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/curly_group_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/equation_missing_end.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/escaped_brackets.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/unmatched_braces.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/unmatched_brackets.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/group/unmatched_brackets_with_group.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/hello_world.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/biblatex_include_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/biblatex_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/bibtex_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/class_include_empty.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/class_include_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/class_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/graphics_include_command.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/graphics_include_complicated_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/graphics_include_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/graphics_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/import_incomplete.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/import_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/inkscape_include_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/inkscape_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/latex_include_equality_sign.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/latex_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/latex_input_path_brackets.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/package_include_empty.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/package_include_multiple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/package_include_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/package_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/pgf_library_import_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/svg_include_options.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/svg_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/tikz_library_import_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/include/verbatim_include_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/inline.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/inline_double_dollar.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/issue_745.txt21
-rw-r--r--support/texlab/src/parser/test_data/latex/issue_789.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_definition_line_break.txt2
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_definition_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_number.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_equation.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_incomplete.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_multiple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_range_error.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_range_incomplete.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_range_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/label/label_reference_simple.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/parameter.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/parameter_error.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/structure/structure_enum_item.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/structure/structure_invalid_nesting.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/structure/structure_nested.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/structure/structure_siblings.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_full.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_counter.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description_and_counter.txt1
-rw-r--r--support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_only_name.txt1
-rw-r--r--support/texlab/src/range.rs20
-rw-r--r--support/texlab/src/req_queue.rs10
-rw-r--r--support/texlab/src/server.rs1156
-rw-r--r--support/texlab/src/server/dispatch.rs (renamed from support/texlab/src/dispatch.rs)0
-rw-r--r--support/texlab/src/server/query.rs32
-rw-r--r--support/texlab/src/syntax.rs21
-rw-r--r--support/texlab/src/syntax/bibtex.rs247
-rw-r--r--support/texlab/src/syntax/bibtex/ast.rs244
-rw-r--r--support/texlab/src/syntax/bibtex/lexer.rs180
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_001.snap430
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_003.snap208
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__comment.snap9
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__entry_colon.snap51
-rw-r--r--support/texlab/src/syntax/bibtex/tests.rs126
-rw-r--r--support/texlab/src/syntax/build_log.rs1332
-rw-r--r--support/texlab/src/syntax/latex.rs5
-rw-r--r--support/texlab/src/syntax/latex/analysis.rs40
-rw-r--r--support/texlab/src/syntax/latex/analysis/command.rs26
-rw-r--r--support/texlab/src/syntax/latex/analysis/distro_file.rs73
-rw-r--r--support/texlab/src/syntax/latex/analysis/environment.rs13
-rw-r--r--support/texlab/src/syntax/latex/analysis/explicit_link.rs72
-rw-r--r--support/texlab/src/syntax/latex/analysis/graphics_path.rs21
-rw-r--r--support/texlab/src/syntax/latex/analysis/implicit_link.rs64
-rw-r--r--support/texlab/src/syntax/latex/analysis/label_name.rs67
-rw-r--r--support/texlab/src/syntax/latex/analysis/label_number.rs24
-rw-r--r--support/texlab/src/syntax/latex/analysis/theorem.rs22
-rw-r--r--support/texlab/src/syntax/latex/analysis/types.rs75
-rw-r--r--support/texlab/src/syntax/latex/cst.rs2
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__asymptote.snap43
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__block_comment.snap27
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap47
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap23
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap36
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap79
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap6
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap55
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap11
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__line_break.snap20
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap8
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap10
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap10
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_path.snap17
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap10
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap10
-rw-r--r--support/texlab/src/syntax/snapshots/texlab__syntax__build_log__tests__parse_007.snap15
-rw-r--r--support/texlab/src/tests.rs5
-rw-r--r--support/texlab/src/tests/client.rs159
-rw-r--r--support/texlab/src/tests/fixture.rs124
-rw-r--r--support/texlab/src/tests/issues.rs12
-rw-r--r--support/texlab/src/tests/snapshots/texlab__tests__issues__issue_707.snap45
-rw-r--r--support/texlab/src/tests/text_document.rs12
-rw-r--r--support/texlab/src/tests/text_document/completion.rs812
-rw-r--r--support/texlab/src/tests/text_document/definition.rs140
-rw-r--r--support/texlab/src/tests/text_document/did_change_configuration.rs37
-rw-r--r--support/texlab/src/tests/text_document/document_highlight.rs60
-rw-r--r--support/texlab/src/tests/text_document/document_link.rs71
-rw-r--r--support/texlab/src/tests/text_document/document_symbol.rs408
-rw-r--r--support/texlab/src/tests/text_document/folding_range.rs96
-rw-r--r--support/texlab/src/tests/text_document/formatting.rs132
-rw-r--r--support/texlab/src/tests/text_document/hover.rs303
-rw-r--r--support/texlab/src/tests/text_document/inlay_hint.rs65
-rw-r--r--support/texlab/src/tests/text_document/references.rs271
-rw-r--r--support/texlab/src/tests/text_document/rename.rs110
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_package_ref.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_empty.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_open_brace.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_simple.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_empty.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_open_brace.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_word.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__begin_environment_without_snippet_support.snap62
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation.snap31
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_acronym.snap18
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace.snap18
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace_multiple.snap18
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_open_brace.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_open_brace.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_simple.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_simple.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_open_brace.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_simple.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_word.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_bibtex.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple.snap63
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple_package.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_class.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_command_definition.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple_end.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_preamble.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_string.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_at_empty.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_article.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_preamble.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_string.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_complete_entry.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_closed.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_open.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_field_name.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_closed.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_open.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_open_brace.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_simple.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_closed_brace.snap66
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_open_brace.snap66
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_closed_brace.snap66
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_open_brace.snap66
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__label.snap67
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__project_resolution.snap61
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_begin.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_end.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_closed_brace.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_open_brace.snap56
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_command.snap17
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_environment.snap28
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__did_change_configuration__invalid_configuration.snap9
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_flat.snap96
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_nested.snap140
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_flat.snap60
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_nested.snap86
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_flat.snap60
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_nested.snap86
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_flat.snap60
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_nested.snap87
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_flat.snap78
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_nested.snap113
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_command.snap8
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_insert_braces.snap7
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_join_strings.snap8
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_multiple_entries.snap16
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_parens.snap7
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_preamble.snap6
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_string.snap6
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_trailing_comma.snap8
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_wrap_long_lines.snap10
-rw-r--r--support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__inlay_hint__label_definition.snap38
-rw-r--r--support/texlab/src/tests/workspace.rs1
-rw-r--r--support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_bibtex.snap42
-rw-r--r--support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_figure.snap24
-rw-r--r--support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_item.snap60
-rw-r--r--support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_math.snap42
-rw-r--r--support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_section.snap78
-rw-r--r--support/texlab/src/tests/workspace/symbol.rs142
-rw-r--r--support/texlab/src/util.rs15
-rw-r--r--support/texlab/src/util/capabilities.rs (renamed from support/texlab/src/capabilities.rs)25
-rw-r--r--support/texlab/src/util/chktex.rs112
-rw-r--r--support/texlab/src/util/components.rs120
-rw-r--r--support/texlab/src/util/cursor.rs (renamed from support/texlab/src/features/cursor.rs)171
-rw-r--r--support/texlab/src/util/label.rs (renamed from support/texlab/src/label.rs)203
-rw-r--r--support/texlab/src/util/lang_data.rs (renamed from support/texlab/src/lang_data.rs)2
-rw-r--r--support/texlab/src/util/line_index.rs (renamed from support/texlab/src/line_index.rs)0
-rw-r--r--support/texlab/src/util/line_index_ext.rs (renamed from support/texlab/src/line_index_ext.rs)2
-rw-r--r--support/texlab/src/util/lsp_enums.rs (renamed from support/texlab/src/features/lsp_kinds.rs)2
-rw-r--r--support/texlab/src/workspace.rs225
529 files changed, 18169 insertions, 9534 deletions
diff --git a/support/texlab/src/citation/driver.rs b/support/texlab/src/citation/driver.rs
index 815474a107..ab3fe6d80f 100644
--- a/support/texlab/src/citation/driver.rs
+++ b/support/texlab/src/citation/driver.rs
@@ -1,7 +1,7 @@
use isocountry::CountryCode;
use itertools::Itertools;
+use lsp_types::Url;
use titlecase::titlecase;
-use url::Url;
use crate::syntax::bibtex;
diff --git a/support/texlab/src/citation/field/date.rs b/support/texlab/src/citation/field/date.rs
index b67972e691..4eba9cdea4 100644
--- a/support/texlab/src/citation/field/date.rs
+++ b/support/texlab/src/citation/field/date.rs
@@ -42,7 +42,7 @@ impl Add for DateFieldData {
| (Self::Other(_), date)
| (date, Self::Other(_)) => date,
(Self::Year(year), Self::Month(month)) | (Self::Month(month), Self::Year(year)) => {
- let new_date = NaiveDate::from_ymd(year, month.number_from_month(), 1);
+ let new_date = NaiveDate::from_ymd_opt(year, month.number_from_month(), 1).unwrap();
Self::Date(new_date)
}
(Self::Year(year), Self::Date(date)) | (Self::Date(date), Self::Year(year)) => {
diff --git a/support/texlab/src/citation/tests.rs b/support/texlab/src/citation/tests.rs
index 16aefea11d..6caff1add0 100644
--- a/support/texlab/src/citation/tests.rs
+++ b/support/texlab/src/citation/tests.rs
@@ -1,10 +1,10 @@
use insta::assert_snapshot;
use rowan::ast::AstNode;
-use crate::syntax::bibtex;
+use crate::{parser::parse_bibtex, syntax::bibtex};
fn render_entry(input: &str) -> String {
- let green = bibtex::parse(input);
+ let green = parse_bibtex(input);
let root = bibtex::Root::cast(bibtex::SyntaxNode::new_root(green)).unwrap();
let entry = root.entries().next().unwrap();
super::render(&entry).unwrap()
diff --git a/support/texlab/src/client.rs b/support/texlab/src/client.rs
index 72f2ac9eba..ab912653b0 100644
--- a/support/texlab/src/client.rs
+++ b/support/texlab/src/client.rs
@@ -1,53 +1,109 @@
-use std::sync::Mutex;
+use std::sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc,
+};
-use anyhow::{anyhow, Result};
-use crossbeam_channel::{Receiver, Sender};
-use lsp_server::{Message, ResponseError};
+use anyhow::{bail, Result};
+use crossbeam_channel::Sender;
+use dashmap::DashMap;
+use lsp_server::{ErrorCode, Message, Request, RequestId, Response};
+use lsp_types::{notification::ShowMessage, MessageType, ShowMessageParams};
use serde::{de::DeserializeOwned, Serialize};
-use crate::req_queue::{OutgoingData, ReqQueue};
+use crate::Options;
-pub fn send_notification<N>(lsp_sender: &Sender<Message>, params: N::Params) -> Result<()>
-where
- N: lsp_types::notification::Notification,
- N::Params: Serialize,
-{
- lsp_sender.send(lsp_server::Notification::new(N::METHOD.to_string(), params).into())?;
- Ok(())
+#[derive(Debug)]
+struct RawClient {
+ sender: Sender<Message>,
+ next_id: AtomicI32,
+ pending: DashMap<RequestId, Sender<Response>>,
}
-pub fn send_request<R>(
- req_queue: &Mutex<ReqQueue>,
- lsp_sender: &Sender<Message>,
- params: R::Params,
-) -> Result<R::Result>
-where
- R: lsp_types::request::Request,
- R::Params: Serialize,
- R::Result: DeserializeOwned,
-{
- let receiver = register_outgoing_request::<R>(req_queue, lsp_sender, params)?;
- let params = receiver.recv()?.map_err(|err| anyhow!(err.message))?;
- let result = serde_json::from_value(params)?;
- Ok(result)
+#[derive(Debug, Clone)]
+pub struct LspClient {
+ raw: Arc<RawClient>,
}
-fn register_outgoing_request<R>(
- req_queue: &Mutex<ReqQueue>,
- lsp_sender: &Sender<Message>,
- params: R::Params,
-) -> Result<Receiver<Result<serde_json::Value, ResponseError>>>
-where
- R: lsp_types::request::Request,
- R::Params: Serialize,
- R::Result: DeserializeOwned,
-{
- let mut req_queue = req_queue.lock().unwrap();
- let (sender, receiver) = crossbeam_channel::bounded(1);
- let method = R::METHOD.to_string();
- let data = OutgoingData { sender };
- let req = req_queue.outgoing.register(method, params, data);
- drop(req_queue);
- lsp_sender.send(req.into())?;
- Ok(receiver)
+impl LspClient {
+ pub fn new(sender: Sender<Message>) -> Self {
+ let raw = Arc::new(RawClient {
+ sender,
+ next_id: AtomicI32::new(1),
+ pending: DashMap::default(),
+ });
+
+ Self { raw }
+ }
+
+ pub fn send_notification<N>(&self, params: N::Params) -> Result<()>
+ where
+ N: lsp_types::notification::Notification,
+ N::Params: Serialize,
+ {
+ self.raw
+ .sender
+ .send(lsp_server::Notification::new(N::METHOD.to_string(), params).into())?;
+ Ok(())
+ }
+
+ pub fn send_request<R>(&self, params: R::Params) -> Result<R::Result>
+ where
+ R: lsp_types::request::Request,
+ R::Params: Serialize,
+ R::Result: DeserializeOwned,
+ {
+ let id = RequestId::from(self.raw.next_id.fetch_add(1, Ordering::SeqCst));
+
+ let (tx, rx) = crossbeam_channel::bounded(1);
+ self.raw.pending.insert(id.clone(), tx);
+
+ self.raw
+ .sender
+ .send(Request::new(id, R::METHOD.to_string(), params).into())?;
+
+ let response = rx.recv()?;
+ let result = match response.error {
+ Some(error) => bail!(error.message),
+ None => response.result.unwrap_or_default(),
+ };
+
+ Ok(serde_json::from_value(result)?)
+ }
+
+ pub fn send_response(&self, response: lsp_server::Response) -> Result<()> {
+ self.raw.sender.send(response.into())?;
+ Ok(())
+ }
+
+ pub fn send_error(&self, id: RequestId, code: ErrorCode, message: String) -> Result<()> {
+ self.send_response(lsp_server::Response::new_err(id, code as i32, message))?;
+ Ok(())
+ }
+
+ pub fn recv_response(&self, response: lsp_server::Response) -> Result<()> {
+ let (_, tx) = self
+ .raw
+ .pending
+ .remove(&response.id)
+ .expect("response with known request id received");
+
+ tx.send(response)?;
+ Ok(())
+ }
+
+ pub fn parse_options(&self, value: serde_json::Value) -> Result<Options> {
+ let options = match serde_json::from_value(value) {
+ Ok(new_options) => new_options,
+ Err(why) => {
+ let message = format!(
+ "The texlab configuration is invalid; using the default settings instead.\nDetails: {why}"
+ );
+ let typ = MessageType::WARNING;
+ self.send_notification::<ShowMessage>(ShowMessageParams { message, typ })?;
+ None
+ }
+ };
+
+ Ok(options.unwrap_or_default())
+ }
}
diff --git a/support/texlab/src/component_db.rs b/support/texlab/src/component_db.rs
deleted file mode 100644
index 8dc547369f..0000000000
--- a/support/texlab/src/component_db.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-use std::io::Read;
-
-use flate2::read::GzDecoder;
-use itertools::Itertools;
-use lsp_types::{MarkupContent, MarkupKind};
-use once_cell::sync::Lazy;
-use serde::{Deserialize, Serialize};
-use smol_str::SmolStr;
-
-use crate::{syntax::latex::ExplicitLink, Workspace};
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ComponentDatabase {
- pub components: Vec<Component>,
- pub metadata: Vec<ComponentMetadata>,
-}
-
-impl ComponentDatabase {
- #[must_use]
- pub fn find(&self, name: &str) -> Option<&Component> {
- self.components.iter().find(|component| {
- component
- .file_names
- .iter()
- .any(|file_name| file_name == name)
- })
- }
-
- #[must_use]
- pub fn find_no_ext(&self, name: &str) -> Option<&Component> {
- self.components.iter().find(|component| {
- component
- .file_names
- .iter()
- .any(|file_name| &file_name[0..file_name.len() - 4] == name)
- })
- }
-
- #[must_use]
- pub fn linked_components(&self, workspace: &Workspace) -> Vec<&Component> {
- let mut start_components = vec![self.kernel()];
- for document in workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- data.extras
- .explicit_links
- .iter()
- .filter_map(ExplicitLink::as_component_name)
- .filter_map(|name| self.find(&name))
- .for_each(|component| start_components.push(component));
- }
- }
-
- let mut all_components = Vec::new();
- for component in start_components {
- all_components.push(component);
- component
- .references
- .iter()
- .filter_map(|file| self.find(file))
- .for_each(|component| all_components.push(component));
- }
-
- all_components
- .into_iter()
- .unique_by(|component| &component.file_names)
- .collect()
- }
-
- #[must_use]
- pub fn contains(&self, short_name: &str) -> bool {
- let sty = format!("{}.sty", short_name);
- let cls = format!("{}.cls", short_name);
- self.find(&sty).is_some() || self.find(&cls).is_some()
- }
-
- #[must_use]
- pub fn kernel(&self) -> &Component {
- self.components
- .iter()
- .find(|component| component.file_names.is_empty())
- .unwrap()
- }
-
- #[must_use]
- pub fn exists(&self, file_name: &str) -> bool {
- self.components
- .iter()
- .any(|component| component.file_names.iter().any(|f| f == file_name))
- }
-
- #[must_use]
- pub fn documentation(&self, name: &str) -> Option<MarkupContent> {
- let metadata = self
- .metadata
- .iter()
- .find(|metadata| metadata.name == name)?;
-
- let desc = metadata.description.clone()?;
- Some(MarkupContent {
- kind: MarkupKind::PlainText,
- value: desc,
- })
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Component {
- pub file_names: Vec<SmolStr>,
- pub references: Vec<SmolStr>,
- pub commands: Vec<ComponentCommand>,
- pub environments: Vec<SmolStr>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ComponentCommand {
- pub name: SmolStr,
- pub image: Option<String>,
- pub glyph: Option<SmolStr>,
- pub parameters: Vec<ComponentParameter>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ComponentParameter(pub Vec<ComponentArgument>);
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ComponentArgument {
- pub name: SmolStr,
- pub image: Option<String>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ComponentMetadata {
- pub name: String,
- pub caption: Option<String>,
- pub description: Option<String>,
-}
-
-const JSON_GZ: &[u8] = include_bytes!("../data/components.json.gz");
-
-pub static COMPONENT_DATABASE: Lazy<ComponentDatabase> = Lazy::new(|| {
- let mut decoder = GzDecoder::new(JSON_GZ);
- let mut buf = String::new();
- decoder.read_to_string(&mut buf).unwrap();
- serde_json::from_str(&buf).unwrap()
-});
diff --git a/support/texlab/src/db.rs b/support/texlab/src/db.rs
new file mode 100644
index 0000000000..64165b76c2
--- /dev/null
+++ b/support/texlab/src/db.rs
@@ -0,0 +1,14 @@
+pub mod analysis;
+pub mod diagnostics;
+mod discovery;
+mod document;
+pub mod parse;
+mod workspace;
+
+pub use {discovery::*, document::*, workspace::*};
+
+#[salsa::interned]
+pub struct Word {
+ #[return_ref]
+ pub text: String,
+}
diff --git a/support/texlab/src/db/analysis.rs b/support/texlab/src/db/analysis.rs
new file mode 100644
index 0000000000..35ee4be2a1
--- /dev/null
+++ b/support/texlab/src/db/analysis.rs
@@ -0,0 +1,199 @@
+pub mod label;
+
+use rowan::{ast::AstNode, TextRange};
+
+use crate::{
+ syntax::latex::{self, HasCurly},
+ Db,
+};
+
+use super::Word;
+
+#[salsa::tracked]
+pub struct TexLink {
+ pub kind: TexLinkKind,
+ pub path: Word,
+ pub range: TextRange,
+ pub base_dir: Option<Word>,
+}
+
+impl TexLink {
+ fn of_include(db: &dyn Db, node: latex::SyntaxNode, results: &mut Vec<Self>) -> Option<()> {
+ let include = latex::Include::cast(node)?;
+ let kind = match include.syntax().kind() {
+ latex::LATEX_INCLUDE => TexLinkKind::Tex,
+ latex::BIBLATEX_INCLUDE | latex::BIBTEX_INCLUDE => TexLinkKind::Bib,
+ latex::PACKAGE_INCLUDE => TexLinkKind::Sty,
+ latex::CLASS_INCLUDE => TexLinkKind::Cls,
+ _ => return None,
+ };
+
+ for path in include.path_list()?.keys() {
+ results.push(Self::new(
+ db,
+ kind,
+ Word::new(db, path.to_string()),
+ latex::small_range(&path),
+ None,
+ ));
+ }
+
+ Some(())
+ }
+
+ fn of_import(db: &dyn Db, node: latex::SyntaxNode, results: &mut Vec<Self>) -> Option<()> {
+ let import = latex::Import::cast(node)?;
+
+ let mut base_dir = import.directory()?.key()?.to_string();
+ if !base_dir.ends_with("/") {
+ base_dir.push('/');
+ }
+
+ let path = import.file()?.key()?;
+ results.push(Self::new(
+ db,
+ TexLinkKind::Tex,
+ Word::new(db, path.to_string()),
+ latex::small_range(&path),
+ Some(Word::new(db, base_dir)),
+ ));
+
+ Some(())
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum TexLinkKind {
+ Sty,
+ Cls,
+ Tex,
+ Bib,
+}
+
+impl TexLinkKind {
+ pub fn extensions(self) -> &'static [&'static str] {
+ match self {
+ Self::Sty => &["sty"],
+ Self::Cls => &["cls"],
+ Self::Tex => &["tex"],
+ Self::Bib => &["bib"],
+ }
+ }
+}
+
+#[salsa::tracked]
+pub struct TheoremEnvironment {
+ pub name: Word,
+ pub description: Word,
+}
+
+impl TheoremEnvironment {
+ fn of_definition(db: &dyn Db, node: latex::SyntaxNode, results: &mut Vec<Self>) -> Option<()> {
+ let theorem = latex::TheoremDefinition::cast(node)?;
+ let name = theorem.name()?.key()?.to_string();
+ let description = theorem.description()?;
+ let description = description.content_text()?;
+
+ results.push(Self::new(
+ db,
+ Word::new(db, name),
+ Word::new(db, description),
+ ));
+
+ Some(())
+ }
+}
+
+#[salsa::tracked]
+pub struct GraphicsPath {
+ #[return_ref]
+ pub path: String,
+}
+
+impl GraphicsPath {
+ pub fn of_command(db: &dyn Db, node: latex::SyntaxNode, results: &mut Vec<Self>) -> Option<()> {
+ let definition = latex::GraphicsPath::cast(node)?;
+ for path in definition
+ .path_list()
+ .filter_map(|group| group.key())
+ .map(|path| path.to_string())
+ {
+ results.push(GraphicsPath::new(db, path));
+ }
+
+ Some(())
+ }
+}
+
+#[salsa::tracked]
+pub struct TexAnalysis {
+ #[return_ref]
+ pub links: Vec<TexLink>,
+
+ #[return_ref]
+ pub labels: Vec<label::Name>,
+
+ #[return_ref]
+ pub label_numbers: Vec<label::Number>,
+
+ #[return_ref]
+ pub theorem_environments: Vec<TheoremEnvironment>,
+
+ #[return_ref]
+ pub graphics_paths: Vec<GraphicsPath>,
+
+ #[return_ref]
+ pub command_name_ranges: Vec<TextRange>,
+
+ #[return_ref]
+ pub environment_names: Vec<String>,
+}
+
+impl TexAnalysis {
+ pub(super) fn analyze(db: &dyn Db, root: &latex::SyntaxNode) -> Self {
+ let mut links = Vec::new();
+ let mut labels = Vec::new();
+ let mut label_numbers = Vec::new();
+ let mut theorem_environments = Vec::new();
+ let mut graphics_paths = Vec::new();
+ let mut command_name_ranges = Vec::new();
+ let mut environment_names = Vec::new();
+
+ for node in root.descendants() {
+ TexLink::of_include(db, node.clone(), &mut links)
+ .or_else(|| TexLink::of_import(db, node.clone(), &mut links))
+ .or_else(|| label::Name::of_definition(db, node.clone(), &mut labels))
+ .or_else(|| label::Name::of_reference(db, node.clone(), &mut labels))
+ .or_else(|| label::Name::of_reference_range(db, node.clone(), &mut labels))
+ .or_else(|| label::Number::of_number(db, node.clone(), &mut label_numbers))
+ .or_else(|| {
+ TheoremEnvironment::of_definition(db, node.clone(), &mut theorem_environments)
+ })
+ .or_else(|| GraphicsPath::of_command(db, node.clone(), &mut graphics_paths))
+ .or_else(|| {
+ let range = latex::GenericCommand::cast(node.clone())?
+ .name()?
+ .text_range();
+
+ command_name_ranges.push(range);
+ Some(())
+ })
+ .or_else(|| {
+ let begin = latex::Begin::cast(node.clone())?;
+ environment_names.push(begin.name()?.key()?.to_string());
+ Some(())
+ });
+ }
+
+ Self::new(
+ db,
+ links,
+ labels,
+ label_numbers,
+ theorem_environments,
+ graphics_paths,
+ command_name_ranges,
+ environment_names,
+ )
+ }
+}
diff --git a/support/texlab/src/db/analysis/label.rs b/support/texlab/src/db/analysis/label.rs
new file mode 100644
index 0000000000..77ee5268eb
--- /dev/null
+++ b/support/texlab/src/db/analysis/label.rs
@@ -0,0 +1,122 @@
+use rowan::{
+ ast::{AstNode, AstPtr},
+ TextRange,
+};
+
+use crate::{db::Word, syntax::latex, Db};
+
+#[salsa::tracked]
+pub struct Number {
+ pub name: Word,
+ pub text: Word,
+}
+
+impl Number {
+ pub(super) fn of_number(
+ db: &dyn Db,
+ node: latex::SyntaxNode,
+ results: &mut Vec<Self>,
+ ) -> Option<()> {
+ let number = latex::LabelNumber::cast(node)?;
+ let name = number.name()?.key()?.to_string();
+ let text = number
+ .text()?
+ .syntax()
+ .descendants_with_tokens()
+ .filter_map(|element| element.into_node())
+ .find(|node| node.kind() == latex::TEXT || node.kind() == latex::MIXED_GROUP)?
+ .text()
+ .to_string();
+
+ results.push(Self::new(db, Word::new(db, name), Word::new(db, text)));
+
+ Some(())
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub enum Origin {
+ Definition(AstPtr<latex::LabelDefinition>),
+ Reference(AstPtr<latex::LabelReference>),
+ ReferenceRange(AstPtr<latex::LabelReferenceRange>),
+}
+
+impl Origin {
+ pub fn as_definition(&self) -> Option<&AstPtr<latex::LabelDefinition>> {
+ match self {
+ Self::Definition(ptr) => Some(ptr),
+ _ => None,
+ }
+ }
+}
+
+#[salsa::tracked]
+pub struct Name {
+ pub origin: Origin,
+ pub name: Word,
+ pub range: TextRange,
+}
+
+impl Name {
+ pub(super) fn of_definition(
+ db: &dyn Db,
+ node: latex::SyntaxNode,
+ results: &mut Vec<Self>,
+ ) -> Option<()> {
+ let label = latex::LabelDefinition::cast(node)?;
+ let name = label.name()?.key()?;
+ results.push(Self::new(
+ db,
+ Origin::Definition(AstPtr::new(&label)),
+ Word::new(db, name.to_string()),
+ latex::small_range(&name),
+ ));
+
+ Some(())
+ }
+
+ pub(super) fn of_reference(
+ db: &dyn Db,
+ node: latex::SyntaxNode,
+ results: &mut Vec<Self>,
+ ) -> Option<()> {
+ let label = latex::LabelReference::cast(node)?;
+ for name in label.name_list()?.keys() {
+ results.push(Self::new(
+ db,
+ Origin::Reference(AstPtr::new(&label)),
+ Word::new(db, name.to_string()),
+ latex::small_range(&name),
+ ));
+ }
+
+ Some(())
+ }
+
+ pub(super) fn of_reference_range(
+ db: &dyn Db,
+ node: latex::SyntaxNode,
+ results: &mut Vec<Self>,
+ ) -> Option<()> {
+ let label = latex::LabelReferenceRange::cast(node)?;
+ if let Some(name) = label.from().and_then(|name| name.key()) {
+ results.push(Self::new(
+ db,
+ Origin::ReferenceRange(AstPtr::new(&label)),
+ Word::new(db, name.to_string()),
+ latex::small_range(&name),
+ ));
+ }
+
+ if let Some(name) = label.to().and_then(|name| name.key()) {
+ results.push(Self::new(
+ db,
+ Origin::ReferenceRange(AstPtr::new(&label)),
+ Word::new(db, name.to_string()),
+ latex::small_range(&name),
+ ));
+ }
+
+ Some(())
+ }
+}
diff --git a/support/texlab/src/db/diagnostics.rs b/support/texlab/src/db/diagnostics.rs
new file mode 100644
index 0000000000..9b6691b873
--- /dev/null
+++ b/support/texlab/src/db/diagnostics.rs
@@ -0,0 +1,187 @@
+pub mod bib;
+pub mod log;
+pub mod tex;
+
+use lsp_types::{DiagnosticSeverity, NumberOrString, Range};
+use rustc_hash::FxHashMap;
+
+use crate::{db::workspace::Workspace, Db};
+
+use super::document::{Document, Language};
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Diagnostic {
+ pub severity: DiagnosticSeverity,
+ pub range: Range,
+ pub code: DiagnosticCode,
+ pub message: String,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
+pub enum DiagnosticCode {
+ Tex(TexCode),
+ Bib(BibCode),
+ Log(Document),
+ Chktex(String),
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum TexCode {
+ UnexpectedRCurly,
+ RCurlyInserted,
+ MismatchedEnvironment,
+}
+
+impl From<TexCode> for String {
+ fn from(code: TexCode) -> Self {
+ match code {
+ TexCode::UnexpectedRCurly => "Unexpected \"}\"".to_string(),
+ TexCode::RCurlyInserted => "Missing \"}\" inserted".to_string(),
+ TexCode::MismatchedEnvironment => "Mismatched environment".to_string(),
+ }
+ }
+}
+
+impl From<TexCode> for NumberOrString {
+ fn from(code: TexCode) -> Self {
+ match code {
+ TexCode::UnexpectedRCurly => NumberOrString::Number(1),
+ TexCode::RCurlyInserted => NumberOrString::Number(2),
+ TexCode::MismatchedEnvironment => NumberOrString::Number(3),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+#[allow(clippy::enum_variant_names)]
+pub enum BibCode {
+ ExpectingLCurly,
+ ExpectingKey,
+ ExpectingRCurly,
+ ExpectingEq,
+ ExpectingFieldValue,
+}
+
+impl From<BibCode> for String {
+ fn from(code: BibCode) -> Self {
+ match code {
+ BibCode::ExpectingLCurly => "Expecting a curly bracket: \"{\"".to_string(),
+ BibCode::ExpectingKey => "Expecting a key".to_string(),
+ BibCode::ExpectingRCurly => "Expecting a curly bracket: \"}\"".to_string(),
+ BibCode::ExpectingEq => "Expecting an equality sign: \"=\"".to_string(),
+ BibCode::ExpectingFieldValue => "Expecting a field value".to_string(),
+ }
+ }
+}
+
+impl From<BibCode> for NumberOrString {
+ fn from(code: BibCode) -> Self {
+ match code {
+ BibCode::ExpectingLCurly => NumberOrString::Number(4),
+ BibCode::ExpectingKey => NumberOrString::Number(5),
+ BibCode::ExpectingRCurly => NumberOrString::Number(6),
+ BibCode::ExpectingEq => NumberOrString::Number(7),
+ BibCode::ExpectingFieldValue => NumberOrString::Number(8),
+ }
+ }
+}
+
+#[salsa::tracked(return_ref)]
+pub fn collect(db: &dyn Db, workspace: Workspace) -> FxHashMap<Document, Vec<Diagnostic>> {
+ let mut results: FxHashMap<Document, Vec<Diagnostic>> = FxHashMap::default();
+
+ for document in workspace.documents(db).iter().copied() {
+ match document.language(db) {
+ Language::Tex => {
+ results.entry(document).or_default().extend(
+ tex::collect(db, document)
+ .iter()
+ .chain(document.linter(db).chktex(db))
+ .cloned(),
+ );
+ }
+ Language::Bib => {
+ results
+ .entry(document)
+ .or_default()
+ .extend(bib::collect(db, document).iter().cloned());
+ }
+ Language::Log => {
+ log::collect(db, workspace, document)
+ .iter()
+ .for_each(|(document, diagnostics)| {
+ results
+ .entry(*document)
+ .or_default()
+ .extend(diagnostics.clone());
+ });
+ }
+ }
+ }
+
+ results
+}
+
+#[salsa::tracked]
+pub fn collect_filtered(
+ db: &dyn Db,
+ workspace: Workspace,
+) -> FxHashMap<Document, Vec<lsp_types::Diagnostic>> {
+ let all_diagnostics = collect(db, workspace);
+ let mut all_filtered: FxHashMap<Document, Vec<lsp_types::Diagnostic>> = FxHashMap::default();
+
+ let options = &workspace.options(db).diagnostics;
+ for document in workspace.documents(db) {
+ let mut filtered = Vec::new();
+ if !matches!(document.language(db), Language::Tex | Language::Bib) {
+ continue;
+ }
+
+ if let Some(diagnostics) = all_diagnostics.get(document) {
+ for diagnostic in diagnostics.iter() {
+ if !options.allowed_patterns.is_empty()
+ && !options
+ .allowed_patterns
+ .iter()
+ .any(|pattern| pattern.0.is_match(&diagnostic.message))
+ {
+ continue;
+ }
+
+ if options
+ .ignored_patterns
+ .iter()
+ .any(|pattern| pattern.0.is_match(&diagnostic.message))
+ {
+ continue;
+ }
+
+ let source = match diagnostic.code {
+ DiagnosticCode::Tex(_) | DiagnosticCode::Bib(_) => "texlab",
+ DiagnosticCode::Log(_) => "latex-build",
+ DiagnosticCode::Chktex(_) => "chktex",
+ };
+
+ let code = match diagnostic.code.clone() {
+ DiagnosticCode::Tex(code) => Some(code.into()),
+ DiagnosticCode::Bib(code) => Some(code.into()),
+ DiagnosticCode::Chktex(code) => Some(NumberOrString::String(code)),
+ DiagnosticCode::Log(_) => None,
+ };
+
+ filtered.push(lsp_types::Diagnostic {
+ range: diagnostic.range,
+ code,
+ severity: Some(diagnostic.severity),
+ message: diagnostic.message.clone(),
+ source: Some(source.to_string()),
+ ..Default::default()
+ });
+ }
+ }
+
+ all_filtered.insert(*document, filtered);
+ }
+
+ all_filtered
+}
diff --git a/support/texlab/src/db/diagnostics/bib.rs b/support/texlab/src/db/diagnostics/bib.rs
new file mode 100644
index 0000000000..6d0f66e960
--- /dev/null
+++ b/support/texlab/src/db/diagnostics/bib.rs
@@ -0,0 +1,112 @@
+use lsp_types::DiagnosticSeverity;
+use rowan::{ast::AstNode, TextRange};
+
+use crate::{
+ db::document::Document,
+ syntax::bibtex::{self, HasDelims, HasEq, HasName, HasType, HasValue},
+ util::line_index_ext::LineIndexExt,
+ Db,
+};
+
+use super::{BibCode, Diagnostic, DiagnosticCode};
+
+#[salsa::tracked(return_ref)]
+pub fn collect(db: &dyn Db, document: Document) -> Vec<Diagnostic> {
+ let mut results = Vec::new();
+ let data = match document.parse(db).as_bib() {
+ Some(data) => data,
+ None => return results,
+ };
+
+ for node in data.root(db).descendants() {
+ analyze_entry(db, document, node.clone(), &mut results)
+ .or_else(|| analyze_field(db, document, node, &mut results));
+ }
+
+ results
+}
+
+fn analyze_entry(
+ db: &dyn Db,
+ document: Document,
+ node: bibtex::SyntaxNode,
+ results: &mut Vec<Diagnostic>,
+) -> Option<()> {
+ let line_index = document.contents(db).line_index(db);
+
+ let entry = bibtex::Entry::cast(node)?;
+ if entry.left_delim_token().is_none() {
+ let code = BibCode::ExpectingLCurly;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: line_index.line_col_lsp_range(entry.type_token()?.text_range()),
+ code: DiagnosticCode::Bib(code),
+ message: String::from(code),
+ });
+
+ return Some(());
+ }
+
+ if entry.name_token().is_none() {
+ let code = BibCode::ExpectingKey;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: line_index.line_col_lsp_range(entry.left_delim_token()?.text_range()),
+ code: DiagnosticCode::Bib(code),
+ message: String::from(code),
+ });
+
+ return Some(());
+ }
+
+ if entry.right_delim_token().is_none() {
+ let code = BibCode::ExpectingRCurly;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: line_index
+ .line_col_lsp_range(TextRange::empty(entry.syntax().text_range().end())),
+ code: DiagnosticCode::Bib(code),
+ message: String::from(code),
+ });
+
+ return Some(());
+ }
+
+ Some(())
+}
+
+fn analyze_field(
+ db: &dyn Db,
+ document: Document,
+ node: bibtex::SyntaxNode,
+ results: &mut Vec<Diagnostic>,
+) -> Option<()> {
+ let line_index = document.contents(db).line_index(db);
+
+ let field = bibtex::Field::cast(node)?;
+ if field.eq_token().is_none() {
+ let code = BibCode::ExpectingEq;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: line_index.line_col_lsp_range(field.name_token()?.text_range()),
+ code: DiagnosticCode::Bib(code),
+ message: String::from(code),
+ });
+
+ return Some(());
+ }
+
+ if field.value().is_none() {
+ let code = BibCode::ExpectingFieldValue;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: line_index.line_col_lsp_range(field.name_token()?.text_range()),
+ code: DiagnosticCode::Bib(code),
+ message: String::from(code),
+ });
+
+ return Some(());
+ }
+
+ Some(())
+}
diff --git a/support/texlab/src/db/diagnostics/log.rs b/support/texlab/src/db/diagnostics/log.rs
new file mode 100644
index 0000000000..436999259b
--- /dev/null
+++ b/support/texlab/src/db/diagnostics/log.rs
@@ -0,0 +1,100 @@
+use lsp_types::{DiagnosticSeverity, Position, Range, Url};
+use rowan::{TextLen, TextRange, TextSize};
+use rustc_hash::FxHashMap;
+
+use crate::{
+ db::{document::Document, workspace::Workspace},
+ syntax::{BuildError, BuildErrorLevel},
+ util::line_index_ext::LineIndexExt,
+ Db,
+};
+
+use super::{Diagnostic, DiagnosticCode};
+
+#[salsa::tracked(return_ref)]
+pub fn collect(
+ db: &dyn Db,
+ workspace: Workspace,
+ log_document: Document,
+) -> FxHashMap<Document, Vec<Diagnostic>> {
+ let mut results = FxHashMap::default();
+
+ let log = match log_document.parse(db).as_log() {
+ Some(data) => data.log(db),
+ None => return results,
+ };
+
+ let root_document = match workspace.parents(db, log_document).iter().next().copied() {
+ Some(document) => document,
+ None => return results,
+ };
+
+ let base_path = match root_document
+ .location(db)
+ .path(db)
+ .as_deref()
+ .and_then(|path| path.parent())
+ {
+ Some(path) => path,
+ None => return results,
+ };
+
+ for error in &log.errors {
+ let full_path = base_path.join(&error.relative_path);
+ let full_path_uri = if let Ok(uri) = Url::from_file_path(&full_path) {
+ uri
+ } else {
+ continue;
+ };
+
+ let severity = match error.level {
+ BuildErrorLevel::Error => DiagnosticSeverity::ERROR,
+ BuildErrorLevel::Warning => DiagnosticSeverity::WARNING,
+ };
+
+ let range = find_range_of_hint(db, workspace, &full_path_uri, error).unwrap_or_else(|| {
+ let line = error.line.unwrap_or(0);
+ Range::new(Position::new(line, 0), Position::new(line, 0))
+ });
+
+ let diagnostic = Diagnostic {
+ severity,
+ range,
+ code: DiagnosticCode::Log(log_document),
+ message: error.message.clone(),
+ };
+
+ let tex_document = workspace
+ .lookup_uri(db, &full_path_uri)
+ .unwrap_or(root_document);
+
+ results.entry(tex_document).or_default().push(diagnostic);
+ }
+
+ results
+}
+
+fn find_range_of_hint(
+ db: &dyn Db,
+ workspace: Workspace,
+ uri: &Url,
+ error: &BuildError,
+) -> Option<Range> {
+ let document = workspace.lookup_uri(db, uri)?;
+ let text = document.contents(db).text(db);
+ let line = error.line? as usize;
+ let hint = error.hint.as_deref()?;
+ let line_index = document.contents(db).line_index(db);
+
+ let line_start = line_index.newlines.get(line).copied()?;
+ let line_end = line_index
+ .newlines
+ .get(line + 1)
+ .copied()
+ .unwrap_or(text.text_len());
+
+ let line_text = &text[line_start.into()..line_end.into()];
+ let hint_start = line_start + TextSize::try_from(line_text.find(hint)?).unwrap();
+ let hint_end = hint_start + hint.text_len();
+ Some(line_index.line_col_lsp_range(TextRange::new(hint_start, hint_end)))
+}
diff --git a/support/texlab/src/db/diagnostics/tex.rs b/support/texlab/src/db/diagnostics/tex.rs
new file mode 100644
index 0000000000..50233f8bd6
--- /dev/null
+++ b/support/texlab/src/db/diagnostics/tex.rs
@@ -0,0 +1,125 @@
+use lsp_types::DiagnosticSeverity;
+use rowan::{ast::AstNode, NodeOrToken, TextRange};
+
+use crate::{db::document::Document, syntax::latex, util::line_index_ext::LineIndexExt, Db};
+
+use super::{Diagnostic, DiagnosticCode, TexCode};
+
+#[salsa::tracked(return_ref)]
+pub fn collect(db: &dyn Db, document: Document) -> Vec<Diagnostic> {
+ let mut results = Vec::new();
+
+ if !document.location(db).uri(db).as_str().ends_with(".tex") {
+ return results;
+ }
+
+ let data = match document.parse(db).as_tex() {
+ Some(data) => data,
+ None => return results,
+ };
+
+ for node in data.root(db).descendants() {
+ analyze_environment(db, document, node.clone(), &mut results)
+ .or_else(|| analyze_curly_group(db, document, node.clone(), &mut results))
+ .or_else(|| analyze_curly_braces(document, db, node, &mut results));
+ }
+
+ results
+}
+
+fn analyze_environment(
+ db: &dyn Db,
+ document: Document,
+ node: latex::SyntaxNode,
+ results: &mut Vec<Diagnostic>,
+) -> Option<()> {
+ let environment = latex::Environment::cast(node)?;
+ let name1 = environment.begin()?.name()?.key()?;
+ let name2 = environment.end()?.name()?.key()?;
+ if name1 != name2 {
+ let code = TexCode::MismatchedEnvironment;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: document
+ .contents(db)
+ .line_index(db)
+ .line_col_lsp_range(latex::small_range(&name1)),
+ code: DiagnosticCode::Tex(code),
+ message: String::from(code),
+ });
+ }
+
+ Some(())
+}
+
+fn analyze_curly_group(
+ db: &dyn Db,
+ document: Document,
+ node: latex::SyntaxNode,
+ results: &mut Vec<Diagnostic>,
+) -> Option<()> {
+ if !matches!(
+ node.kind(),
+ latex::CURLY_GROUP
+ | latex::CURLY_GROUP_COMMAND
+ | latex::CURLY_GROUP_KEY_VALUE
+ | latex::CURLY_GROUP_WORD
+ | latex::CURLY_GROUP_WORD_LIST
+ ) {
+ return None;
+ }
+
+ let is_inside_verbatim_environment = node
+ .ancestors()
+ .filter_map(latex::Environment::cast)
+ .filter_map(|env| env.begin())
+ .filter_map(|begin| begin.name())
+ .filter_map(|name| name.key())
+ .any(|name| {
+ ["asy", "lstlisting", "minted", "verbatim"].contains(&name.to_string().as_str())
+ });
+
+ if !is_inside_verbatim_environment
+ && !node
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .any(|token| token.kind() == latex::R_CURLY)
+ {
+ let code = TexCode::RCurlyInserted;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: document
+ .contents(db)
+ .line_index(db)
+ .line_col_lsp_range(TextRange::empty(node.text_range().end())),
+ code: DiagnosticCode::Tex(code),
+ message: String::from(code),
+ });
+ }
+
+ Some(())
+}
+
+fn analyze_curly_braces(
+ document: Document,
+ db: &dyn Db,
+ node: rowan::SyntaxNode<latex::LatexLanguage>,
+ results: &mut Vec<Diagnostic>,
+) -> Option<()> {
+ if node.kind() == latex::ERROR && node.first_token()?.text() == "}" {
+ let code = TexCode::UnexpectedRCurly;
+ results.push(Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ range: document
+ .contents(db)
+ .line_index(db)
+ .line_col_lsp_range(node.text_range()),
+ code: DiagnosticCode::Tex(code),
+ message: String::from(code),
+ });
+
+ Some(())
+ } else {
+ None
+ }
+}
diff --git a/support/texlab/src/db/discovery.rs b/support/texlab/src/db/discovery.rs
new file mode 100644
index 0000000000..0e1ec314f7
--- /dev/null
+++ b/support/texlab/src/db/discovery.rs
@@ -0,0 +1,251 @@
+use std::path::Path;
+
+use itertools::Itertools;
+use lsp_types::Url;
+use rustc_hash::FxHashSet;
+
+use crate::{util::HOME_DIR, Db};
+
+use super::{
+ analysis::TexLink,
+ document::{Document, Language, Location, Owner},
+ workspace::Workspace,
+};
+
+#[salsa::accumulator]
+pub struct MissingDependencies(MissingDependency);
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub struct MissingDependency {
+ pub location: Location,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub struct Dependency {
+ pub source: Document,
+ pub target: Document,
+ pub origin: Option<DependencyOrigin>,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub struct DependencyOrigin {
+ pub link: TexLink,
+ pub old_base_dir: Location,
+ pub new_base_dir: Location,
+}
+
+pub fn hidden_dependencies(
+ db: &dyn Db,
+ document: Document,
+ base_dir: Location,
+ dependencies: &mut Vec<Dependency>,
+) {
+ dependencies.extend(hidden_dependency(db, document, base_dir, "log"));
+ dependencies.extend(hidden_dependency(db, document, base_dir, "aux"));
+}
+
+#[salsa::tracked]
+pub fn hidden_dependency(
+ db: &dyn Db,
+ source: Document,
+ base_dir: Location,
+ extension: &'static str,
+) -> Option<Dependency> {
+ let workspace = Workspace::get(db);
+
+ let stem = source.location(db).stem(db)?;
+ let name = format!("{stem}.{extension}");
+
+ let location = workspace.output_dir(db, base_dir).join(db, &name)?;
+ match workspace.lookup(db, location) {
+ Some(target) => Some(Dependency {
+ source,
+ target,
+ origin: None,
+ }),
+ None => {
+ MissingDependencies::push(db, MissingDependency { location });
+ None
+ }
+ }
+}
+
+pub fn source_dependencies<'db>(
+ db: &'db dyn Db,
+ source: Document,
+ base_dir: Location,
+) -> impl Iterator<Item = Dependency> + 'db {
+ source
+ .parse(db)
+ .as_tex()
+ .into_iter()
+ .flat_map(|data| data.analyze(db).links(db))
+ .filter_map(move |link| source_dependency(db, source, base_dir, *link))
+}
+
+#[salsa::tracked]
+pub fn source_dependency(
+ db: &dyn Db,
+ source: Document,
+ base_dir: Location,
+ link: TexLink,
+) -> Option<Dependency> {
+ let workspace = Workspace::get(db);
+
+ let stem = link.path(db).text(db);
+
+ let mut file_names = vec![stem.clone()];
+ link.kind(db)
+ .extensions()
+ .iter()
+ .map(|ext| format!("{stem}.{ext}"))
+ .for_each(|name| file_names.push(name));
+
+ let file_name_db = workspace.file_name_db(db);
+ let distro_files = file_names
+ .iter()
+ .filter_map(|name| file_name_db.get(name))
+ .filter(|path| {
+ HOME_DIR
+ .as_deref()
+ .map_or(false, |dir| path.starts_with(dir))
+ })
+ .flat_map(|path| Url::from_file_path(path))
+ .map(|uri| Location::new(db, uri));
+
+ for location in file_names
+ .iter()
+ .filter_map(|file_name| base_dir.join(db, &file_name))
+ .chain(distro_files)
+ {
+ match workspace.lookup(db, location) {
+ Some(target) => {
+ let origin = Some(DependencyOrigin {
+ link,
+ old_base_dir: base_dir,
+ new_base_dir: link
+ .base_dir(db)
+ .and_then(|path| base_dir.join(db, path.text(db)))
+ .unwrap_or(base_dir),
+ });
+
+ return Some(Dependency {
+ source,
+ target,
+ origin,
+ });
+ }
+ None => {
+ MissingDependencies::push(db, MissingDependency { location });
+ }
+ };
+ }
+
+ None
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub struct DependencyGraph {
+ pub start: Document,
+ pub edges: Vec<Dependency>,
+}
+
+impl DependencyGraph {
+ pub fn preorder(&self) -> impl DoubleEndedIterator<Item = Document> + '_ {
+ std::iter::once(self.start)
+ .chain(self.edges.iter().map(|group| group.target))
+ .unique()
+ }
+}
+
+#[salsa::tracked]
+pub fn dependency_graph(db: &dyn Db, start: Document) -> DependencyGraph {
+ let workspace = Workspace::get(db);
+
+ let base_dir = workspace.working_dir(db, start.directory(db));
+ let mut edges = Vec::new();
+ let mut stack = vec![(start, base_dir)];
+ let mut visited = FxHashSet::default();
+
+ while let Some((source, base_dir)) = stack.pop() {
+ for edge in source_dependencies(db, source, base_dir) {
+ edges.push(edge);
+ if visited.insert(edge.target) {
+ stack.push((edge.target, edge.origin.unwrap().new_base_dir));
+ }
+ }
+
+ hidden_dependencies(db, source, base_dir, &mut edges);
+ }
+
+ DependencyGraph { start, edges }
+}
+
+pub fn discover_dependencies(db: &mut dyn Db) {
+ let workspace = Workspace::get(db);
+ loop {
+ let mut changed = discover_parents(db, workspace);
+
+ let paths: FxHashSet<_> = workspace
+ .documents(db)
+ .iter()
+ .flat_map(|&start| dependency_graph::accumulated::<MissingDependencies>(db, start))
+ .filter_map(|link| link.location.path(db).as_deref())
+ .filter(|path| path.is_file())
+ .map(Path::to_path_buf)
+ .collect();
+
+ for path in paths {
+ if workspace.lookup_path(db, &path).is_none() {
+ let language = Language::from_path(&path).unwrap_or(Language::Tex);
+ changed |= workspace.load(db, &path, language, Owner::Server).is_some();
+ }
+ }
+
+ if !changed {
+ break;
+ }
+ }
+}
+
+fn discover_parents(db: &mut dyn Db, workspace: Workspace) -> bool {
+ let mut changed = false;
+
+ let dirs: FxHashSet<_> = workspace
+ .documents(db)
+ .iter()
+ .flat_map(|document| document.ancestor_dirs(db))
+ .filter(|path| is_part_of_workspace(db, workspace, path))
+ .map(Path::to_path_buf)
+ .collect();
+
+ for path in dirs
+ .iter()
+ .flat_map(std::fs::read_dir)
+ .flatten()
+ .flatten()
+ .filter(|entry| entry.file_type().map_or(false, |ty| ty.is_file()))
+ .map(|entry| entry.path())
+ .filter(|path| Language::from_path(&path) == Some(Language::Tex))
+ {
+ if workspace.lookup_path(db, &path).is_none() {
+ changed |= workspace
+ .load(db, &path, Language::Tex, Owner::Server)
+ .is_some();
+ }
+ }
+
+ changed
+}
+
+fn is_part_of_workspace(db: &dyn Db, workspace: Workspace, path: &Path) -> bool {
+ let root_dirs = workspace.root_dirs(db);
+ if root_dirs.is_empty() {
+ return true;
+ }
+
+ root_dirs
+ .iter()
+ .filter_map(|dir| dir.path(db).as_deref())
+ .any(|dir| path.starts_with(dir))
+}
diff --git a/support/texlab/src/db/document.rs b/support/texlab/src/db/document.rs
new file mode 100644
index 0000000000..186bd06ab3
--- /dev/null
+++ b/support/texlab/src/db/document.rs
@@ -0,0 +1,193 @@
+use std::{
+ ffi::OsStr,
+ path::{Path, PathBuf},
+};
+
+use lsp_types::Url;
+use rowan::{TextRange, TextSize};
+
+use crate::{
+ db::{
+ diagnostics::Diagnostic,
+ parse::{BibDocumentData, LogDocumentData, TexDocumentData},
+ },
+ parser::{parse_bibtex, parse_build_log, parse_latex},
+ util::line_index::LineIndex,
+ Db,
+};
+
+use super::{
+ analysis::TexLinkKind,
+ parse::{self, DocumentData},
+};
+
+#[salsa::interned]
+pub struct Location {
+ #[return_ref]
+ pub uri: Url,
+}
+
+#[salsa::tracked]
+impl Location {
+ #[salsa::tracked(return_ref)]
+ pub fn path(self, db: &dyn Db) -> Option<PathBuf> {
+ let uri = self.uri(db);
+ if uri.scheme() == "file" {
+ uri.to_file_path().ok()
+ } else {
+ None
+ }
+ }
+
+ pub fn stem<'db>(self, db: &'db dyn Db) -> Option<&'db str> {
+ let name = self.uri(db).path_segments()?.last()?;
+ let stem = name.rsplit_once('.').map_or(name, |(stem, _)| stem);
+ Some(stem)
+ }
+
+ pub fn join(self, db: &dyn Db, path: &str) -> Option<Location> {
+ let uri = self.uri(db).join(path).ok()?;
+ Some(Location::new(db, uri))
+ }
+
+ pub fn into_dir(self, db: &dyn Db) -> Location {
+ if self.uri(db).path().ends_with("/") {
+ self.join(db, ".").unwrap()
+ } else {
+ self
+ }
+ }
+}
+
+#[salsa::input]
+pub struct Contents {
+ #[return_ref]
+ pub text: String,
+}
+
+#[salsa::tracked]
+impl Contents {
+ #[salsa::tracked(return_ref)]
+ pub fn line_index(self, db: &dyn Db) -> LineIndex {
+ LineIndex::new(self.text(db))
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum Owner {
+ Client,
+ Server,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum Language {
+ Tex,
+ Bib,
+ Log,
+}
+
+impl Language {
+ pub fn from_path(path: &Path) -> Option<Self> {
+ path.extension()
+ .and_then(OsStr::to_str)
+ .and_then(Self::from_extension)
+ }
+
+ pub fn from_extension(extension: &str) -> Option<Self> {
+ match extension.to_lowercase().as_str() {
+ "tex" | "sty" | "cls" | "def" | "lco" | "aux" | "rnw" => Some(Self::Tex),
+ "bib" | "bibtex" => Some(Self::Bib),
+ "log" => Some(Self::Log),
+ _ => None,
+ }
+ }
+
+ pub fn from_id(id: &str) -> Option<Self> {
+ match id {
+ "tex" | "latex" => Some(Self::Tex),
+ "bib" | "bibtex" => Some(Self::Bib),
+ _ => None,
+ }
+ }
+}
+
+#[salsa::input]
+pub struct LinterData {
+ #[return_ref]
+ pub chktex: Vec<Diagnostic>,
+}
+
+#[salsa::input]
+pub struct Document {
+ pub location: Location,
+ pub contents: Contents,
+ pub language: Language,
+ pub owner: Owner,
+ pub cursor: TextSize,
+ pub linter: LinterData,
+}
+
+impl Document {
+ pub fn edit(self, db: &mut dyn Db, range: TextRange, replace_with: &str) {
+ let mut text = self.contents(db).set_text(db).to(String::new());
+ text.replace_range(std::ops::Range::<usize>::from(range), &replace_with);
+ self.contents(db).set_text(db).to(text);
+ self.set_cursor(db).to(range.start());
+ }
+
+ pub fn directory(self, db: &dyn Db) -> Location {
+ self.location(db).into_dir(db)
+ }
+
+ pub fn ancestor_dirs<'db>(self, db: &'db dyn Db) -> impl Iterator<Item = &'db Path> + 'db {
+ self.location(db)
+ .path(db)
+ .as_deref()
+ .into_iter()
+ .flat_map(|path| path.ancestors())
+ .skip(1)
+ }
+}
+
+#[salsa::tracked]
+impl Document {
+ #[salsa::tracked]
+ pub fn parse(self, db: &dyn Db) -> DocumentData {
+ let text = self.contents(db).text(db);
+ match self.language(db) {
+ Language::Tex => {
+ let data = TexDocumentData::new(db, parse_latex(text));
+ parse::DocumentData::Tex(data)
+ }
+ Language::Bib => {
+ let data = BibDocumentData::new(db, parse_bibtex(text));
+ DocumentData::Bib(data)
+ }
+ Language::Log => {
+ let data = LogDocumentData::new(db, parse_build_log(text));
+ DocumentData::Log(data)
+ }
+ }
+ }
+
+ #[salsa::tracked]
+ pub fn can_be_index(self, db: &dyn Db) -> bool {
+ self.parse(db).as_tex().map_or(false, |data| {
+ data.analyze(db)
+ .links(db)
+ .iter()
+ .filter(|link| link.kind(db) == TexLinkKind::Cls)
+ .any(|link| link.path(db).text(db) != "subfiles")
+ })
+ }
+
+ #[salsa::tracked]
+ pub fn can_be_built(self, db: &dyn Db) -> bool {
+ self.parse(db).as_tex().map_or(false, |data| {
+ data.analyze(db)
+ .links(db)
+ .iter()
+ .any(|link| link.kind(db) == TexLinkKind::Cls)
+ })
+ }
+}
diff --git a/support/texlab/src/db/parse.rs b/support/texlab/src/db/parse.rs
new file mode 100644
index 0000000000..d532c536a3
--- /dev/null
+++ b/support/texlab/src/db/parse.rs
@@ -0,0 +1,71 @@
+use crate::{
+ db::analysis::TexAnalysis,
+ syntax::{bibtex, latex, BuildLog},
+ Db,
+};
+
+#[salsa::interned]
+pub struct TexDocumentData {
+ pub green: rowan::GreenNode,
+}
+
+impl TexDocumentData {
+ pub fn root(self, db: &dyn Db) -> latex::SyntaxNode {
+ latex::SyntaxNode::new_root(self.green(db))
+ }
+}
+
+#[salsa::tracked]
+impl TexDocumentData {
+ #[salsa::tracked]
+ pub fn analyze(self, db: &dyn Db) -> TexAnalysis {
+ let root = latex::SyntaxNode::new_root(self.green(db));
+ TexAnalysis::analyze(db, &root)
+ }
+}
+
+#[salsa::interned]
+pub struct BibDocumentData {
+ pub green: rowan::GreenNode,
+}
+
+impl BibDocumentData {
+ pub fn root(self, db: &dyn Db) -> bibtex::SyntaxNode {
+ bibtex::SyntaxNode::new_root(self.green(db))
+ }
+}
+
+#[salsa::interned]
+pub struct LogDocumentData {
+ pub log: BuildLog,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum DocumentData {
+ Tex(TexDocumentData),
+ Bib(BibDocumentData),
+ Log(LogDocumentData),
+}
+
+impl DocumentData {
+ pub fn as_tex(self) -> Option<TexDocumentData> {
+ match self {
+ Self::Tex(data) => Some(data),
+ _ => None,
+ }
+ }
+
+ pub fn as_bib(self) -> Option<BibDocumentData> {
+ match self {
+ Self::Bib(data) => Some(data),
+ _ => None,
+ }
+ }
+
+ pub fn as_log(self) -> Option<LogDocumentData> {
+ match self {
+ Self::Log(data) => Some(data),
+ _ => None,
+ }
+ }
+}
diff --git a/support/texlab/src/db/workspace.rs b/support/texlab/src/db/workspace.rs
new file mode 100644
index 0000000000..b8bdb27f1f
--- /dev/null
+++ b/support/texlab/src/db/workspace.rs
@@ -0,0 +1,245 @@
+use std::{
+ borrow::Cow,
+ path::{Path, PathBuf},
+};
+
+use itertools::Itertools;
+use lsp_types::{ClientCapabilities, ClientInfo, Url};
+use rowan::TextSize;
+use rustc_hash::FxHashSet;
+
+use crate::{
+ db::document::{Document, Location},
+ distro::FileNameDB,
+ util::HOME_DIR,
+ Db, Options,
+};
+
+use super::{
+ dependency_graph,
+ document::{Contents, Language, LinterData, Owner},
+ Word,
+};
+
+#[salsa::input(singleton)]
+pub struct Workspace {
+ #[return_ref]
+ pub documents: FxHashSet<Document>,
+
+ #[return_ref]
+ pub options: Options,
+
+ #[return_ref]
+ pub client_capabilities: ClientCapabilities,
+
+ #[return_ref]
+ pub client_info: Option<ClientInfo>,
+
+ #[return_ref]
+ pub root_dirs: Vec<Location>,
+
+ #[return_ref]
+ pub file_name_db: FileNameDB,
+}
+
+impl Workspace {
+ pub fn lookup(self, db: &dyn Db, location: Location) -> Option<Document> {
+ self.documents(db)
+ .iter()
+ .find(|document| document.location(db) == location)
+ .copied()
+ }
+
+ pub fn lookup_uri(self, db: &dyn Db, uri: &Url) -> Option<Document> {
+ self.documents(db)
+ .iter()
+ .find(|document| document.location(db).uri(db) == uri)
+ .copied()
+ }
+
+ pub fn lookup_path(self, db: &dyn Db, path: &Path) -> Option<Document> {
+ self.documents(db)
+ .iter()
+ .find(|document| document.location(db).path(db).as_deref() == Some(path))
+ .copied()
+ }
+
+ pub fn index_files<'db>(self, db: &'db dyn Db) -> impl Iterator<Item = Document> + 'db {
+ self.documents(db)
+ .iter()
+ .copied()
+ .filter(|&document| document.can_be_index(db))
+ }
+
+ pub fn open(
+ self,
+ db: &mut dyn Db,
+ uri: Url,
+ text: String,
+ language: Language,
+ owner: Owner,
+ ) -> Document {
+ let location = Location::new(db, uri);
+ let contents = Contents::new(db, text);
+ let cursor = TextSize::from(0);
+ match self.lookup(db, location) {
+ Some(document) => {
+ document.set_contents(db).to(contents);
+ document.set_language(db).to(language);
+ document.set_owner(db).to(owner);
+ document.set_cursor(db).to(cursor);
+ document
+ }
+ None => {
+ let document = Document::new(
+ db,
+ location,
+ contents,
+ language,
+ owner,
+ cursor,
+ LinterData::new(db, Vec::new()),
+ );
+
+ let mut documents = self.set_documents(db).to(FxHashSet::default());
+ documents.insert(document);
+ self.set_documents(db).to(documents);
+ document
+ }
+ }
+ }
+
+ pub fn load(
+ self,
+ db: &mut dyn Db,
+ path: &Path,
+ language: Language,
+ owner: Owner,
+ ) -> Option<Document> {
+ log::debug!("Loading document {} from disk...", path.display());
+
+ let uri = Url::from_file_path(path).ok()?;
+ let data = std::fs::read(path).ok()?;
+ let text = match String::from_utf8_lossy(&data) {
+ Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(data) },
+ Cow::Owned(text) => text,
+ };
+
+ Some(self.open(db, uri, text, language, owner))
+ }
+
+ pub fn watch(
+ self,
+ db: &dyn Db,
+ watcher: &mut dyn notify::Watcher,
+ watched_dirs: &mut FxHashSet<PathBuf>,
+ ) {
+ let output_dirs = self
+ .documents(db)
+ .iter()
+ .map(|document| self.working_dir(db, document.directory(db)))
+ .map(|base_dir| self.output_dir(db, base_dir))
+ .filter_map(|location| location.path(db).as_deref());
+
+ self.documents(db)
+ .iter()
+ .map(|document| document.location(db))
+ .filter_map(|location| location.path(db).as_deref())
+ .filter_map(|path| path.parent())
+ .chain(output_dirs)
+ .filter(|path| watched_dirs.insert(path.to_path_buf()))
+ .for_each(|path| {
+ let _ = watcher.watch(path, notify::RecursiveMode::NonRecursive);
+ });
+ }
+}
+
+const SPECIAL_ENTRIES: &[&str] = &[
+ ".git",
+ "Tectonic.toml",
+ ".latexmkrc",
+ "latexmkrc",
+ ".chktexrc",
+ "chktexrc",
+];
+
+#[salsa::tracked]
+impl Workspace {
+ #[salsa::tracked]
+ pub fn working_dir(self, db: &dyn Db, base_dir: Location) -> Location {
+ let mut path = self
+ .options(db)
+ .root_directory
+ .as_deref()
+ .and_then(|path| path.to_str())
+ .or_else(|| {
+ for dir in base_dir
+ .path(db)
+ .as_deref()?
+ .ancestors()
+ .skip(1)
+ .filter(|path| Some(*path) != HOME_DIR.as_deref())
+ {
+ for name in SPECIAL_ENTRIES {
+ if dir.join(name).exists() {
+ return Some(dir.to_str()?);
+ }
+ }
+ }
+
+ None
+ })
+ .unwrap_or(".")
+ .to_string();
+
+ if !path.ends_with(".") {
+ path.push('/');
+ }
+
+ base_dir.join(db, &path).unwrap_or(base_dir)
+ }
+
+ #[salsa::tracked]
+ pub fn output_dir(self, db: &dyn Db, base_dir: Location) -> Location {
+ let mut path = self
+ .options(db)
+ .aux_directory
+ .as_deref()
+ .and_then(|path| path.to_str())
+ .unwrap_or(".")
+ .to_string();
+
+ if !path.ends_with("/") {
+ path.push('/');
+ }
+
+ base_dir.join(db, &path).unwrap_or(base_dir)
+ }
+
+ #[salsa::tracked(return_ref)]
+ pub fn parents(self, db: &dyn Db, child: Document) -> Vec<Document> {
+ self.index_files(db)
+ .filter(|&parent| dependency_graph(db, parent).preorder().contains(&child))
+ .collect()
+ }
+
+ #[salsa::tracked(return_ref)]
+ pub fn related(self, db: &dyn Db, child: Document) -> FxHashSet<Document> {
+ self.index_files(db)
+ .chain(self.documents(db).iter().copied())
+ .map(|start| dependency_graph(db, start).preorder().collect_vec())
+ .filter(|project| project.contains(&child))
+ .flatten()
+ .collect()
+ }
+
+ #[salsa::tracked]
+ pub fn number_of_label(self, db: &dyn Db, child: Document, name: Word) -> Option<Word> {
+ self.related(db, child)
+ .iter()
+ .filter_map(|document| document.parse(db).as_tex())
+ .flat_map(|data| data.analyze(db).label_numbers(db))
+ .find(|number| number.name(db) == name)
+ .map(|number| number.text(db))
+ }
+}
diff --git a/support/texlab/src/debouncer.rs b/support/texlab/src/debouncer.rs
deleted file mode 100644
index b123dfc433..0000000000
--- a/support/texlab/src/debouncer.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use std::time::{Duration, Instant};
-
-use anyhow::Result;
-
-pub struct Sender<T> {
- tx: crossbeam_channel::Sender<(T, crossbeam_channel::Receiver<Instant>)>,
-}
-
-impl<T> Clone for Sender<T> {
- fn clone(&self) -> Self {
- Self {
- tx: self.tx.clone(),
- }
- }
-}
-
-impl<T> Sender<T>
-where
- T: Send + Sync + 'static,
-{
- pub fn send(&self, msg: T, delay: Duration) -> Result<()> {
- self.tx.send((msg, crossbeam_channel::after(delay)))?;
- Ok(())
- }
-}
-
-pub struct Receiver<T> {
- rx: crossbeam_channel::Receiver<(T, crossbeam_channel::Receiver<Instant>)>,
-}
-
-impl<T> Clone for Receiver<T> {
- fn clone(&self) -> Self {
- Self {
- rx: self.rx.clone(),
- }
- }
-}
-
-impl<T> Receiver<T> {
- pub fn recv(&self) -> Result<T> {
- let (mut last_msg, delay) = self.rx.recv()?;
- delay.recv()?;
- while let Ok((msg, delay)) = self.rx.try_recv() {
- delay.recv()?;
- last_msg = msg;
- }
-
- Ok(last_msg)
- }
-}
-
-pub fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
- let (tx, rx) = crossbeam_channel::unbounded();
- (Sender { tx }, Receiver { rx })
-}
diff --git a/support/texlab/src/diagnostics.rs b/support/texlab/src/diagnostics.rs
deleted file mode 100644
index bdd8faf106..0000000000
--- a/support/texlab/src/diagnostics.rs
+++ /dev/null
@@ -1,167 +0,0 @@
-mod bibtex;
-mod build;
-mod chktex;
-mod latex;
-
-use std::sync::Arc;
-
-use dashmap::DashMap;
-use lsp_types::{DiagnosticSeverity, NumberOrString, Range, Url};
-use regex::Regex;
-
-use crate::Workspace;
-
-use self::{
- bibtex::collect_bibtex_diagnostics, build::collect_build_diagnostics,
- chktex::collect_chktex_diagnostics, latex::collect_latex_diagnostics,
-};
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct Diagnostic {
- pub severity: DiagnosticSeverity,
- pub range: Range,
- pub code: DiagnosticCode,
- pub message: String,
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
-pub enum DiagnosticCode {
- Latex(LatexCode),
- Bibtex(BibtexCode),
- Chktex(String),
- Build(Arc<Url>),
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
-pub enum LatexCode {
- UnexpectedRCurly,
- RCurlyInserted,
- MismatchedEnvironment,
-}
-
-impl From<LatexCode> for String {
- fn from(code: LatexCode) -> Self {
- match code {
- LatexCode::UnexpectedRCurly => "Unexpected \"}\"".to_string(),
- LatexCode::RCurlyInserted => "Missing \"}\" inserted".to_string(),
- LatexCode::MismatchedEnvironment => "Mismatched environment".to_string(),
- }
- }
-}
-
-impl From<LatexCode> for NumberOrString {
- fn from(code: LatexCode) -> Self {
- match code {
- LatexCode::UnexpectedRCurly => NumberOrString::Number(1),
- LatexCode::RCurlyInserted => NumberOrString::Number(2),
- LatexCode::MismatchedEnvironment => NumberOrString::Number(3),
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
-#[allow(clippy::enum_variant_names)]
-pub enum BibtexCode {
- ExpectingLCurly,
- ExpectingKey,
- ExpectingRCurly,
- ExpectingEq,
- ExpectingFieldValue,
-}
-
-impl From<BibtexCode> for String {
- fn from(code: BibtexCode) -> Self {
- match code {
- BibtexCode::ExpectingLCurly => "Expecting a curly bracket: \"{\"".to_string(),
- BibtexCode::ExpectingKey => "Expecting a key".to_string(),
- BibtexCode::ExpectingRCurly => "Expecting a curly bracket: \"}\"".to_string(),
- BibtexCode::ExpectingEq => "Expecting an equality sign: \"=\"".to_string(),
- BibtexCode::ExpectingFieldValue => "Expecting a field value".to_string(),
- }
- }
-}
-
-impl From<BibtexCode> for NumberOrString {
- fn from(code: BibtexCode) -> Self {
- match code {
- BibtexCode::ExpectingLCurly => NumberOrString::Number(4),
- BibtexCode::ExpectingKey => NumberOrString::Number(5),
- BibtexCode::ExpectingRCurly => NumberOrString::Number(6),
- BibtexCode::ExpectingEq => NumberOrString::Number(7),
- BibtexCode::ExpectingFieldValue => NumberOrString::Number(8),
- }
- }
-}
-
-#[derive(Default, Clone)]
-pub struct DiagnosticManager {
- all_diagnostics: Arc<DashMap<Arc<Url>, Vec<Diagnostic>>>,
-}
-
-impl DiagnosticManager {
- pub fn push_syntax(&self, workspace: &Workspace, uri: &Url) {
- collect_bibtex_diagnostics(&self.all_diagnostics, workspace, uri)
- .or_else(|| collect_latex_diagnostics(&self.all_diagnostics, workspace, uri))
- .or_else(|| collect_build_diagnostics(&self.all_diagnostics, workspace, uri));
- }
-
- pub fn push_chktex(&self, workspace: &Workspace, uri: &Url) {
- collect_chktex_diagnostics(&self.all_diagnostics, workspace, uri);
- }
-
- pub fn publish(&self, workspace: &Workspace, uri: &Url) -> Vec<lsp_types::Diagnostic> {
- let options = &workspace.environment.options.diagnostics;
-
- let mut results = Vec::new();
- if let Some(diagnostics) = self.all_diagnostics.get(uri) {
- for diagnostic in diagnostics.iter() {
- if !options.allowed_patterns.is_empty()
- && !options
- .allowed_patterns
- .iter()
- .any(|pattern| pattern.0.is_match(&diagnostic.message))
- {
- continue;
- }
-
- if options
- .ignored_patterns
- .iter()
- .any(|pattern| pattern.0.is_match(&diagnostic.message))
- {
- continue;
- }
-
- let source = match diagnostic.code {
- DiagnosticCode::Latex(_) | DiagnosticCode::Bibtex(_) => "texlab",
- DiagnosticCode::Chktex(_) => "chktex",
- DiagnosticCode::Build(_) => "latex-build",
- };
-
- let code = match diagnostic.code.clone() {
- DiagnosticCode::Latex(code) => Some(code.into()),
- DiagnosticCode::Bibtex(code) => Some(code.into()),
- DiagnosticCode::Chktex(code) => Some(NumberOrString::String(code)),
- DiagnosticCode::Build(_) => None,
- };
-
- results.push(lsp_types::Diagnostic {
- range: diagnostic.range,
- code,
- severity: Some(diagnostic.severity),
- message: diagnostic.message.clone(),
- source: Some(source.to_string()),
- ..Default::default()
- });
- }
- }
-
- results
- }
-}
-
-#[derive(Debug, Default)]
-pub struct DiagnosticFilter {
- pub allowed_patterns: Vec<Regex>,
- pub ignored_patterns: Vec<Regex>,
-}
diff --git a/support/texlab/src/diagnostics/bibtex.rs b/support/texlab/src/diagnostics/bibtex.rs
deleted file mode 100644
index 42e215a794..0000000000
--- a/support/texlab/src/diagnostics/bibtex.rs
+++ /dev/null
@@ -1,139 +0,0 @@
-use std::sync::Arc;
-
-use dashmap::DashMap;
-use lsp_types::{DiagnosticSeverity, Url};
-use rowan::{ast::AstNode, TextRange};
-
-use crate::{
- syntax::bibtex::{self, HasDelims, HasEq, HasName, HasType, HasValue},
- Document, LineIndexExt, Workspace,
-};
-
-use super::{BibtexCode, Diagnostic, DiagnosticCode};
-
-pub fn collect_bibtex_diagnostics(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- workspace: &Workspace,
- uri: &Url,
-) -> Option<()> {
- let document = workspace.documents_by_uri.get(uri)?;
- let data = document.data.as_bibtex()?;
-
- all_diagnostics.alter(uri, |_, mut diagnostics| {
- diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Bibtex(_)));
- diagnostics
- });
-
- let root = bibtex::SyntaxNode::new_root(data.green.clone());
- for node in root.descendants() {
- analyze_entry(all_diagnostics, document, node.clone())
- .or_else(|| analyze_field(all_diagnostics, document, node));
- }
-
- Some(())
-}
-
-fn analyze_entry(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- document: &Document,
- node: bibtex::SyntaxNode,
-) -> Option<()> {
- let entry = bibtex::Entry::cast(node)?;
- if entry.left_delim_token().is_none() {
- let code = BibtexCode::ExpectingLCurly;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(entry.type_token()?.text_range()),
- code: DiagnosticCode::Bibtex(code),
- message: String::from(code),
- });
-
- return Some(());
- }
-
- if entry.name_token().is_none() {
- let code = BibtexCode::ExpectingKey;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(entry.left_delim_token()?.text_range()),
- code: DiagnosticCode::Bibtex(code),
- message: String::from(code),
- });
-
- return Some(());
- }
-
- if entry.right_delim_token().is_none() {
- let code = BibtexCode::ExpectingRCurly;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(TextRange::empty(entry.syntax().text_range().end())),
- code: DiagnosticCode::Bibtex(code),
- message: String::from(code),
- });
-
- return Some(());
- }
-
- Some(())
-}
-
-fn analyze_field(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- document: &Document,
- node: bibtex::SyntaxNode,
-) -> Option<()> {
- let field = bibtex::Field::cast(node)?;
- if field.eq_token().is_none() {
- let code = BibtexCode::ExpectingEq;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(field.name_token()?.text_range()),
-
- code: DiagnosticCode::Bibtex(code),
- message: String::from(code),
- });
-
- return Some(());
- }
-
- if field.value().is_none() {
- let code = BibtexCode::ExpectingFieldValue;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(field.name_token()?.text_range()),
-
- code: DiagnosticCode::Bibtex(code),
- message: String::from(code),
- });
-
- return Some(());
- }
-
- Some(())
-}
diff --git a/support/texlab/src/diagnostics/build.rs b/support/texlab/src/diagnostics/build.rs
deleted file mode 100644
index 4b4ffcc8b4..0000000000
--- a/support/texlab/src/diagnostics/build.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-use std::{path::PathBuf, sync::Arc};
-
-use dashmap::DashMap;
-use lsp_types::{DiagnosticSeverity, Position, Range, Url};
-
-use crate::{syntax::build_log::BuildErrorLevel, Workspace};
-
-use super::{Diagnostic, DiagnosticCode};
-
-pub fn collect_build_diagnostics(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- workspace: &Workspace,
- build_log_uri: &Url,
-) -> Option<()> {
- let build_log_document = workspace.documents_by_uri.get(build_log_uri)?;
- let build_log = build_log_document.data.as_build_log()?;
-
- all_diagnostics.alter_all(|_, mut diagnostics| {
- diagnostics.retain(
- |diag| !matches!(&diag.code, DiagnosticCode::Build(uri) if uri.as_ref() == build_log_uri),
- );
- diagnostics
- });
-
- let root_document = workspace.documents_by_uri.values().find(|document| {
- document.data.as_latex().map_or(false, |data| {
- !document.uri.as_str().ends_with(".aux")
- && data
- .extras
- .implicit_links
- .log
- .iter()
- .any(|u| u.as_ref() == build_log_uri)
- })
- })?;
-
- let base_path = PathBuf::from(root_document.uri.path());
- for error in &build_log.errors {
- let position = Position::new(error.line.unwrap_or(0), 0);
- let severity = match error.level {
- BuildErrorLevel::Error => DiagnosticSeverity::ERROR,
- BuildErrorLevel::Warning => DiagnosticSeverity::WARNING,
- };
- let range = Range::new(position, position);
- let diagnostic = Diagnostic {
- severity,
- range,
- code: DiagnosticCode::Build(Arc::clone(&build_log_document.uri)),
- message: error.message.clone(),
- };
-
- let full_path = base_path.join(&error.relative_path);
-
- let uri = if full_path.starts_with(&base_path) {
- error
- .relative_path
- .to_str()
- .and_then(|path| root_document.uri.join(path).map(Into::into).ok())
- .map_or_else(|| Arc::clone(&root_document.uri), Arc::new)
- } else {
- Arc::clone(&root_document.uri)
- };
-
- all_diagnostics.entry(uri).or_default().push(diagnostic);
- }
-
- Some(())
-}
diff --git a/support/texlab/src/diagnostics/chktex.rs b/support/texlab/src/diagnostics/chktex.rs
deleted file mode 100644
index ad676bd70d..0000000000
--- a/support/texlab/src/diagnostics/chktex.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use std::{
- fs, io,
- path::Path,
- process::{Command, Stdio},
- sync::Arc,
-};
-
-use dashmap::DashMap;
-use lsp_types::{DiagnosticSeverity, Range, Url};
-use once_cell::sync::Lazy;
-use regex::Regex;
-use tempfile::tempdir;
-
-use crate::{RangeExt, Workspace};
-
-use super::{Diagnostic, DiagnosticCode};
-
-pub fn collect_chktex_diagnostics(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- workspace: &Workspace,
- uri: &Url,
-) -> Option<()> {
- let document = workspace.documents_by_uri.get(uri)?;
- document.data.as_latex()?;
-
- all_diagnostics.alter(uri, |_, mut diagnostics| {
- diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Chktex(_)));
- diagnostics
- });
-
- let current_dir = workspace
- .environment
- .options
- .root_directory
- .as_ref()
- .cloned()
- .or_else(|| {
- if document.uri.scheme() == "file" {
- document
- .uri
- .to_file_path()
- .unwrap()
- .parent()
- .map(ToOwned::to_owned)
- } else {
- None
- }
- })
- .unwrap_or_else(|| ".".into());
-
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .extend(lint(&document.text, &current_dir).unwrap_or_default());
-
- Some(())
-}
-
-static LINE_REGEX: Lazy<Regex> =
- Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap());
-
-fn lint(text: &str, current_dir: &Path) -> io::Result<Vec<Diagnostic>> {
- let directory = tempdir()?;
- fs::write(directory.path().join("file.tex"), text)?;
- drop(fs::copy(
- current_dir.join("chktexrc"),
- directory.path().join("chktexrc"),
- ));
-
- let output = Command::new("chktex")
- .args(&["-I0", "-f%l:%c:%d:%k:%n:%m\n", "file.tex"])
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .current_dir(directory.path())
- .output()?;
-
- let mut diagnostics = Vec::new();
- for line in String::from_utf8_lossy(&output.stdout).lines() {
- let captures = LINE_REGEX.captures(line).unwrap();
- let line = captures[1].parse::<u32>().unwrap() - 1;
- let character = captures[2].parse::<u32>().unwrap() - 1;
- let digit = captures[3].parse::<u32>().unwrap();
- let kind = &captures[4];
- let code = &captures[5];
- let message = captures[6].into();
- let range = Range::new_simple(line, character, line, character + digit);
- let severity = match kind {
- "Message" => DiagnosticSeverity::INFORMATION,
- "Warning" => DiagnosticSeverity::WARNING,
- _ => DiagnosticSeverity::ERROR,
- };
-
- diagnostics.push(Diagnostic {
- range,
- severity,
- code: DiagnosticCode::Chktex(code.into()),
- message,
- });
- }
-
- Ok(diagnostics)
-}
diff --git a/support/texlab/src/diagnostics/latex.rs b/support/texlab/src/diagnostics/latex.rs
deleted file mode 100644
index a17aab3f77..0000000000
--- a/support/texlab/src/diagnostics/latex.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-use std::sync::Arc;
-
-use dashmap::DashMap;
-use lsp_types::{DiagnosticSeverity, Url};
-use rowan::{ast::AstNode, NodeOrToken, TextRange};
-
-use crate::{syntax::latex, Document, LineIndexExt, Workspace};
-
-use super::{Diagnostic, DiagnosticCode, LatexCode};
-
-pub fn collect_latex_diagnostics(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- workspace: &Workspace,
- uri: &Url,
-) -> Option<()> {
- let document = workspace.documents_by_uri.get(uri)?;
- if !document.uri.as_str().ends_with(".tex") {
- return None;
- }
-
- let data = document.data.as_latex()?;
-
- all_diagnostics.alter(uri, |_, mut diagnostics| {
- diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Latex(_)));
- diagnostics
- });
-
- for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() {
- analyze_environment(all_diagnostics, document, node.clone())
- .or_else(|| analyze_curly_group(all_diagnostics, document, &node))
- .or_else(|| {
- if node.kind() == latex::ERROR && node.first_token()?.text() == "}" {
- let code = LatexCode::UnexpectedRCurly;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document.line_index.line_col_lsp_range(node.text_range()),
- code: DiagnosticCode::Latex(code),
- message: String::from(code),
- });
-
- Some(())
- } else {
- None
- }
- });
- }
-
- Some(())
-}
-
-fn analyze_environment(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- document: &Document,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let environment = latex::Environment::cast(node)?;
- let name1 = environment.begin()?.name()?.key()?;
- let name2 = environment.end()?.name()?.key()?;
- if name1 != name2 {
- let code = LatexCode::MismatchedEnvironment;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(latex::small_range(&name1)),
- code: DiagnosticCode::Latex(code),
- message: String::from(code),
- });
- }
- Some(())
-}
-
-fn analyze_curly_group(
- all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>,
- document: &Document,
- node: &latex::SyntaxNode,
-) -> Option<()> {
- if !matches!(
- node.kind(),
- latex::CURLY_GROUP
- | latex::CURLY_GROUP_COMMAND
- | latex::CURLY_GROUP_KEY_VALUE
- | latex::CURLY_GROUP_WORD
- | latex::CURLY_GROUP_WORD_LIST
- ) {
- return None;
- }
-
- let is_inside_verbatim_environment = node
- .ancestors()
- .filter_map(latex::Environment::cast)
- .filter_map(|env| env.begin())
- .filter_map(|begin| begin.name())
- .filter_map(|name| name.key())
- .any(|name| {
- ["asy", "lstlisting", "minted", "verbatim"].contains(&name.to_string().as_str())
- });
-
- if !is_inside_verbatim_environment
- && !node
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .any(|token| token.kind() == latex::R_CURLY)
- {
- let code = LatexCode::RCurlyInserted;
- all_diagnostics
- .entry(Arc::clone(&document.uri))
- .or_default()
- .push(Diagnostic {
- severity: DiagnosticSeverity::ERROR,
- range: document
- .line_index
- .line_col_lsp_range(TextRange::empty(node.text_range().end())),
- code: DiagnosticCode::Latex(code),
- message: String::from(code),
- });
- }
-
- Some(())
-}
diff --git a/support/texlab/src/distro.rs b/support/texlab/src/distro.rs
index f869392366..759374da81 100644
--- a/support/texlab/src/distro.rs
+++ b/support/texlab/src/distro.rs
@@ -1,3 +1,4 @@
+mod file_name_db;
mod kpsewhich;
mod miktex;
mod texlive;
@@ -5,41 +6,40 @@ mod texlive;
use std::process::{Command, Stdio};
use anyhow::Result;
-use derive_more::Display;
-use log::warn;
-pub use kpsewhich::Resolver;
+pub use file_name_db::FileNameDB;
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Display)]
-pub enum DistributionKind {
- #[display(fmt = "TeXLive")]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum DistroKind {
Texlive,
- #[display(fmt = "MikTeX")]
Miktex,
- #[display(fmt = "Tectonic")]
Tectonic,
- #[display(fmt = "Unknown")]
Unknown,
}
-#[derive(Debug, Clone)]
-pub struct Distribution {
- pub kind: DistributionKind,
- pub resolver: Resolver,
+impl Default for DistroKind {
+ fn default() -> Self {
+ Self::Unknown
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct Distro {
+ pub kind: DistroKind,
+ pub file_name_db: FileNameDB,
}
-impl Distribution {
- #[must_use]
- pub fn detect() -> Self {
+impl Distro {
+ pub fn detect() -> Result<Self> {
let kind = match Command::new("latex").arg("--version").output() {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("TeX Live") {
- DistributionKind::Texlive
+ DistroKind::Texlive
} else if stdout.contains("MiKTeX") {
- DistributionKind::Miktex
+ DistroKind::Miktex
} else {
- DistributionKind::Unknown
+ DistroKind::Unknown
}
}
Err(_) => {
@@ -50,26 +50,25 @@ impl Distribution {
.status()
.is_ok()
{
- DistributionKind::Tectonic
+ DistroKind::Tectonic
} else {
- DistributionKind::Unknown
+ DistroKind::Unknown
}
}
};
- let resolver = match kind {
- DistributionKind::Texlive => Self::load_resolver(texlive::load_resolver),
- DistributionKind::Miktex => Self::load_resolver(miktex::load_resolver),
- DistributionKind::Tectonic | DistributionKind::Unknown => Resolver::default(),
+ let file_name_db = match kind {
+ DistroKind::Texlive => {
+ let root_dirs = kpsewhich::root_directories()?;
+ FileNameDB::parse(&root_dirs, &mut texlive::read_database)?
+ }
+ DistroKind::Miktex => {
+ let root_dirs = kpsewhich::root_directories()?;
+ FileNameDB::parse(&root_dirs, &mut miktex::read_database)?
+ }
+ DistroKind::Tectonic | DistroKind::Unknown => FileNameDB::default(),
};
- Self { kind, resolver }
- }
- fn load_resolver(loader: impl FnOnce() -> Result<Resolver>) -> Resolver {
- match loader() {
- Ok(resolver) => return resolver,
- Err(why) => warn!("Failed to load resolver: {}", why),
- };
- Resolver::default()
+ Ok(Self { kind, file_name_db })
}
}
diff --git a/support/texlab/src/distro/file_name_db.rs b/support/texlab/src/distro/file_name_db.rs
new file mode 100644
index 0000000000..9234d0a3f9
--- /dev/null
+++ b/support/texlab/src/distro/file_name_db.rs
@@ -0,0 +1,80 @@
+use std::{
+ borrow::Borrow,
+ path::{Path, PathBuf},
+};
+
+use anyhow::Result;
+use rustc_hash::FxHashSet;
+
+use crate::db::Language;
+
+#[derive(Debug)]
+pub struct DistroFile(PathBuf);
+
+impl DistroFile {
+ pub fn path(&self) -> &Path {
+ &self.0
+ }
+
+ pub fn name(&self) -> &str {
+ self.0.file_name().unwrap().to_str().unwrap()
+ }
+}
+
+impl PartialEq for DistroFile {
+ fn eq(&self, other: &Self) -> bool {
+ self.name() == other.name()
+ }
+}
+
+impl Eq for DistroFile {}
+
+impl std::hash::Hash for DistroFile {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.name().hash(state)
+ }
+}
+
+impl Borrow<str> for DistroFile {
+ fn borrow(&self) -> &str {
+ self.name()
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct FileNameDB {
+ files: FxHashSet<DistroFile>,
+}
+
+impl FileNameDB {
+ pub fn get(&self, name: &str) -> Option<&Path> {
+ self.files.get(name).map(|file| file.path())
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (&str, &Path)> + '_ {
+ self.files.iter().map(|file| (file.name(), file.path()))
+ }
+
+ pub fn parse(
+ root_dirs: &[PathBuf],
+ reader: &mut dyn FnMut(&Path) -> Result<Vec<PathBuf>>,
+ ) -> Result<Self> {
+ let files = root_dirs
+ .iter()
+ .flat_map(|dir| reader(dir))
+ .flatten()
+ .filter_map(|rel_path| {
+ Language::from_path(&rel_path)?;
+ rel_path.file_name()?.to_str()?;
+ let abs_path = root_dirs
+ .iter()
+ .rev()
+ .map(|dir| dir.join(&rel_path))
+ .find_map(|path| std::fs::canonicalize(path).ok())?;
+ Some(DistroFile(abs_path))
+ })
+ .collect();
+
+ Ok(Self { files })
+ }
+}
diff --git a/support/texlab/src/distro/kpsewhich.rs b/support/texlab/src/distro/kpsewhich.rs
index b14e64bf56..0a6d1d35ad 100644
--- a/support/texlab/src/distro/kpsewhich.rs
+++ b/support/texlab/src/distro/kpsewhich.rs
@@ -1,56 +1,6 @@
-use std::{
- env,
- ffi::OsStr,
- fs,
- path::{Path, PathBuf},
- process::Command,
-};
+use std::{env, ffi::OsStr, path::PathBuf, process::Command};
use anyhow::Result;
-use rustc_hash::FxHashMap;
-use smol_str::SmolStr;
-
-use crate::DocumentLanguage;
-
-#[derive(Debug, PartialEq, Eq, Clone, Default)]
-pub struct Resolver {
- pub files_by_name: FxHashMap<SmolStr, PathBuf>,
-}
-
-impl Resolver {
- #[must_use]
- pub fn new(files_by_name: FxHashMap<SmolStr, PathBuf>) -> Self {
- Self { files_by_name }
- }
-}
-
-pub fn parse_database(
- root_directories: &[PathBuf],
- mut reader: impl FnMut(&Path) -> Result<Vec<PathBuf>>,
-) -> Result<Resolver> {
- let mut files_by_name = FxHashMap::default();
- for directory in root_directories {
- for path in reader(directory)? {
- if DocumentLanguage::by_path(&path).is_some() {
- if let Some(path) = make_absolute(root_directories, &path) {
- if let Some(name) = path.file_name().and_then(OsStr::to_str).map(Into::into) {
- files_by_name.insert(name, path);
- }
- }
- }
- }
- }
- Ok(Resolver::new(files_by_name))
-}
-
-fn make_absolute(root_directories: &[PathBuf], relative_path: &Path) -> Option<PathBuf> {
- for dir in root_directories.iter().rev() {
- if let Ok(path) = fs::canonicalize(dir.join(&relative_path)) {
- return Some(path);
- }
- }
- None
-}
pub fn root_directories() -> Result<Vec<PathBuf>> {
let texmf = run(&["-var-value", "TEXMF"])?;
diff --git a/support/texlab/src/distro/miktex.rs b/support/texlab/src/distro/miktex.rs
index c7a92d572e..fc778752db 100644
--- a/support/texlab/src/distro/miktex.rs
+++ b/support/texlab/src/distro/miktex.rs
@@ -1,20 +1,11 @@
use std::{
ffi::OsStr,
fs,
- io::{self, Cursor},
+ io::{self, Cursor, Read},
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
-use byteorder::{LittleEndian, ReadBytesExt};
-
-use super::kpsewhich::{self, Resolver};
-
-pub fn load_resolver() -> Result<Resolver> {
- let root_directories = kpsewhich::root_directories()?;
- let resolver = kpsewhich::parse_database(&root_directories, read_database)?;
- Ok(resolver)
-}
const DATABASE_PATH: &str = "miktex/data/le";
const FNDB_SIGNATURE: u32 = 0x42_44_4e_46;
@@ -23,7 +14,7 @@ const FNDB_TABLE_POINTER_OFFSET: u32 = 4 * FNDB_WORD_SIZE;
const FNDB_TABLE_SIZE_OFFSET: u32 = 6 * FNDB_WORD_SIZE;
const FNDB_ENTRY_SIZE: u32 = 4 * FNDB_WORD_SIZE;
-fn read_database(directory: &Path) -> Result<Vec<PathBuf>> {
+pub(super) fn read_database(directory: &Path) -> Result<Vec<PathBuf>> {
let database_directory = directory.join(DATABASE_PATH);
if !database_directory.exists() {
return Ok(Vec::new());
@@ -36,27 +27,28 @@ fn read_database(directory: &Path) -> Result<Vec<PathBuf>> {
database.extend(parse_database(&bytes).context("parsing kpsewhich database")?);
}
}
+
Ok(database)
}
fn parse_database(bytes: &[u8]) -> io::Result<Vec<PathBuf>> {
let mut reader = Cursor::new(bytes);
- if reader.read_u32::<LittleEndian>()? != FNDB_SIGNATURE {
+ if read_u32(&mut reader)? != FNDB_SIGNATURE {
return Err(io::ErrorKind::InvalidData.into());
}
reader.set_position(u64::from(FNDB_TABLE_POINTER_OFFSET));
- let table_address = reader.read_u32::<LittleEndian>()?;
+ let table_address = read_u32(&mut reader)?;
reader.set_position(u64::from(FNDB_TABLE_SIZE_OFFSET));
- let table_size = reader.read_u32::<LittleEndian>()?;
+ let table_size = read_u32(&mut reader)?;
let mut files = Vec::new();
for i in 0..table_size {
let offset = table_address + i * FNDB_ENTRY_SIZE as u32;
reader.set_position(u64::from(offset));
- let file_name_offset = reader.read_u32::<LittleEndian>()? as usize;
- let directory_offset = reader.read_u32::<LittleEndian>()? as usize;
+ let file_name_offset = read_u32(&mut reader)? as usize;
+ let directory_offset = read_u32(&mut reader)? as usize;
let file_name = read_string(bytes, file_name_offset)?;
let directory = read_string(bytes, directory_offset)?;
@@ -78,3 +70,9 @@ fn read_string(bytes: &[u8], offset: usize) -> io::Result<&str> {
std::str::from_utf8(&bytes[offset..offset + length])
.map_err(|_| io::ErrorKind::InvalidData.into())
}
+
+fn read_u32(reader: &mut Cursor<&[u8]>) -> io::Result<u32> {
+ let mut buf = [0u8; std::mem::size_of::<u32>()];
+ reader.read_exact(&mut buf)?;
+ Ok(u32::from_le_bytes(buf))
+}
diff --git a/support/texlab/src/distro/texlive.rs b/support/texlab/src/distro/texlive.rs
index 47159b2efc..8013033e6d 100644
--- a/support/texlab/src/distro/texlive.rs
+++ b/support/texlab/src/distro/texlive.rs
@@ -6,17 +6,9 @@ use std::{
use anyhow::Result;
-use super::kpsewhich::{self, Resolver};
-
-pub fn load_resolver() -> Result<Resolver> {
- let root_directories = kpsewhich::root_directories()?;
- let resolver = kpsewhich::parse_database(&root_directories, read_database)?;
- Ok(resolver)
-}
-
const DATABASE_PATH: &str = "ls-R";
-fn read_database(directory: &Path) -> Result<Vec<PathBuf>> {
+pub(super) fn read_database(directory: &Path) -> Result<Vec<PathBuf>> {
let file = directory.join(DATABASE_PATH);
if !file.is_file() {
return Ok(Vec::new());
diff --git a/support/texlab/src/document.rs b/support/texlab/src/document.rs
deleted file mode 100644
index 60e295b2e4..0000000000
--- a/support/texlab/src/document.rs
+++ /dev/null
@@ -1,135 +0,0 @@
-use std::{fmt, sync::Arc};
-
-use derive_more::From;
-use lsp_types::Url;
-
-use crate::{
- line_index::LineIndex,
- syntax::{
- bibtex, build_log,
- latex::{self, LatexAnalyzerContext},
- },
- DocumentLanguage, Environment,
-};
-
-#[derive(Debug, Clone)]
-pub struct LatexDocumentData {
- pub green: rowan::GreenNode,
- pub extras: Arc<latex::Extras>,
-}
-
-#[derive(Debug, Clone)]
-pub struct BibtexDocumentData {
- pub green: rowan::GreenNode,
-}
-
-#[derive(Debug, Clone, From)]
-pub enum DocumentData {
- Latex(Box<LatexDocumentData>),
- Bibtex(BibtexDocumentData),
- BuildLog(Arc<build_log::Parse>),
-}
-
-impl DocumentData {
- #[must_use]
- pub fn language(&self) -> DocumentLanguage {
- match self {
- Self::Latex(_) => DocumentLanguage::Latex,
- Self::Bibtex(_) => DocumentLanguage::Bibtex,
- Self::BuildLog(_) => DocumentLanguage::BuildLog,
- }
- }
-
- #[must_use]
- pub fn as_latex(&self) -> Option<&LatexDocumentData> {
- if let Self::Latex(data) = self {
- Some(data)
- } else {
- None
- }
- }
-
- #[must_use]
- pub fn as_bibtex(&self) -> Option<&BibtexDocumentData> {
- if let Self::Bibtex(data) = self {
- Some(data)
- } else {
- None
- }
- }
-
- #[must_use]
- pub fn as_build_log(&self) -> Option<&build_log::Parse> {
- if let Self::BuildLog(v) = self {
- Some(v)
- } else {
- None
- }
- }
-}
-
-#[derive(Clone)]
-pub struct Document {
- pub uri: Arc<Url>,
- pub text: Arc<String>,
- pub line_index: Arc<LineIndex>,
- pub data: DocumentData,
-}
-
-impl fmt::Debug for Document {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.uri)
- }
-}
-
-impl Document {
- #[must_use]
- pub fn parse(
- environment: &Environment,
- uri: Arc<Url>,
- text: Arc<String>,
- language: DocumentLanguage,
- ) -> Self {
- let line_index = Arc::new(LineIndex::new(&text));
- let data = match language {
- DocumentLanguage::Latex => {
- let green = latex::parse(&text).green;
- let root = latex::SyntaxNode::new_root(green.clone());
-
- let base_uri = match &environment.options.root_directory {
- Some(root_dir) => {
- let root_dir = environment.current_directory.join(&root_dir);
- Url::from_directory_path(root_dir)
- .map_or_else(|()| Arc::clone(&uri), Arc::new)
- }
- None => Arc::clone(&uri),
- };
-
- let mut context = LatexAnalyzerContext {
- environment,
- extras: latex::Extras::default(),
- document_uri: Arc::clone(&uri),
- base_uri,
- };
- latex::analyze(&mut context, &root);
- let extras = Arc::new(context.extras);
- DocumentData::Latex(Box::new(LatexDocumentData { green, extras }))
- }
- DocumentLanguage::Bibtex => {
- let green = bibtex::parse(&text);
- DocumentData::Bibtex(BibtexDocumentData { green })
- }
- DocumentLanguage::BuildLog => {
- let data = Arc::new(build_log::parse(&text));
- DocumentData::BuildLog(data)
- }
- };
-
- Self {
- uri,
- text,
- line_index,
- data,
- }
- }
-}
diff --git a/support/texlab/src/environment.rs b/support/texlab/src/environment.rs
deleted file mode 100644
index 54b6279f30..0000000000
--- a/support/texlab/src/environment.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use std::{path::PathBuf, sync::Arc};
-
-use lsp_types::{ClientCapabilities, ClientInfo};
-
-use crate::{distro::Resolver, Options};
-
-#[derive(Debug, Clone)]
-pub struct Environment {
- pub current_directory: Arc<PathBuf>,
- pub client_capabilities: Arc<ClientCapabilities>,
- pub client_info: Option<Arc<ClientInfo>>,
- pub options: Arc<Options>,
- pub resolver: Arc<Resolver>,
-}
-
-impl Environment {
- #[must_use]
- pub fn new(current_directory: Arc<PathBuf>) -> Self {
- Self {
- current_directory,
- client_capabilities: Arc::new(ClientCapabilities::default()),
- client_info: None,
- options: Arc::new(Options::default()),
- resolver: Arc::new(Resolver::default()),
- }
- }
-}
-
-impl Default for Environment {
- fn default() -> Self {
- Self::new(Arc::new(std::env::temp_dir()))
- }
-}
diff --git a/support/texlab/src/features.rs b/support/texlab/src/features.rs
index 9576f266a9..0d82bcb4f4 100644
--- a/support/texlab/src/features.rs
+++ b/support/texlab/src/features.rs
@@ -1,52 +1,14 @@
-mod build;
-#[cfg(feature = "completion")]
-mod completion;
-mod cursor;
-mod definition;
-mod execute_command;
-mod folding;
-mod formatting;
-mod forward_search;
-mod highlight;
-mod hover;
-mod link;
-mod lsp_kinds;
-mod reference;
-mod rename;
-mod symbol;
-
-use std::sync::Arc;
-
-use lsp_types::Url;
-
-use crate::{Document, Workspace};
-
-#[cfg(feature = "completion")]
-pub use self::completion::{complete, CompletionItemData, COMPLETION_LIMIT};
-pub use self::{
- build::{BuildEngine, BuildParams, BuildResult, BuildStatus},
- definition::goto_definition,
- execute_command::execute_command,
- folding::find_foldings,
- formatting::format_source_code,
- forward_search::{execute_forward_search, ForwardSearchResult, ForwardSearchStatus},
- highlight::find_document_highlights,
- hover::find_hover,
- link::find_document_links,
- reference::find_all_references,
- rename::{prepare_rename_all, rename_all},
- symbol::{find_document_symbols, find_workspace_symbols},
-};
-
-#[derive(Clone)]
-pub struct FeatureRequest<P> {
- pub params: P,
- pub workspace: Workspace,
- pub uri: Arc<Url>,
-}
-
-impl<P> FeatureRequest<P> {
- pub fn main_document(&self) -> &Document {
- &self.workspace.documents_by_uri[&self.uri]
- }
-}
+pub mod build;
+pub mod completion;
+pub mod definition;
+pub mod folding;
+pub mod formatting;
+pub mod forward_search;
+pub mod highlight;
+pub mod hover;
+pub mod inlay_hint;
+pub mod link;
+pub mod reference;
+pub mod rename;
+pub mod symbol;
+pub mod workspace_command;
diff --git a/support/texlab/src/features/build.rs b/support/texlab/src/features/build.rs
index d961052061..645f1e8e0e 100644
--- a/support/texlab/src/features/build.rs
+++ b/support/texlab/src/features/build.rs
@@ -1,28 +1,18 @@
+mod progress;
+
use std::{
io::{BufRead, BufReader, Read},
- path::Path,
- process::{Command, Stdio},
- sync::{Arc, Mutex},
+ path::{Path, PathBuf},
+ process::Stdio,
thread::{self, JoinHandle},
};
-use anyhow::Result;
-use crossbeam_channel::{Receiver, Sender};
-use dashmap::DashMap;
use encoding_rs_io::DecodeReaderBytesBuilder;
-use lsp_types::{
- notification::{LogMessage, Progress},
- LogMessageParams, NumberOrString, Position, ProgressParams, ProgressParamsValue,
- TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkDoneProgress,
- WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
-};
+use lsp_types::{notification::LogMessage, LogMessageParams, TextDocumentIdentifier, Url};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
-use uuid::Uuid;
-use crate::{client, req_queue::ReqQueue, ClientCapabilitiesExt, DocumentLanguage};
-
-use super::{forward_search, FeatureRequest};
+use crate::{client::LspClient, db::Workspace, util::capabilities::ClientCapabilitiesExt, Db};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -30,6 +20,12 @@ pub struct BuildParams {
pub text_document: TextDocumentIdentifier,
}
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildResult {
+ pub status: BuildStatus,
+}
+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum BuildStatus {
@@ -39,217 +35,132 @@ pub enum BuildStatus {
CANCELLED = 3,
}
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct BuildResult {
- pub status: BuildStatus,
-}
-
-struct ProgressReporter<'a> {
- supports_progress: bool,
- req_queue: &'a Mutex<ReqQueue>,
- lsp_sender: Sender<lsp_server::Message>,
- token: &'a str,
+#[derive(Debug)]
+pub struct Command {
+ uri: Url,
+ progress: bool,
+ executable: String,
+ args: Vec<String>,
+ working_dir: PathBuf,
+ client: LspClient,
}
-impl<'a> ProgressReporter<'a> {
- pub fn start(&self, uri: &Url) -> Result<()> {
- if self.supports_progress {
- client::send_request::<lsp_types::request::WorkDoneProgressCreate>(
- self.req_queue,
- &self.lsp_sender,
- WorkDoneProgressCreateParams {
- token: NumberOrString::String(self.token.to_string()),
- },
- )?;
- client::send_notification::<Progress>(
- &self.lsp_sender,
- ProgressParams {
- token: NumberOrString::String(self.token.to_string()),
- value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(
- WorkDoneProgressBegin {
- title: "Building".to_string(),
- message: Some(uri.as_str().to_string()),
- cancellable: Some(false),
- percentage: None,
- },
- )),
- },
- )?;
+impl Command {
+ pub fn new(db: &dyn Db, uri: Url, client: LspClient) -> Option<Self> {
+ let workspace = Workspace::get(db);
+ let document = match workspace.lookup_uri(db, &uri) {
+ Some(child) => workspace
+ .parents(db, child)
+ .iter()
+ .next()
+ .copied()
+ .unwrap_or(child),
+ None => return None,
};
- Ok(())
- }
-}
-impl<'a> Drop for ProgressReporter<'a> {
- fn drop(&mut self) {
- if self.supports_progress {
- drop(client::send_notification::<Progress>(
- &self.lsp_sender,
- ProgressParams {
- token: NumberOrString::String(self.token.to_string()),
- value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(
- WorkDoneProgressEnd { message: None },
- )),
- },
- ));
+ if document.location(db).path(db).is_none() {
+ log::warn!("Document {uri} cannot be compiled; skipping...");
+ return None;
}
- }
-}
-
-#[derive(Default)]
-pub struct BuildEngine {
- lock: Mutex<()>,
- pub positions_by_uri: DashMap<Arc<Url>, Position>,
-}
-
-impl BuildEngine {
- pub fn build(
- &self,
- request: FeatureRequest<BuildParams>,
- req_queue: &Mutex<ReqQueue>,
- lsp_sender: &Sender<lsp_server::Message>,
- ) -> Result<BuildResult> {
- let lock = self.lock.lock().unwrap();
-
- let document = request
- .workspace
- .documents_by_uri
- .values()
- .find(|document| {
- if let Some(data) = document.data.as_latex() {
- data.extras.has_document_environment
- } else {
- false
- }
- })
- .unwrap_or_else(|| request.main_document());
-
- if document.data.language() != DocumentLanguage::Latex {
- return Ok(BuildResult {
- status: BuildStatus::SUCCESS,
- });
- }
-
- if document.uri.scheme() != "file" {
- return Ok(BuildResult {
- status: BuildStatus::FAILURE,
- });
- }
- let path = document.uri.to_file_path().unwrap();
-
- let supports_progress = request
- .workspace
- .environment
- .client_capabilities
- .has_work_done_progress_support();
-
- let token = format!("texlab-build-{}", Uuid::new_v4());
- let progress_reporter = ProgressReporter {
- supports_progress,
- req_queue,
- lsp_sender: lsp_sender.clone(),
- token: &token,
- };
- progress_reporter.start(&document.uri)?;
- let options = &request.workspace.environment.options;
-
- let build_dir = options
- .root_directory
- .as_ref()
- .map(AsRef::as_ref)
- .or_else(|| path.parent())
- .unwrap();
-
- let args: Vec<_> = options
- .build
+ let options = &workspace.options(db).build;
+ let executable = options.executable.0.clone();
+ let path = document.location(db).path(db).as_deref().unwrap();
+ let args = options
.args
+ .0
.iter()
- .map(|arg| replace_placeholder(arg.clone(), &path))
+ .map(|arg| replace_placeholder(arg, path))
.collect();
- let mut process = Command::new(&options.build.executable)
- .args(args)
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .current_dir(build_dir)
- .spawn()?;
-
- let (exit_sender, exit_receiver) = crossbeam_channel::bounded(1);
- let log_handle = capture_output(&mut process, lsp_sender, exit_receiver);
- let success = process.wait().map(|status| status.success())?;
- exit_sender.send(())?;
- drop(exit_sender);
+ let working_dir = workspace
+ .working_dir(db, document.directory(db))
+ .path(db)
+ .clone()?;
+
+ Some(Self {
+ uri: document.location(db).uri(db).clone(),
+ progress: workspace
+ .client_capabilities(db)
+ .has_work_done_progress_support(),
+ executable,
+ args,
+ working_dir,
+ client,
+ })
+ }
- log_handle.join().unwrap();
- let status = if success {
- BuildStatus::SUCCESS
+ pub fn run(self) -> BuildStatus {
+ let reporter = if self.progress {
+ let inner = progress::Reporter::new(&self.client);
+ inner.start(&self.uri).expect("report progress");
+ Some(inner)
} else {
- BuildStatus::ERROR
+ None
};
- drop(progress_reporter);
- drop(lock);
-
- if options.build.forward_search_after {
- let request = FeatureRequest {
- params: TextDocumentPositionParams {
- position: self
- .positions_by_uri
- .get(&request.main_document().uri)
- .map(|guard| *guard)
- .unwrap_or_default(),
- text_document: TextDocumentIdentifier::new(request.uri.as_ref().clone()),
- },
- uri: request.uri,
- workspace: request.workspace,
- };
- forward_search::execute_forward_search(request);
- }
-
- Ok(BuildResult { status })
- }
-}
-
-fn capture_output(
- process: &mut std::process::Child,
- lsp_sender: &Sender<lsp_server::Message>,
- exit_receiver: Receiver<()>,
-) -> JoinHandle<()> {
- let (log_sender, log_receiver) = crossbeam_channel::unbounded();
- track_output(process.stdout.take().unwrap(), log_sender.clone());
- track_output(process.stderr.take().unwrap(), log_sender);
- let lsp_sender = lsp_sender.clone();
- thread::spawn(move || loop {
- crossbeam_channel::select! {
- recv(&log_receiver) -> message => {
- if let Ok(message) = message {
- client::send_notification::<LogMessage>(
- &lsp_sender,
- LogMessageParams {
- message,
- typ: lsp_types::MessageType::LOG,
- },
- )
- .unwrap();
- }
- },
- recv(&exit_receiver) -> _ => break,
+ let mut process = match std::process::Command::new(&self.executable)
+ .args(self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .current_dir(self.working_dir)
+ .spawn()
+ {
+ Ok(process) => process,
+ Err(_) => {
+ log::error!("Failed to spawn process: {:?}", self.executable);
+ return BuildStatus::FAILURE;
+ }
};
- })
-}
-fn replace_placeholder(arg: String, file: &Path) -> String {
- if arg.starts_with('"') || arg.ends_with('"') {
- arg
- } else {
- arg.replace("%f", &file.to_string_lossy())
+ let (line_sender, line_receiver) = flume::unbounded();
+ let (exit_sender, exit_receiver) = flume::unbounded();
+ track_output(process.stderr.take().unwrap(), line_sender.clone());
+ track_output(process.stdout.take().unwrap(), line_sender);
+ let client = self.client.clone();
+ let handle = std::thread::spawn(move || {
+ let typ = lsp_types::MessageType::LOG;
+
+ loop {
+ let done = flume::Selector::new()
+ .recv(&line_receiver, |line| match line {
+ Ok(message) => {
+ let params = LogMessageParams { message, typ };
+ client.send_notification::<LogMessage>(params).unwrap();
+ false
+ }
+ Err(_) => true,
+ })
+ .recv(&exit_receiver, |_| true)
+ .wait();
+
+ if done {
+ break;
+ }
+ }
+ });
+
+ let status = process.wait().map_or(BuildStatus::FAILURE, |result| {
+ if result.success() {
+ BuildStatus::SUCCESS
+ } else {
+ BuildStatus::ERROR
+ }
+ });
+
+ let _ = exit_sender.send(());
+ handle.join().unwrap();
+
+ drop(reporter);
+ status
}
}
-fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) -> JoinHandle<()> {
+fn track_output(
+ output: impl Read + Send + 'static,
+ sender: flume::Sender<String>,
+) -> JoinHandle<()> {
let reader = BufReader::new(
DecodeReaderBytesBuilder::new()
.encoding(Some(encoding_rs::UTF_8))
@@ -264,3 +175,11 @@ fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) -> J
}
})
}
+
+fn replace_placeholder(arg: &str, file: &Path) -> String {
+ if arg.starts_with('"') || arg.ends_with('"') {
+ arg.to_string()
+ } else {
+ arg.replace("%f", &file.to_string_lossy())
+ }
+}
diff --git a/support/texlab/src/features/build/progress.rs b/support/texlab/src/features/build/progress.rs
new file mode 100644
index 0000000000..6f235bebd6
--- /dev/null
+++ b/support/texlab/src/features/build/progress.rs
@@ -0,0 +1,54 @@
+use std::sync::atomic::{AtomicI32, Ordering};
+
+use anyhow::Result;
+use lsp_types::{
+ notification::Progress, request::WorkDoneProgressCreate, NumberOrString, ProgressParams,
+ ProgressParamsValue, Url, WorkDoneProgress, WorkDoneProgressBegin,
+ WorkDoneProgressCreateParams, WorkDoneProgressEnd,
+};
+
+use crate::client::LspClient;
+
+static NEXT_TOKEN: AtomicI32 = AtomicI32::new(1);
+
+pub struct Reporter<'a> {
+ client: &'a LspClient,
+ token: i32,
+}
+
+impl<'a> Reporter<'a> {
+ pub fn new(client: &'a LspClient) -> Self {
+ let token = NEXT_TOKEN.fetch_add(1, Ordering::SeqCst);
+ Self { client, token }
+ }
+
+ pub fn start(&self, uri: &Url) -> Result<()> {
+ self.client
+ .send_request::<WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
+ token: NumberOrString::Number(self.token),
+ })?;
+
+ self.client.send_notification::<Progress>(ProgressParams {
+ token: NumberOrString::Number(self.token),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(WorkDoneProgressBegin {
+ title: "Building".to_string(),
+ message: Some(uri.as_str().to_string()),
+ cancellable: Some(false),
+ percentage: None,
+ })),
+ })?;
+
+ Ok(())
+ }
+}
+
+impl<'a> Drop for Reporter<'a> {
+ fn drop(&mut self) {
+ let _ = self.client.send_notification::<Progress>(ProgressParams {
+ token: NumberOrString::Number(self.token),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd {
+ message: None,
+ })),
+ });
+ }
+}
diff --git a/support/texlab/src/features/completion.rs b/support/texlab/src/features/completion.rs
index 3009b437e7..949b5552e5 100644
--- a/support/texlab/src/features/completion.rs
+++ b/support/texlab/src/features/completion.rs
@@ -1,6 +1,7 @@
mod acronym_ref;
mod argument;
-mod begin_command;
+mod begin_snippet;
+pub mod builder;
mod citation;
mod color;
mod color_model;
@@ -14,584 +15,36 @@ mod include;
mod label;
mod theorem;
mod tikz_library;
-mod types;
mod user_command;
mod user_environment;
-mod util;
-use std::borrow::Cow;
+use lsp_types::{CompletionList, Position, Url};
-use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
-use itertools::Itertools;
-use lsp_types::{
- CompletionItem, CompletionList, CompletionParams, CompletionTextEdit, Documentation,
- InsertTextFormat, MarkupContent, MarkupKind, TextEdit,
-};
-use rowan::{ast::AstNode, TextSize};
-use rustc_hash::FxHashSet;
-
-use crate::{
- syntax::{
- bibtex::{self},
- latex,
- },
- LineIndexExt,
-};
-
-use self::{
- acronym_ref::complete_acronyms,
- argument::complete_arguments,
- begin_command::complete_begin_command,
- citation::complete_citations,
- color::complete_colors,
- color_model::complete_color_models,
- component_command::complete_component_commands,
- component_environment::complete_component_environments,
- entry_type::complete_entry_types,
- field::complete_fields,
- glossary_ref::complete_glossary_entries,
- import::complete_imports,
- include::complete_includes,
- label::complete_labels,
- theorem::complete_theorem_environments,
- tikz_library::complete_tikz_libraries,
- types::{InternalCompletionItem, InternalCompletionItemData},
- user_command::complete_user_commands,
- user_environment::complete_user_environments,
- util::{adjust_kind, component_detail, image_documentation},
-};
-
-pub use self::types::CompletionItemData;
-
-use super::{
- cursor::{Cursor, CursorContext},
- lsp_kinds::Structure,
- FeatureRequest,
-};
+use crate::{features::completion::builder::CompletionBuilder, util::cursor::CursorContext, Db};
pub const COMPLETION_LIMIT: usize = 50;
-#[must_use]
-pub fn complete(request: FeatureRequest<CompletionParams>) -> Option<CompletionList> {
- let mut items = Vec::new();
- let context = CursorContext::new(request);
+pub fn complete(db: &dyn Db, uri: &Url, position: Position) -> Option<CompletionList> {
+ let context = CursorContext::new(db, uri, position, ())?;
+ let mut builder = CompletionBuilder::new(&context);
log::debug!("[Completion] Cursor: {:?}", context.cursor);
- complete_entry_types(&context, &mut items);
- complete_fields(&context, &mut items);
- complete_arguments(&context, &mut items);
- complete_citations(&context, &mut items);
- complete_imports(&context, &mut items);
- complete_colors(&context, &mut items);
- complete_color_models(&context, &mut items);
- complete_acronyms(&context, &mut items);
- complete_glossary_entries(&context, &mut items);
- complete_includes(&context, &mut items);
- complete_labels(&context, &mut items);
- complete_tikz_libraries(&context, &mut items);
- complete_component_environments(&context, &mut items);
- complete_theorem_environments(&context, &mut items);
- complete_user_environments(&context, &mut items);
- complete_begin_command(&context, &mut items);
- complete_component_commands(&context, &mut items);
- complete_user_commands(&context, &mut items);
-
- let mut items = dedup(items);
- preselect(&context, &mut items);
- score(&context, &mut items);
-
- items.sort_by(|a, b| {
- a.preselect
- .cmp(&b.preselect)
- .reverse()
- .then_with(|| a.score.cmp(&b.score).reverse())
- .then_with(|| a.data.label().cmp(b.data.label()))
- });
-
- let items: Vec<_> = items
- .into_iter()
- .take(COMPLETION_LIMIT)
- .filter(|item| item.score.is_some())
- .map(|item| convert_internal_items(&context, item))
- .enumerate()
- .map(|(i, item)| append_sort_text(item, i))
- .collect();
-
- let is_incomplete = if context
- .request
- .workspace
- .environment
- .client_info
- .as_ref()
- .as_ref()
- .map_or(false, |info| info.name.as_str() == "Visual Studio Code")
- {
- true
- } else {
- items.len() >= COMPLETION_LIMIT
- };
-
- Some(CompletionList {
- is_incomplete,
- items,
- })
-}
-
-fn dedup(items: Vec<InternalCompletionItem>) -> Vec<InternalCompletionItem> {
- let mut labels = FxHashSet::default();
- let mut insert = vec![false; items.len()];
- for (i, item) in items.iter().enumerate() {
- insert[i] = labels.insert(item.data.label());
- }
- items
- .into_iter()
- .enumerate()
- .filter(|(i, _)| insert[*i])
- .map(|(_, item)| item)
- .collect()
-}
-
-fn score(context: &CursorContext<CompletionParams>, items: &mut Vec<InternalCompletionItem>) {
- let pattern: Cow<str> = match &context.cursor {
- Cursor::Latex(token) if token.kind().is_command_name() => {
- if token.text_range().start() + TextSize::from(1) == context.offset {
- // Handle cases similar to this one correctly:
- // $\|$ % (| is the cursor)
- "\\".into()
- } else {
- token.text().trim_end().into()
- }
- }
- Cursor::Latex(token) if token.kind() == latex::WORD => {
- if let Some(key) = token.parent().and_then(latex::Key::cast) {
- key.words()
- .take_while(|word| word.text_range() != token.text_range())
- .chain(std::iter::once(token.clone()))
- .filter(|word| word.text_range().start() < context.offset)
- .join(" ")
- .into()
- } else {
- token.text().into()
- }
- }
- Cursor::Bibtex(token)
- if matches!(
- token.kind(),
- bibtex::TYPE
- | bibtex::NAME
- | bibtex::WORD
- | bibtex::COMMAND_NAME
- | bibtex::ACCENT_NAME
- ) =>
- {
- token.text().into()
- }
- Cursor::Latex(_) | Cursor::Bibtex(_) | Cursor::Nothing => "".into(),
- };
-
- let file_pattern = pattern.split('/').last().unwrap();
- let matcher = SkimMatcherV2::default().ignore_case();
- for item in items {
- item.score = match &item.data {
- InternalCompletionItemData::EntryType { ty } => {
- matcher.fuzzy_match(&ty.name, &pattern[1..])
- }
- InternalCompletionItemData::Field { field } => {
- matcher.fuzzy_match(&field.name, &pattern)
- }
- InternalCompletionItemData::Argument { name, .. } => {
- matcher.fuzzy_match(name, &pattern)
- }
- InternalCompletionItemData::BeginCommand => matcher.fuzzy_match("begin", &pattern[1..]),
- InternalCompletionItemData::Citation { key, .. } => matcher.fuzzy_match(key, &pattern),
- InternalCompletionItemData::ComponentCommand { name, .. } => {
- matcher.fuzzy_match(name, &pattern[1..])
- }
- InternalCompletionItemData::ComponentEnvironment { name, .. } => {
- matcher.fuzzy_match(name, &pattern)
- }
- InternalCompletionItemData::Class { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::Package { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::Color { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::ColorModel { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::Acronym { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::GlossaryEntry { name } => {
- matcher.fuzzy_match(name, &pattern)
- }
- InternalCompletionItemData::File { name } => matcher.fuzzy_match(name, file_pattern),
- InternalCompletionItemData::Directory { name } => {
- matcher.fuzzy_match(name, file_pattern)
- }
- InternalCompletionItemData::Label { name, .. } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::UserCommand { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::UserEnvironment { name } => {
- matcher.fuzzy_match(name, &pattern)
- }
- InternalCompletionItemData::PgfLibrary { name } => matcher.fuzzy_match(name, &pattern),
- InternalCompletionItemData::TikzLibrary { name } => matcher.fuzzy_match(name, &pattern),
- };
- }
-}
-
-fn preselect(
- context: &CursorContext<CompletionParams>,
- items: &mut [InternalCompletionItem],
-) -> Option<()> {
- let name = context.cursor.as_latex()?;
- let group = latex::CurlyGroupWord::cast(name.parent()?)?;
- let end = latex::End::cast(group.syntax().parent()?)?;
- let environment = latex::Environment::cast(end.syntax().parent()?)?;
- let name = environment.begin()?.name()?.key()?.to_string();
-
- for item in items {
- if item.data.label() == name {
- item.preselect = true;
- }
- }
- Some(())
-}
-
-fn convert_internal_items(
- context: &CursorContext<CompletionParams>,
- item: InternalCompletionItem,
-) -> CompletionItem {
- let range = context
- .request
- .main_document()
- .line_index
- .line_col_lsp_range(item.range);
-
- let mut new_item = match item.data {
- InternalCompletionItemData::EntryType { ty } => {
- let text_edit = TextEdit::new(range, (&ty.name).into());
- let kind = Structure::Entry(ty.category).completion_kind();
- CompletionItem {
- label: (&ty.name).into(),
- kind: Some(adjust_kind(&context.request, kind)),
- documentation: ty.documentation.as_ref().map(|doc| {
- Documentation::MarkupContent(MarkupContent {
- kind: MarkupKind::Markdown,
- value: doc.into(),
- })
- }),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- data: Some(serde_json::to_value(CompletionItemData::EntryType).unwrap()),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Field { field } => {
- let text_edit = TextEdit::new(range, (&field.name).into());
- CompletionItem {
- label: (&field.name).into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::Field.completion_kind(),
- )),
- documentation: Some(Documentation::MarkupContent(MarkupContent {
- kind: MarkupKind::Markdown,
- value: (&field.documentation).into(),
- })),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- data: Some(serde_json::to_value(CompletionItemData::FieldName).unwrap()),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Argument { name, image } => {
- let text_edit = TextEdit::new(range, name.into());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::Argument.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Argument).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- documentation: image
- .and_then(|image| image_documentation(&context.request, name, image)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::BeginCommand => {
- if context
- .request
- .workspace
- .environment
- .client_capabilities
- .text_document
- .as_ref()
- .and_then(|cap| cap.completion.as_ref())
- .and_then(|cap| cap.completion_item.as_ref())
- .and_then(|cap| cap.snippet_support)
- == Some(true)
- {
- let text_edit = TextEdit::new(range, "begin{$1}\n\t$0\n\\end{$1}".into());
- CompletionItem {
- kind: Some(adjust_kind(
- &context.request,
- Structure::Snippet.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::CommandSnippet).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- insert_text_format: Some(InsertTextFormat::SNIPPET),
- ..CompletionItem::new_simple("begin".into(), component_detail(&[]))
- }
- } else {
- let text_edit = TextEdit::new(range, "begin".to_string());
- CompletionItem {
- kind: Some(adjust_kind(
- &context.request,
- Structure::Command.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Command).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::new_simple("begin".to_string(), component_detail(&[]))
- }
- }
- }
- InternalCompletionItemData::Citation { uri, key, text, ty } => {
- let text_edit = TextEdit::new(range, key.to_string());
- CompletionItem {
- label: key.to_string(),
- kind: Some(adjust_kind(&context.request, ty.completion_kind())),
- filter_text: Some(text.clone()),
- sort_text: Some(text),
- data: Some(
- serde_json::to_value(CompletionItemData::Citation {
- uri: uri.as_ref().clone(),
- key: key.into(),
- })
- .unwrap(),
- ),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::ComponentCommand {
- name,
- image,
- glyph,
- file_names,
- } => {
- let detail = glyph.map_or_else(
- || component_detail(file_names),
- |glyph| format!("{}, {}", glyph, component_detail(file_names)),
- );
- let documentation =
- image.and_then(|img| image_documentation(&context.request, name, img));
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- kind: Some(adjust_kind(
- &context.request,
- Structure::Command.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Command).unwrap()),
- documentation,
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::new_simple(name.to_string(), detail)
- }
- }
- InternalCompletionItemData::ComponentEnvironment { name, file_names } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- kind: Some(adjust_kind(
- &context.request,
- Structure::Environment.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Environment).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::new_simple(name.to_string(), component_detail(file_names))
- }
- }
- InternalCompletionItemData::Class { name } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::Class.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Class).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Package { name } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::Package.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Package).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Color { name } => {
- let text_edit = TextEdit::new(range, name.into());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::Color.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Color).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::ColorModel { name } => {
- let text_edit = TextEdit::new(range, name.into());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::ColorModel.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::ColorModel).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Acronym { name } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name,
- kind: Some(adjust_kind(
- &context.request,
- Structure::GlossaryEntry.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Acronym).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::GlossaryEntry { name } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name,
- kind: Some(adjust_kind(
- &context.request,
- Structure::GlossaryEntry.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::GlossaryEntry).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::File { name } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::File.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::File).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Directory { name } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::Folder.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Folder).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::Label {
- name,
- kind,
- header,
- footer,
- text,
- } => {
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- label: name,
- kind: Some(adjust_kind(&context.request, kind.completion_kind())),
- detail: header,
- documentation: footer.map(Documentation::String),
- sort_text: Some(text.clone()),
- filter_text: Some(text),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- data: Some(serde_json::to_value(CompletionItemData::Label).unwrap()),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::UserCommand { name } => {
- let detail = "user-defined".into();
- let name = &name[1..];
- let text_edit = TextEdit::new(range, name.to_string());
- CompletionItem {
- kind: Some(adjust_kind(
- &context.request,
- Structure::Command.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Command).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::new_simple(name.into(), detail)
- }
- }
- InternalCompletionItemData::UserEnvironment { name } => {
- let detail = "user-defined".into();
- let text_edit = TextEdit::new(range, name.clone());
- CompletionItem {
- kind: Some(adjust_kind(
- &context.request,
- Structure::Environment.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::Environment).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::new_simple(name, detail)
- }
- }
- InternalCompletionItemData::PgfLibrary { name } => {
- let text_edit = TextEdit::new(range, name.into());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::PgfLibrary.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::PgfLibrary).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- InternalCompletionItemData::TikzLibrary { name } => {
- let text_edit = TextEdit::new(range, name.into());
- CompletionItem {
- label: name.into(),
- kind: Some(adjust_kind(
- &context.request,
- Structure::TikzLibrary.completion_kind(),
- )),
- data: Some(serde_json::to_value(CompletionItemData::TikzLibrary).unwrap()),
- text_edit: Some(CompletionTextEdit::Edit(text_edit)),
- ..CompletionItem::default()
- }
- }
- };
- new_item.preselect = Some(item.preselect);
- new_item
-}
-
-fn append_sort_text(mut item: CompletionItem, index: usize) -> CompletionItem {
- let sort_prefix = format!("{:0>2}", index);
- match &item.sort_text {
- Some(sort_text) => {
- item.sort_text = Some(format!("{} {}", sort_prefix, sort_text));
- }
- None => {
- item.sort_text = Some(sort_prefix);
- }
- };
- item
+ entry_type::complete(&context, &mut builder);
+ field::complete(&context, &mut builder);
+ argument::complete(&context, &mut builder);
+ citation::complete(&context, &mut builder);
+ import::complete(&context, &mut builder);
+ color::complete(&context, &mut builder);
+ color_model::complete(&context, &mut builder);
+ acronym_ref::complete(&context, &mut builder);
+ glossary_ref::complete(&context, &mut builder);
+ include::complete(&context, &mut builder);
+ label::complete(&context, &mut builder);
+ tikz_library::complete(&context, &mut builder);
+ component_environment::complete(&context, &mut builder);
+ theorem::complete(&context, &mut builder);
+ user_environment::complete(&context, &mut builder);
+ begin_snippet::complete(&context, &mut builder);
+ component_command::complete(&context, &mut builder);
+ user_command::complete(&context, &mut builder);
+ Some(builder.finish())
}
diff --git a/support/texlab/src/features/completion/acronym_ref.rs b/support/texlab/src/features/completion/acronym_ref.rs
index 84cbd8bc23..be7725fa2f 100644
--- a/support/texlab/src/features/completion/acronym_ref.rs
+++ b/support/texlab/src/features/completion/acronym_ref.rs
@@ -1,32 +1,29 @@
-use lsp_types::CompletionParams;
use rowan::ast::AstNode;
-use crate::{features::cursor::CursorContext, syntax::latex};
+use crate::{syntax::latex, util::cursor::CursorContext};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_acronyms<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range, group) = context.find_curly_group_word()?;
latex::AcronymReference::cast(group.syntax().parent()?)?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- for name in latex::SyntaxNode::new_root(data.green.clone())
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
+ for name in data
+ .root(context.db)
.descendants()
.filter_map(latex::AcronymDefinition::cast)
.filter_map(|node| node.name())
.filter_map(|name| name.key())
- .map(|name| name.to_string())
{
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Acronym { name },
- ));
+ builder.glossary_entry(range, name.to_string());
}
}
}
+
Some(())
}
diff --git a/support/texlab/src/features/completion/argument.rs b/support/texlab/src/features/completion/argument.rs
index 3ba28f9dea..10b377cb0e 100644
--- a/support/texlab/src/features/completion/argument.rs
+++ b/support/texlab/src/features/completion/argument.rs
@@ -1,15 +1,17 @@
-use lsp_types::CompletionParams;
use rowan::{ast::AstNode, TextRange};
-use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext, syntax::latex};
+use crate::{
+ syntax::latex,
+ util::{components::COMPONENT_DATABASE, cursor::CursorContext},
+};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_arguments<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
- let token = context.cursor.as_latex()?;
+ let token = context.cursor.as_tex()?;
let range = if token.kind() == latex::WORD {
token.text_range()
@@ -37,7 +39,7 @@ pub fn complete_arguments<'a>(
let command_name = command.name()?;
let command_name = &command_name.text()[1..];
- for component in COMPONENT_DATABASE.linked_components(&context.request.workspace) {
+ for component in COMPONENT_DATABASE.linked_components(context.db, context.document) {
for component_command in component
.commands
.iter()
@@ -50,14 +52,7 @@ pub fn complete_arguments<'a>(
.filter(|(i, _)| *i == index)
{
for arg in &param.0 {
- let item = InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Argument {
- name: &arg.name,
- image: arg.image.as_deref(),
- },
- );
- items.push(item);
+ builder.generic_argument(range, &arg.name, arg.image.as_deref());
}
}
}
diff --git a/support/texlab/src/features/completion/begin_command.rs b/support/texlab/src/features/completion/begin_command.rs
deleted file mode 100644
index 699b06c067..0000000000
--- a/support/texlab/src/features/completion/begin_command.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use lsp_types::CompletionParams;
-
-use crate::features::cursor::CursorContext;
-
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
-
-pub fn complete_begin_command(
- context: &CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem>,
-) -> Option<()> {
- let range = context.cursor.command_range(context.offset)?;
-
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::BeginCommand,
- ));
- Some(())
-}
diff --git a/support/texlab/src/features/completion/begin_snippet.rs b/support/texlab/src/features/completion/begin_snippet.rs
new file mode 100644
index 0000000000..06be4faf13
--- /dev/null
+++ b/support/texlab/src/features/completion/begin_snippet.rs
@@ -0,0 +1,9 @@
+use crate::util::cursor::CursorContext;
+
+use super::builder::CompletionBuilder;
+
+pub fn complete(context: &CursorContext, builder: &mut CompletionBuilder) -> Option<()> {
+ let range = context.cursor.command_range(context.offset)?;
+ builder.begin_snippet(range);
+ Some(())
+}
diff --git a/support/texlab/src/features/completion/builder.rs b/support/texlab/src/features/completion/builder.rs
new file mode 100644
index 0000000000..f8ea4c5d15
--- /dev/null
+++ b/support/texlab/src/features/completion/builder.rs
@@ -0,0 +1,784 @@
+use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
+use itertools::Itertools;
+use lsp_types::{
+ CompletionItem, CompletionItemKind, CompletionList, CompletionTextEdit, Documentation,
+ InsertTextFormat, MarkupContent, MarkupKind, TextEdit, Url,
+};
+use once_cell::sync::Lazy;
+use regex::Regex;
+use rowan::{ast::AstNode, TextRange, TextSize};
+use serde::{Deserialize, Serialize};
+use smol_str::SmolStr;
+
+use crate::{
+ db::{Document, Workspace},
+ syntax::{
+ bibtex::{self, HasName, HasType},
+ latex,
+ },
+ util::{
+ capabilities::ClientCapabilitiesExt,
+ cursor::{Cursor, CursorContext},
+ lang_data::{BibtexEntryTypeCategory, BibtexEntryTypeDoc, BibtexFieldDoc, LANGUAGE_DATA},
+ line_index_ext::LineIndexExt,
+ lsp_enums::Structure,
+ },
+};
+
+use super::COMPLETION_LIMIT;
+
+pub struct CompletionBuilder<'db> {
+ context: &'db CursorContext<'db>,
+ items: Vec<Item<'db>>,
+ matcher: SkimMatcherV2,
+ text_pattern: String,
+ file_pattern: String,
+ preselect: Option<String>,
+ snippets: bool,
+ markdown: bool,
+ item_kinds: &'db [CompletionItemKind],
+}
+
+impl<'db> CompletionBuilder<'db> {
+ pub fn new(context: &'db CursorContext) -> Self {
+ let items = Vec::new();
+ let matcher = SkimMatcherV2::default().ignore_case();
+ let text_pattern = match &context.cursor {
+ Cursor::Tex(token) if token.kind().is_command_name() => {
+ if token.text_range().start() + TextSize::from(1) == context.offset {
+ // Handle cases similar to this one correctly:
+ // $\|$ % (| is the cursor)
+ String::from("\\")
+ } else {
+ token.text().trim_end().into()
+ }
+ }
+ Cursor::Tex(token) if token.kind() == latex::WORD => {
+ match token.parent().and_then(latex::Key::cast) {
+ Some(key) => key
+ .words()
+ .take_while(|word| word.text_range() != token.text_range())
+ .chain(std::iter::once(token.clone()))
+ .filter(|word| word.text_range().start() < context.offset)
+ .join(" ")
+ .into(),
+ None => token.text().into(),
+ }
+ }
+ Cursor::Bib(token)
+ if matches!(
+ token.kind(),
+ bibtex::TYPE
+ | bibtex::NAME
+ | bibtex::WORD
+ | bibtex::COMMAND_NAME
+ | bibtex::ACCENT_NAME
+ ) =>
+ {
+ token.text().into()
+ }
+ Cursor::Tex(_) | Cursor::Bib(_) | Cursor::Nothing => "".into(),
+ };
+
+ let file_pattern = text_pattern.split('/').last().unwrap().to_string();
+
+ let preselect = context
+ .cursor
+ .as_tex()
+ .and_then(|name| name.parent())
+ .and_then(latex::CurlyGroupWord::cast)
+ .and_then(|group| group.syntax().parent())
+ .and_then(|end| end.parent())
+ .and_then(latex::Environment::cast)
+ .and_then(|env| env.begin())
+ .and_then(|begin| begin.name())
+ .and_then(|name| name.key())
+ .map(|name| name.to_string());
+
+ let client_capabilities = context.workspace.client_capabilities(context.db);
+ let snippets = client_capabilities.has_snippet_support();
+ let markdown = client_capabilities.has_completion_markdown_support();
+ let item_kinds = client_capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.completion.as_ref())
+ .and_then(|cap| cap.completion_item_kind.as_ref())
+ .and_then(|cap| cap.value_set.as_deref())
+ .unwrap_or_default();
+
+ Self {
+ context,
+ items,
+ matcher,
+ text_pattern,
+ file_pattern,
+ preselect,
+ snippets,
+ markdown,
+ item_kinds,
+ }
+ }
+
+ pub fn glossary_entry(&mut self, range: TextRange, name: String) -> Option<()> {
+ let score = self.matcher.fuzzy_match(&name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::GlossaryEntry { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn generic_argument(
+ &mut self,
+ range: TextRange,
+ name: &'db str,
+ image: Option<&'db str>,
+ ) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Argument { name, image },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn begin_snippet(&mut self, range: TextRange) -> Option<()> {
+ let capabilities = Workspace::get(self.context.db).client_capabilities(self.context.db);
+ if capabilities.has_snippet_support() {
+ let score = self.matcher.fuzzy_match("begin", &self.text_pattern[1..])?;
+ self.items.push(Item {
+ range,
+ data: Data::BeginSnippet,
+ preselect: false,
+ score,
+ });
+ }
+
+ Some(())
+ }
+
+ pub fn citation(
+ &mut self,
+ range: TextRange,
+ document: Document,
+ entry: &bibtex::Entry,
+ ) -> Option<()> {
+ let key = entry.name_token()?.to_string();
+
+ let category = LANGUAGE_DATA
+ .find_entry_type(&entry.type_token()?.text()[1..])
+ .map_or(BibtexEntryTypeCategory::Misc, |ty| ty.category);
+
+ let code = entry.syntax().text().to_string();
+ let filter_text = format!(
+ "{} {}",
+ key,
+ WHITESPACE_REGEX
+ .replace_all(
+ &code
+ .replace('{', " ")
+ .replace('}', " ")
+ .replace(',', " ")
+ .replace('=', " "),
+ " "
+ )
+ .trim(),
+ );
+
+ let score = self.matcher.fuzzy_match(&filter_text, &self.text_pattern)?;
+
+ let data = Data::Citation {
+ document,
+ key,
+ filter_text,
+ category,
+ };
+
+ self.items.push(Item {
+ range,
+ data,
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn color_model(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::ColorModel { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn color(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Color { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn component_command(
+ &mut self,
+ range: TextRange,
+ name: &'db str,
+ image: Option<&'db str>,
+ glyph: Option<&'db str>,
+ file_names: &'db [SmolStr],
+ ) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern[1..])?;
+ let data = Data::ComponentCommand {
+ name,
+ image,
+ glyph,
+ file_names,
+ };
+
+ self.items.push(Item {
+ range,
+ data,
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn component_environment(
+ &mut self,
+ range: TextRange,
+ name: &'db str,
+ file_names: &'db [SmolStr],
+ ) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::ComponentEnvironment { name, file_names },
+ preselect: Some(name) == self.preselect.as_deref(),
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn entry_type(
+ &mut self,
+ range: TextRange,
+ entry_type: &'db BibtexEntryTypeDoc,
+ ) -> Option<()> {
+ let score = self
+ .matcher
+ .fuzzy_match(&entry_type.name, &self.text_pattern[1..])?;
+
+ self.items.push(Item {
+ range,
+ data: Data::EntryType { entry_type },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn field(&mut self, range: TextRange, field: &'db BibtexFieldDoc) -> Option<()> {
+ let score = self.matcher.fuzzy_match(&field.name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Field { field },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn class(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Class { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn package(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Package { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn file(&mut self, range: TextRange, name: String) -> Option<()> {
+ let score = self.matcher.fuzzy_match(&name, &self.file_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::File { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn directory(&mut self, range: TextRange, name: String) -> Option<()> {
+ let score = self.matcher.fuzzy_match(&name, &self.file_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Directory { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn label(
+ &mut self,
+ range: TextRange,
+ name: &'db str,
+ kind: Structure,
+ header: Option<String>,
+ footer: Option<String>,
+ text: String,
+ ) -> Option<()> {
+ let score = self.matcher.fuzzy_match(&text, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::Label {
+ name,
+ kind,
+ header,
+ footer,
+ text,
+ },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn tikz_library(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::TikzLibrary { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn user_command(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern[1..])?;
+ self.items.push(Item {
+ range,
+ data: Data::UserCommand { name },
+ preselect: false,
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn user_environment(&mut self, range: TextRange, name: &'db str) -> Option<()> {
+ let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ self.items.push(Item {
+ range,
+ data: Data::UserEnvironment { name },
+ preselect: Some(name) == self.preselect.as_deref(),
+ score,
+ });
+
+ Some(())
+ }
+
+ pub fn finish(mut self) -> CompletionList {
+ let mut list = CompletionList::default();
+ list.items = std::mem::take(&mut self.items)
+ .into_iter()
+ .sorted_by(|a, b| {
+ b.preselect
+ .cmp(&a.preselect)
+ .then_with(|| b.score.cmp(&a.score))
+ .then_with(|| a.data.label().cmp(b.data.label()))
+ })
+ .dedup_by(|a, b| a.data.label() == b.data.label())
+ .take(COMPLETION_LIMIT)
+ .enumerate()
+ .map(|(i, item)| self.convert_item(item, i))
+ .collect();
+
+ list.is_incomplete = if self
+ .context
+ .workspace
+ .client_info(self.context.db)
+ .as_ref()
+ .map_or(false, |client| client.name.as_str() == "Visual Studio Code")
+ {
+ true
+ } else {
+ list.items.len() >= COMPLETION_LIMIT
+ };
+
+ list
+ }
+
+ fn convert_item(&self, item: Item, index: usize) -> CompletionItem {
+ let range = self.context.line_index.line_col_lsp_range(item.range);
+ let preselect = item.preselect;
+ let mut item = match item.data {
+ Data::EntryType { entry_type } => CompletionItem {
+ label: entry_type.name.clone(),
+ kind: Some(Structure::Entry(entry_type.category).completion_kind()),
+ documentation: entry_type.documentation.clone().map(|value| {
+ let kind = MarkupKind::Markdown;
+ Documentation::MarkupContent(MarkupContent { kind, value })
+ }),
+ text_edit: Some(TextEdit::new(range, entry_type.name.clone()).into()),
+ ..CompletionItem::default()
+ },
+ Data::Field { field } => CompletionItem {
+ label: field.name.clone(),
+ kind: Some(Structure::Field.completion_kind()),
+ documentation: Some(Documentation::MarkupContent(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value: field.documentation.clone(),
+ })),
+ text_edit: Some(TextEdit::new(range, field.name.clone()).into()),
+ ..CompletionItem::default()
+ },
+ Data::Argument { name, image } => {
+ let text_edit = TextEdit::new(range, String::from(name));
+ CompletionItem {
+ label: name.into(),
+ kind: Some(Structure::Argument.completion_kind()),
+ text_edit: Some(CompletionTextEdit::Edit(text_edit)),
+ documentation: image.and_then(|base64| self.inline_image(name, base64)),
+ ..CompletionItem::default()
+ }
+ }
+ Data::BeginSnippet => {
+ if self.snippets {
+ CompletionItem {
+ kind: Some(Structure::Snippet.completion_kind()),
+ text_edit: Some(
+ TextEdit::new(range, "begin{$1}\n\t\n\\end{$1}".into()).into(),
+ ),
+ insert_text_format: Some(InsertTextFormat::SNIPPET),
+ ..CompletionItem::new_simple("begin".into(), self.component_detail(&[]))
+ }
+ } else {
+ CompletionItem {
+ kind: Some(Structure::Command.completion_kind()),
+ text_edit: Some(TextEdit::new(range, "begin".to_string()).into()),
+ ..CompletionItem::new_simple("begin".into(), self.component_detail(&[]))
+ }
+ }
+ }
+ Data::Citation {
+ document,
+ key,
+ filter_text,
+ category,
+ } => CompletionItem {
+ label: key.clone(),
+ kind: Some(Structure::Entry(category).completion_kind()),
+ filter_text: Some(filter_text.clone()),
+ sort_text: Some(filter_text),
+ data: Some(
+ serde_json::to_value(CompletionItemData::Citation {
+ uri: document
+ .location(self.context.db)
+ .uri(self.context.db)
+ .clone(),
+ key: key.clone(),
+ })
+ .unwrap(),
+ ),
+ text_edit: Some(TextEdit::new(range, key).into()),
+ ..CompletionItem::default()
+ },
+ Data::ComponentCommand {
+ name,
+ image,
+ glyph,
+ file_names,
+ } => CompletionItem {
+ label: name.into(),
+ detail: Some(glyph.map_or_else(
+ || self.component_detail(file_names),
+ |glyph| format!("{}, {}", glyph, self.component_detail(file_names)),
+ )),
+ kind: Some(Structure::Command.completion_kind()),
+ documentation: image.and_then(|base64| self.inline_image(name, base64)),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ Data::ComponentEnvironment { name, file_names } => CompletionItem {
+ kind: Some(Structure::Environment.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::new_simple(name.into(), self.component_detail(file_names))
+ },
+ Data::Class { name } => CompletionItem {
+ label: name.into(),
+ kind: Some(Structure::Class.completion_kind()),
+ data: Some(serde_json::to_value(CompletionItemData::Package).unwrap()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ Data::Package { name } => CompletionItem {
+ label: name.into(),
+ kind: Some(Structure::Package.completion_kind()),
+ data: Some(serde_json::to_value(CompletionItemData::Class).unwrap()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ Data::Color { name } => CompletionItem {
+ label: name.into(),
+ kind: Some(Structure::Color.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ Data::ColorModel { name } => CompletionItem {
+ label: name.into(),
+ kind: Some(Structure::ColorModel.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ Data::GlossaryEntry { name } => CompletionItem {
+ label: name.clone(),
+ kind: Some(Structure::GlossaryEntry.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name).into()),
+ ..CompletionItem::default()
+ },
+ Data::File { name } => CompletionItem {
+ label: name.clone(),
+ kind: Some(Structure::File.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name).into()),
+ ..CompletionItem::default()
+ },
+ Data::Directory { name } => CompletionItem {
+ label: name.clone(),
+ kind: Some(Structure::Folder.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name).into()),
+ ..CompletionItem::default()
+ },
+ Data::Label {
+ name,
+ kind,
+ header,
+ footer,
+ text,
+ } => CompletionItem {
+ label: name.into(),
+ kind: Some(kind.completion_kind()),
+ detail: header,
+ documentation: footer.map(Documentation::String),
+ sort_text: Some(text.clone()),
+ filter_text: Some(text),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ Data::UserCommand { name } => {
+ let detail = "user-defined".into();
+ let name = &name[1..];
+ CompletionItem {
+ kind: Some(Structure::Command.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::new_simple(name.into(), detail)
+ }
+ }
+ Data::UserEnvironment { name } => {
+ let detail = "user-defined".into();
+ CompletionItem {
+ kind: Some(Structure::Environment.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::new_simple(name.into(), detail)
+ }
+ }
+ Data::TikzLibrary { name } => CompletionItem {
+ label: name.into(),
+ kind: Some(Structure::TikzLibrary.completion_kind()),
+ text_edit: Some(TextEdit::new(range, name.into()).into()),
+ ..CompletionItem::default()
+ },
+ };
+
+ item.preselect = Some(preselect);
+
+ if !self.item_kinds.contains(&item.kind.unwrap()) {
+ item.kind = Some(CompletionItemKind::TEXT);
+ }
+
+ let sort_prefix = format!("{:0>2}", index);
+ match &item.sort_text {
+ Some(sort_text) => {
+ item.sort_text = Some(format!("{} {}", sort_prefix, sort_text));
+ }
+ None => {
+ item.sort_text = Some(sort_prefix);
+ }
+ };
+
+ item
+ }
+
+ fn inline_image(&self, name: &str, base64: &str) -> Option<Documentation> {
+ if self.markdown {
+ let kind = MarkupKind::Markdown;
+ let value = format!(
+ "![{}](data:image/png;base64,{}|width=48,height=48)",
+ name, base64
+ );
+
+ Some(Documentation::MarkupContent(MarkupContent { kind, value }))
+ } else {
+ None
+ }
+ }
+
+ fn component_detail(&self, file_names: &[SmolStr]) -> String {
+ if file_names.is_empty() {
+ "built-in".into()
+ } else {
+ file_names.join(", ")
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Item<'db> {
+ range: TextRange,
+ data: Data<'db>,
+ preselect: bool,
+ score: i32,
+}
+
+#[derive(Debug, Clone)]
+enum Data<'db> {
+ EntryType {
+ entry_type: &'db BibtexEntryTypeDoc,
+ },
+ Field {
+ field: &'db BibtexFieldDoc,
+ },
+ Argument {
+ name: &'db str,
+ image: Option<&'db str>,
+ },
+ BeginSnippet,
+ Citation {
+ document: Document,
+ key: String,
+ filter_text: String,
+ category: BibtexEntryTypeCategory,
+ },
+ ComponentCommand {
+ name: &'db str,
+ image: Option<&'db str>,
+ glyph: Option<&'db str>,
+ file_names: &'db [SmolStr],
+ },
+ ComponentEnvironment {
+ name: &'db str,
+ file_names: &'db [SmolStr],
+ },
+ Class {
+ name: &'db str,
+ },
+ Package {
+ name: &'db str,
+ },
+ Color {
+ name: &'db str,
+ },
+ ColorModel {
+ name: &'db str,
+ },
+ GlossaryEntry {
+ name: String,
+ },
+ File {
+ name: String,
+ },
+ Directory {
+ name: String,
+ },
+ Label {
+ name: &'db str,
+ kind: Structure,
+ header: Option<String>,
+ footer: Option<String>,
+ text: String,
+ },
+ UserCommand {
+ name: &'db str,
+ },
+ UserEnvironment {
+ name: &'db str,
+ },
+ TikzLibrary {
+ name: &'db str,
+ },
+}
+
+impl<'db> Data<'db> {
+ pub fn label<'this: 'db>(&'this self) -> &'db str {
+ match self {
+ Self::EntryType { entry_type } => &entry_type.name,
+ Self::Field { field } => &field.name,
+ Self::Argument { name, .. } => name,
+ Self::BeginSnippet => "begin",
+ Self::Citation { key, .. } => key,
+ Self::ComponentCommand { name, .. } => name,
+ Self::ComponentEnvironment { name, .. } => name,
+ Self::Class { name } => name,
+ Self::Package { name } => name,
+ Self::Color { name } => name,
+ Self::ColorModel { name } => name,
+ Self::GlossaryEntry { name } => name,
+ Self::File { name } => name,
+ Self::Directory { name } => name,
+ Self::Label { name, .. } => name,
+ Self::UserCommand { name } => name,
+ Self::UserEnvironment { name } => name,
+ Self::TikzLibrary { name } => name,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) enum CompletionItemData {
+ Package,
+ Class,
+ Citation { uri: Url, key: String },
+}
+
+static WHITESPACE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\s+").unwrap());
diff --git a/support/texlab/src/features/completion/citation.rs b/support/texlab/src/features/completion/citation.rs
index e6436689d7..68d72717a7 100644
--- a/support/texlab/src/features/completion/citation.rs
+++ b/support/texlab/src/features/completion/citation.rs
@@ -1,26 +1,17 @@
-use std::sync::Arc;
-
-use lsp_types::CompletionParams;
-use once_cell::sync::Lazy;
-use regex::Regex;
use rowan::{ast::AstNode, TextRange};
use crate::{
- features::{cursor::CursorContext, lsp_kinds::Structure},
- syntax::{
- bibtex::{self, HasName, HasType},
- latex,
- },
- BibtexEntryTypeCategory, Document, LANGUAGE_DATA,
+ syntax::{bibtex, latex},
+ util::cursor::CursorContext,
};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_citations<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
- let token = context.cursor.as_latex()?;
+ let token = context.cursor.as_tex()?;
let range = if token.kind() == latex::WORD {
latex::Key::cast(token.parent()?)
@@ -36,15 +27,14 @@ pub fn complete_citations<'a>(
};
check_citation(context).or_else(|| check_acronym(context))?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_bibtex() {
- for entry in bibtex::SyntaxNode::new_root(data.green.clone())
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_bib() {
+ for entry in data
+ .root(context.db)
.children()
.filter_map(bibtex::Entry::cast)
{
- if let Some(item) = make_item(document, &entry, range) {
- items.push(item);
- }
+ builder.citation(range, document, &entry);
}
}
}
@@ -52,14 +42,14 @@ pub fn complete_citations<'a>(
Some(())
}
-fn check_citation(context: &CursorContext<CompletionParams>) -> Option<()> {
+fn check_citation(context: &CursorContext) -> Option<()> {
let (_, _, group) = context.find_curly_group_word_list()?;
latex::Citation::cast(group.syntax().parent()?)?;
Some(())
}
-fn check_acronym(context: &CursorContext<CompletionParams>) -> Option<()> {
- let token = context.cursor.as_latex()?;
+fn check_acronym(context: &CursorContext) -> Option<()> {
+ let token = context.cursor.as_tex()?;
let pair = token
.parent_ancestors()
@@ -71,45 +61,3 @@ fn check_acronym(context: &CursorContext<CompletionParams>) -> Option<()> {
latex::AcronymDeclaration::cast(pair.syntax().parent()?.parent()?.parent()?)?;
Some(())
}
-
-fn make_item<'a>(
- document: &'a Document,
- entry: &bibtex::Entry,
- range: TextRange,
-) -> Option<InternalCompletionItem<'a>> {
- let key = entry.name_token()?.to_string();
- let ty = LANGUAGE_DATA
- .find_entry_type(&entry.type_token()?.text()[1..])
- .map_or_else(
- || Structure::Entry(BibtexEntryTypeCategory::Misc),
- |ty| Structure::Entry(ty.category),
- );
-
- let entry_code = entry.syntax().text().to_string();
- let text = format!(
- "{} {}",
- key,
- WHITESPACE_REGEX
- .replace_all(
- &entry_code
- .replace('{', " ")
- .replace('}', " ")
- .replace(',', " ")
- .replace('=', " "),
- " "
- )
- .trim(),
- );
-
- Some(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Citation {
- uri: Arc::clone(&document.uri),
- key,
- text,
- ty,
- },
- ))
-}
-
-static WHITESPACE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\s+").unwrap());
diff --git a/support/texlab/src/features/completion/color.rs b/support/texlab/src/features/completion/color.rs
index a9a385c3c9..cce6517cb4 100644
--- a/support/texlab/src/features/completion/color.rs
+++ b/support/texlab/src/features/completion/color.rs
@@ -1,22 +1,21 @@
-use lsp_types::CompletionParams;
use rowan::ast::AstNode;
-use crate::{features::cursor::CursorContext, syntax::latex, LANGUAGE_DATA};
+use crate::{
+ syntax::latex,
+ util::{cursor::CursorContext, lang_data::LANGUAGE_DATA},
+};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_colors<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range, group) = context.find_curly_group_word()?;
latex::ColorReference::cast(group.syntax().parent()?)?;
for name in &LANGUAGE_DATA.colors {
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Color { name },
- ));
+ builder.color(range, name);
}
Some(())
diff --git a/support/texlab/src/features/completion/color_model.rs b/support/texlab/src/features/completion/color_model.rs
index cd582d0031..af700a96fb 100644
--- a/support/texlab/src/features/completion/color_model.rs
+++ b/support/texlab/src/features/completion/color_model.rs
@@ -1,29 +1,25 @@
-use lsp_types::CompletionParams;
use rowan::{ast::AstNode, TextRange};
-use crate::{features::cursor::CursorContext, syntax::latex};
+use crate::{syntax::latex, util::cursor::CursorContext};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
const MODEL_NAMES: &[&str] = &["gray", "rgb", "RGB", "HTML", "cmyk"];
-pub fn complete_color_models<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let range = check_color_definition(context).or_else(|| check_color_definition_set(context))?;
for name in MODEL_NAMES {
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::ColorModel { name },
- ));
+ builder.color_model(range, name);
}
Some(())
}
-fn check_color_definition(context: &CursorContext<CompletionParams>) -> Option<TextRange> {
+fn check_color_definition(context: &CursorContext) -> Option<TextRange> {
let (_, range, group) = context.find_curly_group_word()?;
let definition = latex::ColorDefinition::cast(group.syntax().parent()?)?;
@@ -33,7 +29,7 @@ fn check_color_definition(context: &CursorContext<CompletionParams>) -> Option<T
Some(range)
}
-fn check_color_definition_set(context: &CursorContext<CompletionParams>) -> Option<TextRange> {
+fn check_color_definition_set(context: &CursorContext) -> Option<TextRange> {
let (_, range, group) = context.find_curly_group_word_list()?;
let definition = latex::ColorSetDefinition::cast(group.syntax().parent()?)?;
definition
diff --git a/support/texlab/src/features/completion/component_command.rs b/support/texlab/src/features/completion/component_command.rs
index 097b28633f..20cef73848 100644
--- a/support/texlab/src/features/completion/component_command.rs
+++ b/support/texlab/src/features/completion/component_command.rs
@@ -1,26 +1,22 @@
-use lsp_types::CompletionParams;
+use crate::util::{components::COMPONENT_DATABASE, cursor::CursorContext};
-use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext};
+use super::builder::CompletionBuilder;
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
-
-pub fn complete_component_commands<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let range = context.cursor.command_range(context.offset)?;
- for component in COMPONENT_DATABASE.linked_components(&context.request.workspace) {
+ for component in COMPONENT_DATABASE.linked_components(context.db, context.document) {
for command in &component.commands {
- items.push(InternalCompletionItem::new(
+ builder.component_command(
range,
- InternalCompletionItemData::ComponentCommand {
- name: &command.name,
- image: command.image.as_deref(),
- glyph: command.glyph.as_deref(),
- file_names: &component.file_names,
- },
- ));
+ &command.name,
+ command.image.as_deref(),
+ command.glyph.as_deref(),
+ &component.file_names,
+ );
}
}
diff --git a/support/texlab/src/features/completion/component_environment.rs b/support/texlab/src/features/completion/component_environment.rs
index a9478694bb..e58e319a46 100644
--- a/support/texlab/src/features/completion/component_environment.rs
+++ b/support/texlab/src/features/completion/component_environment.rs
@@ -1,24 +1,16 @@
-use lsp_types::CompletionParams;
+use crate::util::{components::COMPONENT_DATABASE, cursor::CursorContext};
-use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext};
+use super::builder::CompletionBuilder;
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
-
-pub fn complete_component_environments<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range) = context.find_environment_name()?;
- for component in COMPONENT_DATABASE.linked_components(&context.request.workspace) {
+ for component in COMPONENT_DATABASE.linked_components(context.db, context.document) {
for name in &component.environments {
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::ComponentEnvironment {
- name,
- file_names: &component.file_names,
- },
- ));
+ builder.component_environment(range, name, &component.file_names);
}
}
diff --git a/support/texlab/src/features/completion/entry_type.rs b/support/texlab/src/features/completion/entry_type.rs
index 519737ed08..38bc0e3f3a 100644
--- a/support/texlab/src/features/completion/entry_type.rs
+++ b/support/texlab/src/features/completion/entry_type.rs
@@ -1,26 +1,26 @@
-use lsp_types::CompletionParams;
use rowan::{TextRange, TextSize};
-use crate::{features::cursor::CursorContext, syntax::bibtex, LANGUAGE_DATA};
+use crate::{
+ syntax::bibtex,
+ util::{cursor::CursorContext, lang_data::LANGUAGE_DATA},
+};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_entry_types<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let range = context
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::TYPE)
.map(bibtex::SyntaxToken::text_range)
.filter(|range| range.start() != context.offset)
.map(|range| TextRange::new(range.start() + TextSize::from(1), range.end()))?;
- for ty in &LANGUAGE_DATA.entry_types {
- let data = InternalCompletionItemData::EntryType { ty };
- let item = InternalCompletionItem::new(range, data);
- items.push(item);
+ for entry_type in &LANGUAGE_DATA.entry_types {
+ builder.entry_type(range, entry_type);
}
Some(())
diff --git a/support/texlab/src/features/completion/field.rs b/support/texlab/src/features/completion/field.rs
index 4f386ed1b1..ff6e54a562 100644
--- a/support/texlab/src/features/completion/field.rs
+++ b/support/texlab/src/features/completion/field.rs
@@ -1,19 +1,17 @@
-use lsp_types::CompletionParams;
use rowan::{ast::AstNode, TextRange};
use crate::{
- features::cursor::CursorContext,
syntax::bibtex::{self, HasName},
- LANGUAGE_DATA,
+ util::{cursor::CursorContext, lang_data::LANGUAGE_DATA},
};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_fields<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
- let token = context.cursor.as_bibtex()?;
+ let token = context.cursor.as_bib()?;
let range = if token.kind() == bibtex::NAME {
token.text_range()
@@ -31,9 +29,8 @@ pub fn complete_fields<'a>(
}
for field in &LANGUAGE_DATA.fields {
- let data = InternalCompletionItemData::Field { field };
- let item = InternalCompletionItem::new(range, data);
- items.push(item);
+ builder.field(range, field);
}
+
Some(())
}
diff --git a/support/texlab/src/features/completion/glossary_ref.rs b/support/texlab/src/features/completion/glossary_ref.rs
index 6223079fdf..34d53bf24d 100644
--- a/support/texlab/src/features/completion/glossary_ref.rs
+++ b/support/texlab/src/features/completion/glossary_ref.rs
@@ -1,38 +1,31 @@
-use lsp_types::CompletionParams;
use rowan::ast::AstNode;
-use crate::{features::cursor::CursorContext, syntax::latex};
+use crate::{syntax::latex, util::cursor::CursorContext};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_glossary_entries<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range, group) = context.find_curly_group_word()?;
latex::GlossaryEntryReference::cast(group.syntax().parent()?)?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() {
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
+ for node in data.root(context.db).descendants() {
if let Some(name) = latex::GlossaryEntryDefinition::cast(node.clone())
.and_then(|entry| entry.name())
.and_then(|name| name.key())
.map(|name| name.to_string())
{
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::GlossaryEntry { name },
- ));
+ builder.glossary_entry(range, name);
} else if let Some(name) = latex::AcronymDefinition::cast(node)
.and_then(|entry| entry.name())
.and_then(|name| name.key())
.map(|name| name.to_string())
{
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Acronym { name },
- ));
+ builder.glossary_entry(range, name);
}
}
}
diff --git a/support/texlab/src/features/completion/import.rs b/support/texlab/src/features/completion/import.rs
index 9cb35b68a7..bf6da78a43 100644
--- a/support/texlab/src/features/completion/import.rs
+++ b/support/texlab/src/features/completion/import.rs
@@ -1,31 +1,24 @@
-use lsp_types::CompletionParams;
use rowan::ast::AstNode;
use rustc_hash::FxHashSet;
-use smol_str::SmolStr;
-use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext, syntax::latex};
+use crate::{
+ syntax::latex,
+ util::{components::COMPONENT_DATABASE, cursor::CursorContext},
+};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_imports<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range, group) = context.find_curly_group_word_list()?;
- let (extension, mut factory): (
- &str,
- Box<dyn FnMut(SmolStr) -> InternalCompletionItemData<'a>>,
- ) = match group.syntax().parent()?.kind() {
- latex::PACKAGE_INCLUDE => (
- "sty",
- Box::new(|name| InternalCompletionItemData::Package { name }),
- ),
- latex::CLASS_INCLUDE => (
- "cls",
- Box::new(|name| InternalCompletionItemData::Class { name }),
- ),
- _ => return None,
+ let kind = group.syntax().parent()?.kind();
+ let extension = match kind {
+ latex::PACKAGE_INCLUDE => "sty",
+ latex::CLASS_INCLUDE => "cls",
+ _ => return Some(()),
};
let mut file_names = FxHashSet::default();
@@ -35,21 +28,27 @@ pub fn complete_imports<'a>(
.flat_map(|comp| comp.file_names.iter())
.filter(|file_name| file_name.ends_with(extension))
{
- file_names.insert(file_name);
+ file_names.insert(file_name.as_str());
let stem = &file_name[0..file_name.len() - 4];
- let data = factory(stem.into());
- items.push(InternalCompletionItem::new(range, data));
+ if kind == latex::PACKAGE_INCLUDE {
+ builder.package(range, stem);
+ } else {
+ builder.class(range, stem);
+ }
}
- let resolver = &context.request.workspace.environment.resolver;
- for file_name in resolver
- .files_by_name
- .keys()
+ let file_name_db = context.workspace.file_name_db(context.db);
+ for file_name in file_name_db
+ .iter()
+ .map(|(file_name, _)| file_name)
.filter(|file_name| file_name.ends_with(extension) && !file_names.contains(file_name))
{
let stem = &file_name[0..file_name.len() - 4];
- let data = factory(stem.into());
- items.push(InternalCompletionItem::new(range, data));
+ if kind == latex::PACKAGE_INCLUDE {
+ builder.package(range, stem);
+ } else {
+ builder.class(range, stem);
+ }
}
Some(())
diff --git a/support/texlab/src/features/completion/include.rs b/support/texlab/src/features/completion/include.rs
index d851aca820..bde7544570 100644
--- a/support/texlab/src/features/completion/include.rs
+++ b/support/texlab/src/features/completion/include.rs
@@ -4,18 +4,22 @@ use std::{
path::{Path, PathBuf},
};
-use lsp_types::CompletionParams;
use rowan::{ast::AstNode, TextRange, TextSize};
-use crate::{features::cursor::CursorContext, syntax::latex};
+use crate::{syntax::latex, util::cursor::CursorContext};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_includes<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
- if context.request.main_document().uri.scheme() != "file" {
+ if context
+ .document
+ .location(context.db)
+ .path(context.db)
+ .is_none()
+ {
return None;
}
@@ -51,10 +55,15 @@ pub fn complete_includes<'a>(
let mut dirs = vec![current_dir(context, &path_text, None)];
if include.kind() == latex::GRAPHICS_INCLUDE {
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- for graphics_path in &data.extras.graphics_paths {
- dirs.push(current_dir(context, &path_text, Some(graphics_path)));
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
+ for path in data
+ .analyze(context.db)
+ .graphics_paths(context.db)
+ .iter()
+ .map(|node| node.path(context.db))
+ {
+ dirs.push(current_dir(context, &path_text, Some(path)));
}
}
}
@@ -74,15 +83,12 @@ pub fn complete_includes<'a>(
if !include_extension {
remove_extension(&mut path);
}
+
let name = path.file_name()?.to_str()?.into();
- let data = InternalCompletionItemData::File { name };
- let item = InternalCompletionItem::new(segment_range, data);
- items.push(item);
+ builder.file(segment_range, name);
} else if file_type.is_dir() {
let name = path.file_name()?.to_str()?.into();
- let data = InternalCompletionItemData::Directory { name };
- let item = InternalCompletionItem::new(segment_range, data);
- items.push(item);
+ builder.directory(segment_range, name);
}
}
@@ -90,34 +96,24 @@ pub fn complete_includes<'a>(
}
fn current_dir(
- context: &CursorContext<CompletionParams>,
+ context: &CursorContext,
path_text: &str,
graphics_path: Option<&str>,
) -> Option<PathBuf> {
- let mut path = context
- .request
+ let parent = context
.workspace
- .environment
- .options
- .root_directory
- .as_ref()
- .map_or_else(
- || {
- let mut path = context.request.main_document().uri.to_file_path().unwrap();
- path.pop();
- path
- },
- |root_directory| {
- context
- .request
- .workspace
- .environment
- .current_directory
- .join(root_directory)
- },
- );
-
- path = PathBuf::from(path.to_str()?.replace('\\', "/"));
+ .parents(context.db, context.document)
+ .iter()
+ .next()
+ .map_or(context.document, Clone::clone);
+
+ let path = context
+ .workspace
+ .working_dir(context.db, parent.directory(context.db))
+ .path(context.db)
+ .as_deref()?;
+
+ let mut path = PathBuf::from(path.to_str()?.replace('\\', "/"));
if !path_text.is_empty() {
if let Some(graphics_path) = graphics_path {
path.push(graphics_path);
diff --git a/support/texlab/src/features/completion/label.rs b/support/texlab/src/features/completion/label.rs
index b662f98c8a..ae3e010b0e 100644
--- a/support/texlab/src/features/completion/label.rs
+++ b/support/texlab/src/features/completion/label.rs
@@ -1,83 +1,61 @@
-use lsp_types::CompletionParams;
use rowan::{ast::AstNode, TextRange};
use crate::{
- features::{cursor::CursorContext, lsp_kinds::Structure},
- render_label,
syntax::latex,
- LabelledObject,
+ util::{self, cursor::CursorContext, label::LabeledObject, lsp_enums::Structure},
};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_labels<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (range, is_math) = find_reference(context).or_else(|| find_reference_range(context))?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- for label in latex::SyntaxNode::new_root(data.green.clone())
- .descendants()
- .filter_map(latex::LabelDefinition::cast)
+ let db = context.db;
+ for document in context.related() {
+ if let Some(data) = document.parse(db).as_tex() {
+ for label in data
+ .analyze(db)
+ .labels(db)
+ .iter()
+ .filter(|label| label.origin(db).as_definition().is_some())
{
- if let Some(name) = label
- .name()
- .and_then(|name| name.key())
- .map(|name| name.to_string())
- {
- match render_label(&context.request.workspace, &name, Some(label)) {
- Some(rendered_label) => {
- let kind = match &rendered_label.object {
- LabelledObject::Section { .. } => Structure::Section,
- LabelledObject::Float { .. } => Structure::Float,
- LabelledObject::Theorem { .. } => Structure::Theorem,
- LabelledObject::Equation => Structure::Equation,
- LabelledObject::EnumItem => Structure::Item,
- };
+ match util::label::render(db, document, *label) {
+ Some(rendered_label) => {
+ let kind = match &rendered_label.object {
+ LabeledObject::Section { .. } => Structure::Section,
+ LabeledObject::Float { .. } => Structure::Float,
+ LabeledObject::Theorem { .. } => Structure::Theorem,
+ LabeledObject::Equation => Structure::Equation,
+ LabeledObject::EnumItem => Structure::Item,
+ };
- if is_math && kind != Structure::Equation {
- continue;
- }
+ if is_math && kind != Structure::Equation {
+ continue;
+ }
- let header = rendered_label.detail();
- let footer = match &rendered_label.object {
- LabelledObject::Float { caption, .. } => Some(caption.clone()),
- _ => None,
- };
+ let header = rendered_label.detail(db);
+ let footer = match &rendered_label.object {
+ LabeledObject::Float { caption, .. } => Some(caption.clone()),
+ _ => None,
+ };
- let text = format!("{} {}", name, rendered_label.reference());
+ let text = format!(
+ "{} {}",
+ label.name(db).text(db),
+ rendered_label.reference(db)
+ );
- let item = InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Label {
- name,
- kind,
- header,
- footer,
- text,
- },
- );
- items.push(item);
- }
- None => {
- let kind = Structure::Label;
- let header = None;
- let footer = None;
- let text = name.to_string();
- let item = InternalCompletionItem::new(
- range,
- InternalCompletionItemData::Label {
- name,
- kind,
- header,
- footer,
- text,
- },
- );
- items.push(item);
- }
+ builder.label(range, label.name(db).text(db), kind, header, footer, text);
+ }
+ None => {
+ let kind = Structure::Label;
+ let header = None;
+ let footer = None;
+ let text = label.name(db).text(db).clone();
+ builder.label(range, label.name(db).text(db), kind, header, footer, text);
}
}
}
@@ -87,14 +65,14 @@ pub fn complete_labels<'a>(
Some(())
}
-fn find_reference(context: &CursorContext<CompletionParams>) -> Option<(TextRange, bool)> {
+fn find_reference(context: &CursorContext) -> Option<(TextRange, bool)> {
let (_, range, group) = context.find_curly_group_word_list()?;
let reference = latex::LabelReference::cast(group.syntax().parent()?)?;
let is_math = reference.command()?.text() == "\\eqref";
Some((range, is_math))
}
-fn find_reference_range(context: &CursorContext<CompletionParams>) -> Option<(TextRange, bool)> {
+fn find_reference_range(context: &CursorContext) -> Option<(TextRange, bool)> {
let (_, range, group) = context.find_curly_group_word()?;
latex::LabelReferenceRange::cast(group.syntax().parent()?)?;
Some((range, false))
diff --git a/support/texlab/src/features/completion/theorem.rs b/support/texlab/src/features/completion/theorem.rs
index 65b511f7ad..cd0dfe3a84 100644
--- a/support/texlab/src/features/completion/theorem.rs
+++ b/support/texlab/src/features/completion/theorem.rs
@@ -1,24 +1,18 @@
-use lsp_types::CompletionParams;
+use crate::util::cursor::CursorContext;
-use crate::features::cursor::CursorContext;
+use super::builder::CompletionBuilder;
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
-
-pub fn complete_theorem_environments<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range) = context.find_environment_name()?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- for environment in &data.extras.theorem_environments {
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::UserEnvironment {
- name: environment.name.clone(),
- },
- ));
+ let db = context.db;
+ for document in context.related() {
+ if let Some(data) = document.parse(db).as_tex() {
+ for environment in data.analyze(db).theorem_environments(db) {
+ builder.user_environment(range, environment.name(db).text(db));
}
}
}
diff --git a/support/texlab/src/features/completion/tikz_library.rs b/support/texlab/src/features/completion/tikz_library.rs
index bf15695722..7ea26d50c2 100644
--- a/support/texlab/src/features/completion/tikz_library.rs
+++ b/support/texlab/src/features/completion/tikz_library.rs
@@ -1,13 +1,15 @@
-use lsp_types::CompletionParams;
use rowan::ast::AstNode;
-use crate::{features::cursor::CursorContext, syntax::latex, LANGUAGE_DATA};
+use crate::{
+ syntax::latex,
+ util::{cursor::CursorContext, lang_data::LANGUAGE_DATA},
+};
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
+use super::builder::CompletionBuilder;
-pub fn complete_tikz_libraries<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (_, range, group) = context.find_curly_group_word_list()?;
@@ -15,17 +17,11 @@ pub fn complete_tikz_libraries<'a>(
if import.command()?.text() == "\\usepgflibrary" {
for name in &LANGUAGE_DATA.pgf_libraries {
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::PgfLibrary { name },
- ));
+ builder.tikz_library(range, name);
}
} else {
for name in &LANGUAGE_DATA.tikz_libraries {
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::TikzLibrary { name },
- ));
+ builder.tikz_library(range, name);
}
}
diff --git a/support/texlab/src/features/completion/types.rs b/support/texlab/src/features/completion/types.rs
deleted file mode 100644
index 3655c6a074..0000000000
--- a/support/texlab/src/features/completion/types.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-use std::sync::Arc;
-
-use lsp_types::Url;
-use rowan::TextRange;
-use serde::{Deserialize, Serialize};
-use smol_str::SmolStr;
-
-use crate::{features::lsp_kinds::Structure, BibtexEntryTypeDoc, BibtexFieldDoc};
-
-#[derive(Debug, Clone)]
-pub struct InternalCompletionItem<'a> {
- pub range: TextRange,
- pub data: InternalCompletionItemData<'a>,
- pub preselect: bool,
- pub score: Option<i64>,
-}
-
-impl<'a> InternalCompletionItem<'a> {
- pub fn new(range: TextRange, data: InternalCompletionItemData<'a>) -> Self {
- Self {
- range,
- data,
- preselect: false,
- score: None,
- }
- }
-}
-
-#[derive(Debug, Clone)]
-pub enum InternalCompletionItemData<'a> {
- EntryType {
- ty: &'a BibtexEntryTypeDoc,
- },
- Field {
- field: &'a BibtexFieldDoc,
- },
- Argument {
- name: &'a str,
- image: Option<&'a str>,
- },
- BeginCommand,
- Citation {
- uri: Arc<Url>,
- key: String,
- text: String,
- ty: Structure,
- },
- ComponentCommand {
- name: &'a SmolStr,
- image: Option<&'a str>,
- glyph: Option<&'a str>,
- file_names: &'a [SmolStr],
- },
- ComponentEnvironment {
- name: &'a SmolStr,
- file_names: &'a [SmolStr],
- },
- Class {
- name: SmolStr,
- },
- Package {
- name: SmolStr,
- },
- Color {
- name: &'a str,
- },
- ColorModel {
- name: &'a str,
- },
- Acronym {
- name: String,
- },
- GlossaryEntry {
- name: String,
- },
- File {
- name: SmolStr,
- },
- Directory {
- name: SmolStr,
- },
- Label {
- name: String,
- kind: Structure,
- header: Option<String>,
- footer: Option<String>,
- text: String,
- },
- UserCommand {
- name: SmolStr,
- },
- UserEnvironment {
- name: String,
- },
- PgfLibrary {
- name: &'a str,
- },
- TikzLibrary {
- name: &'a str,
- },
-}
-
-impl<'a> InternalCompletionItemData<'a> {
- pub fn label<'b: 'a>(&'b self) -> &'a str {
- match self {
- Self::EntryType { ty } => &ty.name,
- Self::Field { field } => &field.name,
- Self::Argument { name, .. } => name,
- Self::BeginCommand => "begin",
- Self::Citation { key, .. } => key,
- Self::ComponentCommand { name, .. } => name,
- Self::ComponentEnvironment { name, .. } => name,
- Self::Class { name } => name,
- Self::Package { name } => name,
- Self::Color { name } => name,
- Self::ColorModel { name } => name,
- Self::Acronym { name } => name,
- Self::GlossaryEntry { name } => name,
- Self::File { name } => name,
- Self::Directory { name } => name,
- Self::Label { name, .. } => name,
- Self::UserCommand { name } => name,
- Self::UserEnvironment { name } => name,
- Self::PgfLibrary { name } => name,
- Self::TikzLibrary { name } => name,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum CompletionItemData {
- Command,
- CommandSnippet,
- Environment,
- Label,
- Folder,
- File,
- PgfLibrary,
- TikzLibrary,
- Color,
- ColorModel,
- Package,
- Class,
- EntryType,
- FieldName,
- Citation { uri: Url, key: SmolStr },
- Argument,
- Acronym,
- GlossaryEntry,
-}
diff --git a/support/texlab/src/features/completion/user_command.rs b/support/texlab/src/features/completion/user_command.rs
index 8c4d593bee..99d9883ee9 100644
--- a/support/texlab/src/features/completion/user_command.rs
+++ b/support/texlab/src/features/completion/user_command.rs
@@ -1,29 +1,27 @@
-use lsp_types::CompletionParams;
+use crate::util::cursor::CursorContext;
-use crate::features::cursor::CursorContext;
+use super::builder::CompletionBuilder;
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
-
-pub fn complete_user_commands<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let range = context.cursor.command_range(context.offset)?;
- let token = context.cursor.as_latex()?;
+ let token = context.cursor.as_tex()?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
+ let db = context.db;
+ for document in context.related() {
+ if let Some(data) = document.parse(db).as_tex() {
+ let text = document.contents(db).text(db);
for name in data
- .extras
- .command_names
+ .analyze(db)
+ .command_name_ranges(db)
.iter()
- .filter(|name| name.as_str() != token.text())
- .cloned()
+ .copied()
+ .filter(|range| *range != token.text_range())
+ .map(|range| &text[std::ops::Range::<usize>::from(range)])
{
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::UserCommand { name },
- ));
+ builder.user_command(range, name);
}
}
}
diff --git a/support/texlab/src/features/completion/user_environment.rs b/support/texlab/src/features/completion/user_environment.rs
index 91dd5a9ea2..cf6c2a7c78 100644
--- a/support/texlab/src/features/completion/user_environment.rs
+++ b/support/texlab/src/features/completion/user_environment.rs
@@ -1,28 +1,22 @@
-use lsp_types::CompletionParams;
+use crate::util::cursor::CursorContext;
-use crate::features::cursor::CursorContext;
+use super::builder::CompletionBuilder;
-use super::types::{InternalCompletionItem, InternalCompletionItemData};
-
-pub fn complete_user_environments<'a>(
- context: &'a CursorContext<CompletionParams>,
- items: &mut Vec<InternalCompletionItem<'a>>,
+pub fn complete<'db>(
+ context: &'db CursorContext,
+ builder: &mut CompletionBuilder<'db>,
) -> Option<()> {
let (name, range) = context.find_environment_name()?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
for name in data
- .extras
- .environment_names
+ .analyze(context.db)
+ .environment_names(context.db)
.iter()
.filter(|n| n.as_str() != name)
- .cloned()
{
- items.push(InternalCompletionItem::new(
- range,
- InternalCompletionItemData::UserEnvironment { name },
- ));
+ builder.user_environment(range, name);
}
}
}
diff --git a/support/texlab/src/features/completion/util.rs b/support/texlab/src/features/completion/util.rs
deleted file mode 100644
index 05827bbf24..0000000000
--- a/support/texlab/src/features/completion/util.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use lsp_types::{CompletionItemKind, CompletionParams, Documentation, MarkupContent, MarkupKind};
-use smol_str::SmolStr;
-
-use crate::features::FeatureRequest;
-
-pub fn component_detail(file_names: &[SmolStr]) -> String {
- if file_names.is_empty() {
- "built-in".to_owned()
- } else {
- file_names.join(", ")
- }
-}
-
-pub fn image_documentation(
- request: &FeatureRequest<CompletionParams>,
- name: &str,
- image: &str,
-) -> Option<Documentation> {
- if supports_images(request) {
- Some(Documentation::MarkupContent(MarkupContent {
- kind: MarkupKind::Markdown,
- value: format!(
- "![{}](data:image/png;base64,{}|width=48,height=48)",
- name, image
- ),
- }))
- } else {
- None
- }
-}
-
-fn supports_images(request: &FeatureRequest<CompletionParams>) -> bool {
- request
- .workspace
- .environment
- .client_capabilities
- .text_document
- .as_ref()
- .and_then(|cap| cap.completion.as_ref())
- .and_then(|cap| cap.completion_item.as_ref())
- .and_then(|cap| cap.documentation_format.as_ref())
- .map_or(true, |formats| formats.contains(&MarkupKind::Markdown))
-}
-
-pub fn adjust_kind(
- request: &FeatureRequest<CompletionParams>,
- kind: CompletionItemKind,
-) -> CompletionItemKind {
- if let Some(value_set) = request
- .workspace
- .environment
- .client_capabilities
- .text_document
- .as_ref()
- .and_then(|cap| cap.completion.as_ref())
- .and_then(|cap| cap.completion_item_kind.as_ref())
- .and_then(|cap| cap.value_set.as_ref())
- {
- if value_set.contains(&kind) {
- return kind;
- }
- }
- CompletionItemKind::TEXT
-}
diff --git a/support/texlab/src/features/definition.rs b/support/texlab/src/features/definition.rs
index 20da72a142..7c9e6d4841 100644
--- a/support/texlab/src/features/definition.rs
+++ b/support/texlab/src/features/definition.rs
@@ -4,48 +4,44 @@ mod entry;
mod label;
mod string;
-use std::sync::Arc;
-
-use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, LocationLink, Url};
+use lsp_types::{GotoDefinitionResponse, LocationLink, Position, Url};
use rowan::TextRange;
-use crate::LineIndexExt;
-
-use self::{
- command::goto_command_definition, document::goto_document_definition,
- entry::goto_entry_definition, label::goto_label_definition, string::goto_string_definition,
+use crate::{
+ db::Document,
+ util::{cursor::CursorContext, line_index_ext::LineIndexExt},
+ Db,
};
-use super::{cursor::CursorContext, FeatureRequest};
-
pub fn goto_definition(
- request: FeatureRequest<GotoDefinitionParams>,
+ db: &dyn Db,
+ uri: &Url,
+ position: Position,
) -> Option<GotoDefinitionResponse> {
- let context = CursorContext::new(request);
+ let context = CursorContext::new(db, uri, position, ())?;
log::debug!("[Definition] Cursor: {:?}", context.cursor);
- let origin_document = context.request.main_document();
- let links: Vec<_> = goto_command_definition(&context)
- .or_else(|| goto_document_definition(&context))
- .or_else(|| goto_entry_definition(&context))
- .or_else(|| goto_label_definition(&context))
- .or_else(|| goto_string_definition(&context))?
+ let links: Vec<_> = command::goto_definition(&context)
+ .or_else(|| document::goto_definition(&context))
+ .or_else(|| entry::goto_definition(&context))
+ .or_else(|| label::goto_definition(&context))
+ .or_else(|| string::goto_definition(&context))?
.into_iter()
.map(|result| {
let origin_selection_range = Some(
- origin_document
- .line_index
+ context
+ .document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(result.origin_selection_range),
);
- let target_document = &context.request.workspace.documents_by_uri[&result.target_uri];
- let target_uri = result.target_uri.as_ref().clone();
- let target_range = target_document
- .line_index
- .line_col_lsp_range(result.target_range);
- let target_selection_range = target_document
- .line_index
- .line_col_lsp_range(result.target_selection_range);
+ let target_line_index = result.target.contents(db).line_index(db);
+ let target_uri = result.target.location(context.db).uri(context.db).clone();
+ let target_range = target_line_index.line_col_lsp_range(result.target_range);
+
+ let target_selection_range =
+ target_line_index.line_col_lsp_range(result.target_selection_range);
LocationLink {
origin_selection_range,
@@ -62,7 +58,7 @@ pub fn goto_definition(
#[derive(Debug, Clone)]
struct DefinitionResult {
origin_selection_range: TextRange,
- target_uri: Arc<Url>,
+ target: Document,
target_range: TextRange,
target_selection_range: TextRange,
}
diff --git a/support/texlab/src/features/definition/command.rs b/support/texlab/src/features/definition/command.rs
index 599c792dfd..cf9fe1b511 100644
--- a/support/texlab/src/features/definition/command.rs
+++ b/support/texlab/src/features/definition/command.rs
@@ -1,26 +1,20 @@
-use std::sync::Arc;
-
-use lsp_types::GotoDefinitionParams;
use rowan::ast::AstNode;
-use crate::{features::cursor::CursorContext, syntax::latex};
+use crate::{syntax::latex, util::cursor::CursorContext};
use super::DefinitionResult;
-pub(super) fn goto_command_definition(
- context: &CursorContext<GotoDefinitionParams>,
-) -> Option<Vec<DefinitionResult>> {
+pub(super) fn goto_definition(context: &CursorContext) -> Option<Vec<DefinitionResult>> {
let name = context
.cursor
- .as_latex()
+ .as_tex()
.filter(|token| token.kind().is_command_name())?;
let origin_selection_range = name.text_range();
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- let root = latex::SyntaxNode::new_root(data.green.clone());
-
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
+ let root = data.root(context.db);
if let Some(result) = root
.descendants()
.filter_map(latex::CommandDefinition::cast)
@@ -32,7 +26,7 @@ pub(super) fn goto_command_definition(
.find_map(|def| {
Some(DefinitionResult {
origin_selection_range,
- target_uri: Arc::clone(&document.uri),
+ target: document,
target_range: latex::small_range(&def),
target_selection_range: def.name()?.command()?.text_range(),
})
diff --git a/support/texlab/src/features/definition/document.rs b/support/texlab/src/features/definition/document.rs
index 1c09bbc88d..58237f7521 100644
--- a/support/texlab/src/features/definition/document.rs
+++ b/support/texlab/src/features/definition/document.rs
@@ -1,40 +1,30 @@
-use std::sync::Arc;
-
-use lsp_types::GotoDefinitionParams;
use rowan::TextRange;
-use crate::features::cursor::CursorContext;
+use crate::{db::dependency_graph, util::cursor::CursorContext};
use super::DefinitionResult;
-pub(super) fn goto_document_definition(
- context: &CursorContext<GotoDefinitionParams>,
-) -> Option<Vec<DefinitionResult>> {
- let data = context.request.main_document().data.as_latex()?;
-
- for include in data
- .extras
- .explicit_links
+pub(super) fn goto_definition(context: &CursorContext) -> Option<Vec<DefinitionResult>> {
+ let db = context.db;
+ context
+ .workspace
+ .parents(db, context.document)
.iter()
- .filter(|link| link.stem_range.contains_inclusive(context.offset))
- {
- for target in &include.targets {
- if context
- .request
- .workspace
- .documents_by_uri
- .values()
- .any(|document| document.uri.as_ref() == target.as_ref())
- {
- return Some(vec![DefinitionResult {
- origin_selection_range: include.stem_range,
- target_uri: Arc::clone(target),
+ .copied()
+ .chain(std::iter::once(context.document))
+ .flat_map(|parent| dependency_graph(db, parent).edges)
+ .filter(|edge| edge.source == context.document)
+ .find_map(|edge| {
+ let range = edge.origin?.link.range(db);
+ if range.contains_inclusive(context.offset) {
+ Some(vec![DefinitionResult {
+ origin_selection_range: range,
+ target: edge.target,
target_range: TextRange::default(),
target_selection_range: TextRange::default(),
- }]);
+ }])
+ } else {
+ None
}
- }
- }
-
- None
+ })
}
diff --git a/support/texlab/src/features/definition/entry.rs b/support/texlab/src/features/definition/entry.rs
index 1dcd983af1..a945b18c41 100644
--- a/support/texlab/src/features/definition/entry.rs
+++ b/support/texlab/src/features/definition/entry.rs
@@ -1,24 +1,21 @@
-use std::sync::Arc;
-
-use lsp_types::GotoDefinitionParams;
use rowan::ast::AstNode;
use crate::{
- features::cursor::CursorContext,
syntax::{
bibtex::{self, HasName},
latex,
},
+ util::cursor::CursorContext,
};
use super::DefinitionResult;
-pub(super) fn goto_entry_definition(
- context: &CursorContext<GotoDefinitionParams>,
-) -> Option<Vec<DefinitionResult>> {
+pub(super) fn goto_definition(context: &CursorContext) -> Option<Vec<DefinitionResult>> {
+ let db = context.db;
+
let word = context
.cursor
- .as_latex()
+ .as_tex()
.filter(|token| token.kind() == latex::WORD)?;
let key = latex::Key::cast(word.parent()?)?;
@@ -27,16 +24,13 @@ pub(super) fn goto_entry_definition(
let origin_selection_range = latex::small_range(&key);
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_bibtex() {
- for entry in bibtex::SyntaxNode::new_root(data.green.clone())
- .children()
- .filter_map(bibtex::Entry::cast)
- {
+ for document in context.related() {
+ if let Some(data) = document.parse(db).as_bib() {
+ for entry in data.root(db).children().filter_map(bibtex::Entry::cast) {
if let Some(key) = entry.name_token().filter(|k| k.text() == word.text()) {
return Some(vec![DefinitionResult {
origin_selection_range,
- target_uri: Arc::clone(&document.uri),
+ target: document,
target_selection_range: key.text_range(),
target_range: entry.syntax().text_range(),
}]);
diff --git a/support/texlab/src/features/definition/label.rs b/support/texlab/src/features/definition/label.rs
index 867e8a1076..a1cc4ed15b 100644
--- a/support/texlab/src/features/definition/label.rs
+++ b/support/texlab/src/features/definition/label.rs
@@ -1,31 +1,32 @@
-use std::sync::Arc;
-
-use lsp_types::GotoDefinitionParams;
-
-use crate::{features::cursor::CursorContext, find_label_definition, render_label, syntax::latex};
+use crate::{
+ db::analysis::label,
+ util::{self, cursor::CursorContext},
+};
use super::DefinitionResult;
-pub(super) fn goto_label_definition(
- context: &CursorContext<GotoDefinitionParams>,
-) -> Option<Vec<DefinitionResult>> {
+pub(super) fn goto_definition(context: &CursorContext) -> Option<Vec<DefinitionResult>> {
+ let db = context.db;
let (name_text, origin_selection_range) = context
.find_label_name_key()
.or_else(|| context.find_label_name_command())?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- let root = latex::SyntaxNode::new_root(data.green.clone());
- if let Some(definition) = find_label_definition(&root, &name_text) {
- let target_selection_range = latex::small_range(&definition.name()?.key()?);
- let target_range =
- render_label(&context.request.workspace, &name_text, Some(definition))
- .map(|label| label.range)
- .unwrap_or(target_selection_range);
+ for document in context.related() {
+ if let Some(data) = document.parse(db).as_tex() {
+ if let Some(label) = data
+ .analyze(db)
+ .labels(db)
+ .iter()
+ .filter(|label| matches!(label.origin(db), label::Origin::Definition(_)))
+ .find(|label| label.name(db).text(db) == name_text.as_str())
+ {
+ let target_selection_range = label.range(db);
+ let target_range = util::label::render(db, document, *label)
+ .map_or(target_selection_range, |label| label.range);
return Some(vec![DefinitionResult {
origin_selection_range,
- target_uri: Arc::clone(&document.uri),
+ target: document,
target_range,
target_selection_range,
}]);
diff --git a/support/texlab/src/features/definition/string.rs b/support/texlab/src/features/definition/string.rs
index c774e24e1d..44ea1e9094 100644
--- a/support/texlab/src/features/definition/string.rs
+++ b/support/texlab/src/features/definition/string.rs
@@ -1,43 +1,34 @@
-use std::sync::Arc;
-
-use lsp_types::GotoDefinitionParams;
use rowan::ast::AstNode;
use crate::{
- features::cursor::CursorContext,
syntax::bibtex::{self, HasName},
+ util::cursor::CursorContext,
};
use super::DefinitionResult;
-pub(super) fn goto_string_definition(
- context: &CursorContext<GotoDefinitionParams>,
-) -> Option<Vec<DefinitionResult>> {
- let main_document = context.request.main_document();
-
- let data = main_document.data.as_bibtex()?;
+pub(super) fn goto_definition(context: &CursorContext) -> Option<Vec<DefinitionResult>> {
+ let db = context.db;
+ let data = context.document.parse(db).as_bib()?;
let key = context
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::NAME)?;
bibtex::Value::cast(key.parent()?)?;
let origin_selection_range = key.text_range();
- for string in bibtex::SyntaxNode::new_root(data.green.clone())
+ data.root(db)
.children()
.filter_map(bibtex::StringDef::cast)
- {
- if let Some(string_name) = string.name_token().filter(|k| k.text() == key.text()) {
- return Some(vec![DefinitionResult {
+ .find_map(|string| {
+ let string_name = string.name_token().filter(|k| k.text() == key.text())?;
+ Some(vec![DefinitionResult {
origin_selection_range,
- target_uri: Arc::clone(&main_document.uri),
+ target: context.document,
target_selection_range: string_name.text_range(),
target_range: string.syntax().text_range(),
- }]);
- }
- }
-
- None
+ }])
+ })
}
diff --git a/support/texlab/src/features/execute_command.rs b/support/texlab/src/features/execute_command.rs
deleted file mode 100644
index b1b89d4f50..0000000000
--- a/support/texlab/src/features/execute_command.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use std::{path::PathBuf, process::Stdio, sync::Arc};
-
-use anyhow::Result;
-use lsp_types::{TextDocumentIdentifier, Url};
-
-use crate::Workspace;
-
-pub fn execute_command(
- workspace: &Workspace,
- name: &str,
- args: Vec<serde_json::Value>,
-) -> Result<()> {
- match name {
- "texlab.cleanAuxiliary" => {
- let params = args
- .into_iter()
- .next()
- .ok_or_else(|| anyhow::anyhow!("texlab.cleanAuxiliary requires one argument"))?;
-
- clean_output_files(workspace, CleanOptions::Auxiliary, params)?;
- }
- "texlab.cleanArtifacts" => {
- let params = args
- .into_iter()
- .next()
- .ok_or_else(|| anyhow::anyhow!("texlab.cleanArtifacts requires one argument"))?;
-
- clean_output_files(workspace, CleanOptions::Artifacts, params)?;
- }
- _ => anyhow::bail!("Unknown command: {}", name),
- }
-
- Ok(())
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
-enum CleanOptions {
- Auxiliary,
- Artifacts,
-}
-
-fn clean_output_files(
- workspace: &Workspace,
- options: CleanOptions,
- params: serde_json::Value,
-) -> Result<()> {
- let params: TextDocumentIdentifier = serde_json::from_value(params)?;
-
- let uri = workspace
- .find_parent(&params.uri)
- .map(|document| document.uri)
- .unwrap_or_else(|| Arc::new(params.uri));
-
- if let Some(cx) = BuildContext::find(workspace, &uri) {
- let flag = match options {
- CleanOptions::Auxiliary => "-c",
- CleanOptions::Artifacts => "-C",
- };
-
- std::process::Command::new("latexmk")
- .arg(format!("-outdir={}", cx.output_dir.to_string_lossy()))
- .arg(flag)
- .arg(cx.input_file)
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .status()?;
- }
-
- Ok(())
-}
-
-struct BuildContext {
- input_file: PathBuf,
- output_dir: PathBuf,
-}
-
-impl BuildContext {
- pub fn find(workspace: &Workspace, uri: &Url) -> Option<Self> {
- if uri.scheme() != "file" {
- return None;
- }
-
- let input_file = uri.to_file_path().ok()?;
- let options = &workspace.environment.options;
- let current_dir = &workspace.environment.current_directory;
- let output_dir = match (
- options.root_directory.as_ref(),
- options.aux_directory.as_ref(),
- ) {
- (_, Some(aux_dir)) => current_dir.join(aux_dir),
- (Some(root_dir), None) => current_dir.join(root_dir),
- (None, None) => input_file.parent()?.to_path_buf(),
- };
-
- log::info!("Output = {:#?}", output_dir);
-
- Some(Self {
- input_file,
- output_dir,
- })
- }
-}
diff --git a/support/texlab/src/features/folding.rs b/support/texlab/src/features/folding.rs
index 71fb0bee6f..ea26121f3f 100644
--- a/support/texlab/src/features/folding.rs
+++ b/support/texlab/src/features/folding.rs
@@ -1,49 +1,52 @@
-use lsp_types::{FoldingRange, FoldingRangeKind, FoldingRangeParams, Range};
+use lsp_types::{FoldingRange, FoldingRangeKind, Range, Url};
use rowan::ast::AstNode;
use crate::{
+ db::{parse::DocumentData, Workspace},
syntax::{bibtex, latex},
- DocumentData, LineIndexExt,
+ util::line_index_ext::LineIndexExt,
+ Db,
};
-use super::FeatureRequest;
-
-pub fn find_foldings(request: FeatureRequest<FoldingRangeParams>) -> Vec<FoldingRange> {
- let mut foldings = Vec::new();
- let main_document = request.main_document();
- match &main_document.data {
- DocumentData::Latex(data) => {
- for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() {
+pub fn find_all(db: &dyn Db, uri: &Url) -> Option<Vec<FoldingRange>> {
+ let document = Workspace::get(db).lookup_uri(db, uri)?;
+ let line_index = document.contents(db).line_index(db);
+ let foldings = match document.parse(db) {
+ DocumentData::Tex(data) => {
+ let mut results = Vec::new();
+ let root = data.root(db);
+ for node in root.descendants() {
if let Some(folding) = latex::Environment::cast(node.clone())
.map(|node| latex::small_range(&node))
.or_else(|| {
latex::Section::cast(node.clone()).map(|node| latex::small_range(&node))
})
.or_else(|| latex::EnumItem::cast(node).map(|node| latex::small_range(&node)))
- .map(|node| main_document.line_index.line_col_lsp_range(node))
+ .map(|node| line_index.line_col_lsp_range(node))
.map(create_range)
{
- foldings.push(folding);
+ results.push(folding);
}
}
+
+ results
}
- DocumentData::Bibtex(data) => {
- for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() {
- if matches!(
- node.kind(),
- bibtex::PREAMBLE | bibtex::STRING | bibtex::ENTRY
- ) {
- foldings.push(create_range(
- main_document
- .line_index
- .line_col_lsp_range(node.text_range()),
- ));
- }
- }
+ DocumentData::Bib(data) => {
+ let root = data.root(db);
+ root.descendants()
+ .filter(|node| {
+ matches!(
+ node.kind(),
+ bibtex::PREAMBLE | bibtex::STRING | bibtex::ENTRY
+ )
+ })
+ .map(|node| create_range(line_index.line_col_lsp_range(node.text_range())))
+ .collect()
}
- DocumentData::BuildLog(_) => {}
- }
- foldings
+ DocumentData::Log(_) => return None,
+ };
+
+ Some(foldings)
}
fn create_range(range: Range) -> FoldingRange {
diff --git a/support/texlab/src/features/formatting.rs b/support/texlab/src/features/formatting.rs
index 1107e5fd38..66375bd576 100644
--- a/support/texlab/src/features/formatting.rs
+++ b/support/texlab/src/features/formatting.rs
@@ -1,26 +1,31 @@
mod bibtex_internal;
mod latexindent;
-use lsp_types::{DocumentFormattingParams, TextEdit};
+use lsp_types::{FormattingOptions, TextEdit, Url};
-use crate::{BibtexFormatter, LatexFormatter};
+use crate::{
+ db::{Language, Workspace},
+ BibtexFormatter, Db, LatexFormatter,
+};
use self::{bibtex_internal::format_bibtex_internal, latexindent::format_with_latexindent};
-use super::FeatureRequest;
-
pub fn format_source_code(
- request: FeatureRequest<DocumentFormattingParams>,
+ db: &dyn Db,
+ uri: &Url,
+ options: &FormattingOptions,
) -> Option<Vec<TextEdit>> {
- let mut edits = None;
- if request.workspace.environment.options.bibtex_formatter == BibtexFormatter::Texlab {
- edits = edits.or_else(|| format_bibtex_internal(&request));
- }
-
- if request.workspace.environment.options.latex_formatter == LatexFormatter::Texlab {
- edits = edits.or_else(|| Some(vec![]));
+ let workspace = Workspace::get(db);
+ let document = workspace.lookup_uri(db, uri)?;
+ match document.language(db) {
+ Language::Tex => match workspace.options(db).latex_formatter {
+ LatexFormatter::Texlab => None,
+ LatexFormatter::Latexindent => format_with_latexindent(db, document),
+ },
+ Language::Bib => match workspace.options(db).bibtex_formatter {
+ BibtexFormatter::Texlab => format_bibtex_internal(db, document, options),
+ BibtexFormatter::Latexindent => format_with_latexindent(db, document),
+ },
+ Language::Log => None,
}
-
- edits = edits.or_else(|| format_with_latexindent(&request));
- edits
}
diff --git a/support/texlab/src/features/formatting/bibtex_internal.rs b/support/texlab/src/features/formatting/bibtex_internal.rs
index 2dcc507be9..8447778d90 100644
--- a/support/texlab/src/features/formatting/bibtex_internal.rs
+++ b/support/texlab/src/features/formatting/bibtex_internal.rs
@@ -1,63 +1,57 @@
-use lsp_types::{DocumentFormattingParams, TextEdit};
+use lsp_types::{FormattingOptions, TextEdit};
use rowan::{ast::AstNode, NodeOrToken};
use crate::{
- features::FeatureRequest,
+ db::{Document, Workspace},
syntax::bibtex::{self, HasName, HasType, HasValue},
- LineIndex, LineIndexExt,
+ util::{line_index::LineIndex, line_index_ext::LineIndexExt},
+ Db,
};
pub fn format_bibtex_internal(
- request: &FeatureRequest<DocumentFormattingParams>,
+ db: &dyn Db,
+ document: Document,
+ options: &FormattingOptions,
) -> Option<Vec<TextEdit>> {
let mut indent = String::new();
- if request.params.options.insert_spaces {
- for _ in 0..request.params.options.tab_size {
+
+ if options.insert_spaces {
+ for _ in 0..options.tab_size {
indent.push(' ');
}
} else {
indent.push('\t');
}
- let line_length = request
- .workspace
- .environment
- .options
+ let line_length = Workspace::get(db)
+ .options(db)
.formatter_line_length
- .map(|value| {
+ .map_or(80, |value| {
if value <= 0 {
usize::MAX
} else {
value as usize
}
- })
- .unwrap_or(80);
+ });
- let document = request.main_document();
- let data = document.data.as_bibtex()?;
+ let line_index = document.contents(db).line_index(db);
+ let data = document.parse(db).as_bib()?;
let mut edits = Vec::new();
- for node in bibtex::SyntaxNode::new_root(data.green.clone())
- .children()
- .filter(|node| {
- matches!(
- node.kind(),
- bibtex::PREAMBLE | bibtex::STRING | bibtex::ENTRY
- )
- })
- {
+ for node in data.root(db).children().filter(|node| {
+ matches!(
+ node.kind(),
+ bibtex::PREAMBLE | bibtex::STRING | bibtex::ENTRY
+ )
+ }) {
let range = node.text_range();
- let mut formatter = Formatter::new(
- indent.clone(),
- request.params.options.tab_size,
- line_length,
- &document.line_index,
- );
+ let mut formatter =
+ Formatter::new(indent.clone(), options.tab_size, line_length, line_index);
formatter.visit_node(node);
edits.push(TextEdit {
- range: document.line_index.line_col_lsp_range(range),
+ range: line_index.line_col_lsp_range(range),
new_text: formatter.output,
});
}
diff --git a/support/texlab/src/features/formatting/latexindent.rs b/support/texlab/src/features/formatting/latexindent.rs
index 4898aa4415..3dab10c923 100644
--- a/support/texlab/src/features/formatting/latexindent.rs
+++ b/support/texlab/src/features/formatting/latexindent.rs
@@ -1,94 +1,78 @@
use std::{
- fs,
+ path::Path,
process::{Command, Stdio},
};
-use lsp_types::{DocumentFormattingParams, TextEdit};
+use lsp_types::TextEdit;
use rowan::{TextLen, TextRange};
use tempfile::tempdir;
-use crate::{features::FeatureRequest, DocumentLanguage, LineIndexExt};
-
-pub fn format_with_latexindent(
- request: &FeatureRequest<DocumentFormattingParams>,
-) -> Option<Vec<TextEdit>> {
- let directory = tempdir().ok()?;
- let document = request.main_document();
+use crate::{
+ db::{Document, Language, Workspace},
+ util::line_index_ext::LineIndexExt,
+ Db, LatexindentOptions,
+};
- let options = &request.workspace.environment.options;
- let current_dir = options
- .root_directory
- .as_ref()
- .cloned()
- .or_else(|| {
- if document.uri.scheme() == "file" {
- document
- .uri
- .to_file_path()
- .unwrap()
- .parent()
- .map(ToOwned::to_owned)
- } else {
- None
- }
- })
- .unwrap_or_else(|| ".".into());
+pub fn format_with_latexindent(db: &dyn Db, document: Document) -> Option<Vec<TextEdit>> {
+ let workspace = Workspace::get(db);
+ let options = workspace.options(db);
+ let target_dir = tempdir().ok()?;
+ let source_dir = workspace
+ .working_dir(db, document.directory(db))
+ .path(db)
+ .as_deref()?;
- let local = match &options.latexindent.local {
- Some(local) => format!("--local={}", local),
- None => "-l".to_string(),
- };
+ let target_file = target_dir
+ .path()
+ .join(if document.language(db) == Language::Bib {
+ "file.bib"
+ } else {
+ "file.tex"
+ });
+ std::fs::write(&target_file, document.contents(db).text(db)).ok()?;
- let modify_line_breaks = options.latexindent.modify_line_breaks;
+ let args = build_arguments(&options.latexindent, &target_file);
- let path = directory.path();
- let _ = fs::copy(
- current_dir.join("localSettings.yaml"),
- path.join("localSettings.yaml"),
- );
- let _ = fs::copy(
- current_dir.join(".localSettings.yaml"),
- path.join(".localSettings.yaml"),
- );
- let _ = fs::copy(
- current_dir.join("latexindent.yaml"),
- path.join("latexindent.yaml"),
+ log::debug!(
+ "Running latexindent in folder \"{}\" with args: {:?}",
+ source_dir.display(),
+ args,
);
- let name = if document.data.language() == DocumentLanguage::Bibtex {
- "file.bib"
- } else {
- "file.tex"
- };
-
- fs::write(directory.path().join(name), document.text.as_str()).ok()?;
-
- let mut args = Vec::new();
- if modify_line_breaks {
- args.push("--modifylinebreaks");
- }
- args.push(&local);
- args.push(name);
-
let output = Command::new("latexindent")
.args(&args)
- .current_dir(current_dir)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
- .current_dir(directory.path())
+ .current_dir(source_dir)
.output()
.ok()?;
+ let old_text = document.contents(db).text(db);
let new_text = String::from_utf8_lossy(&output.stdout).into_owned();
if new_text.is_empty() {
None
} else {
+ let line_index = document.contents(db).line_index(db);
Some(vec![TextEdit {
- range: document
- .line_index
- .line_col_lsp_range(TextRange::new(0.into(), document.text.text_len())),
+ range: line_index.line_col_lsp_range(TextRange::new(0.into(), old_text.text_len())),
new_text,
}])
}
}
+
+fn build_arguments(options: &LatexindentOptions, target_file: &Path) -> Vec<String> {
+ let mut args = Vec::new();
+
+ args.push(match &options.local {
+ Some(yaml_file) => format!("--local={yaml_file}"),
+ None => "--local".to_string(),
+ });
+
+ if options.modify_line_breaks {
+ args.push("--modifylinebreaks".to_string());
+ }
+
+ args.push(target_file.display().to_string());
+ args
+}
diff --git a/support/texlab/src/features/forward_search.rs b/support/texlab/src/features/forward_search.rs
index 54629d0dc0..47be702659 100644
--- a/support/texlab/src/features/forward_search.rs
+++ b/support/texlab/src/features/forward_search.rs
@@ -1,94 +1,120 @@
use std::{
io,
- path::Path,
- process::{Command, Stdio},
+ path::{Path, PathBuf},
+ process::Stdio,
};
use log::error;
-use lsp_types::TextDocumentPositionParams;
-use serde::{Deserialize, Serialize};
-use serde_repr::{Deserialize_repr, Serialize_repr};
-
-use super::FeatureRequest;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
-#[repr(i32)]
-pub enum ForwardSearchStatus {
- SUCCESS = 0,
- ERROR = 1,
- FAILURE = 2,
- UNCONFIGURED = 3,
+use lsp_types::{Position, Url};
+use thiserror::Error;
+
+use crate::{db::Workspace, util::line_index_ext::LineIndexExt, Db};
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("TeX document '{0}' not found")]
+ TexNotFound(Url),
+
+ #[error("PDF document '{0}' not found")]
+ PdfNotFound(PathBuf),
+
+ #[error("TeX document '{0}' is not a local file")]
+ NoLocalFile(Url),
+
+ #[error("PDF viewer is not configured")]
+ Unconfigured,
+
+ #[error("Failed to spawn process: {0}")]
+ Spawn(io::Error),
}
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct ForwardSearchResult {
- pub status: ForwardSearchStatus,
+pub struct Command {
+ executable: String,
+ args: Vec<String>,
}
-pub fn execute_forward_search(
- request: FeatureRequest<TextDocumentPositionParams>,
-) -> Option<ForwardSearchResult> {
- let options = &request.workspace.environment.options.forward_search;
+impl Command {
+ pub fn configure(db: &dyn Db, uri: &Url, position: Option<Position>) -> Result<Self, Error> {
+ let workspace = Workspace::get(db);
+ let child = workspace
+ .lookup_uri(db, uri)
+ .ok_or_else(|| Error::TexNotFound(uri.clone()))?;
+
+ let parent = workspace
+ .parents(db, child)
+ .iter()
+ .copied()
+ .next()
+ .unwrap_or(child);
+
+ let output_dir = workspace
+ .output_dir(db, workspace.working_dir(db, parent.directory(db)))
+ .path(db)
+ .as_deref()
+ .ok_or_else(|| Error::NoLocalFile(uri.clone()))?;
+
+ let tex_path = child
+ .location(db)
+ .path(db)
+ .as_deref()
+ .ok_or_else(|| Error::NoLocalFile(uri.clone()))?;
+
+ let pdf_name = format!("{}.pdf", parent.location(db).stem(db).unwrap());
+ let pdf_path = output_dir.join(pdf_name);
+ if !pdf_path.exists() {
+ return Err(Error::PdfNotFound(pdf_path));
+ }
- if options.executable.is_none() || options.args.is_none() {
- return Some(ForwardSearchResult {
- status: ForwardSearchStatus::UNCONFIGURED,
+ let position = position.unwrap_or_else(|| {
+ child
+ .contents(db)
+ .line_index(db)
+ .line_col_lsp(child.cursor(db))
});
+
+ let options = &workspace.options(db).forward_search;
+
+ let executable = options
+ .executable
+ .as_deref()
+ .ok_or(Error::Unconfigured)?
+ .to_string();
+
+ let args: Vec<_> = options
+ .args
+ .as_deref()
+ .ok_or(Error::Unconfigured)?
+ .iter()
+ .flat_map(|arg| replace_placeholder(tex_path, &pdf_path, position.line, arg))
+ .collect();
+
+ Ok(Self { executable, args })
}
+}
- let root_document = request
- .workspace
- .documents_by_uri
- .values()
- .find(|document| {
- if let Some(data) = document.data.as_latex() {
- data.extras.has_document_environment
- && !data
- .extras
- .explicit_links
- .iter()
- .filter_map(|link| link.as_component_name())
- .any(|name| name == "subfiles.cls")
- } else {
- false
- }
- })
- .filter(|document| document.uri.scheme() == "file")?;
-
- let data = root_document.data.as_latex()?;
- let pdf_path = data
- .extras
- .implicit_links
- .pdf
- .iter()
- .filter_map(|uri| uri.to_file_path().ok())
- .find(|path| path.exists())?;
-
- let tex_path = request.main_document().uri.to_file_path().ok()?;
-
- let args: Vec<String> = options
- .args
- .as_ref()
- .unwrap()
- .iter()
- .flat_map(|arg| {
- replace_placeholder(&tex_path, &pdf_path, request.params.position.line, arg)
- })
- .collect();
-
- let status = match run_process(options.executable.as_ref().unwrap(), args) {
- Ok(()) => ForwardSearchStatus::SUCCESS,
- Err(why) => {
- error!("Unable to execute forward search: {}", why);
- ForwardSearchStatus::FAILURE
- }
- };
- Some(ForwardSearchResult { status })
+impl Command {
+ pub fn run(self) -> Result<(), Error> {
+ log::debug!(
+ "Executing forward search: {} {:?}",
+ self.executable,
+ self.args
+ );
+
+ std::process::Command::new(self.executable)
+ .args(self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status()
+ .map_err(Error::Spawn)?;
+
+ Ok(())
+ }
}
/// Iterate overs chunks of a string. Either returns a slice of the
/// original string, or the placeholder replacement.
-pub struct PlaceHolderIterator<'a> {
+struct PlaceHolderIterator<'a> {
remainder: &'a str,
tex_file: &'a str,
pdf_file: &'a str,
@@ -168,13 +194,3 @@ fn replace_placeholder(
};
Some(result)
}
-
-fn run_process(executable: &str, args: Vec<String>) -> io::Result<()> {
- Command::new(executable)
- .args(args)
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .status()?;
- Ok(())
-}
diff --git a/support/texlab/src/features/highlight.rs b/support/texlab/src/features/highlight.rs
index b2c5794461..52746a040f 100644
--- a/support/texlab/src/features/highlight.rs
+++ b/support/texlab/src/features/highlight.rs
@@ -1,14 +1,10 @@
mod label;
-use lsp_types::{DocumentHighlight, DocumentHighlightParams};
+use lsp_types::{DocumentHighlight, Position, Url};
-use self::label::find_label_highlights;
+use crate::{util::cursor::CursorContext, Db};
-use super::{cursor::CursorContext, FeatureRequest};
-
-pub fn find_document_highlights(
- request: FeatureRequest<DocumentHighlightParams>,
-) -> Option<Vec<DocumentHighlight>> {
- let context = CursorContext::new(request);
- find_label_highlights(&context)
+pub fn find_all(db: &dyn Db, uri: &Url, position: Position) -> Option<Vec<DocumentHighlight>> {
+ let context = CursorContext::new(db, uri, position, ())?;
+ label::find_highlights(&context)
}
diff --git a/support/texlab/src/features/highlight/label.rs b/support/texlab/src/features/highlight/label.rs
index cb51abdd68..2ae66d5c47 100644
--- a/support/texlab/src/features/highlight/label.rs
+++ b/support/texlab/src/features/highlight/label.rs
@@ -1,78 +1,31 @@
-use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams};
-use rowan::ast::AstNode;
+use lsp_types::{DocumentHighlight, DocumentHighlightKind};
-use crate::{features::cursor::CursorContext, syntax::latex, LineIndexExt};
+use crate::{
+ db::analysis::label,
+ util::{cursor::CursorContext, line_index_ext::LineIndexExt},
+};
-pub fn find_label_highlights(
- context: &CursorContext<DocumentHighlightParams>,
-) -> Option<Vec<DocumentHighlight>> {
+pub fn find_highlights(context: &CursorContext) -> Option<Vec<DocumentHighlight>> {
+ let db = context.db;
let (name_text, _) = context.find_label_name_key()?;
-
- let main_document = context.request.main_document();
- let data = main_document.data.as_latex()?;
+ let data = context.document.parse(db).as_tex()?;
let mut highlights = Vec::new();
- for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() {
- if let Some(label_name) = latex::LabelDefinition::cast(node.clone())
- .and_then(|label| label.name())
- .and_then(|label_name| label_name.key())
- .filter(|label_name| label_name.to_string() == name_text)
- {
- let range = main_document
- .line_index
- .line_col_lsp_range(latex::small_range(&label_name));
-
- highlights.push(DocumentHighlight {
- range,
- kind: Some(DocumentHighlightKind::WRITE),
- });
- } else if let Some(label) = latex::LabelReference::cast(node.clone()) {
- for label_name in label
- .name_list()
- .into_iter()
- .flat_map(|name| name.keys())
- .filter(|label_name| label_name.to_string() == name_text)
- {
- let range = main_document
- .line_index
- .line_col_lsp_range(latex::small_range(&label_name));
-
- highlights.push(DocumentHighlight {
- range,
- kind: Some(DocumentHighlightKind::READ),
- });
- }
- } else if let Some(label) = latex::LabelReferenceRange::cast(node.clone()) {
- if let Some(label_name) = label
- .from()
- .and_then(|label_name| label_name.key())
- .filter(|label_name| label_name.to_string() == name_text)
- {
- let range = main_document
- .line_index
- .line_col_lsp_range(latex::small_range(&label_name));
-
- highlights.push(DocumentHighlight {
- range,
- kind: Some(DocumentHighlightKind::READ),
- });
- }
-
- if let Some(label_name) = label
- .to()
- .and_then(|label_name| label_name.key())
- .filter(|label_name| label_name.to_string() == name_text)
- {
- let range = main_document
- .line_index
- .line_col_lsp_range(latex::small_range(&label_name));
-
- highlights.push(DocumentHighlight {
- range,
- kind: Some(DocumentHighlightKind::READ),
- });
- }
- }
+ let line_index = context.document.contents(db).line_index(db);
+ for label in data
+ .analyze(db)
+ .labels(db)
+ .iter()
+ .filter(|label| label.name(db).text(db) == &name_text)
+ {
+ let range = line_index.line_col_lsp_range(label.range(db));
+ let kind = Some(match label.origin(db) {
+ label::Origin::Definition(_) => DocumentHighlightKind::WRITE,
+ label::Origin::Reference(_) => DocumentHighlightKind::READ,
+ label::Origin::ReferenceRange(_) => DocumentHighlightKind::READ,
+ });
+
+ highlights.push(DocumentHighlight { range, kind });
}
Some(highlights)
diff --git a/support/texlab/src/features/hover.rs b/support/texlab/src/features/hover.rs
index 8663f6256d..27d07f74cd 100644
--- a/support/texlab/src/features/hover.rs
+++ b/support/texlab/src/features/hover.rs
@@ -5,43 +5,32 @@ mod field;
mod label;
mod string_ref;
-use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind};
+use lsp_types::{Hover, HoverContents, MarkupContent, MarkupKind, Position, Url};
use rowan::TextRange;
use crate::{
- features::{cursor::CursorContext, hover::citation::find_citation_hover},
- LineIndexExt,
+ util::{cursor::CursorContext, line_index_ext::LineIndexExt},
+ Db,
};
-use self::{
- component::find_component_hover, entry_type::find_entry_type_hover, field::find_field_hover,
- label::find_label_hover, string_ref::find_string_reference_hover,
-};
-
-use super::FeatureRequest;
-
-pub fn find_hover(request: FeatureRequest<HoverParams>) -> Option<Hover> {
- let context = CursorContext::new(request);
+pub fn find(db: &dyn Db, uri: &Url, position: Position) -> Option<Hover> {
+ let context = CursorContext::new(db, uri, position, ())?;
log::debug!("[Hover] Cursor: {:?}", context.cursor);
- let result = find_label_hover(&context)
- .or_else(|| find_citation_hover(&context))
- .or_else(|| find_component_hover(&context))
- .or_else(|| find_string_reference_hover(&context))
- .or_else(|| find_field_hover(&context))
- .or_else(|| find_entry_type_hover(&context))?;
+ let result = label::find_hover(&context)
+ .or_else(|| citation::find_hover(&context))
+ .or_else(|| component::find_hover(&context))
+ .or_else(|| string_ref::find_hover(&context))
+ .or_else(|| field::find_hover(&context))
+ .or_else(|| entry_type::find_hover(&context))?;
+
+ let line_index = context.document.contents(db).line_index(db);
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: result.value_kind,
value: result.value,
}),
- range: Some(
- context
- .request
- .main_document()
- .line_index
- .line_col_lsp_range(result.range),
- ),
+ range: Some(line_index.line_col_lsp_range(result.range)),
})
}
diff --git a/support/texlab/src/features/hover/citation.rs b/support/texlab/src/features/hover/citation.rs
index 5e86019eee..e0e17176d5 100644
--- a/support/texlab/src/features/hover/citation.rs
+++ b/support/texlab/src/features/hover/citation.rs
@@ -1,28 +1,23 @@
-use lsp_types::{HoverParams, MarkupKind};
+use lsp_types::MarkupKind;
use rowan::ast::AstNode;
-use crate::{citation, features::cursor::CursorContext, syntax::bibtex};
+use crate::{citation, syntax::bibtex, util::cursor::CursorContext};
use super::HoverResult;
-pub(super) fn find_citation_hover(context: &CursorContext<HoverParams>) -> Option<HoverResult> {
+pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
let (key, range) = context
.find_citation_key_word()
.or_else(|| context.find_citation_key_command())
.or_else(|| context.find_entry_key())?;
- let value = context
- .request
- .workspace
- .documents_by_uri
- .values()
- .find_map(|document| {
- let data = document.data.as_bibtex()?;
- let root = bibtex::SyntaxNode::new_root(data.green.clone());
- let root = bibtex::Root::cast(root)?;
- let entry = root.find_entry(&key)?;
- citation::render(&entry)
- })?;
+ let value = context.related().find_map(|document| {
+ let data = document.parse(context.db).as_bib()?;
+ let root = data.root(context.db);
+ let root = bibtex::Root::cast(root)?;
+ let entry = root.find_entry(&key)?;
+ citation::render(&entry)
+ })?;
Some(HoverResult {
range,
diff --git a/support/texlab/src/features/hover/component.rs b/support/texlab/src/features/hover/component.rs
index 737a99d3ea..61a428a9ce 100644
--- a/support/texlab/src/features/hover/component.rs
+++ b/support/texlab/src/features/hover/component.rs
@@ -1,25 +1,28 @@
-use lsp_types::{HoverParams, MarkupKind};
+use lsp_types::MarkupKind;
-use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext, syntax::latex};
+use crate::{
+ db::analysis::TexLinkKind,
+ util::{components::COMPONENT_DATABASE, cursor::CursorContext},
+};
use super::HoverResult;
-pub(super) fn find_component_hover(context: &CursorContext<HoverParams>) -> Option<HoverResult> {
- let data = context.request.main_document().data.as_latex()?;
- for link in &data.extras.explicit_links {
- if matches!(
- link.kind,
- latex::ExplicitLinkKind::Package | latex::ExplicitLinkKind::Class
- ) && link.stem_range.contains_inclusive(context.offset)
- {
- let value = COMPONENT_DATABASE.documentation(&link.stem)?.value;
- return Some(HoverResult {
+pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
+ let db = context.db;
+ let links = context.document.parse(db).as_tex()?.analyze(db).links(db);
+ links
+ .iter()
+ .filter(|link| matches!(link.kind(db), TexLinkKind::Sty | TexLinkKind::Cls))
+ .filter(|link| link.range(db).contains_inclusive(context.offset))
+ .find_map(|link| {
+ let value = COMPONENT_DATABASE
+ .documentation(link.path(db).text(db))?
+ .value;
+
+ Some(HoverResult {
value,
value_kind: MarkupKind::PlainText,
- range: link.stem_range,
- });
- }
- }
-
- None
+ range: link.range(db),
+ })
+ })
}
diff --git a/support/texlab/src/features/hover/entry_type.rs b/support/texlab/src/features/hover/entry_type.rs
index 41a561395c..0f4ccbdb72 100644
--- a/support/texlab/src/features/hover/entry_type.rs
+++ b/support/texlab/src/features/hover/entry_type.rs
@@ -1,13 +1,16 @@
-use lsp_types::{HoverParams, MarkupKind};
+use lsp_types::MarkupKind;
-use crate::{features::cursor::CursorContext, syntax::bibtex, LANGUAGE_DATA};
+use crate::{
+ syntax::bibtex,
+ util::{cursor::CursorContext, lang_data::LANGUAGE_DATA},
+};
use super::HoverResult;
-pub(super) fn find_entry_type_hover(context: &CursorContext<HoverParams>) -> Option<HoverResult> {
+pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
let name = context
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::TYPE)?;
let docs = LANGUAGE_DATA.entry_type_documentation(&name.text()[1..])?;
diff --git a/support/texlab/src/features/hover/field.rs b/support/texlab/src/features/hover/field.rs
index 6102bd6b68..48193997d6 100644
--- a/support/texlab/src/features/hover/field.rs
+++ b/support/texlab/src/features/hover/field.rs
@@ -1,14 +1,17 @@
-use lsp_types::{HoverParams, MarkupKind};
+use lsp_types::MarkupKind;
use rowan::ast::AstNode;
-use crate::{features::cursor::CursorContext, syntax::bibtex, LANGUAGE_DATA};
+use crate::{
+ syntax::bibtex,
+ util::{cursor::CursorContext, lang_data::LANGUAGE_DATA},
+};
use super::HoverResult;
-pub(super) fn find_field_hover(context: &CursorContext<HoverParams>) -> Option<HoverResult> {
+pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
let name = context
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::NAME)?;
bibtex::Field::cast(name.parent()?)?;
diff --git a/support/texlab/src/features/hover/label.rs b/support/texlab/src/features/hover/label.rs
index 2f8489d769..eafc104de6 100644
--- a/support/texlab/src/features/hover/label.rs
+++ b/support/texlab/src/features/hover/label.rs
@@ -1,19 +1,23 @@
-use lsp_types::{HoverParams, MarkupKind};
+use lsp_types::MarkupKind;
-use crate::{features::cursor::CursorContext, render_label};
+use crate::{
+ db::Word,
+ util::{self, cursor::CursorContext},
+};
use super::HoverResult;
-pub(super) fn find_label_hover(context: &CursorContext<HoverParams>) -> Option<HoverResult> {
+pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
let (name_text, range) = context
.find_label_name_key()
.or_else(|| context.find_label_name_command())?;
- let label = render_label(&context.request.workspace, &name_text, None)?;
-
- Some(HoverResult {
- range,
- value: label.reference(),
- value_kind: MarkupKind::PlainText,
- })
+ let db = context.db;
+ util::label::find_label_definition(db, context.document, Word::new(db, name_text))
+ .and_then(|(document, label)| util::label::render(db, document, label))
+ .map(|label| HoverResult {
+ range,
+ value: label.reference(db),
+ value_kind: MarkupKind::PlainText,
+ })
}
diff --git a/support/texlab/src/features/hover/string_ref.rs b/support/texlab/src/features/hover/string_ref.rs
index adccdd556c..41c4c5bddb 100644
--- a/support/texlab/src/features/hover/string_ref.rs
+++ b/support/texlab/src/features/hover/string_ref.rs
@@ -1,29 +1,28 @@
-use lsp_types::{HoverParams, MarkupKind};
+use lsp_types::MarkupKind;
use rowan::ast::AstNode;
use crate::{
citation::field::text::TextFieldData,
- features::cursor::CursorContext,
syntax::bibtex::{self, HasName, HasValue},
+ util::cursor::CursorContext,
};
use super::HoverResult;
-pub(super) fn find_string_reference_hover(
- context: &CursorContext<HoverParams>,
-) -> Option<HoverResult> {
- let data = context.request.main_document().data.as_bibtex()?;
+pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
+ let data = context.document.parse(context.db).as_bib()?;
let name = context
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::NAME)
.filter(|token| {
let parent = token.parent().unwrap();
bibtex::Value::can_cast(parent.kind()) || bibtex::StringDef::can_cast(parent.kind())
})?;
- for string in bibtex::SyntaxNode::new_root(data.green.clone())
+ for string in data
+ .root(context.db)
.children()
.filter_map(bibtex::StringDef::cast)
{
diff --git a/support/texlab/src/features/inlay_hint.rs b/support/texlab/src/features/inlay_hint.rs
new file mode 100644
index 0000000000..6d53259900
--- /dev/null
+++ b/support/texlab/src/features/inlay_hint.rs
@@ -0,0 +1,45 @@
+mod label;
+
+use lsp_types::{InlayHint, InlayHintLabel, Range, Url};
+use rowan::TextSize;
+
+use crate::{
+ db::Workspace,
+ util::{line_index::LineIndex, line_index_ext::LineIndexExt},
+ Db,
+};
+
+pub fn find_all(db: &dyn Db, uri: &Url, range: Range) -> Option<Vec<InlayHint>> {
+ let document = Workspace::get(db).lookup_uri(db, uri)?;
+ let line_index = document.contents(db).line_index(db);
+
+ let mut builder = InlayHintBuilder {
+ line_index,
+ hints: Vec::new(),
+ };
+
+ let range = line_index.offset_lsp_range(range);
+ label::find_hints(db, document, range, &mut builder);
+ Some(builder.hints)
+}
+
+struct InlayHintBuilder<'db> {
+ line_index: &'db LineIndex,
+ hints: Vec<InlayHint>,
+}
+
+impl<'db> InlayHintBuilder<'db> {
+ pub fn push(&mut self, offset: TextSize, text: String) {
+ let position = self.line_index.line_col_lsp(offset);
+ self.hints.push(InlayHint {
+ position,
+ label: InlayHintLabel::String(text),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: Some(true),
+ padding_right: None,
+ data: None,
+ });
+ }
+}
diff --git a/support/texlab/src/features/inlay_hint/label.rs b/support/texlab/src/features/inlay_hint/label.rs
new file mode 100644
index 0000000000..5654f79bf4
--- /dev/null
+++ b/support/texlab/src/features/inlay_hint/label.rs
@@ -0,0 +1,31 @@
+use rowan::TextRange;
+
+use crate::{
+ db::{analysis::label, Document},
+ util, Db,
+};
+
+use super::InlayHintBuilder;
+
+pub(super) fn find_hints(
+ db: &dyn Db,
+ document: Document,
+ range: TextRange,
+ builder: &mut InlayHintBuilder,
+) -> Option<()> {
+ let data = document.parse(db).as_tex()?;
+ for label in data
+ .analyze(db)
+ .labels(db)
+ .iter()
+ .copied()
+ .filter(|label| matches!(label.origin(db), label::Origin::Definition(_)))
+ .filter(|label| label.range(db).intersect(range).is_some())
+ {
+ if let Some(rendered) = util::label::render(db, document, label) {
+ builder.push(label.range(db).end(), rendered.reference(db));
+ }
+ }
+
+ Some(())
+}
diff --git a/support/texlab/src/features/link.rs b/support/texlab/src/features/link.rs
index c4ce4413f5..71bba58ba7 100644
--- a/support/texlab/src/features/link.rs
+++ b/support/texlab/src/features/link.rs
@@ -1,33 +1,41 @@
mod include;
-use std::sync::Arc;
-
-use lsp_types::{DocumentLink, DocumentLinkParams, Url};
+use lsp_types::{DocumentLink, Url};
use rowan::TextRange;
-use crate::LineIndexExt;
+use crate::{
+ db::{Document, Workspace},
+ util::{line_index::LineIndex, line_index_ext::LineIndexExt},
+ Db,
+};
-use self::include::find_include_links;
+pub fn find_all(db: &dyn Db, uri: &Url) -> Option<Vec<DocumentLink>> {
+ let document = Workspace::get(db).lookup_uri(db, uri)?;
+ let mut builder = LinkBuilder {
+ db,
+ line_index: document.contents(db).line_index(db),
+ links: Vec::new(),
+ };
-use super::FeatureRequest;
+ include::find_links(db, document, &mut builder);
+ Some(builder.links)
+}
-pub fn find_document_links(request: FeatureRequest<DocumentLinkParams>) -> Vec<DocumentLink> {
- let document = request.main_document();
- let mut results = Vec::new();
- find_include_links(&request, &mut results);
- results
- .into_iter()
- .map(|result| DocumentLink {
- range: document.line_index.line_col_lsp_range(result.range),
- target: Some(result.target.as_ref().clone()),
- tooltip: None,
- data: None,
- })
- .collect()
+struct LinkBuilder<'db> {
+ db: &'db dyn Db,
+ line_index: &'db LineIndex,
+ links: Vec<DocumentLink>,
}
-#[derive(Debug, Clone)]
-struct LinkResult {
- range: TextRange,
- target: Arc<Url>,
+impl<'db> LinkBuilder<'db> {
+ pub fn push(&mut self, range: TextRange, target: Document) {
+ let range = self.line_index.line_col_lsp_range(range);
+ let target = Some(target.location(self.db).uri(self.db).clone());
+ self.links.push(DocumentLink {
+ range,
+ target,
+ tooltip: None,
+ data: None,
+ });
+ }
}
diff --git a/support/texlab/src/features/link/include.rs b/support/texlab/src/features/link/include.rs
index b4d1804cce..f0c9d147fb 100644
--- a/support/texlab/src/features/link/include.rs
+++ b/support/texlab/src/features/link/include.rs
@@ -1,31 +1,23 @@
-use std::sync::Arc;
+use crate::{
+ db::{dependency_graph, Document, Workspace},
+ Db,
+};
-use lsp_types::DocumentLinkParams;
+use super::LinkBuilder;
-use crate::features::FeatureRequest;
+pub(super) fn find_links(db: &dyn Db, document: Document, builder: &mut LinkBuilder) -> Option<()> {
+ let workspace = Workspace::get(db);
+ let parent = workspace
+ .parents(db, document)
+ .iter()
+ .next()
+ .copied()
+ .unwrap_or(document);
-use super::LinkResult;
-
-pub(super) fn find_include_links(
- request: &FeatureRequest<DocumentLinkParams>,
- results: &mut Vec<LinkResult>,
-) -> Option<()> {
- let data = request.main_document().data.as_latex()?;
-
- for include in &data.extras.explicit_links {
- for target in &include.targets {
- if request
- .workspace
- .documents_by_uri
- .values()
- .any(|document| document.uri.as_ref() == target.as_ref())
- {
- results.push(LinkResult {
- range: include.stem_range,
- target: Arc::clone(target),
- });
- break;
- }
+ let graph = dependency_graph(db, parent);
+ for edge in graph.edges.iter().filter(|edge| edge.source == document) {
+ if let Some(origin) = edge.origin {
+ builder.push(origin.link.range(db), edge.target);
}
}
diff --git a/support/texlab/src/features/reference.rs b/support/texlab/src/features/reference.rs
index f84a3f67d5..54a5fefb27 100644
--- a/support/texlab/src/features/reference.rs
+++ b/support/texlab/src/features/reference.rs
@@ -2,40 +2,45 @@ mod entry;
mod label;
mod string;
-use std::sync::Arc;
-
-use lsp_types::{Location, ReferenceParams, Url};
+use lsp_types::{Location, Position, ReferenceContext, Url};
use rowan::TextRange;
-use crate::LineIndexExt;
-
-use self::{
- entry::find_entry_references, label::find_label_references, string::find_string_references,
+use crate::{
+ db::Document,
+ util::{cursor::CursorContext, line_index_ext::LineIndexExt},
+ Db,
};
-use super::{cursor::CursorContext, FeatureRequest};
-
-pub fn find_all_references(request: FeatureRequest<ReferenceParams>) -> Vec<Location> {
+pub fn find_all(
+ db: &dyn Db,
+ uri: &Url,
+ position: Position,
+ params: &ReferenceContext,
+) -> Option<Vec<Location>> {
let mut results = Vec::new();
- let context = CursorContext::new(request);
+ let context = CursorContext::new(db, uri, position, params)?;
log::debug!("[References] Cursor: {:?}", context.cursor);
- find_label_references(&context, &mut results);
- find_entry_references(&context, &mut results);
- find_string_references(&context, &mut results);
+ label::find_all_references(&context, &mut results);
+ entry::find_all_references(&context, &mut results);
+ string::find_all_references(&context, &mut results);
- results
+ let locations = results
.into_iter()
.map(|result| Location {
- uri: result.uri.as_ref().clone(),
- range: context.request.workspace.documents_by_uri[&result.uri]
- .line_index
+ uri: result.document.location(db).uri(db).clone(),
+ range: result
+ .document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(result.range),
})
- .collect()
+ .collect();
+
+ Some(locations)
}
#[derive(Debug, Clone)]
struct ReferenceResult {
- uri: Arc<Url>,
+ document: Document,
range: TextRange,
}
diff --git a/support/texlab/src/features/reference/entry.rs b/support/texlab/src/features/reference/entry.rs
index 0144d2d074..3dfa10da5b 100644
--- a/support/texlab/src/features/reference/entry.rs
+++ b/support/texlab/src/features/reference/entry.rs
@@ -1,32 +1,31 @@
-use std::sync::Arc;
-
-use lsp_types::ReferenceParams;
+use lsp_types::ReferenceContext;
use rowan::ast::AstNode;
use crate::{
- features::cursor::CursorContext,
+ db::parse::DocumentData,
syntax::{
bibtex::{self, HasName},
latex,
},
- DocumentData,
+ util::cursor::CursorContext,
};
use super::ReferenceResult;
-pub(super) fn find_entry_references(
- context: &CursorContext<ReferenceParams>,
+pub(super) fn find_all_references(
+ context: &CursorContext<&ReferenceContext>,
results: &mut Vec<ReferenceResult>,
) -> Option<()> {
+ let db = context.db;
let (key_text, _) = context
.find_citation_key_word()
.or_else(|| context.find_citation_key_command())
.or_else(|| context.find_entry_key())?;
- for document in context.request.workspace.documents_by_uri.values() {
- match &document.data {
- DocumentData::Latex(data) => {
- latex::SyntaxNode::new_root(data.green.clone())
+ for document in context.related() {
+ match document.parse(db) {
+ DocumentData::Tex(data) => {
+ data.root(db)
.descendants()
.filter_map(latex::Citation::cast)
.filter_map(|citation| citation.key_list())
@@ -34,24 +33,22 @@ pub(super) fn find_entry_references(
.filter(|key| key.to_string() == key_text)
.map(|key| latex::small_range(&key))
.for_each(|range| {
- let uri = Arc::clone(&document.uri);
- results.push(ReferenceResult { uri, range });
+ results.push(ReferenceResult { document, range });
});
}
- DocumentData::Bibtex(data) if context.request.params.context.include_declaration => {
- bibtex::SyntaxNode::new_root(data.green.clone())
+ DocumentData::Bib(data) if context.params.include_declaration => {
+ data.root(db)
.children()
.filter_map(bibtex::Entry::cast)
.filter_map(|entry| entry.name_token())
.filter(|key| key.text() == key_text)
.map(|key| key.text_range())
.for_each(|range| {
- let uri = Arc::clone(&document.uri);
- results.push(ReferenceResult { uri, range });
+ results.push(ReferenceResult { document, range });
});
}
- DocumentData::Bibtex(_) | DocumentData::BuildLog(_) => {}
- }
+ DocumentData::Bib(_) | DocumentData::Log(_) => {}
+ };
}
Some(())
diff --git a/support/texlab/src/features/reference/label.rs b/support/texlab/src/features/reference/label.rs
index 3dadada08b..394542320c 100644
--- a/support/texlab/src/features/reference/label.rs
+++ b/support/texlab/src/features/reference/label.rs
@@ -1,33 +1,32 @@
-use std::sync::Arc;
+use lsp_types::ReferenceContext;
-use lsp_types::ReferenceParams;
-
-use crate::features::cursor::CursorContext;
+use crate::util::cursor::CursorContext;
use super::ReferenceResult;
-pub(super) fn find_label_references(
- context: &CursorContext<ReferenceParams>,
+pub(super) fn find_all_references(
+ context: &CursorContext<&ReferenceContext>,
results: &mut Vec<ReferenceResult>,
) -> Option<()> {
+ let db = context.db;
let (name_text, _) = context
.find_label_name_key()
.or_else(|| context.find_label_name_command())?;
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- for name in data
- .extras
- .label_names
+ for document in context.related() {
+ if let Some(data) = document.parse(db).as_tex() {
+ for label in data
+ .analyze(db)
+ .labels(db)
.iter()
- .filter(|name| name.text == name_text)
- .filter(|name| {
- !name.is_definition || context.request.params.context.include_declaration
+ .filter(|label| label.name(db).text(db) == &name_text)
+ .filter(|label| {
+ label.origin(db).as_definition().is_none() || context.params.include_declaration
})
{
results.push(ReferenceResult {
- uri: Arc::clone(&document.uri),
- range: name.range,
+ document,
+ range: label.range(db),
});
}
}
diff --git a/support/texlab/src/features/reference/string.rs b/support/texlab/src/features/reference/string.rs
index c00cf93546..c4bc6ded3d 100644
--- a/support/texlab/src/features/reference/string.rs
+++ b/support/texlab/src/features/reference/string.rs
@@ -1,22 +1,21 @@
-use std::sync::Arc;
-
-use lsp_types::ReferenceParams;
+use lsp_types::ReferenceContext;
use rowan::ast::AstNode;
use crate::{
- features::cursor::CursorContext,
syntax::bibtex::{self, HasName},
+ util::cursor::CursorContext,
};
use super::ReferenceResult;
-pub(super) fn find_string_references(
- context: &CursorContext<ReferenceParams>,
+pub(super) fn find_all_references(
+ context: &CursorContext<&ReferenceContext>,
results: &mut Vec<ReferenceResult>,
) -> Option<()> {
+ let db = context.db;
let name_text = context
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::NAME)
.filter(|token| {
let parent = token.parent().unwrap();
@@ -24,14 +23,11 @@ pub(super) fn find_string_references(
})?
.text();
- let document = context.request.main_document();
- let data = document.data.as_bibtex()?;
- for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() {
+ let data = context.document.parse(db).as_bib()?;
+ for node in data.root(db).descendants() {
if let Some(name) = bibtex::StringDef::cast(node.clone())
.and_then(|string| string.name_token())
- .filter(|name| {
- context.request.params.context.include_declaration && name.text() == name_text
- })
+ .filter(|name| context.params.include_declaration && name.text() == name_text)
.or_else(|| {
bibtex::Value::cast(node)
.and_then(|token| token.syntax().first_token())
@@ -39,7 +35,7 @@ pub(super) fn find_string_references(
})
{
results.push(ReferenceResult {
- uri: Arc::clone(&document.uri),
+ document: context.document,
range: name.text_range(),
});
}
diff --git a/support/texlab/src/features/rename.rs b/support/texlab/src/features/rename.rs
index c21faf262e..57e1ef5763 100644
--- a/support/texlab/src/features/rename.rs
+++ b/support/texlab/src/features/rename.rs
@@ -2,57 +2,61 @@ mod command;
mod entry;
mod label;
-use std::sync::Arc;
-
-use lsp_types::{Range, RenameParams, TextDocumentPositionParams, TextEdit, Url, WorkspaceEdit};
+use lsp_types::{Position, Range, TextEdit, Url, WorkspaceEdit};
use rowan::TextRange;
use rustc_hash::FxHashMap;
-use crate::LineIndexExt;
-
-use self::{
- command::{prepare_command_rename, rename_command},
- entry::{prepare_entry_rename, rename_entry},
- label::{prepare_label_rename, rename_label},
+use crate::{
+ db::Document,
+ util::{cursor::CursorContext, line_index_ext::LineIndexExt},
+ Db,
};
-use super::{cursor::CursorContext, FeatureRequest};
-
-pub fn prepare_rename_all(request: FeatureRequest<TextDocumentPositionParams>) -> Option<Range> {
- let context = CursorContext::new(request);
- let range = prepare_entry_rename(&context)
- .or_else(|| prepare_label_rename(&context))
- .or_else(|| prepare_command_rename(&context))?;
+pub fn prepare_rename_all(db: &dyn Db, uri: &Url, position: Position) -> Option<Range> {
+ let context = CursorContext::new(db, uri, position, ())?;
+ let range = entry::prepare_rename(&context)
+ .or_else(|| label::prepare_rename(&context))
+ .or_else(|| command::prepare_rename(&context))?;
- let line_index = &context.request.main_document().line_index;
+ let line_index = context.document.contents(db).line_index(db);
Some(line_index.line_col_lsp_range(range))
}
-pub fn rename_all(request: FeatureRequest<RenameParams>) -> Option<WorkspaceEdit> {
- let context = CursorContext::new(request);
- let result = rename_entry(&context)
- .or_else(|| rename_label(&context))
- .or_else(|| rename_command(&context))?;
+pub fn rename_all(
+ db: &dyn Db,
+ uri: &Url,
+ position: Position,
+ new_name: String,
+) -> Option<WorkspaceEdit> {
+ let context = CursorContext::new(db, uri, position, Params { new_name })?;
+ let result = entry::rename(&context)
+ .or_else(|| label::rename(&context))
+ .or_else(|| command::rename(&context))?;
let changes = result
.changes
.into_iter()
- .map(|(uri, old_edits)| {
- let document = &context.request.workspace.documents_by_uri[&uri];
+ .map(|(document, old_edits)| {
+ let line_index = document.contents(db).line_index(db);
let new_edits = old_edits
.into_iter()
.map(|Indel { delete, insert }| {
- TextEdit::new(document.line_index.line_col_lsp_range(delete), insert)
+ TextEdit::new(line_index.line_col_lsp_range(delete), insert)
})
.collect();
- (uri.as_ref().clone(), new_edits)
+ (document.location(db).uri(db).clone(), new_edits)
})
.collect();
Some(WorkspaceEdit::new(changes))
}
+#[derive(Debug)]
+struct Params {
+ new_name: String,
+}
+
#[derive(Debug, PartialEq, Eq, Clone)]
struct Indel {
delete: TextRange,
@@ -61,5 +65,5 @@ struct Indel {
#[derive(Debug, PartialEq, Eq, Clone)]
struct RenameResult {
- changes: FxHashMap<Arc<Url>, Vec<Indel>>,
+ changes: FxHashMap<Document, Vec<Indel>>,
}
diff --git a/support/texlab/src/features/rename/command.rs b/support/texlab/src/features/rename/command.rs
index aaa08f8cb4..5610b2f7ee 100644
--- a/support/texlab/src/features/rename/command.rs
+++ b/support/texlab/src/features/rename/command.rs
@@ -1,29 +1,21 @@
-use std::sync::Arc;
-
-use lsp_types::RenameParams;
use rowan::{TextRange, TextSize};
use rustc_hash::FxHashMap;
-use crate::{
- features::cursor::{CursorContext, HasPosition},
- syntax::latex,
-};
+use crate::util::cursor::CursorContext;
-use super::{Indel, RenameResult};
+use super::{Indel, Params, RenameResult};
-pub(super) fn prepare_command_rename<P: HasPosition>(
- context: &CursorContext<P>,
-) -> Option<TextRange> {
+pub(super) fn prepare_rename<T>(context: &CursorContext<T>) -> Option<TextRange> {
context.cursor.command_range(context.offset)
}
-pub(super) fn rename_command(context: &CursorContext<RenameParams>) -> Option<RenameResult> {
- prepare_command_rename(context)?;
- let name = context.cursor.as_latex()?.text();
+pub(super) fn rename(context: &CursorContext<Params>) -> Option<RenameResult> {
+ prepare_rename(context)?;
+ let name = context.cursor.as_tex()?.text();
let mut changes = FxHashMap::default();
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- let root = latex::SyntaxNode::new_root(data.green.clone());
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
+ let root = data.root(context.db);
let edits = root
.descendants_with_tokens()
.filter_map(|element| element.into_token())
@@ -32,12 +24,12 @@ pub(super) fn rename_command(context: &CursorContext<RenameParams>) -> Option<Re
let range = token.text_range();
Indel {
delete: TextRange::new(range.start() + TextSize::from(1), range.end()),
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
}
})
.collect();
- changes.insert(Arc::clone(&document.uri), edits);
+ changes.insert(document, edits);
}
}
diff --git a/support/texlab/src/features/rename/entry.rs b/support/texlab/src/features/rename/entry.rs
index 6eac79030b..79ee25ac71 100644
--- a/support/texlab/src/features/rename/entry.rs
+++ b/support/texlab/src/features/rename/entry.rs
@@ -1,23 +1,18 @@
-use std::sync::Arc;
-
-use lsp_types::RenameParams;
use rowan::{ast::AstNode, TextRange};
use rustc_hash::FxHashMap;
use crate::{
- features::cursor::{CursorContext, HasPosition},
+ db::parse::DocumentData,
syntax::{
bibtex::{self, HasName},
latex,
},
- DocumentData,
+ util::cursor::CursorContext,
};
-use super::{Indel, RenameResult};
+use super::{Indel, Params, RenameResult};
-pub(super) fn prepare_entry_rename<P: HasPosition>(
- context: &CursorContext<P>,
-) -> Option<TextRange> {
+pub(super) fn prepare_rename<T>(context: &CursorContext<T>) -> Option<TextRange> {
let (_, range) = context
.find_citation_key_word()
.or_else(|| context.find_entry_key())?;
@@ -25,18 +20,17 @@ pub(super) fn prepare_entry_rename<P: HasPosition>(
Some(range)
}
-pub(super) fn rename_entry(context: &CursorContext<RenameParams>) -> Option<RenameResult> {
- prepare_entry_rename(context)?;
+pub(super) fn rename(context: &CursorContext<Params>) -> Option<RenameResult> {
+ prepare_rename(context)?;
let (key_text, _) = context
.find_citation_key_word()
.or_else(|| context.find_entry_key())?;
let mut changes = FxHashMap::default();
- for document in context.request.workspace.documents_by_uri.values() {
- let uri = Arc::clone(&document.uri);
- match &document.data {
- DocumentData::Latex(data) => {
- let root = latex::SyntaxNode::new_root(data.green.clone());
+ for document in context.related() {
+ match document.parse(context.db) {
+ DocumentData::Tex(data) => {
+ let root = data.root(context.db);
let edits: Vec<_> = root
.descendants()
.filter_map(latex::Citation::cast)
@@ -45,13 +39,13 @@ pub(super) fn rename_entry(context: &CursorContext<RenameParams>) -> Option<Rena
.filter(|key| key.to_string() == key_text)
.map(|key| Indel {
delete: latex::small_range(&key),
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
})
.collect();
- changes.insert(uri, edits);
+ changes.insert(document, edits);
}
- DocumentData::Bibtex(data) => {
- let root = bibtex::SyntaxNode::new_root(data.green.clone());
+ DocumentData::Bib(data) => {
+ let root = data.root(context.db);
let edits: Vec<_> = root
.descendants()
.filter_map(bibtex::Entry::cast)
@@ -59,12 +53,12 @@ pub(super) fn rename_entry(context: &CursorContext<RenameParams>) -> Option<Rena
.filter(|key| key.text() == key_text)
.map(|key| Indel {
delete: key.text_range(),
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
})
.collect();
- changes.insert(uri, edits);
+ changes.insert(document, edits);
}
- DocumentData::BuildLog(_) => {}
+ DocumentData::Log(_) => {}
}
}
diff --git a/support/texlab/src/features/rename/label.rs b/support/texlab/src/features/rename/label.rs
index 30a99c3b36..d38d13ae93 100644
--- a/support/texlab/src/features/rename/label.rs
+++ b/support/texlab/src/features/rename/label.rs
@@ -1,32 +1,24 @@
-use std::sync::Arc;
-
-use lsp_types::RenameParams;
use rowan::{ast::AstNode, TextRange};
use rustc_hash::FxHashMap;
-use crate::{
- features::cursor::{CursorContext, HasPosition},
- syntax::latex,
-};
+use crate::{syntax::latex, util::cursor::CursorContext};
-use super::{Indel, RenameResult};
+use super::{Indel, Params, RenameResult};
-pub(super) fn prepare_label_rename<P: HasPosition>(
- context: &CursorContext<P>,
-) -> Option<TextRange> {
+pub(super) fn prepare_rename<T>(context: &CursorContext<T>) -> Option<TextRange> {
let (_, range) = context.find_label_name_key()?;
Some(range)
}
-pub(super) fn rename_label(context: &CursorContext<RenameParams>) -> Option<RenameResult> {
- prepare_label_rename(context)?;
+pub(super) fn rename(context: &CursorContext<Params>) -> Option<RenameResult> {
+ prepare_rename(context)?;
let (name_text, _) = context.find_label_name_key()?;
let mut changes = FxHashMap::default();
- for document in context.request.workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
+ for document in context.related() {
+ if let Some(data) = document.parse(context.db).as_tex() {
let mut edits = Vec::new();
- for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() {
+ for node in data.root(context.db).descendants() {
if let Some(range) = latex::LabelDefinition::cast(node.clone())
.and_then(|label| label.name())
.and_then(|name| name.key())
@@ -35,7 +27,7 @@ pub(super) fn rename_label(context: &CursorContext<RenameParams>) -> Option<Rena
{
edits.push(Indel {
delete: range,
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
});
}
@@ -47,7 +39,7 @@ pub(super) fn rename_label(context: &CursorContext<RenameParams>) -> Option<Rena
.for_each(|name| {
edits.push(Indel {
delete: latex::small_range(&name),
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
});
});
@@ -59,7 +51,7 @@ pub(super) fn rename_label(context: &CursorContext<RenameParams>) -> Option<Rena
{
edits.push(Indel {
delete: latex::small_range(&name1),
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
});
}
@@ -70,13 +62,13 @@ pub(super) fn rename_label(context: &CursorContext<RenameParams>) -> Option<Rena
{
edits.push(Indel {
delete: latex::small_range(&name2),
- insert: context.request.params.new_name.clone(),
+ insert: context.params.new_name.clone(),
});
}
}
}
- changes.insert(Arc::clone(&document.uri), edits);
+ changes.insert(document, edits);
}
}
diff --git a/support/texlab/src/features/symbol.rs b/support/texlab/src/features/symbol.rs
index 24d599c3e1..c16193efcb 100644
--- a/support/texlab/src/features/symbol.rs
+++ b/support/texlab/src/features/symbol.rs
@@ -3,49 +3,44 @@ mod latex;
mod project_order;
mod types;
-use std::{cmp::Reverse, sync::Arc};
+use std::cmp::Reverse;
-use lsp_types::{
- DocumentSymbolParams, DocumentSymbolResponse, PartialResultParams, SymbolInformation,
- TextDocumentIdentifier, WorkDoneProgressParams, WorkspaceSymbolParams,
-};
+use lsp_types::{DocumentSymbolResponse, SymbolInformation, Url, WorkspaceSymbolParams};
-use crate::{ClientCapabilitiesExt, Workspace};
+use crate::{db::Workspace, util::capabilities::ClientCapabilitiesExt, Db};
-use self::{
- bibtex::find_bibtex_symbols, latex::find_latex_symbols, project_order::ProjectOrdering,
- types::InternalSymbol,
-};
+use self::project_order::ProjectOrdering;
-use super::FeatureRequest;
+pub fn find_document_symbols(db: &dyn Db, uri: &Url) -> Option<DocumentSymbolResponse> {
+ let workspace = Workspace::get(db);
+ let document = workspace.lookup_uri(db, uri)?;
-#[must_use]
-pub fn find_document_symbols(req: FeatureRequest<DocumentSymbolParams>) -> DocumentSymbolResponse {
let mut buf = Vec::new();
- find_latex_symbols(&req, &mut buf);
- find_bibtex_symbols(&req, &mut buf);
- if req
- .workspace
- .environment
- .client_capabilities
+ latex::find_symbols(db, document, &mut buf);
+ bibtex::find_symbols(db, document, &mut buf);
+ if workspace
+ .client_capabilities(db)
.has_hierarchical_document_symbol_support()
{
- DocumentSymbolResponse::Nested(
- buf.into_iter()
- .map(InternalSymbol::into_document_symbol)
- .collect(),
- )
+ let symbols = buf
+ .into_iter()
+ .map(|symbol| symbol.into_document_symbol(db))
+ .collect();
+
+ Some(DocumentSymbolResponse::Nested(symbols))
} else {
let mut new_buf = Vec::new();
for symbol in buf {
symbol.flatten(&mut new_buf);
}
+
let mut new_buf: Vec<_> = new_buf
.into_iter()
- .map(|symbol| symbol.into_symbol_info(req.main_document().uri.as_ref().clone()))
+ .map(|symbol| symbol.into_symbol_info(uri.clone()))
.collect();
- sort_symbols(&req.workspace, &mut new_buf);
- DocumentSymbolResponse::Flat(new_buf)
+
+ sort_symbols(db, &mut new_buf);
+ Some(DocumentSymbolResponse::Flat(new_buf))
}
}
@@ -57,25 +52,16 @@ struct WorkspaceSymbol {
#[must_use]
pub fn find_workspace_symbols(
- workspace: &Workspace,
+ db: &dyn Db,
params: &WorkspaceSymbolParams,
) -> Vec<SymbolInformation> {
let mut symbols = Vec::new();
- for document in workspace.documents_by_uri.values() {
- let request = FeatureRequest {
- uri: Arc::clone(&document.uri),
- params: DocumentSymbolParams {
- text_document: TextDocumentIdentifier::new(document.uri.as_ref().clone()),
- partial_result_params: PartialResultParams::default(),
- work_done_progress_params: WorkDoneProgressParams::default(),
- },
- workspace: workspace.slice(&document.uri),
- };
-
+ let workspace = Workspace::get(db);
+ for document in workspace.documents(db).iter().copied() {
let mut buf = Vec::new();
- find_latex_symbols(&request, &mut buf);
- find_bibtex_symbols(&request, &mut buf);
+ latex::find_symbols(db, document, &mut buf);
+ bibtex::find_symbols(db, document, &mut buf);
let mut new_buf = Vec::new();
for symbol in buf {
@@ -85,7 +71,7 @@ pub fn find_workspace_symbols(
for symbol in new_buf {
symbols.push(WorkspaceSymbol {
search_text: symbol.search_text(),
- info: symbol.into_symbol_info(document.uri.as_ref().clone()),
+ info: symbol.into_symbol_info(document.location(db).uri(db).clone()),
});
}
}
@@ -95,6 +81,7 @@ pub fn find_workspace_symbols(
.split_whitespace()
.map(str::to_lowercase)
.collect();
+
let mut filtered = Vec::new();
for symbol in symbols {
let mut included = true;
@@ -109,20 +96,21 @@ pub fn find_workspace_symbols(
filtered.push(symbol.info);
}
}
- sort_symbols(workspace, &mut filtered);
+
+ sort_symbols(db, &mut filtered);
filtered
}
-fn sort_symbols(workspace: &Workspace, symbols: &mut [SymbolInformation]) {
- let ordering = ProjectOrdering::from(workspace);
+fn sort_symbols(db: &dyn Db, symbols: &mut [SymbolInformation]) {
+ let ordering = ProjectOrdering::new(db);
symbols.sort_by(|left, right| {
let left_key = (
- ordering.get(&left.location.uri),
+ ordering.get(db, &left.location.uri),
left.location.range.start,
Reverse(left.location.range.end),
);
let right_key = (
- ordering.get(&right.location.uri),
+ ordering.get(db, &right.location.uri),
right.location.range.start,
Reverse(right.location.range.end),
);
diff --git a/support/texlab/src/features/symbol/bibtex.rs b/support/texlab/src/features/symbol/bibtex.rs
index bbc9026b0b..6a92405f17 100644
--- a/support/texlab/src/features/symbol/bibtex.rs
+++ b/support/texlab/src/features/symbol/bibtex.rs
@@ -1,82 +1,87 @@
-use lsp_types::DocumentSymbolParams;
use rowan::ast::AstNode;
use crate::{
- features::FeatureRequest,
+ db::Document,
syntax::bibtex::{self, HasName, HasType},
- BibtexEntryTypeCategory, LineIndexExt, LANGUAGE_DATA,
+ util::{
+ lang_data::{BibtexEntryTypeCategory, LANGUAGE_DATA},
+ line_index::LineIndex,
+ line_index_ext::LineIndexExt,
+ },
+ Db,
};
use super::types::{InternalSymbol, InternalSymbolKind};
-pub fn find_bibtex_symbols(
- request: &FeatureRequest<DocumentSymbolParams>,
+pub fn find_symbols(db: &dyn Db, document: Document, buf: &mut Vec<InternalSymbol>) -> Option<()> {
+ let data = document.parse(db).as_bib()?;
+ let line_index = document.contents(db).line_index(db);
+ for node in data.root(db).children() {
+ process_string(node.clone(), line_index, buf)
+ .or_else(|| process_entry(node, line_index, buf));
+ }
+
+ Some(())
+}
+
+fn process_string(
+ node: bibtex::SyntaxNode,
+ line_index: &LineIndex,
buf: &mut Vec<InternalSymbol>,
) -> Option<()> {
- let main_document = request.main_document();
- let data = main_document.data.as_bibtex()?;
-
- for node in bibtex::SyntaxNode::new_root(data.green.clone()).children() {
- if let Some(string) = bibtex::StringDef::cast(node.clone()) {
- if let Some(name) = string.name_token() {
- buf.push(InternalSymbol {
- name: name.text().into(),
- label: None,
- kind: InternalSymbolKind::String,
- deprecated: false,
- full_range: main_document
- .line_index
- .line_col_lsp_range(string.syntax().text_range()),
- selection_range: main_document
- .line_index
- .line_col_lsp_range(name.text_range()),
- children: Vec::new(),
- })
- }
- } else if let Some(entry) = bibtex::Entry::cast(node) {
- if let Some(ty) = entry.type_token() {
- if let Some(key) = entry.name_token() {
- let mut children = Vec::new();
- for field in entry.fields() {
- if let Some(name) = field.name_token() {
- let symbol = InternalSymbol {
- name: name.text().to_string(),
- label: None,
- kind: InternalSymbolKind::Field,
- deprecated: false,
- full_range: main_document
- .line_index
- .line_col_lsp_range(field.syntax().text_range()),
- selection_range: main_document
- .line_index
- .line_col_lsp_range(name.text_range()),
- children: Vec::new(),
- };
- children.push(symbol);
- }
- }
+ let string = bibtex::StringDef::cast(node)?;
+ let name = string.name_token()?;
+ buf.push(InternalSymbol {
+ name: name.text().into(),
+ label: None,
+ kind: InternalSymbolKind::String,
+ deprecated: false,
+ full_range: line_index.line_col_lsp_range(string.syntax().text_range()),
+ selection_range: line_index.line_col_lsp_range(name.text_range()),
+ children: Vec::new(),
+ });
- let category = LANGUAGE_DATA
- .find_entry_type(&ty.text()[1..])
- .map(|ty| ty.category)
- .unwrap_or(BibtexEntryTypeCategory::Misc);
+ Some(())
+}
- buf.push(InternalSymbol {
- name: key.to_string(),
- label: None,
- kind: InternalSymbolKind::Entry(category),
- deprecated: false,
- full_range: main_document
- .line_index
- .line_col_lsp_range(entry.syntax().text_range()),
- selection_range: main_document
- .line_index
- .line_col_lsp_range(key.text_range()),
- children,
- });
- }
- }
+fn process_entry(
+ node: bibtex::SyntaxNode,
+ line_index: &LineIndex,
+ buf: &mut Vec<InternalSymbol>,
+) -> Option<()> {
+ let entry = bibtex::Entry::cast(node)?;
+ let ty = entry.type_token()?;
+ let key = entry.name_token()?;
+ let mut children = Vec::new();
+ for field in entry.fields() {
+ if let Some(name) = field.name_token() {
+ let symbol = InternalSymbol {
+ name: name.text().to_string(),
+ label: None,
+ kind: InternalSymbolKind::Field,
+ deprecated: false,
+ full_range: line_index.line_col_lsp_range(field.syntax().text_range()),
+ selection_range: line_index.line_col_lsp_range(name.text_range()),
+ children: Vec::new(),
+ };
+ children.push(symbol);
}
}
+
+ let category = LANGUAGE_DATA
+ .find_entry_type(&ty.text()[1..])
+ .map(|ty| ty.category)
+ .unwrap_or(BibtexEntryTypeCategory::Misc);
+
+ buf.push(InternalSymbol {
+ name: key.to_string(),
+ label: None,
+ kind: InternalSymbolKind::Entry(category),
+ deprecated: false,
+ full_range: line_index.line_col_lsp_range(entry.syntax().text_range()),
+ selection_range: line_index.line_col_lsp_range(key.text_range()),
+ children,
+ });
+
Some(())
}
diff --git a/support/texlab/src/features/symbol/latex.rs b/support/texlab/src/features/symbol/latex.rs
index 8a88ffb0d9..81927ab515 100644
--- a/support/texlab/src/features/symbol/latex.rs
+++ b/support/texlab/src/features/symbol/latex.rs
@@ -1,38 +1,30 @@
use std::str::FromStr;
-use lsp_types::{DocumentSymbolParams, Range};
+use lsp_types::Range;
use rowan::ast::AstNode;
-use smol_str::SmolStr;
use titlecase::titlecase;
use crate::{
- features::FeatureRequest,
- find_caption_by_parent, find_label_number,
+ db::{Document, Word, Workspace},
syntax::latex::{self, HasBrack, HasCurly},
- LabelledFloatKind, LatexDocumentData, LineIndexExt, LANGUAGE_DATA,
+ util::{
+ label::{find_caption_by_parent, LabeledFloatKind},
+ lang_data::LANGUAGE_DATA,
+ line_index_ext::LineIndexExt,
+ },
+ Db,
};
use super::types::{InternalSymbol, InternalSymbolKind};
-pub fn find_latex_symbols(
- request: &FeatureRequest<DocumentSymbolParams>,
- buf: &mut Vec<InternalSymbol>,
-) -> Option<()> {
- let data = request.main_document().data.as_latex()?;
- let mut context = Context { request, data };
-
- let root = context.data.green.clone();
- let mut symbols = visit(&mut context, latex::SyntaxNode::new_root(root));
+pub fn find_symbols(db: &dyn Db, document: Document, buf: &mut Vec<InternalSymbol>) -> Option<()> {
+ let data = document.parse(db).as_tex()?;
+ let mut symbols = visit(db, document, data.root(db));
buf.append(&mut symbols);
Some(())
}
-struct Context<'a> {
- request: &'a FeatureRequest<DocumentSymbolParams>,
- data: &'a LatexDocumentData,
-}
-
-fn visit(context: &mut Context, node: latex::SyntaxNode) -> Vec<InternalSymbol> {
+fn visit(db: &dyn Db, document: Document, node: latex::SyntaxNode) -> Vec<InternalSymbol> {
let symbol = match node.kind() {
latex::PART
| latex::CHAPTER
@@ -40,9 +32,9 @@ fn visit(context: &mut Context, node: latex::SyntaxNode) -> Vec<InternalSymbol>
| latex::SUBSECTION
| latex::SUBSUBSECTION
| latex::PARAGRAPH
- | latex::SUBPARAGRAPH => visit_section(context, node.clone()),
- latex::ENUM_ITEM => visit_enum_item(context, node.clone()),
- latex::EQUATION => visit_equation(context, node.clone()),
+ | latex::SUBPARAGRAPH => visit_section(db, document, node.clone()),
+ latex::ENUM_ITEM => visit_enum_item(db, document, node.clone()),
+ latex::EQUATION => visit_equation(db, document, node.clone()),
latex::ENVIRONMENT => latex::Environment::cast(node.clone())
.and_then(|env| env.begin())
.and_then(|begin| begin.name())
@@ -54,17 +46,17 @@ fn visit(context: &mut Context, node: latex::SyntaxNode) -> Vec<InternalSymbol>
.iter()
.any(|env| env == &name)
{
- visit_equation_environment(context, node.clone())
+ visit_equation_environment(db, document, node.clone())
} else if LANGUAGE_DATA
.enum_environments
.iter()
.any(|env| env == &name)
{
- visit_enumeration(context, node.clone(), &name)
- } else if let Ok(float_kind) = LabelledFloatKind::from_str(&name) {
- visit_float(context, node.clone(), float_kind)
+ visit_enumeration(db, document, node.clone(), &name)
+ } else if let Ok(float_kind) = LabeledFloatKind::from_str(&name) {
+ visit_float(db, document, node.clone(), float_kind)
} else {
- visit_theorem(context, node.clone(), &name)
+ visit_theorem(db, document, node.clone(), &name)
}
}),
_ => None,
@@ -73,39 +65,42 @@ fn visit(context: &mut Context, node: latex::SyntaxNode) -> Vec<InternalSymbol>
match symbol {
Some(mut parent) => {
for child in node.children() {
- parent.children.append(&mut visit(context, child));
+ parent.children.append(&mut visit(db, document, child));
}
vec![parent]
}
None => {
let mut symbols = Vec::new();
for child in node.children() {
- symbols.append(&mut visit(context, child));
+ symbols.append(&mut visit(db, document, child));
}
symbols
}
}
}
-fn visit_section(context: &mut Context, node: latex::SyntaxNode) -> Option<InternalSymbol> {
+fn visit_section(
+ db: &dyn Db,
+ document: Document,
+ node: latex::SyntaxNode,
+) -> Option<InternalSymbol> {
let section = latex::Section::cast(node)?;
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&section));
let group = section.name()?;
let group_text = group.content_text()?;
- let symbol = match find_label_by_parent(context, section.syntax()) {
+ let symbol = match find_label_by_parent(db, document, section.syntax()) {
Some(NumberedLabel {
name: label,
range: selection_range,
number,
}) => {
let name = match number {
- Some(number) => format!("{} {}", number, group_text),
+ Some(number) => format!("{} {}", number.text(db), group_text),
None => group_text,
};
@@ -129,10 +124,15 @@ fn visit_section(context: &mut Context, node: latex::SyntaxNode) -> Option<Inter
children: Vec::new(),
},
};
+
Some(symbol)
}
-fn visit_enum_item(context: &mut Context, node: latex::SyntaxNode) -> Option<InternalSymbol> {
+fn visit_enum_item(
+ db: &dyn Db,
+ document: Document,
+ node: latex::SyntaxNode,
+) -> Option<InternalSymbol> {
let enum_item = latex::EnumItem::cast(node.clone())?;
if !enum_item
.syntax()
@@ -151,10 +151,9 @@ fn visit_enum_item(context: &mut Context, node: latex::SyntaxNode) -> Option<Int
return None;
}
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&enum_item));
let name = enum_item
@@ -162,13 +161,15 @@ fn visit_enum_item(context: &mut Context, node: latex::SyntaxNode) -> Option<Int
.and_then(|label| label.content_text())
.unwrap_or_else(|| "Item".to_string());
- let symbol = match find_label_by_parent(context, &node) {
+ let symbol = match find_label_by_parent(db, document, &node) {
Some(NumberedLabel {
name: label,
range: selection_range,
number,
}) => InternalSymbol {
- name: number.map(Into::into).unwrap_or_else(|| name.to_string()),
+ name: number
+ .map(|num| num.text(db).clone())
+ .unwrap_or_else(|| name.clone()),
label: Some(label),
kind: InternalSymbolKind::EnumerationItem,
deprecated: false,
@@ -189,46 +190,50 @@ fn visit_enum_item(context: &mut Context, node: latex::SyntaxNode) -> Option<Int
Some(symbol)
}
-fn visit_equation(context: &mut Context, node: latex::SyntaxNode) -> Option<InternalSymbol> {
+fn visit_equation(
+ db: &dyn Db,
+ document: Document,
+ node: latex::SyntaxNode,
+) -> Option<InternalSymbol> {
let equation = latex::Equation::cast(node)?;
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&equation));
- make_equation_symbol(context, equation.syntax(), full_range)
+ make_equation_symbol(db, document, equation.syntax(), full_range)
}
fn visit_equation_environment(
- context: &mut Context,
+ db: &dyn Db,
+ document: Document,
node: latex::SyntaxNode,
) -> Option<InternalSymbol> {
let environment = latex::Environment::cast(node)?;
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&environment));
- make_equation_symbol(context, environment.syntax(), full_range)
+ make_equation_symbol(db, document, environment.syntax(), full_range)
}
fn make_equation_symbol(
- context: &mut Context,
+ db: &dyn Db,
+ document: Document,
node: &latex::SyntaxNode,
full_range: Range,
) -> Option<InternalSymbol> {
- let symbol = match find_label_by_parent(context, node) {
+ let symbol = match find_label_by_parent(db, document, node) {
Some(NumberedLabel {
name: label,
range: selection_range,
number,
}) => {
let name = match number {
- Some(number) => format!("Equation ({})", number),
+ Some(number) => format!("Equation ({})", number.text(db)),
None => "Equation".to_string(),
};
@@ -256,26 +261,26 @@ fn make_equation_symbol(
}
fn visit_enumeration(
- context: &mut Context,
+ db: &dyn Db,
+ document: Document,
node: latex::SyntaxNode,
env_name: &str,
) -> Option<InternalSymbol> {
let environment = latex::Environment::cast(node)?;
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&environment));
let name = titlecase(env_name);
- let symbol = match find_label_by_parent(context, environment.syntax()) {
+ let symbol = match find_label_by_parent(db, document, environment.syntax()) {
Some(NumberedLabel {
name: label,
range: selection_range,
number,
}) => {
let name = match number {
- Some(number) => format!("{} {}", name, number),
+ Some(number) => format!("{} {}", name, number.text(db)),
None => name,
};
@@ -303,33 +308,33 @@ fn visit_enumeration(
}
fn visit_float(
- context: &mut Context,
+ db: &dyn Db,
+ document: Document,
node: latex::SyntaxNode,
- float_kind: LabelledFloatKind,
+ float_kind: LabeledFloatKind,
) -> Option<InternalSymbol> {
let environment = latex::Environment::cast(node)?;
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&environment));
let (float_kind, symbol_kind) = match float_kind {
- LabelledFloatKind::Algorithm => ("Algorithm", InternalSymbolKind::Algorithm),
- LabelledFloatKind::Figure => ("Figure", InternalSymbolKind::Figure),
- LabelledFloatKind::Listing => ("Listing", InternalSymbolKind::Listing),
- LabelledFloatKind::Table => ("Table", InternalSymbolKind::Table),
+ LabeledFloatKind::Algorithm => ("Algorithm", InternalSymbolKind::Algorithm),
+ LabeledFloatKind::Figure => ("Figure", InternalSymbolKind::Figure),
+ LabeledFloatKind::Listing => ("Listing", InternalSymbolKind::Listing),
+ LabeledFloatKind::Table => ("Table", InternalSymbolKind::Table),
};
let caption = find_caption_by_parent(environment.syntax())?;
- let symbol = match find_label_by_parent(context, environment.syntax()) {
+ let symbol = match find_label_by_parent(db, document, environment.syntax()) {
Some(NumberedLabel {
name: label,
range: selection_range,
number,
}) => {
let name = match number {
- Some(number) => format!("{} {}: {}", float_kind, number, caption),
+ Some(number) => format!("{} {}: {}", float_kind, number.text(db), caption),
None => format!("{}: {}", float_kind, caption),
};
@@ -358,23 +363,17 @@ fn visit_float(
}
fn visit_theorem(
- context: &mut Context,
+ db: &dyn Db,
+ document: Document,
node: latex::SyntaxNode,
environment_name: &str,
) -> Option<InternalSymbol> {
- let definition = context
- .request
- .workspace
- .documents_by_uri
- .values()
- .filter_map(|document| document.data.as_latex())
- .find_map(|data| {
- data.extras
- .theorem_environments
- .iter()
- .find(|environment| environment.name == environment_name)
- .cloned()
- })?;
+ let definition = Workspace::get(db)
+ .related(db, document)
+ .iter()
+ .filter_map(|document| document.parse(db).as_tex())
+ .flat_map(|data| data.analyze(db).theorem_environments(db))
+ .find(|env| env.name(db).text(db) == environment_name)?;
let node = latex::Environment::cast(node)?;
let theorem_description = node
@@ -382,13 +381,12 @@ fn visit_theorem(
.options()
.and_then(|option| option.content_text());
- let full_range = context
- .request
- .main_document()
- .line_index
+ let full_range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&node));
- let symbol = match find_label_by_parent(context, node.syntax()) {
+ let symbol = match find_label_by_parent(db, document, node.syntax()) {
Some(NumberedLabel {
name: label,
range: selection_range,
@@ -396,11 +394,20 @@ fn visit_theorem(
}) => {
let name = match (number, theorem_description) {
(Some(number), Some(desc)) => {
- format!("{} {} ({})", definition.description, number, desc)
+ format!(
+ "{} {} ({})",
+ definition.description(db).text(db),
+ number.text(db),
+ desc
+ )
}
- (Some(number), None) => format!("{} {}", definition.description, number),
- (None, Some(desc)) => format!("{} ({})", definition.description, desc),
- (None, None) => definition.description,
+ (Some(number), None) => format!(
+ "{} {}",
+ definition.description(db).text(db),
+ number.text(db)
+ ),
+ (None, Some(desc)) => format!("{} ({})", definition.description(db).text(db), desc),
+ (None, None) => definition.description(db).text(db).clone(),
};
InternalSymbol {
@@ -415,8 +422,8 @@ fn visit_theorem(
}
None => {
let name = match theorem_description {
- Some(desc) => format!("{} ({})", definition.description, desc),
- None => definition.description,
+ Some(desc) => format!("{} ({})", definition.description(db).text(db), desc),
+ None => definition.description(db).text(db).clone(),
};
InternalSymbol {
name,
@@ -434,28 +441,27 @@ fn visit_theorem(
#[derive(Debug, PartialEq, Eq, Clone)]
struct NumberedLabel {
- name: String,
+ name: Word,
range: Range,
- number: Option<SmolStr>,
+ number: Option<Word>,
}
fn find_label_by_parent(
- context: &mut Context,
+ db: &dyn Db,
+ document: Document,
parent: &latex::SyntaxNode,
) -> Option<NumberedLabel> {
let node = parent.children().find_map(latex::LabelDefinition::cast)?;
-
- let name = node.name()?.key()?.to_string();
- let range = context
- .request
- .main_document()
- .line_index
+ let name = Word::new(db, node.name()?.key()?.to_string());
+ let range = document
+ .contents(db)
+ .line_index(db)
.line_col_lsp_range(latex::small_range(&node));
- let number = find_label_number(&context.request.workspace, &name);
+ let number = Workspace::get(db).number_of_label(db, document, name);
Some(NumberedLabel {
- name: name.to_string(),
+ name,
range,
- number: number.map(Into::into),
+ number,
})
}
diff --git a/support/texlab/src/features/symbol/project_order.rs b/support/texlab/src/features/symbol/project_order.rs
index 794447ecf6..f4aeb63bca 100644
--- a/support/texlab/src/features/symbol/project_order.rs
+++ b/support/texlab/src/features/symbol/project_order.rs
@@ -1,209 +1,202 @@
-use std::{sync::Arc, usize};
-
+use itertools::Itertools;
use lsp_types::Url;
-use petgraph::{algo::tarjan_scc, Directed, Graph};
-use rustc_hash::FxHashSet;
-use crate::{Document, Workspace};
+use crate::{
+ db::{dependency_graph, Document, Workspace},
+ Db,
+};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ProjectOrdering {
- ordering: Vec<Arc<Url>>,
+ ordering: Vec<Document>,
}
impl ProjectOrdering {
- pub fn get(&self, uri: &Url) -> usize {
- self.ordering
- .iter()
- .position(|u| u.as_ref() == uri)
- .unwrap_or(usize::MAX)
- }
-}
-
-impl From<&Workspace> for ProjectOrdering {
- fn from(workspace: &Workspace) -> Self {
- let mut ordering = Vec::new();
- let uris: FxHashSet<Arc<Url>> = workspace
- .documents_by_uri
- .values()
- .map(|document| Arc::clone(&document.uri))
+ pub fn new(db: &dyn Db) -> Self {
+ let workspace = Workspace::get(db);
+
+ let ordering: Vec<_> = workspace
+ .index_files(db)
+ .chain(workspace.documents(db).iter().copied())
+ .flat_map(|document| {
+ dependency_graph(db, document)
+ .preorder()
+ .rev()
+ .collect_vec()
+ })
+ .unique()
.collect();
- let comps = connected_components(workspace);
- for comp in comps {
- let (graph, documents) = build_dependency_graph(&comp);
-
- let mut visited = FxHashSet::default();
- let root_index = *graph.node_weight(tarjan_scc(&graph)[0][0]).unwrap();
- let mut stack = vec![Arc::clone(&documents[root_index].uri)];
-
- while let Some(uri) = stack.pop() {
- if !visited.insert(Arc::clone(&uri)) {
- continue;
- }
-
- ordering.push(Arc::clone(&uri));
- if let Some(document) = workspace.documents_by_uri.get(&uri) {
- if let Some(data) = document.data.as_latex() {
- for link in data.extras.explicit_links.iter().rev() {
- for target in &link.targets {
- if uris.contains(target.as_ref()) {
- stack.push(Arc::clone(target));
- }
- }
- }
- }
- }
- }
- }
-
Self { ordering }
}
-}
-
-fn connected_components(workspace: &Workspace) -> Vec<Workspace> {
- let mut components = Vec::new();
- let mut visited = FxHashSet::default();
- for root_document in workspace.documents_by_uri.values() {
- if !visited.insert(Arc::clone(&root_document.uri)) {
- continue;
- }
-
- let slice = workspace.slice(&root_document.uri);
- for uri in slice.documents_by_uri.keys() {
- visited.insert(Arc::clone(uri));
- }
- components.push(slice);
- }
- components
-}
-fn build_dependency_graph(workspace: &Workspace) -> (Graph<usize, (), Directed>, Vec<&Document>) {
- let mut graph = Graph::new();
- let documents: Vec<_> = workspace.documents_by_uri.values().collect();
- let nodes: Vec<_> = (0..documents.len()).map(|i| graph.add_node(i)).collect();
-
- for (i, document) in documents.iter().enumerate() {
- if let Some(data) = document.data.as_latex() {
- for link in &data.extras.explicit_links {
- for target in &link.targets {
- if let Some(j) = documents
- .iter()
- .position(|document| document.uri.as_ref() == target.as_ref())
- {
- graph.add_edge(nodes[j], nodes[i], ());
- break;
- }
- }
- }
- }
+ pub fn get(&self, db: &dyn Db, uri: &Url) -> usize {
+ self.ordering
+ .iter()
+ .position(|doc| doc.location(db).uri(db) == uri)
+ .unwrap_or(std::usize::MAX)
}
-
- (graph, documents)
}
#[cfg(test)]
mod tests {
- use std::sync::Arc;
-
- use anyhow::Result;
-
- use crate::DocumentLanguage;
+ use crate::{
+ db::{Language, Owner},
+ Database,
+ };
use super::*;
#[test]
- fn test_no_cycles() -> Result<()> {
- let mut workspace = Workspace::default();
+ fn test_no_cycles() {
+ let mut db = Database::default();
+ let workspace = Workspace::get(&db);
let a = workspace.open(
- Arc::new(Url::parse("http://example.com/a.tex")?),
- Arc::new(String::new()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/a.tex").unwrap(),
+ String::new(),
+ Language::Tex,
+ Owner::Client,
+ );
let b = workspace.open(
- Arc::new(Url::parse("http://example.com/b.tex")?),
- Arc::new(String::new()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/b.tex").unwrap(),
+ String::new(),
+ Language::Tex,
+ Owner::Client,
+ );
let c = workspace.open(
- Arc::new(Url::parse("http://example.com/c.tex")?),
- Arc::new(r#"\include{b}\include{a}"#.to_string()),
- DocumentLanguage::Latex,
- )?;
-
- let ordering = ProjectOrdering::from(&workspace);
-
- assert_eq!(ordering.get(&a.uri), 2);
- assert_eq!(ordering.get(&b.uri), 1);
- assert_eq!(ordering.get(&c.uri), 0);
- Ok(())
+ &mut db,
+ Url::parse("http://example.com/c.tex").unwrap(),
+ r#"\documentclass{article}\include{b}\include{a}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
+
+ let ordering = ProjectOrdering::new(&db);
+
+ assert_eq!(ordering.get(&db, a.location(&db).uri(&db)), 0);
+ assert_eq!(ordering.get(&db, b.location(&db).uri(&db)), 1);
+ assert_eq!(ordering.get(&db, c.location(&db).uri(&db)), 2);
}
#[test]
- fn test_cycles() -> Result<()> {
- let mut workspace = Workspace::default();
+ fn test_two_layers() {
+ let mut db = Database::default();
+ let workspace = Workspace::get(&db);
let a = workspace.open(
- Arc::new(Url::parse("http://example.com/a.tex")?),
- Arc::new(r#"\include{b}"#.to_string()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/a.tex").unwrap(),
+ String::new(),
+ Language::Tex,
+ Owner::Client,
+ );
let b = workspace.open(
- Arc::new(Url::parse("http://example.com/b.tex")?),
- Arc::new(r#"\include{a}"#.to_string()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/b.tex").unwrap(),
+ r#"\include{a}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
let c = workspace.open(
- Arc::new(Url::parse("http://example.com/c.tex")?),
- Arc::new(r#"\include{a}"#.to_string()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/c.tex").unwrap(),
+ r#"\documentclass{article}\include{b}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
+
+ let ordering = ProjectOrdering::new(&db);
+
+ assert_eq!(ordering.get(&db, a.location(&db).uri(&db)), 0);
+ assert_eq!(ordering.get(&db, b.location(&db).uri(&db)), 1);
+ assert_eq!(ordering.get(&db, c.location(&db).uri(&db)), 2);
+ }
- let ordering = ProjectOrdering::from(&workspace);
+ #[test]
+ fn test_cycles() {
+ let mut db = Database::default();
+ let workspace = Workspace::get(&db);
- assert_eq!(ordering.get(&a.uri), 1);
- assert_eq!(ordering.get(&b.uri), 2);
- assert_eq!(ordering.get(&c.uri), 0);
- Ok(())
+ let a = workspace.open(
+ &mut db,
+ Url::parse("http://example.com/a.tex").unwrap(),
+ r#"\documentclass{article}\include{b}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
+
+ workspace.open(
+ &mut db,
+ Url::parse("http://example.com/b.tex").unwrap(),
+ r#"\include{a}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
+
+ workspace.open(
+ &mut db,
+ Url::parse("http://example.com/c.tex").unwrap(),
+ r#"\include{a}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
+
+ let ordering = ProjectOrdering::new(&db);
+ assert_ne!(ordering.get(&db, a.location(&db).uri(&db)), 0);
}
#[test]
- fn test_multiple_roots() -> Result<()> {
- let mut workspace = Workspace::default();
+ fn test_multiple_roots() {
+ let mut db = Database::default();
+ let workspace = Workspace::get(&db);
let a = workspace.open(
- Arc::new(Url::parse("http://example.com/a.tex")?),
- Arc::new(r#"\include{b}"#.to_string()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/a.tex").unwrap(),
+ r#"\documentclass{article}\include{b}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
let b = workspace.open(
- Arc::new(Url::parse("http://example.com/b.tex")?),
- Arc::new(r#""#.to_string()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/b.tex").unwrap(),
+ String::new(),
+ Language::Tex,
+ Owner::Client,
+ );
let c = workspace.open(
- Arc::new(Url::parse("http://example.com/c.tex")?),
- Arc::new(r#""#.to_string()),
- DocumentLanguage::Latex,
- )?;
+ &mut db,
+ Url::parse("http://example.com/c.tex").unwrap(),
+ String::new(),
+ Language::Tex,
+ Owner::Client,
+ );
let d = workspace.open(
- Arc::new(Url::parse("http://example.com/d.tex")?),
- Arc::new(r#"\include{c}"#.to_string()),
- DocumentLanguage::Latex,
- )?;
-
- let ordering = ProjectOrdering::from(&workspace);
-
- assert!(ordering.get(&a.uri) < ordering.get(&b.uri));
- assert!(ordering.get(&d.uri) < ordering.get(&c.uri));
- Ok(())
+ &mut db,
+ Url::parse("http://example.com/d.tex").unwrap(),
+ r#"\documentclass{article}\include{c}"#.to_string(),
+ Language::Tex,
+ Owner::Client,
+ );
+
+ let ordering = ProjectOrdering::new(&db);
+ assert!(
+ ordering.get(&db, b.location(&db).uri(&db))
+ < ordering.get(&db, a.location(&db).uri(&db))
+ );
+ assert!(
+ ordering.get(&db, c.location(&db).uri(&db))
+ < ordering.get(&db, d.location(&db).uri(&db))
+ );
}
}
diff --git a/support/texlab/src/features/symbol/types.rs b/support/texlab/src/features/symbol/types.rs
index 88e7ffcb86..be0ffeceda 100644
--- a/support/texlab/src/features/symbol/types.rs
+++ b/support/texlab/src/features/symbol/types.rs
@@ -1,6 +1,10 @@
use lsp_types::{DocumentSymbol, Location, Range, SymbolInformation, SymbolKind, Url};
-use crate::{features::lsp_kinds::Structure, BibtexEntryTypeCategory};
+use crate::{
+ db::Word,
+ util::{lang_data::BibtexEntryTypeCategory, lsp_enums::Structure},
+ Db,
+};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum InternalSymbolKind {
@@ -39,7 +43,7 @@ impl InternalSymbolKind {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct InternalSymbol {
pub name: String,
- pub label: Option<String>,
+ pub label: Option<Word>,
pub kind: InternalSymbolKind,
pub deprecated: bool,
pub full_range: Range,
@@ -76,16 +80,17 @@ impl InternalSymbol {
buffer.push(self);
}
- pub fn into_document_symbol(self) -> DocumentSymbol {
+ pub fn into_document_symbol(self, db: &dyn Db) -> DocumentSymbol {
let children = self
.children
.into_iter()
- .map(|child| child.into_document_symbol())
+ .map(|child| child.into_document_symbol(db))
.collect();
+
#[allow(deprecated)]
DocumentSymbol {
name: self.name,
- detail: self.label,
+ detail: self.label.map(|word| word.text(db).clone()),
kind: self.kind.into_symbol_kind(),
deprecated: Some(self.deprecated),
range: self.full_range,
diff --git a/support/texlab/src/features/workspace_command.rs b/support/texlab/src/features/workspace_command.rs
new file mode 100644
index 0000000000..8d6732cd1c
--- /dev/null
+++ b/support/texlab/src/features/workspace_command.rs
@@ -0,0 +1,103 @@
+use std::process::Stdio;
+
+use anyhow::{bail, Result};
+use lsp_types::{TextDocumentIdentifier, Url};
+use thiserror::Error;
+
+use crate::{db::Workspace, normalize_uri, Db};
+
+pub fn select(db: &dyn Db, name: &str, args: Vec<serde_json::Value>) -> Result<CleanCommand> {
+ Ok(match name {
+ "texlab.cleanAuxiliary" => CleanCommand::new(db, CleanOptions::Auxiliary, args)?,
+ "texlab.cleanArtifacts" => CleanCommand::new(db, CleanOptions::Artifacts, args)?,
+ _ => bail!("Unknown command: {}", name),
+ })
+}
+
+#[derive(Debug, Error)]
+pub enum CleanError {
+ #[error("document '{0}' not found")]
+ DocumentNotFound(Url),
+
+ #[error("document '{0}' is not a local file")]
+ NoLocalFile(Url),
+
+ #[error("document was not provided as an argument")]
+ MissingArg,
+
+ #[error("invalid argument: {0}")]
+ InvalidArg(serde_json::Error),
+
+ #[error("failed to spawn process: {0}")]
+ Spawn(std::io::Error),
+}
+
+#[derive(Debug)]
+pub struct CleanCommand {
+ executable: String,
+ args: Vec<String>,
+}
+
+impl CleanCommand {
+ fn new(db: &dyn Db, options: CleanOptions, args: Vec<serde_json::Value>) -> Result<Self> {
+ let params: TextDocumentIdentifier =
+ serde_json::from_value(args.into_iter().next().ok_or(CleanError::MissingArg)?)
+ .map_err(CleanError::InvalidArg)?;
+
+ let mut uri = params.uri;
+ normalize_uri(&mut uri);
+
+ let workspace = Workspace::get(db);
+
+ let document = workspace
+ .lookup_uri(db, &uri)
+ .ok_or_else(|| CleanError::DocumentNotFound(uri.clone()))?;
+
+ let working_dir = workspace.working_dir(db, document.directory(db));
+
+ let output_dir = workspace
+ .output_dir(db, working_dir)
+ .path(db)
+ .as_deref()
+ .ok_or_else(|| CleanError::NoLocalFile(uri.clone()))?;
+
+ let path = document
+ .location(db)
+ .path(db)
+ .as_deref()
+ .ok_or_else(|| CleanError::NoLocalFile(uri.clone()))?;
+
+ let flag = match options {
+ CleanOptions::Auxiliary => "-c",
+ CleanOptions::Artifacts => "-C",
+ };
+
+ let executable = "latexmk".to_string();
+ let args = vec![
+ format!("-outdir={}", output_dir.display()),
+ flag.to_string(),
+ path.display().to_string(),
+ ];
+
+ Ok(Self { executable, args })
+ }
+
+ pub fn run(self) -> Result<(), CleanError> {
+ log::debug!("Cleaning output files: {} {:?}", self.executable, self.args);
+ std::process::Command::new(self.executable)
+ .args(self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status()
+ .map_err(CleanError::Spawn)?;
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+enum CleanOptions {
+ Auxiliary,
+ Artifacts,
+}
diff --git a/support/texlab/src/language.rs b/support/texlab/src/language.rs
deleted file mode 100644
index 45ef22daba..0000000000
--- a/support/texlab/src/language.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use std::{ffi::OsStr, path::Path};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum DocumentLanguage {
- Latex,
- Bibtex,
- BuildLog,
-}
-
-impl DocumentLanguage {
- pub fn by_path(path: &Path) -> Option<Self> {
- path.extension()
- .and_then(OsStr::to_str)
- .and_then(Self::by_extension)
- }
-
- pub fn by_extension(extension: &str) -> Option<Self> {
- match extension.to_lowercase().as_str() {
- "tex" | "sty" | "cls" | "def" | "lco" | "aux" | "rnw" => Some(Self::Latex),
- "bib" | "bibtex" => Some(Self::Bibtex),
- "log" => Some(Self::BuildLog),
- _ => None,
- }
- }
-
- pub fn by_language_id(language_id: &str) -> Option<Self> {
- match language_id {
- "latex" | "tex" => Some(Self::Latex),
- "bibtex" | "bib" => Some(Self::Bibtex),
- _ => None,
- }
- }
-}
diff --git a/support/texlab/src/lib.rs b/support/texlab/src/lib.rs
index 1f3d22d7aa..9a7b587ab8 100644
--- a/support/texlab/src/lib.rs
+++ b/support/texlab/src/lib.rs
@@ -1,37 +1,120 @@
-mod capabilities;
-pub mod citation;
+#![allow(clippy::needless_lifetimes)]
+
+pub(crate) mod citation;
mod client;
-pub mod component_db;
-mod debouncer;
-mod diagnostics;
-mod dispatch;
-pub mod distro;
-mod document;
-mod environment;
+pub mod db;
+pub(crate) mod distro;
pub mod features;
-mod label;
-mod lang_data;
-mod language;
-mod line_index;
-mod line_index_ext;
mod options;
-mod range;
-mod req_queue;
+pub mod parser;
mod server;
pub mod syntax;
-mod workspace;
-
-pub use self::{
- capabilities::ClientCapabilitiesExt,
- document::*,
- environment::Environment,
- label::*,
- lang_data::*,
- language::DocumentLanguage,
- line_index::{LineCol, LineColUtf16, LineIndex},
- line_index_ext::LineIndexExt,
- options::*,
- range::RangeExt,
- server::Server,
- workspace::{Workspace, WorkspaceEvent},
-};
+pub(crate) mod util;
+
+pub use self::{options::*, server::Server};
+
+#[salsa::jar(db = Db)]
+pub struct Jar(
+ db::Word,
+ db::Location,
+ db::Location_path,
+ db::Contents,
+ db::Contents_line_index,
+ db::LinterData,
+ db::Document,
+ db::Document_parse,
+ db::Document_can_be_index,
+ db::Document_can_be_built,
+ db::parse::TexDocumentData,
+ db::parse::TexDocumentData_analyze,
+ db::parse::BibDocumentData,
+ db::parse::LogDocumentData,
+ db::analysis::TexLink,
+ db::analysis::label::Number,
+ db::analysis::label::Name,
+ db::analysis::TheoremEnvironment,
+ db::analysis::GraphicsPath,
+ db::analysis::TexAnalysis,
+ db::MissingDependencies,
+ db::hidden_dependency,
+ db::source_dependency,
+ db::dependency_graph,
+ db::Workspace,
+ db::Workspace_working_dir,
+ db::Workspace_output_dir,
+ db::Workspace_parents,
+ db::Workspace_related,
+ db::Workspace_number_of_label,
+ db::diagnostics::tex::collect,
+ db::diagnostics::bib::collect,
+ db::diagnostics::log::collect,
+ db::diagnostics::collect,
+ db::diagnostics::collect_filtered,
+);
+
+pub trait Db: salsa::DbWithJar<Jar> {}
+
+impl<DB> Db for DB where DB: ?Sized + salsa::DbWithJar<Jar> {}
+
+#[salsa::db(crate::Jar)]
+pub struct Database {
+ storage: salsa::Storage<Self>,
+}
+
+impl Default for Database {
+ fn default() -> Self {
+ let storage = salsa::Storage::default();
+ let db = Self { storage };
+ db::Workspace::new(
+ &db,
+ Default::default(),
+ Default::default(),
+ Default::default(),
+ Default::default(),
+ Default::default(),
+ Default::default(),
+ );
+
+ db
+ }
+}
+
+impl salsa::Database for Database {}
+
+impl salsa::ParallelDatabase for Database {
+ fn snapshot(&self) -> salsa::Snapshot<Self> {
+ salsa::Snapshot::new(Self {
+ storage: self.storage.snapshot(),
+ })
+ }
+}
+
+pub(crate) fn normalize_uri(uri: &mut lsp_types::Url) {
+ fn fix_drive_letter(text: &str) -> Option<String> {
+ if !text.is_ascii() {
+ return None;
+ }
+
+ match &text[1..] {
+ ":" => Some(text.to_ascii_uppercase()),
+ "%3A" | "%3a" => Some(format!("{}:", text[0..1].to_ascii_uppercase())),
+ _ => None,
+ }
+ }
+
+ if let Some(mut segments) = uri.path_segments() {
+ if let Some(mut path) = segments.next().and_then(fix_drive_letter) {
+ for segment in segments {
+ path.push('/');
+ path.push_str(segment);
+ }
+
+ uri.set_path(&path);
+ }
+ }
+
+ uri.set_fragment(None);
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/support/texlab/src/main.rs b/support/texlab/src/main.rs
index 06cbba3f5d..ed807f8799 100644
--- a/support/texlab/src/main.rs
+++ b/support/texlab/src/main.rs
@@ -1,4 +1,4 @@
-use std::{env, fs::OpenOptions, io, path::PathBuf};
+use std::{fs::OpenOptions, io, path::PathBuf};
use anyhow::Result;
use clap::{ArgAction, Parser};
@@ -21,10 +21,6 @@ struct Opts {
/// Write the logging output to FILE
#[clap(long, name = "FILE", value_parser)]
log_file: Option<PathBuf>,
-
- /// Print version information and exit
- #[clap(short = 'V', long)]
- version: bool,
}
fn main() -> Result<()> {
@@ -32,7 +28,7 @@ fn main() -> Result<()> {
setup_logger(opts);
let (connection, threads) = Connection::stdio();
- Server::with_connection(connection, env::current_dir()?, true).run()?;
+ Server::new(connection).run()?;
threads.join()?;
Ok(())
diff --git a/support/texlab/src/options.rs b/support/texlab/src/options.rs
index 241126d908..cc0058fc68 100644
--- a/support/texlab/src/options.rs
+++ b/support/texlab/src/options.rs
@@ -1,47 +1,32 @@
-use std::path::PathBuf;
+use std::{path::PathBuf, time::Duration};
use regex::Regex;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
pub struct Options {
- #[serde(default)]
pub root_directory: Option<PathBuf>,
-
- #[serde(default)]
pub aux_directory: Option<PathBuf>,
-
- #[serde(default)]
pub bibtex_formatter: BibtexFormatter,
-
- #[serde(default)]
pub latex_formatter: LatexFormatter,
-
- #[serde(default)]
pub formatter_line_length: Option<i32>,
-
- #[serde(default)]
pub diagnostics: DiagnosticsOptions,
-
- #[serde(default = "default_diagnostics_delay")]
- pub diagnostics_delay: u64,
-
- #[serde(default)]
+ pub diagnostics_delay: DiagnosticsDelay,
pub build: BuildOptions,
-
- #[serde(default)]
pub chktex: ChktexOptions,
-
- #[serde(default)]
pub latexindent: LatexindentOptions,
-
- #[serde(default)]
pub forward_search: ForwardSearchOptions,
}
-fn default_diagnostics_delay() -> u64 {
- 300
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct DiagnosticsDelay(#[serde(with = "serde_millis")] pub Duration);
+
+impl Default for DiagnosticsDelay {
+ fn default() -> Self {
+ Self(Duration::from_millis(300))
+ }
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
@@ -72,75 +57,77 @@ impl Default for LatexFormatter {
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
pub struct LatexindentOptions {
- #[serde(default)]
pub local: Option<String>,
-
- #[serde(default)]
pub modify_line_breaks: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
pub struct BuildOptions {
- #[serde(default = "default_build_executable")]
- pub executable: String,
-
- #[serde(default = "default_build_args")]
- pub args: Vec<String>,
-
- #[serde(default)]
- pub is_continuous: bool,
-
- #[serde(default)]
+ pub executable: BuildExecutable,
+ pub args: BuildArgs,
pub on_save: bool,
-
- #[serde(default)]
pub forward_search_after: bool,
}
-fn default_build_executable() -> String {
- "latexmk".to_string()
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct BuildExecutable(pub String);
+
+impl Default for BuildExecutable {
+ fn default() -> Self {
+ Self("latexmk".to_string())
+ }
}
-fn default_build_args() -> Vec<String> {
- vec![
- "-pdf".to_string(),
- "-interaction=nonstopmode".to_string(),
- "-synctex=1".to_string(),
- "%f".to_string(),
- ]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct BuildArgs(pub Vec<String>);
+
+impl Default for BuildArgs {
+ fn default() -> Self {
+ Self(vec![
+ "-pdf".to_string(),
+ "-interaction=nonstopmode".to_string(),
+ "-synctex=1".to_string(),
+ "%f".to_string(),
+ ])
+ }
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
pub struct ChktexOptions {
- #[serde(default)]
pub on_open_and_save: bool,
-
- #[serde(default)]
pub on_edit: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
pub struct ForwardSearchOptions {
- #[serde(default)]
pub executable: Option<String>,
-
- #[serde(default)]
pub args: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
pub struct DiagnosticsOptions {
- #[serde(default)]
pub allowed_patterns: Vec<DiagnosticsPattern>,
-
- #[serde(default)]
pub ignored_patterns: Vec<DiagnosticsPattern>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticsPattern(#[serde(with = "serde_regex")] pub Regex);
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(default)]
+pub struct StartupOptions {
+ pub skip_distro: bool,
+}
diff --git a/support/texlab/src/parser.rs b/support/texlab/src/parser.rs
new file mode 100644
index 0000000000..51d56475cf
--- /dev/null
+++ b/support/texlab/src/parser.rs
@@ -0,0 +1,5 @@
+mod bibtex;
+mod build_log;
+mod latex;
+
+pub use self::{bibtex::parse_bibtex, build_log::parse_build_log, latex::parse_latex};
diff --git a/support/texlab/src/syntax/bibtex/parser.rs b/support/texlab/src/parser/bibtex.rs
index 4a8a429b18..e03a39c9ae 100644
--- a/support/texlab/src/syntax/bibtex/parser.rs
+++ b/support/texlab/src/parser/bibtex.rs
@@ -1,12 +1,9 @@
-use logos::{Lexer, Logos};
+use logos::Logos;
use rowan::{GreenNode, GreenNodeBuilder};
-use super::{
- lexer::*,
- SyntaxKind::{self, *},
-};
+use crate::syntax::bibtex::SyntaxKind::{self, *};
-pub fn parse(input: &str) -> GreenNode {
+pub fn parse_bibtex(input: &str) -> GreenNode {
let mut ptr = TokenPtr {
builder: GreenNodeBuilder::new(),
lexer: RootToken::lexer(input),
@@ -136,9 +133,9 @@ fn curly_group(mut ptr: TokenPtr<ContentToken>) -> TokenPtr<ContentToken> {
| ContentToken::Nbsp
| ContentToken::Comma
| ContentToken::Integer
+ | ContentToken::Quote
| ContentToken::Word => ptr.bump(),
ContentToken::LCurly => ptr = curly_group(ptr),
- ContentToken::Quote => ptr = quote_group(ptr),
ContentToken::AccentName => ptr = accent(ptr),
ContentToken::CommandName => ptr = command(ptr),
};
@@ -205,7 +202,7 @@ fn command(mut ptr: TokenPtr<ContentToken>) -> TokenPtr<ContentToken> {
struct TokenPtr<'a, T: Logos<'a>> {
builder: GreenNodeBuilder<'static>,
- lexer: Lexer<'a, T>,
+ lexer: logos::Lexer<'a, T>,
token: Option<(T, &'a str)>,
}
@@ -262,3 +259,196 @@ where
self.token
}
}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
+enum RootToken {
+ #[token(r"@preamble", ignore(ascii_case))]
+ Preamble,
+
+ #[token(r"@string", ignore(ascii_case))]
+ String,
+
+ #[token(r"@comment", ignore(ascii_case))]
+ Comment,
+
+ #[regex(r"@[a-zA-Z]*")]
+ Entry,
+
+ #[regex(r"[^@]+")]
+ #[error]
+ Junk,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
+enum BodyToken {
+ #[regex(r"\s+")]
+ Whitespace,
+
+ #[token("{")]
+ #[token("(")]
+ LDelim,
+
+ #[token("}")]
+ #[token(")")]
+ RDelim,
+
+ #[token(",")]
+ Comma,
+
+ #[token("=")]
+ Eq,
+
+ #[regex(r"[^\s\(\)\{\}@,=]+")]
+ Name,
+
+ #[error]
+ Error,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
+enum ValueToken {
+ #[regex(r"\s+")]
+ Whitespace,
+
+ #[token("#")]
+ Pound,
+
+ #[token(",")]
+ Comma,
+
+ #[token("{")]
+ LCurly,
+
+ #[token("}")]
+ RCurly,
+
+ #[token("\"")]
+ Quote,
+
+ #[regex(r"\d+", priority = 2)]
+ Integer,
+
+ #[regex(r#"[^\s"\{\},]+"#)]
+ #[error]
+ Name,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
+enum ContentToken {
+ #[regex(r"\s+")]
+ Whitespace,
+
+ #[token(",")]
+ Comma,
+
+ #[token("{")]
+ LCurly,
+
+ #[token("}")]
+ RCurly,
+
+ #[token("\"")]
+ Quote,
+
+ #[regex(r"\d+", priority = 2)]
+ Integer,
+
+ #[token(r#"~"#)]
+ Nbsp,
+
+ #[token(r#"\`"#)]
+ #[token(r#"\'"#)]
+ #[token(r#"\^"#)]
+ #[token(r#"\""#)]
+ #[token(r#"\H"#)]
+ #[token(r#"\~"#)]
+ #[token(r#"\c"#)]
+ #[token(r#"\k"#)]
+ #[token(r#"\="#)]
+ #[token(r#"\b"#)]
+ #[token(r#"\."#)]
+ #[token(r#"\d"#)]
+ #[token(r#"\r"#)]
+ #[token(r#"\u"#)]
+ #[token(r#"\v"#)]
+ #[token(r#"\t"#)]
+ AccentName,
+
+ #[regex(r"\\([^\r\n]|[@a-zA-Z:_]+\*?)?")]
+ CommandName,
+
+ #[regex(r#"[^\s"\{\}\\~,]+"#)]
+ #[error]
+ Word,
+}
+
+impl From<RootToken> for SyntaxKind {
+ fn from(token: RootToken) -> Self {
+ match token {
+ RootToken::Preamble | RootToken::String | RootToken::Comment | RootToken::Entry => TYPE,
+ RootToken::Junk => JUNK,
+ }
+ }
+}
+
+impl From<BodyToken> for SyntaxKind {
+ fn from(token: BodyToken) -> Self {
+ match token {
+ BodyToken::Whitespace => WHITESPACE,
+ BodyToken::LDelim => L_DELIM,
+ BodyToken::RDelim => R_DELIM,
+ BodyToken::Comma => COMMA,
+ BodyToken::Eq => EQ,
+ BodyToken::Name => NAME,
+ BodyToken::Error => unreachable!(),
+ }
+ }
+}
+
+impl From<ValueToken> for SyntaxKind {
+ fn from(token: ValueToken) -> Self {
+ match token {
+ ValueToken::Whitespace => WHITESPACE,
+ ValueToken::Pound => POUND,
+ ValueToken::Comma => COMMA,
+ ValueToken::LCurly => L_CURLY,
+ ValueToken::RCurly => R_CURLY,
+ ValueToken::Quote => QUOTE,
+ ValueToken::Integer => INTEGER,
+ ValueToken::Name => NAME,
+ }
+ }
+}
+
+impl From<ContentToken> for SyntaxKind {
+ fn from(token: ContentToken) -> Self {
+ match token {
+ ContentToken::Whitespace => WHITESPACE,
+ ContentToken::Comma => COMMA,
+ ContentToken::LCurly => L_CURLY,
+ ContentToken::RCurly => R_CURLY,
+ ContentToken::Quote => QUOTE,
+ ContentToken::Integer => INTEGER,
+ ContentToken::Nbsp => NBSP,
+ ContentToken::AccentName => ACCENT_NAME,
+ ContentToken::CommandName => COMMAND_NAME,
+ ContentToken::Word => WORD,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::syntax::bibtex;
+
+ use super::parse_bibtex;
+
+ #[test]
+ fn test_parse() {
+ insta::glob!("test_data/bibtex/{,**/}*.txt", |path| {
+ let text = std::fs::read_to_string(path).unwrap().replace("\r\n", "\n");
+ let root = bibtex::SyntaxNode::new_root(parse_bibtex(&text));
+ insta::assert_debug_snapshot!(root);
+ });
+ }
+}
diff --git a/support/texlab/src/parser/build_log.rs b/support/texlab/src/parser/build_log.rs
new file mode 100644
index 0000000000..5f99129cdb
--- /dev/null
+++ b/support/texlab/src/parser/build_log.rs
@@ -0,0 +1,184 @@
+use std::{cmp::Ordering, path::PathBuf};
+
+use once_cell::sync::Lazy;
+use regex::{Match, Regex};
+
+use crate::syntax::{BuildError, BuildErrorLevel, BuildLog};
+
+const MAX_LINE_LENGTH: usize = 79;
+
+static PACKAGE_MESSAGE_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("^\\([a-zA-Z_\\-]+\\)\\s*(?P<msg>.*)$").unwrap());
+
+static FILE_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("\\((?P<file>[^\r\n()]+\\.(tex|sty|cls))").unwrap());
+
+static TEX_ERROR_REGEX: Lazy<Regex> = Lazy::new(|| {
+ Regex::new("(?m)^! ((?P<msg1>(.|\r|\n)*?)\r?\nl\\.(?P<line>\\d+)( (\\.\\.\\.)?(?P<hint>[^\r\n]+))?|(?P<msg2>[^\r\n]*))").unwrap()
+});
+
+static WARNING_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("(LaTeX|Package [a-zA-Z_\\-]+) Warning: (?P<msg>[^\r\n]*)").unwrap());
+
+static BAD_BOX_REGEX: Lazy<Regex> = Lazy::new(|| {
+ Regex::new("(?P<msg>(Ov|Und)erfull \\\\[hv]box[^\r\n]*lines? (?P<line>\\d+)[^\r\n]*)").unwrap()
+});
+
+pub fn parse_build_log(log: &str) -> BuildLog {
+ let log = prepare_log(log);
+ let mut ranges: Vec<FileRange> = FILE_REGEX
+ .find_iter(&log)
+ .map(|result| FileRange::create(&log, result))
+ .collect();
+ ranges.sort();
+
+ let tex_errors = extract_matches(&log, &ranges, &TEX_ERROR_REGEX, BuildErrorLevel::Error);
+ let warnings = extract_matches(&log, &ranges, &WARNING_REGEX, BuildErrorLevel::Warning);
+ let bad_boxes = extract_matches(&log, &ranges, &BAD_BOX_REGEX, BuildErrorLevel::Warning);
+
+ let errors = vec![tex_errors, warnings, bad_boxes].concat();
+ BuildLog { errors }
+}
+
+fn extract_matches(
+ log: &str,
+ ranges: &[FileRange],
+ regex: &Regex,
+ level: BuildErrorLevel,
+) -> Vec<BuildError> {
+ let mut errors = Vec::new();
+ for result in regex.find_iter(log) {
+ let captures = regex.captures(&log[result.start()..result.end()]).unwrap();
+ let message = captures
+ .name("msg")
+ .or_else(|| captures.name("msg1"))
+ .or_else(|| captures.name("msg2"))
+ .unwrap()
+ .as_str()
+ .lines()
+ .next()
+ .unwrap_or_default()
+ .to_owned();
+
+ if let Some(range) = ranges.iter().find(|range| range.contains(result.start())) {
+ let line = captures
+ .name("line")
+ .map(|result| result.as_str().parse::<u32>().unwrap() - 1);
+
+ let hint: Option<String> = if line.is_some() {
+ captures
+ .name("hint")
+ .map(|r| String::from(r.as_str().trim()))
+ } else {
+ None
+ };
+
+ errors.push(BuildError {
+ relative_path: range.path.clone(),
+ level,
+ message,
+ line,
+ hint,
+ });
+ }
+ }
+ errors
+}
+
+fn prepare_log(log: &str) -> String {
+ let mut old_lines = log.lines();
+ let mut new_lines: Vec<String> = Vec::new();
+ while let Some(line) = old_lines.next() {
+ if PACKAGE_MESSAGE_REGEX.is_match(line) {
+ let captures = PACKAGE_MESSAGE_REGEX.captures(line).unwrap();
+ if let Some(last_line) = new_lines.last_mut() {
+ last_line.push(' ');
+ last_line.push_str(captures.name("msg").unwrap().as_str());
+ }
+ } else if line.ends_with("...") {
+ let mut new_line = line[line.len() - 3..].to_owned();
+ if let Some(old_line) = old_lines.next() {
+ new_line.push_str(old_line);
+ }
+ new_lines.push(new_line);
+ } else if line.chars().count() == MAX_LINE_LENGTH {
+ let mut new_line = String::new();
+ new_line.push_str(line);
+ if let Some(old_line) = old_lines.next() {
+ new_line.push_str(old_line);
+ }
+ new_lines.push(new_line);
+ } else {
+ new_lines.push(line.to_owned());
+ }
+ }
+ new_lines.join("\n")
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+struct FileRange {
+ pub path: PathBuf,
+ pub start: usize,
+ pub end: usize,
+}
+
+impl FileRange {
+ fn create(log: &str, result: Match) -> Self {
+ let mut balance = 1;
+ let mut end = result.start() + 1;
+ let chars = log[result.start() + 1..].chars();
+ for c in chars {
+ if balance <= 0 {
+ break;
+ }
+
+ if c == '(' {
+ balance += 1;
+ } else if c == ')' {
+ balance -= 1;
+ }
+ end += c.len_utf8();
+ }
+
+ let captures = FILE_REGEX.captures(result.as_str()).unwrap();
+ let path = PathBuf::from(captures.name("file").unwrap().as_str());
+ Self {
+ path,
+ start: result.start(),
+ end,
+ }
+ }
+
+ fn len(&self) -> usize {
+ self.end - self.start + 1
+ }
+
+ fn contains(&self, index: usize) -> bool {
+ index >= self.start && index <= self.end
+ }
+}
+
+impl Ord for FileRange {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.len().cmp(&other.len())
+ }
+}
+
+impl PartialOrd for FileRange {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::parse_build_log;
+
+ #[test]
+ fn test_parse() {
+ insta::glob!("test_data/build_log/*.txt", |path| {
+ let text = std::fs::read_to_string(path).unwrap().replace("\r\n", "\n");
+ insta::assert_debug_snapshot!(parse_build_log(&text));
+ });
+ }
+}
diff --git a/support/texlab/src/syntax/latex/parser.rs b/support/texlab/src/parser/latex.rs
index a43a77ebc9..a103561685 100644
--- a/support/texlab/src/syntax/latex/parser.rs
+++ b/support/texlab/src/parser/latex.rs
@@ -1,14 +1,10 @@
+mod lexer;
+
use rowan::{GreenNode, GreenNodeBuilder};
-use super::{
- lexer::Lexer,
- SyntaxKind::{self, *},
-};
+use crate::syntax::latex::SyntaxKind::{self, *};
-#[derive(Clone)]
-pub struct Parse {
- pub green: GreenNode,
-}
+use self::lexer::Lexer;
#[derive(Debug, Clone, Copy)]
struct ParserContext {
@@ -80,15 +76,14 @@ impl<'a> Parser<'a> {
}
}
- pub fn parse(mut self) -> Parse {
+ pub fn parse(mut self) -> GreenNode {
self.builder.start_node(ROOT.into());
self.preamble();
while self.peek().is_some() {
self.content(ParserContext::default());
}
self.builder.finish_node();
- let green = self.builder.finish();
- Parse { green }
+ self.builder.finish()
}
fn content(&mut self, context: ParserContext) {
@@ -177,7 +172,7 @@ impl<'a> Parser<'a> {
self.eat();
while self
.peek()
- .filter(|&kind| !matches!(kind, R_CURLY | END_ENVIRONMENT_NAME))
+ .filter(|&kind| !matches!(kind, R_CURLY))
.is_some()
{
self.content(ParserContext::default());
@@ -518,7 +513,7 @@ impl<'a> Parser<'a> {
while self
.peek()
- .filter(|&kind| kind != END_ENVIRONMENT_NAME)
+ .filter(|&kind| !matches!(kind, R_CURLY | END_ENVIRONMENT_NAME))
.is_some()
{
self.content(ParserContext::default());
@@ -854,11 +849,16 @@ impl<'a> Parser<'a> {
fn curly_group_path(&mut self) {
self.builder.start_node(CURLY_GROUP_WORD.into());
self.eat();
- while matches!(
- self.peek(),
- Some(WORD | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME)
- ) {
- self.path();
+ self.trivia();
+
+ while let Some(kind) = self.lexer.peek() {
+ match kind {
+ COMMENT | WORD | EQUALITY_SIGN | COMMA | L_BRACK | R_BRACK
+ | GENERIC_COMMAND_NAME => self.path(),
+ L_CURLY => self.curly_group_path(),
+ WHITESPACE => self.eat(),
+ _ => break,
+ };
}
self.expect(R_CURLY);
@@ -868,29 +868,17 @@ impl<'a> Parser<'a> {
fn curly_group_path_list(&mut self) {
self.builder.start_node(CURLY_GROUP_WORD_LIST.into());
self.eat();
+ self.trivia();
- while self.peek().map_or(false, |kind| {
- matches!(
- kind,
- LINE_BREAK
- | WHITESPACE
- | COMMENT
- | WORD
- | COMMA
- | EQUALITY_SIGN
- | L_BRACK
- | R_BRACK
- | GENERIC_COMMAND_NAME
- )
- }) {
- if matches!(
- self.peek(),
- Some(WORD | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME)
- ) {
- self.path();
- } else {
- self.eat();
- }
+ while let Some(kind) = self.peek() {
+ match kind {
+ COMMENT | WORD | EQUALITY_SIGN | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME => {
+ self.path()
+ }
+ WHITESPACE | LINE_BREAK | COMMA => self.eat(),
+ L_CURLY => self.curly_group_path(),
+ _ => break,
+ };
}
self.expect(R_CURLY);
@@ -900,22 +888,16 @@ impl<'a> Parser<'a> {
fn path(&mut self) {
self.builder.start_node(KEY.into());
self.eat();
- while self.peek().map_or(false, |kind| {
- matches!(
- kind,
- WHITESPACE
- | COMMENT
- | WORD
- | EQUALITY_SIGN
- | L_BRACK
- | R_BRACK
- | GENERIC_COMMAND_NAME
- )
- }) {
- self.eat();
+
+ while let Some(kind) = self.peek() {
+ match kind {
+ WHITESPACE | COMMENT | WORD | EQUALITY_SIGN | L_BRACK | R_BRACK
+ | GENERIC_COMMAND_NAME => self.eat(),
+ L_CURLY => self.curly_group_path(),
+ _ => break,
+ };
}
- self.trivia();
self.builder.finish_node();
}
@@ -1126,15 +1108,15 @@ impl<'a> Parser<'a> {
if self.lexer.peek() == Some(L_CURLY) {
self.curly_group_word();
- } else {
- self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group();
}
for _ in 0..2 {
if self.lexer.peek() == Some(L_CURLY) {
self.curly_group();
- } else {
- self.builder.token(MISSING.into(), "");
}
}
@@ -1321,603 +1303,49 @@ impl<'a> Parser<'a> {
self.eat();
self.trivia();
+ let checkpoint = self.builder.checkpoint();
if self.lexer.peek() == Some(L_CURLY) {
self.eat();
self.trivia();
- while matches!(self.lexer.peek(), Some(L_CURLY)) {
- self.curly_group_path();
+ if matches!(
+ self.lexer.peek(),
+ Some(WORD | EQUALITY_SIGN | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME)
+ ) {
+ self.builder
+ .start_node_at(checkpoint, CURLY_GROUP_WORD.into());
+ self.path();
+ } else {
+ self.builder.start_node_at(checkpoint, CURLY_GROUP.into());
+ while matches!(self.lexer.peek(), Some(L_CURLY)) {
+ self.curly_group_path();
+ }
}
- } else {
- self.builder.token(MISSING.into(), "");
+
+ self.expect(R_CURLY);
+ self.builder.finish_node();
}
- self.expect(R_CURLY);
self.builder.finish_node();
}
}
-pub fn parse(text: &str) -> Parse {
+pub fn parse_latex(text: &str) -> GreenNode {
Parser::new(text).parse()
}
#[cfg(test)]
mod tests {
- use insta::assert_debug_snapshot;
-
use crate::syntax::latex;
- use super::*;
-
- fn setup(text: &str) -> latex::SyntaxNode {
- latex::SyntaxNode::new_root(parse(&text.trim().replace('\r', "")).green)
- }
-
- #[test]
- fn test_empty() {
- assert_debug_snapshot!(setup(r#""#));
- }
-
- #[test]
- fn test_hello_world() {
- assert_debug_snapshot!(setup(r#"Hello World!"#));
- }
-
- #[test]
- fn test_generic_command_empty() {
- assert_debug_snapshot!(setup(r#"\foo"#));
- }
-
- #[test]
- fn test_generic_command_escape() {
- assert_debug_snapshot!(setup(r#"\#"#));
- }
-
- #[test]
- fn test_generic_command_args() {
- assert_debug_snapshot!(setup(r#"\foo{bar}[qux]"#));
- }
-
- #[test]
- fn test_inline() {
- assert_debug_snapshot!(setup(r#"$x \in [0, \infty)$"#));
- }
-
- #[test]
- fn test_inline_double_dollar() {
- assert_debug_snapshot!(setup(r#"$$x \in [0, \infty)$$"#));
- }
-
- #[test]
- fn test_brace_group_simple() {
- assert_debug_snapshot!(setup(r#"{hello world}"#));
- }
-
- #[test]
- fn test_brace_group_missing_end() {
- assert_debug_snapshot!(setup(r#"{hello world"#));
- }
-
- #[test]
- fn test_unmatched_braces() {
- assert_debug_snapshot!(setup(r#"}{"#));
- }
-
- #[test]
- fn test_unmatched_brackets() {
- assert_debug_snapshot!(setup(r#"]["#));
- }
-
- #[test]
- fn test_unmatched_brackets_with_group() {
- assert_debug_snapshot!(setup(r#"{][}"#));
- }
-
- #[test]
- fn test_escaped_brackets() {
- assert_debug_snapshot!(setup(r#"{[}{]}"#));
- }
-
- #[test]
- fn test_parameter() {
- assert_debug_snapshot!(setup(r#"#1"#));
- }
-
- #[test]
- fn test_parameter_error() {
- assert_debug_snapshot!(setup(r#"#"#));
- }
-
- #[test]
- fn test_environment_simple() {
- assert_debug_snapshot!(setup(r#"\begin{foo} Hello World \end{bar}"#));
- }
-
- #[test]
- fn test_environment_nested() {
- assert_debug_snapshot!(setup(r#"\begin{foo} \begin{qux} \end{baz} \end{bar}"#));
- }
-
- #[test]
- fn test_environment_nested_missing_braces() {
- assert_debug_snapshot!(setup(
- r#"\begin{foo \begin{qux Hello World \end{baz} \end{bar"#
- ));
- }
-
- #[test]
- fn test_structure_siblings() {
- assert_debug_snapshot!(setup(r#"\section{Foo} Foo \section{Bar} Bar"#));
- }
-
- #[test]
- fn test_structure_nested() {
- assert_debug_snapshot!(setup(
- r#"\part{1}\chapter{2}\section{3}\subsection{4}\subsubsection{5}\paragraph{6}\subparagraph{7}"#
- ));
- }
-
- #[test]
- fn test_structure_enum_item() {
- assert_debug_snapshot!(setup(
- r#"\begin{enumerate} \item 1 \item[2] 2 \item 3 \end{enumerate}"#
- ));
- }
-
- #[test]
- fn test_structure_invalid_nesting() {
- assert_debug_snapshot!(setup(r#"\section{Foo} \chapter{Bar}"#));
- }
-
- #[test]
- fn test_equation() {
- assert_debug_snapshot!(setup(r#"\[ foo bar \]"#));
- }
-
- #[test]
- fn test_equation_missing_end() {
- assert_debug_snapshot!(setup(r#"\begin{a} \[ foo bar \end{b}"#));
- }
-
- #[test]
- fn test_equation_missing_begin() {
- assert_debug_snapshot!(setup(r#"\begin{a} foo bar \] \end{b}"#));
- }
-
- #[test]
- fn test_caption_minimal() {
- assert_debug_snapshot!(setup(r#"\caption{Foo \Bar Baz}"#));
- }
-
- #[test]
- fn test_caption_minimal_error() {
- assert_debug_snapshot!(setup(r#"\caption{Foo \Bar Baz"#));
- }
-
- #[test]
- fn test_caption() {
- assert_debug_snapshot!(setup(r#"\caption[qux]{Foo \Bar Baz}"#));
- }
-
- #[test]
- fn test_caption_error() {
- assert_debug_snapshot!(setup(r#"\caption[qux]{Foo \Bar Baz"#));
- }
-
- #[test]
- fn test_caption_figure() {
- assert_debug_snapshot!(setup(r#"\begin{figure}\caption{Foo}\end{figure}"#));
- }
-
- #[test]
- fn test_citation_empty() {
- assert_debug_snapshot!(setup(r#"\cite{}"#));
- }
-
- #[test]
- fn test_citation_simple() {
- assert_debug_snapshot!(setup(r#"\cite{foo}"#));
- }
-
- #[test]
- fn test_citation_multiple_keys() {
- assert_debug_snapshot!(setup(r#"\cite{foo, bar}"#));
- }
-
- #[test]
- fn test_citation_star() {
- assert_debug_snapshot!(setup(r#"\nocite{*}"#));
- }
-
- #[test]
- fn test_citation_prenote() {
- assert_debug_snapshot!(setup(r#"\cite[foo]{bar}"#));
- }
-
- #[test]
- fn test_citation_prenote_postnote() {
- assert_debug_snapshot!(setup(r#"\cite[foo][bar]{baz}"#));
- }
-
- #[test]
- fn test_citation_missing_brace() {
- assert_debug_snapshot!(setup(r#"\cite{foo"#));
- }
-
- #[test]
- fn test_citation_redundant_comma() {
- assert_debug_snapshot!(setup(r#"\cite{,foo,}"#));
- }
-
- #[test]
- fn test_package_include_empty() {
- assert_debug_snapshot!(setup(r#"\usepackage{}"#));
- }
-
- #[test]
- fn test_package_include_simple() {
- assert_debug_snapshot!(setup(r#"\usepackage{amsmath}"#));
- }
-
- #[test]
- fn test_package_include_multiple() {
- assert_debug_snapshot!(setup(r#"\usepackage{amsmath, lipsum}"#));
- }
-
- #[test]
- fn test_package_include_options() {
- assert_debug_snapshot!(setup(r#"\usepackage[foo = bar, baz, qux]{amsmath}"#));
- }
-
- #[test]
- fn test_class_include_empty() {
- assert_debug_snapshot!(setup(r#"\documentclass{}"#));
- }
-
- #[test]
- fn test_class_include_simple() {
- assert_debug_snapshot!(setup(r#"\documentclass{article}"#));
- }
-
- #[test]
- fn test_class_include_options() {
- assert_debug_snapshot!(setup(r#"\documentclass[foo = bar, baz, qux]{article}"#));
- }
-
- #[test]
- fn test_latex_include_simple() {
- assert_debug_snapshot!(setup(r#"\include{foo/bar}"#));
- }
-
- #[test]
- fn test_latex_include_equality_sign() {
- assert_debug_snapshot!(setup(r#"\include{foo=bar}"#));
- }
-
- #[test]
- fn test_latex_input_simple() {
- assert_debug_snapshot!(setup(r#"\input{foo/bar.tex}"#));
- }
-
- #[test]
- fn test_latex_input_path_with_brackets() {
- assert_debug_snapshot!(setup(r#"\input{foo[bar].tex}"#));
- }
-
- #[test]
- fn test_biblatex_include_simple() {
- assert_debug_snapshot!(setup(r#"\addbibresource{foo/bar.bib}"#));
- }
-
- #[test]
- fn test_biblatex_include_options() {
- assert_debug_snapshot!(setup(r#"\addbibresource[foo=bar, baz]{foo/bar.bib}"#));
- }
-
- #[test]
- fn test_bibtex_include_simple() {
- assert_debug_snapshot!(setup(r#"\bibliography{foo/bar}"#));
- }
-
- #[test]
- fn test_graphics_include_simple() {
- assert_debug_snapshot!(setup(r#"\includegraphics{foo/bar.pdf}"#));
- }
-
- #[test]
- fn test_graphics_include_options() {
- assert_debug_snapshot!(setup(r#"\includegraphics[scale=.5]{foo/bar.pdf}"#));
- }
-
- #[test]
- fn test_graphics_include_complicated_options() {
- assert_debug_snapshot!(setup(r#"\includegraphics[width=0.5\textwidth]{}"#));
- }
-
- #[test]
- fn test_graphics_include_command() {
- assert_debug_snapshot!(setup(
- r#"\includegraphics[width=0.5\textwidth]{\foo.\bar.pdf}"#
- ));
- }
-
- #[test]
- fn test_svg_include_simple() {
- assert_debug_snapshot!(setup(r#"\includesvg{foo/bar.svg}"#));
- }
-
- #[test]
- fn test_svg_include_options() {
- assert_debug_snapshot!(setup(r#"\includesvg[scale=.5]{foo/bar.svg}"#));
- }
-
- #[test]
- fn test_inkscape_include_simple() {
- assert_debug_snapshot!(setup(r#"\includesvg{foo/bar}"#));
- }
-
- #[test]
- fn test_inkscape_include_options() {
- assert_debug_snapshot!(setup(r#"\includesvg[scale=.5]{foo/bar}"#));
- }
-
- #[test]
- fn test_verbatim_include_simple() {
- assert_debug_snapshot!(setup(r#"\verbatiminput{foo/bar.txt}"#));
- }
-
- #[test]
- fn test_import_simple() {
- assert_debug_snapshot!(setup(r#"\import{foo}{bar}"#));
- }
-
- #[test]
- fn test_import_incomplete() {
- assert_debug_snapshot!(setup(r#"\import{foo"#));
- }
-
- #[test]
- fn test_label_definition_simple() {
- assert_debug_snapshot!(setup(r#"\label{foo}"#));
- }
-
- #[test]
- fn test_label_reference_simple() {
- assert_debug_snapshot!(setup(r#"\ref{foo}"#));
- }
-
- #[test]
- fn test_label_reference_multiple() {
- assert_debug_snapshot!(setup(r#"\ref{foo, bar}"#));
- }
-
- #[test]
- fn test_label_reference_incomplete() {
- assert_debug_snapshot!(setup(r#"Equation \eqref{eq is a \emph{useful} identity."#));
- }
-
- #[test]
- fn test_equation_label_reference_simple() {
- assert_debug_snapshot!(setup(r#"\eqref{foo}"#));
- }
-
- #[test]
- fn test_label_reference_range_simple() {
- assert_debug_snapshot!(setup(r#"\crefrange{foo}{bar}"#));
- }
-
- #[test]
- fn test_label_reference_range_incomplete() {
- assert_debug_snapshot!(setup(r#"\crefrange{foo}"#));
- }
-
- #[test]
- fn test_label_reference_range_error() {
- assert_debug_snapshot!(setup(r#"\crefrange{foo{bar}"#));
- }
-
- #[test]
- fn test_label_number() {
- assert_debug_snapshot!(setup(r#"\newlabel{foo}{{1.1}}"#));
- }
-
- #[test]
- fn test_command_definition_simple() {
- assert_debug_snapshot!(setup(r#"\newcommand[1]{\id}{#1}"#));
- }
-
- #[test]
- fn test_command_definition_optional() {
- assert_debug_snapshot!(setup(r#"\newcommand{\foo}[1][def]{#1}"#));
- }
-
- #[test]
- fn test_command_definition_no_argc() {
- assert_debug_snapshot!(setup(r#"\newcommand{\foo}{foo}"#));
- }
-
- #[test]
- fn test_command_definition_no_impl() {
- assert_debug_snapshot!(setup(r#"\newcommand{\foo}"#));
- }
-
- #[test]
- fn test_command_definition_no_impl_error() {
- assert_debug_snapshot!(setup(r#"\newcommand{\foo"#));
- }
-
- #[test]
- fn test_command_definition_with_begin() {
- assert_debug_snapshot!(setup(
- r#"\newcommand{\CVSubHeadingListStart}{\begin{itemize}[leftmargin=0.5cm, label={}]}"#
- ));
- }
-
- #[test]
- fn test_math_operator_simple() {
- assert_debug_snapshot!(setup(r#"\DeclareMathOperator{\foo}{foo}"#));
- }
-
- #[test]
- fn test_math_operator_no_impl() {
- assert_debug_snapshot!(setup(r#"\DeclareMathOperator{\foo}"#));
- }
-
- #[test]
- fn test_glossary_entry_definition_simple() {
- assert_debug_snapshot!(setup(r#"\newglossaryentry{foo}{bar = baz, qux,}"#));
- }
-
- #[test]
- fn test_glossary_entry_reference_simple() {
- assert_debug_snapshot!(setup(r#"\gls{foo}"#));
- }
-
- #[test]
- fn test_glossary_entry_reference_options() {
- assert_debug_snapshot!(setup(r#"\gls[foo = bar, qux]{baz}"#));
- }
-
- #[test]
- fn test_acroynm_definition_simple() {
- assert_debug_snapshot!(setup(r#"\newacronym{fpsLabel}{FPS}{Frame per Second}"#));
- }
-
- #[test]
- fn test_acroynm_definition_options() {
- assert_debug_snapshot!(setup(
- r#"\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}"#
- ));
- }
-
- #[test]
- fn test_acroynm_reference_simple() {
- assert_debug_snapshot!(setup(r#"\acrshort{fpsLabel}"#));
- }
-
- #[test]
- fn test_acroynm_reference_options() {
- assert_debug_snapshot!(setup(r#"\acrshort[foo=bar,baz]{fpsLabel}"#));
- }
-
- #[test]
- fn test_theorem_definition_only_name() {
- assert_debug_snapshot!(setup(r#"\newtheorem{foo}"#));
- }
-
- #[test]
- fn test_theorem_definition_name_with_description() {
- assert_debug_snapshot!(setup(r#"\newtheorem{foo}{Foo}"#));
- }
-
- #[test]
- fn test_theorem_definition_name_with_description_and_counter() {
- assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]{Foo}"#));
- }
-
- #[test]
- fn test_theorem_definition_name_with_counter() {
- assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]"#));
- }
-
- #[test]
- fn test_theorem_definition_full() {
- assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]{Foo}[baz]"#));
- }
-
- #[test]
- fn test_color_reference_simple() {
- assert_debug_snapshot!(setup(r#"\color{black}"#));
- }
-
- #[test]
- fn test_color_definition_simple() {
- assert_debug_snapshot!(setup(r#"\definecolor{foo}{rgb}{255,168,0}"#));
- }
-
- #[test]
- fn test_color_set_definition_simple() {
- assert_debug_snapshot!(setup(r#"\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz}"#));
- }
-
- #[test]
- fn test_color_set_definition_error1() {
- assert_debug_snapshot!(setup(r#"\definecolorset[ty]{rgb,HTML}{foo}{bar}"#));
- }
-
- #[test]
- fn test_color_set_definition_error2() {
- assert_debug_snapshot!(setup(r#"\definecolorset{rgb,HTML}{foo}"#));
- }
-
- #[test]
- fn test_color_set_definition_error3() {
- assert_debug_snapshot!(setup(r#"\definecolorset{rgb,HTML}"#));
- }
-
- #[test]
- fn test_color_set_definition_error4() {
- assert_debug_snapshot!(setup(r#"\definecolorset"#));
- }
-
- #[test]
- fn test_pgf_library_import_simple() {
- assert_debug_snapshot!(setup(r#"\usepgflibrary{foo}"#));
- }
-
- #[test]
- fn test_tikz_library_import_simple() {
- assert_debug_snapshot!(setup(r#"\usetikzlibrary{foo}"#));
- }
-
- #[test]
- fn test_environment_definition() {
- assert_debug_snapshot!(setup(r#"\newenvironment{bar}[1]{\begin{foo}}{\end{foo}}"#));
- }
-
- #[test]
- fn test_environment_definition_optional_arg() {
- assert_debug_snapshot!(setup(r#"\newenvironment{foo}[1][default]{begin}{end}"#));
- }
-
- #[test]
- fn test_acronym_declaration() {
- assert_debug_snapshot!(setup(
- r#"\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}"#
- ));
- }
-
- #[test]
- fn test_label_definition_line_break() {
- assert_debug_snapshot!(setup("\\label{hello\nworld}"));
- }
-
- #[test]
- fn test_block_comments() {
- assert_debug_snapshot!(setup(
- r#"Foo
-\iffalse
-Test1
-\fi
-Bar
-\iffalse
-\fii
-\fi
-Baz"#
- ));
- }
-
- #[test]
- fn test_asymptote() {
- assert_debug_snapshot!(setup(
- r#"\begin{asy}
- printf("Hello World\n");
-\end{asy}"#
- ));
- }
+ use super::parse_latex;
#[test]
- fn test_graphics_path() {
- assert_debug_snapshot!(setup(r#"\graphicspath{{../figures/}}"#));
+ fn test_parse() {
+ insta::glob!("test_data/latex/{,**/}*.txt", |path| {
+ let text = std::fs::read_to_string(path).unwrap().replace("\r\n", "\n");
+ let root = latex::SyntaxNode::new_root(parse_latex(&text));
+ insta::assert_debug_snapshot!(root);
+ });
}
}
diff --git a/support/texlab/src/syntax/latex/lexer.rs b/support/texlab/src/parser/latex/lexer.rs
index 39196375b1..9b0a6726eb 100644
--- a/support/texlab/src/syntax/latex/lexer.rs
+++ b/support/texlab/src/parser/latex/lexer.rs
@@ -1,9 +1,8 @@
use logos::Logos;
-use super::kind::SyntaxKind;
+use crate::syntax::latex::SyntaxKind;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Logos)]
-#[allow(non_camel_case_types)]
#[repr(u16)]
enum RootToken {
#[regex(r"[\r\n]+", priority = 2)]
@@ -299,6 +298,15 @@ enum CommandNameToken {
GlossaryEntryReference,
#[token("\\newacronym")]
+ #[token("\\newacro")]
+ #[token("\\acrodef")]
+ #[token("\\acro")]
+ #[token("\\newacroindefinite")]
+ #[token("\\acrodefindefinite")]
+ #[token("\\acroindefinite")]
+ #[token("\\acroplural")]
+ #[token("\\newacroplural")]
+ #[token("\\acrodefplural")]
AcronymDefinition,
#[token("\\DeclareAcronym")]
@@ -332,11 +340,42 @@ enum CommandNameToken {
#[token("\\Aclp")]
#[token("\\acf")]
#[token("\\Acf")]
+ #[token("\\acfi")]
+ #[token("\\Acfi")]
#[token("\\acfp")]
#[token("\\Acfp")]
#[token("\\ac")]
#[token("\\Ac")]
#[token("\\acp")]
+ #[token("\\Acp")]
+ #[token("\\acused")]
+ #[token("\\acsu")]
+ #[token("\\Aclu")]
+ #[token("\\iac")]
+ #[token("\\Iac")]
+ #[token("\\acs*")]
+ #[token("\\Acs*")]
+ #[token("\\acsp*")]
+ #[token("\\Acsp*")]
+ #[token("\\acl*")]
+ #[token("\\Acl*")]
+ #[token("\\aclp*")]
+ #[token("\\Aclp*")]
+ #[token("\\acf*")]
+ #[token("\\Acf*")]
+ #[token("\\acfi*")]
+ #[token("\\Acfi*")]
+ #[token("\\acfp*")]
+ #[token("\\Acfp*")]
+ #[token("\\ac*")]
+ #[token("\\Ac*")]
+ #[token("\\acp*")]
+ #[token("\\Acp*")]
+ #[token("\\acused*")]
+ #[token("\\acsu*")]
+ #[token("\\Aclu*")]
+ #[token("\\iac*")]
+ #[token("\\Iac*")]
#[token("\\glsentrylong")]
#[token("\\Glsentrylong")]
#[token("\\glsentrylongpl")]
@@ -600,75 +639,3 @@ fn tokenize_verbatim_environment<'a>(
&input[end..]
}
-
-#[cfg(test)]
-mod tests {
- use insta::assert_debug_snapshot;
-
- use super::*;
-
- fn verify(text: &str) -> Vec<(SyntaxKind, &str)> {
- let mut tokens = Lexer::new(text).tokens;
- tokens.reverse();
- tokens
- }
-
- #[test]
- fn test_empty() {
- assert_debug_snapshot!(verify(r#""#));
- }
-
- #[test]
- fn test_delimiters() {
- assert_debug_snapshot!(verify(r#"{foo} (bar) [baz, qux = foo-bar]"#));
- }
-
- #[test]
- fn test_command_with_parameter() {
- assert_debug_snapshot!(verify(r#"\newcommand{\id}[1]{#1}"#));
- }
-
- #[test]
- fn test_command_with_star() {
- assert_debug_snapshot!(verify(r#"\section*{Foo}"#));
- }
-
- #[test]
- fn test_escape_sequence() {
- assert_debug_snapshot!(verify(r#"\% hello"#));
- }
-
- #[test]
- fn test_formula() {
- assert_debug_snapshot!(verify(r#"$ f(x) = y $$"#));
- }
-
- #[test]
- fn test_comment() {
- assert_debug_snapshot!(verify("hello %world\r\ntest %test"));
- }
-
- #[test]
- fn test_invalid_parameter() {
- assert_debug_snapshot!(verify(r#"#"#))
- }
-
- #[test]
- fn test_line_break() {
- assert_debug_snapshot!(verify("hello\nworld"));
- }
-
- #[test]
- fn test_block_comment() {
- assert_debug_snapshot!(verify("Foo\\iffalse\n\\Bar{Baz}\n\\fi\\Qux"));
- }
-
- #[test]
- fn test_asymptote() {
- assert_debug_snapshot!(verify(
- r#"\begin{asy}
- printf("Hello World\n");
-\end{asy}"#
- ));
- }
-}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@comment.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@comment.txt.snap
new file mode 100644
index 0000000000..f2e1163eff
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@comment.txt.snap
@@ -0,0 +1,10 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/comment.txt
+---
+ROOT@0..82
+ JUNK@0..16 "Some junk here\n\n"
+ TYPE@16..24 "@comment"
+ JUNK@24..82 "{foo,\n author = {F ..."
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@issue_809.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@issue_809.txt.snap
new file mode 100644
index 0000000000..922e926cef
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@issue_809.txt.snap
@@ -0,0 +1,31 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+---
+ROOT@0..50
+ ENTRY@0..50
+ TYPE@0..8 "@article"
+ L_DELIM@8..9 "{"
+ NAME@9..18 "issue_809"
+ COMMA@18..19 ","
+ WHITESPACE@19..22 "\n "
+ FIELD@22..48
+ NAME@22..27 "title"
+ WHITESPACE@27..28 " "
+ EQ@28..29 "="
+ WHITESPACE@29..30 " "
+ CURLY_GROUP@30..47
+ L_CURLY@30..31 "{"
+ WORD@31..34 "foo"
+ WHITESPACE@34..35 " "
+ WORD@35..36 "("
+ QUOTE@36..37 "\""
+ WHITESPACE@37..38 " "
+ WORD@38..42 "bar)"
+ WHITESPACE@42..43 " "
+ WORD@43..46 "baz"
+ R_CURLY@46..47 "}"
+ COMMA@47..48 ","
+ WHITESPACE@48..49 "\n"
+ R_DELIM@49..50 "}"
+
diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__preamble.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@preamble.txt.snap
index 596a9d108a..26d8a919ba 100644
--- a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__preamble.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@preamble.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/bibtex/tests.rs
-assertion_line: 11
-expression: "setup(r#\"@preamble{\"Hello World!\"}\"#)"
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/preamble.txt
---
ROOT@0..25
PREAMBLE@0..25
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aho_2006.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aho_2006.txt.snap
new file mode 100644
index 0000000000..d1d34942d8
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aho_2006.txt.snap
@@ -0,0 +1,132 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/aho_2006.txt
+---
+ROOT@0..314
+ ENTRY@0..314
+ TYPE@0..5 "@book"
+ L_DELIM@5..6 "{"
+ NAME@6..21 "10.5555/1177220"
+ COMMA@21..22 ","
+ WHITESPACE@22..27 "\n "
+ FIELD@27..111
+ NAME@27..33 "author"
+ WHITESPACE@33..34 " "
+ EQ@34..35 "="
+ WHITESPACE@35..36 " "
+ CURLY_GROUP@36..110
+ L_CURLY@36..37 "{"
+ WORD@37..40 "Aho"
+ COMMA@40..41 ","
+ WHITESPACE@41..42 " "
+ WORD@42..48 "Alfred"
+ WHITESPACE@48..49 " "
+ WORD@49..51 "V."
+ WHITESPACE@51..52 " "
+ WORD@52..55 "and"
+ WHITESPACE@55..56 " "
+ WORD@56..59 "Lam"
+ COMMA@59..60 ","
+ WHITESPACE@60..61 " "
+ WORD@61..67 "Monica"
+ WHITESPACE@67..68 " "
+ WORD@68..70 "S."
+ WHITESPACE@70..71 " "
+ WORD@71..74 "and"
+ WHITESPACE@74..75 " "
+ WORD@75..80 "Sethi"
+ COMMA@80..81 ","
+ WHITESPACE@81..82 " "
+ WORD@82..86 "Ravi"
+ WHITESPACE@86..87 " "
+ WORD@87..90 "and"
+ WHITESPACE@90..91 " "
+ WORD@91..97 "Ullman"
+ COMMA@97..98 ","
+ WHITESPACE@98..99 " "
+ WORD@99..106 "Jeffrey"
+ WHITESPACE@106..107 " "
+ WORD@107..109 "D."
+ R_CURLY@109..110 "}"
+ COMMA@110..111 ","
+ WHITESPACE@111..116 "\n "
+ FIELD@116..185
+ NAME@116..121 "title"
+ WHITESPACE@121..122 " "
+ EQ@122..123 "="
+ WHITESPACE@123..124 " "
+ CURLY_GROUP@124..184
+ L_CURLY@124..125 "{"
+ WORD@125..135 "Compilers:"
+ WHITESPACE@135..136 " "
+ WORD@136..146 "Principles"
+ COMMA@146..147 ","
+ WHITESPACE@147..148 " "
+ WORD@148..158 "Techniques"
+ COMMA@158..159 ","
+ WHITESPACE@159..160 " "
+ WORD@160..163 "and"
+ WHITESPACE@163..164 " "
+ WORD@164..169 "Tools"
+ WHITESPACE@169..170 " "
+ WORD@170..174 "(2nd"
+ WHITESPACE@174..175 " "
+ WORD@175..183 "Edition)"
+ R_CURLY@183..184 "}"
+ COMMA@184..185 ","
+ WHITESPACE@185..190 "\n "
+ FIELD@190..204
+ NAME@190..194 "year"
+ WHITESPACE@194..195 " "
+ EQ@195..196 "="
+ WHITESPACE@196..197 " "
+ CURLY_GROUP@197..203
+ L_CURLY@197..198 "{"
+ INTEGER@198..202 "2006"
+ R_CURLY@202..203 "}"
+ COMMA@203..204 ","
+ WHITESPACE@204..209 "\n "
+ FIELD@209..229
+ NAME@209..213 "isbn"
+ WHITESPACE@213..214 " "
+ EQ@214..215 "="
+ WHITESPACE@215..216 " "
+ CURLY_GROUP@216..228
+ L_CURLY@216..217 "{"
+ INTEGER@217..227 "0321486811"
+ R_CURLY@227..228 "}"
+ COMMA@228..229 ","
+ WHITESPACE@229..234 "\n "
+ FIELD@234..292
+ NAME@234..243 "publisher"
+ WHITESPACE@243..244 " "
+ EQ@244..245 "="
+ WHITESPACE@245..246 " "
+ CURLY_GROUP@246..291
+ L_CURLY@246..247 "{"
+ WORD@247..261 "Addison-Wesley"
+ WHITESPACE@261..262 " "
+ WORD@262..269 "Longman"
+ WHITESPACE@269..270 " "
+ WORD@270..280 "Publishing"
+ WHITESPACE@280..281 " "
+ WORD@281..284 "Co."
+ COMMA@284..285 ","
+ WHITESPACE@285..286 " "
+ WORD@286..290 "Inc."
+ R_CURLY@290..291 "}"
+ COMMA@291..292 ","
+ WHITESPACE@292..297 "\n "
+ FIELD@297..313
+ NAME@297..304 "address"
+ WHITESPACE@304..305 " "
+ EQ@305..306 "="
+ WHITESPACE@306..307 " "
+ CURLY_GROUP@307..312
+ L_CURLY@307..308 "{"
+ WORD@308..311 "USA"
+ R_CURLY@311..312 "}"
+ WHITESPACE@312..313 "\n"
+ R_DELIM@313..314 "}"
+
diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_002.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aksin_2006.txt.snap
index 5607e5a507..72e354e3c0 100644
--- a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_002.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__aksin_2006.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/bibtex/tests.rs
-assertion_line: 87
-expression: "setup(r#\"@string{jomch = {J.~Organomet. Chem.}}\n\n@article{aksin,\n author = {Aks{\\i}n, {\\\"O}zge and T{\\\"u}rkmen, Hayati and Artok, Levent\n and {\\c{C}}etinkaya, Bekir and Ni, Chaoying and\n B{\\\"u}y{\\\"u}kg{\\\"u}ng{\\\"o}r, Orhan and {\\\"O}zkal, Erhan},\n title = {Effect of immobilization on catalytic characteristics of\n saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}\n reactions},\n journaltitle = jomch,\n date = 2006,\n volume = 691,\n number = 13,\n pages = {3027-3036},\n indextitle = {Effect of immobilization on catalytic characteristics},\n}\"#)"
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/aksin_2006.txt
---
ROOT@0..679
STRING@0..40
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__almendro_1998.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__almendro_1998.txt.snap
new file mode 100644
index 0000000000..cff0323cb4
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__almendro_1998.txt.snap
@@ -0,0 +1,246 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/almendro_1998.txt
+---
+ROOT@0..706
+ ENTRY@0..706
+ TYPE@0..7 "@patent"
+ L_DELIM@7..8 "{"
+ NAME@8..16 "almendro"
+ COMMA@16..17 ","
+ WHITESPACE@17..22 "\n "
+ FIELD@22..150
+ NAME@22..28 "author"
+ WHITESPACE@28..35 " "
+ EQ@35..36 "="
+ WHITESPACE@36..37 " "
+ CURLY_GROUP@37..149
+ L_CURLY@37..38 "{"
+ WORD@38..46 "Almendro"
+ COMMA@46..47 ","
+ WHITESPACE@47..48 " "
+ WORD@48..51 "Jos"
+ CURLY_GROUP@51..56
+ L_CURLY@51..52 "{"
+ ACCENT@52..55
+ ACCENT_NAME@52..54 "\\'"
+ WORD@54..55 "e"
+ R_CURLY@55..56 "}"
+ WHITESPACE@56..57 " "
+ WORD@57..59 "L."
+ WHITESPACE@59..60 " "
+ WORD@60..63 "and"
+ WHITESPACE@63..64 " "
+ WORD@64..68 "Mart"
+ CURLY_GROUP@68..73
+ L_CURLY@68..69 "{"
+ ACCENT@69..72
+ ACCENT_NAME@69..71 "\\'"
+ WORD@71..72 "i"
+ R_CURLY@72..73 "}"
+ WORD@73..74 "n"
+ COMMA@74..75 ","
+ WHITESPACE@75..76 " "
+ WORD@76..83 "Jacinto"
+ WHITESPACE@83..84 " "
+ WORD@84..87 "and"
+ WHITESPACE@87..88 " "
+ WORD@88..89 "S"
+ CURLY_GROUP@89..94
+ L_CURLY@89..90 "{"
+ ACCENT@90..93
+ ACCENT_NAME@90..92 "\\'"
+ WORD@92..93 "a"
+ R_CURLY@93..94 "}"
+ WORD@94..99 "nchez"
+ COMMA@99..100 ","
+ WHITESPACE@100..121 "\n "
+ WORD@121..128 "Alberto"
+ WHITESPACE@128..129 " "
+ WORD@129..132 "and"
+ WHITESPACE@132..133 " "
+ WORD@133..138 "Nozal"
+ COMMA@138..139 ","
+ WHITESPACE@139..140 " "
+ WORD@140..148 "Fernando"
+ R_CURLY@148..149 "}"
+ COMMA@149..150 ","
+ WHITESPACE@150..155 "\n "
+ FIELD@155..203
+ NAME@155..160 "title"
+ WHITESPACE@160..168 " "
+ EQ@168..169 "="
+ WHITESPACE@169..170 " "
+ CURLY_GROUP@170..202
+ L_CURLY@170..171 "{"
+ WORD@171..190 "Elektromagnetisches"
+ WHITESPACE@190..191 " "
+ WORD@191..201 "Signalhorn"
+ R_CURLY@201..202 "}"
+ COMMA@202..203 ","
+ WHITESPACE@203..208 "\n "
+ FIELD@208..238
+ NAME@208..214 "number"
+ WHITESPACE@214..221 " "
+ EQ@221..222 "="
+ WHITESPACE@222..223 " "
+ CURLY_GROUP@223..237
+ L_CURLY@223..224 "{"
+ WORD@224..236 "EU-29702195U"
+ R_CURLY@236..237 "}"
+ COMMA@237..238 ","
+ WHITESPACE@238..243 "\n "
+ FIELD@243..263
+ NAME@243..247 "date"
+ WHITESPACE@247..256 " "
+ EQ@256..257 "="
+ WHITESPACE@257..258 " "
+ LITERAL@258..262
+ INTEGER@258..262 "1998"
+ COMMA@262..263 ","
+ WHITESPACE@263..268 "\n "
+ FIELD@268..323
+ NAME@268..276 "location"
+ WHITESPACE@276..281 " "
+ EQ@281..282 "="
+ WHITESPACE@282..283 " "
+ CURLY_GROUP@283..322
+ L_CURLY@283..284 "{"
+ WORD@284..293 "countryfr"
+ WHITESPACE@293..294 " "
+ WORD@294..297 "and"
+ WHITESPACE@297..298 " "
+ WORD@298..307 "countryuk"
+ WHITESPACE@307..308 " "
+ WORD@308..311 "and"
+ WHITESPACE@311..312 " "
+ WORD@312..321 "countryde"
+ R_CURLY@321..322 "}"
+ COMMA@322..323 ","
+ WHITESPACE@323..328 "\n "
+ FIELD@328..352
+ NAME@328..334 "langid"
+ WHITESPACE@334..341 " "
+ EQ@341..342 "="
+ WHITESPACE@342..343 " "
+ CURLY_GROUP@343..351
+ L_CURLY@343..344 "{"
+ WORD@344..350 "german"
+ R_CURLY@350..351 "}"
+ COMMA@351..352 ","
+ WHITESPACE@352..357 "\n "
+ FIELD@357..704
+ NAME@357..367 "annotation"
+ WHITESPACE@367..370 " "
+ EQ@370..371 "="
+ WHITESPACE@371..372 " "
+ CURLY_GROUP@372..703
+ L_CURLY@372..373 "{"
+ WORD@373..377 "This"
+ WHITESPACE@377..378 " "
+ WORD@378..380 "is"
+ WHITESPACE@380..381 " "
+ WORD@381..382 "a"
+ WHITESPACE@382..383 " "
+ COMMAND@383..390
+ COMMAND_NAME@383..390 "\\texttt"
+ CURLY_GROUP@390..398
+ L_CURLY@390..391 "{"
+ WORD@391..397 "patent"
+ R_CURLY@397..398 "}"
+ WHITESPACE@398..399 " "
+ WORD@399..404 "entry"
+ WHITESPACE@404..405 " "
+ WORD@405..409 "with"
+ WHITESPACE@409..410 " "
+ WORD@410..411 "a"
+ WHITESPACE@411..412 " "
+ COMMAND@412..419
+ COMMAND_NAME@412..419 "\\texttt"
+ CURLY_GROUP@419..429
+ L_CURLY@419..420 "{"
+ WORD@420..428 "location"
+ R_CURLY@428..429 "}"
+ WHITESPACE@429..450 "\n "
+ WORD@450..456 "field."
+ WHITESPACE@456..457 " "
+ WORD@457..460 "The"
+ WHITESPACE@460..461 " "
+ WORD@461..467 "number"
+ WHITESPACE@467..468 " "
+ WORD@468..470 "is"
+ WHITESPACE@470..471 " "
+ WORD@471..476 "given"
+ WHITESPACE@476..477 " "
+ WORD@477..479 "in"
+ WHITESPACE@479..480 " "
+ WORD@480..483 "the"
+ WHITESPACE@483..484 " "
+ COMMAND@484..491
+ COMMAND_NAME@484..491 "\\texttt"
+ CURLY_GROUP@491..499
+ L_CURLY@491..492 "{"
+ WORD@492..498 "number"
+ R_CURLY@498..499 "}"
+ WHITESPACE@499..500 " "
+ WORD@500..506 "field."
+ WHITESPACE@506..507 " "
+ WORD@507..511 "Note"
+ WHITESPACE@511..532 "\n "
+ WORD@532..535 "the"
+ WHITESPACE@535..536 " "
+ WORD@536..542 "format"
+ WHITESPACE@542..543 " "
+ WORD@543..545 "of"
+ WHITESPACE@545..546 " "
+ WORD@546..549 "the"
+ WHITESPACE@549..550 " "
+ COMMAND@550..557
+ COMMAND_NAME@550..557 "\\texttt"
+ CURLY_GROUP@557..567
+ L_CURLY@557..558 "{"
+ WORD@558..566 "location"
+ R_CURLY@566..567 "}"
+ WHITESPACE@567..568 " "
+ WORD@568..573 "field"
+ WHITESPACE@573..574 " "
+ WORD@574..576 "in"
+ WHITESPACE@576..577 " "
+ WORD@577..580 "the"
+ WHITESPACE@580..581 " "
+ WORD@581..589 "database"
+ WHITESPACE@589..610 "\n "
+ WORD@610..615 "file."
+ WHITESPACE@615..616 " "
+ WORD@616..623 "Compare"
+ WHITESPACE@623..624 " "
+ COMMAND@624..631
+ COMMAND_NAME@624..631 "\\texttt"
+ CURLY_GROUP@631..643
+ L_CURLY@631..632 "{"
+ WORD@632..642 "laufenberg"
+ R_CURLY@642..643 "}"
+ COMMA@643..644 ","
+ WHITESPACE@644..645 " "
+ COMMAND@645..652
+ COMMAND_NAME@645..652 "\\texttt"
+ CURLY_GROUP@652..660
+ L_CURLY@652..653 "{"
+ WORD@653..659 "sorace"
+ R_CURLY@659..660 "}"
+ COMMA@660..661 ","
+ WHITESPACE@661..662 " "
+ WORD@662..665 "and"
+ WHITESPACE@665..686 "\n "
+ COMMAND@686..693
+ COMMAND_NAME@686..693 "\\texttt"
+ CURLY_GROUP@693..702
+ L_CURLY@693..694 "{"
+ WORD@694..701 "kowalik"
+ R_CURLY@701..702 "}"
+ R_CURLY@702..703 "}"
+ COMMA@703..704 ","
+ WHITESPACE@704..705 "\n"
+ R_DELIM@705..706 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__averroes_1998.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__averroes_1998.txt.snap
new file mode 100644
index 0000000000..c02d61d272
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__averroes_1998.txt.snap
@@ -0,0 +1,351 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/averroes_1998.txt
+---
+ROOT@0..1008
+ ENTRY@0..1008
+ TYPE@0..5 "@book"
+ L_DELIM@5..6 "{"
+ NAME@6..20 "averroes/bland"
+ COMMA@20..21 ","
+ WHITESPACE@21..26 "\n "
+ FIELD@26..52
+ NAME@26..32 "author"
+ WHITESPACE@32..39 " "
+ EQ@39..40 "="
+ WHITESPACE@40..41 " "
+ CURLY_GROUP@41..51
+ L_CURLY@41..42 "{"
+ WORD@42..50 "Averroes"
+ R_CURLY@50..51 "}"
+ COMMA@51..52 ","
+ WHITESPACE@52..57 "\n "
+ FIELD@57..220
+ NAME@57..62 "title"
+ WHITESPACE@62..70 " "
+ EQ@70..71 "="
+ WHITESPACE@71..72 " "
+ CURLY_GROUP@72..219
+ L_CURLY@72..73 "{"
+ WORD@73..76 "The"
+ WHITESPACE@76..77 " "
+ WORD@77..84 "Epistle"
+ WHITESPACE@84..85 " "
+ WORD@85..87 "on"
+ WHITESPACE@87..88 " "
+ WORD@88..91 "the"
+ WHITESPACE@91..92 " "
+ WORD@92..103 "Possibility"
+ WHITESPACE@103..104 " "
+ WORD@104..106 "of"
+ WHITESPACE@106..107 " "
+ WORD@107..118 "Conjunction"
+ WHITESPACE@118..119 " "
+ WORD@119..123 "with"
+ WHITESPACE@123..124 " "
+ WORD@124..127 "the"
+ WHITESPACE@127..128 " "
+ WORD@128..134 "Active"
+ WHITESPACE@134..155 "\n "
+ WORD@155..164 "Intellect"
+ WHITESPACE@164..165 " "
+ WORD@165..167 "by"
+ WHITESPACE@167..168 " "
+ CURLY_GROUP@168..179
+ L_CURLY@168..169 "{"
+ WORD@169..172 "Ibn"
+ WHITESPACE@172..173 " "
+ WORD@173..178 "Rushd"
+ R_CURLY@178..179 "}"
+ WHITESPACE@179..180 " "
+ WORD@180..184 "with"
+ WHITESPACE@184..185 " "
+ WORD@185..188 "the"
+ WHITESPACE@188..189 " "
+ WORD@189..199 "Commentary"
+ WHITESPACE@199..200 " "
+ WORD@200..202 "of"
+ WHITESPACE@202..203 " "
+ CURLY_GROUP@203..218
+ L_CURLY@203..204 "{"
+ WORD@204..209 "Moses"
+ WHITESPACE@209..210 " "
+ WORD@210..217 "Narboni"
+ R_CURLY@217..218 "}"
+ R_CURLY@218..219 "}"
+ COMMA@219..220 ","
+ WHITESPACE@220..225 "\n "
+ FIELD@225..245
+ NAME@225..229 "date"
+ WHITESPACE@229..238 " "
+ EQ@238..239 "="
+ WHITESPACE@239..240 " "
+ LITERAL@240..244
+ INTEGER@240..244 "1982"
+ COMMA@244..245 ","
+ WHITESPACE@245..250 "\n "
+ FIELD@250..284
+ NAME@250..256 "editor"
+ WHITESPACE@256..263 " "
+ EQ@263..264 "="
+ WHITESPACE@264..265 " "
+ CURLY_GROUP@265..283
+ L_CURLY@265..266 "{"
+ WORD@266..271 "Bland"
+ COMMA@271..272 ","
+ WHITESPACE@272..273 " "
+ WORD@273..279 "Kalman"
+ WHITESPACE@279..280 " "
+ WORD@280..282 "P."
+ R_CURLY@282..283 "}"
+ COMMA@283..284 ","
+ WHITESPACE@284..289 "\n "
+ FIELD@289..323
+ NAME@289..299 "translator"
+ WHITESPACE@299..302 " "
+ EQ@302..303 "="
+ WHITESPACE@303..304 " "
+ CURLY_GROUP@304..322
+ L_CURLY@304..305 "{"
+ WORD@305..310 "Bland"
+ COMMA@310..311 ","
+ WHITESPACE@311..312 " "
+ WORD@312..318 "Kalman"
+ WHITESPACE@318..319 " "
+ WORD@319..321 "P."
+ R_CURLY@321..322 "}"
+ COMMA@322..323 ","
+ WHITESPACE@323..328 "\n "
+ FIELD@328..407
+ NAME@328..334 "series"
+ WHITESPACE@334..341 " "
+ EQ@341..342 "="
+ WHITESPACE@342..343 " "
+ CURLY_GROUP@343..406
+ L_CURLY@343..344 "{"
+ WORD@344..353 "Moreshet:"
+ WHITESPACE@353..354 " "
+ WORD@354..361 "Studies"
+ WHITESPACE@361..362 " "
+ WORD@362..364 "in"
+ WHITESPACE@364..365 " "
+ CURLY_GROUP@365..373
+ L_CURLY@365..366 "{"
+ WORD@366..372 "Jewish"
+ R_CURLY@372..373 "}"
+ WHITESPACE@373..374 " "
+ WORD@374..381 "History"
+ COMMA@381..382 ","
+ WHITESPACE@382..383 " "
+ WORD@383..393 "Literature"
+ WHITESPACE@393..394 " "
+ WORD@394..397 "and"
+ WHITESPACE@397..398 " "
+ WORD@398..405 "Thought"
+ R_CURLY@405..406 "}"
+ COMMA@406..407 ","
+ WHITESPACE@407..412 "\n "
+ FIELD@412..429
+ NAME@412..418 "number"
+ WHITESPACE@418..425 " "
+ EQ@425..426 "="
+ WHITESPACE@426..427 " "
+ LITERAL@427..428
+ INTEGER@427..428 "7"
+ COMMA@428..429 ","
+ WHITESPACE@429..434 "\n "
+ FIELD@434..490
+ NAME@434..443 "publisher"
+ WHITESPACE@443..447 " "
+ EQ@447..448 "="
+ WHITESPACE@448..449 " "
+ CURLY_GROUP@449..489
+ L_CURLY@449..450 "{"
+ WORD@450..456 "Jewish"
+ WHITESPACE@456..457 " "
+ WORD@457..468 "Theological"
+ WHITESPACE@468..469 " "
+ WORD@469..477 "Seminary"
+ WHITESPACE@477..478 " "
+ WORD@478..480 "of"
+ WHITESPACE@480..481 " "
+ WORD@481..488 "America"
+ R_CURLY@488..489 "}"
+ COMMA@489..490 ","
+ WHITESPACE@490..495 "\n "
+ FIELD@495..521
+ NAME@495..503 "location"
+ WHITESPACE@503..508 " "
+ EQ@508..509 "="
+ WHITESPACE@509..510 " "
+ CURLY_GROUP@510..520
+ L_CURLY@510..511 "{"
+ WORD@511..514 "New"
+ WHITESPACE@514..515 " "
+ WORD@515..519 "York"
+ R_CURLY@519..520 "}"
+ COMMA@520..521 ","
+ WHITESPACE@521..526 "\n "
+ FIELD@526..551
+ NAME@526..534 "keywords"
+ WHITESPACE@534..539 " "
+ EQ@539..540 "="
+ WHITESPACE@540..541 " "
+ CURLY_GROUP@541..550
+ L_CURLY@541..542 "{"
+ WORD@542..549 "primary"
+ R_CURLY@549..550 "}"
+ COMMA@550..551 ","
+ WHITESPACE@551..556 "\n "
+ FIELD@556..581
+ NAME@556..562 "langid"
+ WHITESPACE@562..569 " "
+ EQ@569..570 "="
+ WHITESPACE@570..571 " "
+ CURLY_GROUP@571..580
+ L_CURLY@571..572 "{"
+ WORD@572..579 "english"
+ R_CURLY@579..580 "}"
+ COMMA@580..581 ","
+ WHITESPACE@581..586 "\n "
+ FIELD@586..620
+ NAME@586..596 "langidopts"
+ WHITESPACE@596..599 " "
+ EQ@599..600 "="
+ WHITESPACE@600..601 " "
+ CURLY_GROUP@601..619
+ L_CURLY@601..602 "{"
+ WORD@602..618 "variant=american"
+ R_CURLY@618..619 "}"
+ COMMA@619..620 ","
+ WHITESPACE@620..625 "\n "
+ FIELD@625..689
+ NAME@625..635 "indextitle"
+ WHITESPACE@635..638 " "
+ EQ@638..639 "="
+ WHITESPACE@639..640 " "
+ CURLY_GROUP@640..688
+ L_CURLY@640..641 "{"
+ WORD@641..648 "Epistle"
+ WHITESPACE@648..649 " "
+ WORD@649..651 "on"
+ WHITESPACE@651..652 " "
+ WORD@652..655 "the"
+ WHITESPACE@655..656 " "
+ WORD@656..667 "Possibility"
+ WHITESPACE@667..668 " "
+ WORD@668..670 "of"
+ WHITESPACE@670..671 " "
+ WORD@671..682 "Conjunction"
+ COMMA@682..683 ","
+ WHITESPACE@683..684 " "
+ WORD@684..687 "The"
+ R_CURLY@687..688 "}"
+ COMMA@688..689 ","
+ WHITESPACE@689..694 "\n "
+ FIELD@694..738
+ NAME@694..704 "shorttitle"
+ WHITESPACE@704..707 " "
+ EQ@707..708 "="
+ WHITESPACE@708..709 " "
+ CURLY_GROUP@709..737
+ L_CURLY@709..710 "{"
+ WORD@710..721 "Possibility"
+ WHITESPACE@721..722 " "
+ WORD@722..724 "of"
+ WHITESPACE@724..725 " "
+ WORD@725..736 "Conjunction"
+ R_CURLY@736..737 "}"
+ COMMA@737..738 ","
+ WHITESPACE@738..743 "\n "
+ FIELD@743..1006
+ NAME@743..753 "annotation"
+ WHITESPACE@753..756 " "
+ EQ@756..757 "="
+ WHITESPACE@757..758 " "
+ CURLY_GROUP@758..1005
+ L_CURLY@758..759 "{"
+ WORD@759..760 "A"
+ WHITESPACE@760..761 " "
+ COMMAND@761..768
+ COMMAND_NAME@761..768 "\\texttt"
+ CURLY_GROUP@768..774
+ L_CURLY@768..769 "{"
+ WORD@769..773 "book"
+ R_CURLY@773..774 "}"
+ WHITESPACE@774..775 " "
+ WORD@775..780 "entry"
+ WHITESPACE@780..781 " "
+ WORD@781..785 "with"
+ WHITESPACE@785..786 " "
+ WORD@786..787 "a"
+ WHITESPACE@787..788 " "
+ COMMAND@788..795
+ COMMAND_NAME@788..795 "\\texttt"
+ CURLY_GROUP@795..803
+ L_CURLY@795..796 "{"
+ WORD@796..802 "series"
+ R_CURLY@802..803 "}"
+ WHITESPACE@803..804 " "
+ WORD@804..807 "and"
+ WHITESPACE@807..808 " "
+ WORD@808..809 "a"
+ WHITESPACE@809..830 "\n "
+ COMMAND@830..837
+ COMMAND_NAME@830..837 "\\texttt"
+ CURLY_GROUP@837..845
+ L_CURLY@837..838 "{"
+ WORD@838..844 "number"
+ R_CURLY@844..845 "}"
+ WORD@845..846 "."
+ WHITESPACE@846..847 " "
+ WORD@847..851 "Note"
+ WHITESPACE@851..852 " "
+ WORD@852..855 "the"
+ WHITESPACE@855..856 " "
+ WORD@856..869 "concatenation"
+ WHITESPACE@869..870 " "
+ WORD@870..872 "of"
+ WHITESPACE@872..873 " "
+ WORD@873..876 "the"
+ WHITESPACE@876..877 " "
+ COMMAND@877..884
+ COMMAND_NAME@877..884 "\\texttt"
+ CURLY_GROUP@884..892
+ L_CURLY@884..885 "{"
+ WORD@885..891 "editor"
+ R_CURLY@891..892 "}"
+ WHITESPACE@892..913 "\n "
+ WORD@913..916 "and"
+ WHITESPACE@916..917 " "
+ COMMAND@917..924
+ COMMAND_NAME@917..924 "\\texttt"
+ CURLY_GROUP@924..936
+ L_CURLY@924..925 "{"
+ WORD@925..935 "translator"
+ R_CURLY@935..936 "}"
+ WHITESPACE@936..937 " "
+ WORD@937..943 "fields"
+ WHITESPACE@943..944 " "
+ WORD@944..946 "as"
+ WHITESPACE@946..947 " "
+ WORD@947..951 "well"
+ WHITESPACE@951..952 " "
+ WORD@952..954 "as"
+ WHITESPACE@954..955 " "
+ WORD@955..958 "the"
+ WHITESPACE@958..979 "\n "
+ COMMAND@979..986
+ COMMAND_NAME@979..986 "\\texttt"
+ CURLY_GROUP@986..998
+ L_CURLY@986..987 "{"
+ WORD@987..997 "indextitle"
+ R_CURLY@997..998 "}"
+ WHITESPACE@998..999 " "
+ WORD@999..1004 "field"
+ R_CURLY@1004..1005 "}"
+ COMMA@1005..1006 ","
+ WHITESPACE@1006..1007 "\n"
+ R_DELIM@1007..1008 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__betram_1996.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__betram_1996.txt.snap
new file mode 100644
index 0000000000..c2748c1661
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__betram_1996.txt.snap
@@ -0,0 +1,208 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/betram_1996.txt
+---
+ROOT@0..556
+ STRING@0..40
+ TYPE@0..7 "@string"
+ L_DELIM@7..8 "{"
+ NAME@8..12 "jams"
+ WHITESPACE@12..16 " "
+ EQ@16..17 "="
+ WHITESPACE@17..18 " "
+ CURLY_GROUP@18..39
+ L_CURLY@18..19 "{"
+ WORD@19..21 "J."
+ NBSP@21..22 "~"
+ WORD@22..27 "Amer."
+ WHITESPACE@27..28 " "
+ WORD@28..33 "Math."
+ WHITESPACE@33..34 " "
+ WORD@34..38 "Soc."
+ R_CURLY@38..39 "}"
+ R_DELIM@39..40 "}"
+ JUNK@40..42 "\n\n"
+ ENTRY@42..556
+ TYPE@42..50 "@article"
+ L_DELIM@50..51 "{"
+ NAME@51..58 "bertram"
+ COMMA@58..59 ","
+ WHITESPACE@59..64 "\n "
+ FIELD@64..119
+ NAME@64..70 "author"
+ WHITESPACE@70..77 " "
+ EQ@77..78 "="
+ WHITESPACE@78..79 " "
+ CURLY_GROUP@79..118
+ L_CURLY@79..80 "{"
+ WORD@80..87 "Bertram"
+ COMMA@87..88 ","
+ WHITESPACE@88..89 " "
+ WORD@89..94 "Aaron"
+ WHITESPACE@94..95 " "
+ WORD@95..98 "and"
+ WHITESPACE@98..99 " "
+ WORD@99..108 "Wentworth"
+ COMMA@108..109 ","
+ WHITESPACE@109..110 " "
+ WORD@110..117 "Richard"
+ R_CURLY@117..118 "}"
+ COMMA@118..119 ","
+ WHITESPACE@119..124 "\n "
+ FIELD@124..202
+ NAME@124..129 "title"
+ WHITESPACE@129..137 " "
+ EQ@137..138 "="
+ WHITESPACE@138..139 " "
+ CURLY_GROUP@139..201
+ L_CURLY@139..140 "{"
+ WORD@140..146 "Gromov"
+ WHITESPACE@146..147 " "
+ WORD@147..157 "invariants"
+ WHITESPACE@157..158 " "
+ WORD@158..161 "for"
+ WHITESPACE@161..162 " "
+ WORD@162..173 "holomorphic"
+ WHITESPACE@173..174 " "
+ WORD@174..178 "maps"
+ WHITESPACE@178..179 " "
+ WORD@179..181 "on"
+ WHITESPACE@181..182 " "
+ CURLY_GROUP@182..191
+ L_CURLY@182..183 "{"
+ WORD@183..190 "Riemann"
+ R_CURLY@190..191 "}"
+ WHITESPACE@191..192 " "
+ WORD@192..200 "surfaces"
+ R_CURLY@200..201 "}"
+ COMMA@201..202 ","
+ WHITESPACE@202..207 "\n "
+ FIELD@207..227
+ NAME@207..219 "journaltitle"
+ WHITESPACE@219..220 " "
+ EQ@220..221 "="
+ WHITESPACE@221..222 " "
+ LITERAL@222..226
+ NAME@222..226 "jams"
+ COMMA@226..227 ","
+ WHITESPACE@227..232 "\n "
+ FIELD@232..252
+ NAME@232..236 "date"
+ WHITESPACE@236..245 " "
+ EQ@245..246 "="
+ WHITESPACE@246..247 " "
+ LITERAL@247..251
+ INTEGER@247..251 "1996"
+ COMMA@251..252 ","
+ WHITESPACE@252..257 "\n "
+ FIELD@257..274
+ NAME@257..263 "volume"
+ WHITESPACE@263..270 " "
+ EQ@270..271 "="
+ WHITESPACE@271..272 " "
+ LITERAL@272..273
+ INTEGER@272..273 "9"
+ COMMA@273..274 ","
+ WHITESPACE@274..279 "\n "
+ FIELD@279..296
+ NAME@279..285 "number"
+ WHITESPACE@285..292 " "
+ EQ@292..293 "="
+ WHITESPACE@293..294 " "
+ LITERAL@294..295
+ INTEGER@294..295 "2"
+ COMMA@295..296 ","
+ WHITESPACE@296..301 "\n "
+ FIELD@301..326
+ NAME@301..306 "pages"
+ WHITESPACE@306..314 " "
+ EQ@314..315 "="
+ WHITESPACE@315..316 " "
+ CURLY_GROUP@316..325
+ L_CURLY@316..317 "{"
+ WORD@317..324 "529-571"
+ R_CURLY@324..325 "}"
+ COMMA@325..326 ","
+ WHITESPACE@326..331 "\n "
+ FIELD@331..356
+ NAME@331..337 "langid"
+ WHITESPACE@337..344 " "
+ EQ@344..345 "="
+ WHITESPACE@345..346 " "
+ CURLY_GROUP@346..355
+ L_CURLY@346..347 "{"
+ WORD@347..354 "english"
+ R_CURLY@354..355 "}"
+ COMMA@355..356 ","
+ WHITESPACE@356..361 "\n "
+ FIELD@361..395
+ NAME@361..371 "langidopts"
+ WHITESPACE@371..374 " "
+ EQ@374..375 "="
+ WHITESPACE@375..376 " "
+ CURLY_GROUP@376..394
+ L_CURLY@376..377 "{"
+ WORD@377..393 "variant=american"
+ R_CURLY@393..394 "}"
+ COMMA@394..395 ","
+ WHITESPACE@395..400 "\n "
+ FIELD@400..435
+ NAME@400..410 "shorttitle"
+ WHITESPACE@410..413 " "
+ EQ@413..414 "="
+ WHITESPACE@414..415 " "
+ CURLY_GROUP@415..434
+ L_CURLY@415..416 "{"
+ WORD@416..422 "Gromov"
+ WHITESPACE@422..423 " "
+ WORD@423..433 "invariants"
+ R_CURLY@433..434 "}"
+ COMMA@434..435 ","
+ WHITESPACE@435..440 "\n "
+ FIELD@440..554
+ NAME@440..450 "annotation"
+ WHITESPACE@450..453 " "
+ EQ@453..454 "="
+ WHITESPACE@454..455 " "
+ CURLY_GROUP@455..553
+ L_CURLY@455..456 "{"
+ WORD@456..458 "An"
+ WHITESPACE@458..459 " "
+ COMMAND@459..466
+ COMMAND_NAME@459..466 "\\texttt"
+ CURLY_GROUP@466..475
+ L_CURLY@466..467 "{"
+ WORD@467..474 "article"
+ R_CURLY@474..475 "}"
+ WHITESPACE@475..476 " "
+ WORD@476..481 "entry"
+ WHITESPACE@481..482 " "
+ WORD@482..486 "with"
+ WHITESPACE@486..487 " "
+ WORD@487..488 "a"
+ WHITESPACE@488..489 " "
+ COMMAND@489..496
+ COMMAND_NAME@489..496 "\\texttt"
+ CURLY_GROUP@496..504
+ L_CURLY@496..497 "{"
+ WORD@497..503 "volume"
+ R_CURLY@503..504 "}"
+ WHITESPACE@504..505 " "
+ WORD@505..508 "and"
+ WHITESPACE@508..509 " "
+ WORD@509..510 "a"
+ WHITESPACE@510..531 "\n "
+ COMMAND@531..538
+ COMMAND_NAME@531..538 "\\texttt"
+ CURLY_GROUP@538..546
+ L_CURLY@538..539 "{"
+ WORD@539..545 "number"
+ R_CURLY@545..546 "}"
+ WHITESPACE@546..547 " "
+ WORD@547..552 "field"
+ R_CURLY@552..553 "}"
+ COMMA@553..554 ","
+ WHITESPACE@554..555 "\n"
+ R_DELIM@555..556 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__blom_2021.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__blom_2021.txt.snap
new file mode 100644
index 0000000000..417bda4ec9
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__blom_2021.txt.snap
@@ -0,0 +1,223 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/blom_2021.txt
+---
+ROOT@0..860
+ ENTRY@0..860
+ TYPE@0..8 "@article"
+ L_DELIM@8..9 "{"
+ NAME@9..42 "DBLP:journals/corr/ab ..."
+ COMMA@42..43 ","
+ WHITESPACE@43..48 "\n "
+ FIELD@48..322
+ NAME@48..54 "author"
+ WHITESPACE@54..58 " "
+ EQ@58..59 "="
+ WHITESPACE@59..60 " "
+ CURLY_GROUP@60..321
+ L_CURLY@60..61 "{"
+ WORD@61..69 "Michelle"
+ WHITESPACE@69..70 " "
+ WORD@70..72 "L."
+ WHITESPACE@72..73 " "
+ WORD@73..77 "Blom"
+ WHITESPACE@77..78 " "
+ WORD@78..81 "and"
+ WHITESPACE@81..102 "\n "
+ WORD@102..109 "Jurlind"
+ WHITESPACE@109..110 " "
+ WORD@110..119 "Budurushi"
+ WHITESPACE@119..120 " "
+ WORD@120..123 "and"
+ WHITESPACE@123..144 "\n "
+ WORD@144..150 "Ronald"
+ WHITESPACE@150..151 " "
+ WORD@151..153 "L."
+ WHITESPACE@153..154 " "
+ WORD@154..160 "Rivest"
+ WHITESPACE@160..161 " "
+ WORD@161..164 "and"
+ WHITESPACE@164..185 "\n "
+ WORD@185..191 "Philip"
+ WHITESPACE@191..192 " "
+ WORD@192..194 "B."
+ WHITESPACE@194..195 " "
+ WORD@195..200 "Stark"
+ WHITESPACE@200..201 " "
+ WORD@201..204 "and"
+ WHITESPACE@204..225 "\n "
+ WORD@225..230 "Peter"
+ WHITESPACE@230..231 " "
+ WORD@231..233 "J."
+ WHITESPACE@233..234 " "
+ WORD@234..241 "Stuckey"
+ WHITESPACE@241..242 " "
+ WORD@242..245 "and"
+ WHITESPACE@245..266 "\n "
+ WORD@266..273 "Vanessa"
+ WHITESPACE@273..274 " "
+ WORD@274..280 "Teague"
+ WHITESPACE@280..281 " "
+ WORD@281..284 "and"
+ WHITESPACE@284..305 "\n "
+ WORD@305..311 "Damjan"
+ WHITESPACE@311..312 " "
+ WORD@312..320 "Vukcevic"
+ R_CURLY@320..321 "}"
+ COMMA@321..322 ","
+ WHITESPACE@322..327 "\n "
+ FIELD@327..473
+ NAME@327..332 "title"
+ WHITESPACE@332..337 " "
+ EQ@337..338 "="
+ WHITESPACE@338..339 " "
+ CURLY_GROUP@339..472
+ L_CURLY@339..340 "{"
+ WORD@340..355 "Assertion-based"
+ WHITESPACE@355..356 " "
+ WORD@356..366 "Approaches"
+ WHITESPACE@366..367 " "
+ WORD@367..369 "to"
+ WHITESPACE@369..370 " "
+ WORD@370..378 "Auditing"
+ WHITESPACE@378..379 " "
+ WORD@379..386 "Complex"
+ WHITESPACE@386..387 " "
+ WORD@387..396 "Elections"
+ COMMA@396..397 ","
+ WHITESPACE@397..398 " "
+ WORD@398..402 "with"
+ WHITESPACE@402..403 " "
+ WORD@403..414 "application"
+ WHITESPACE@414..435 "\n "
+ WORD@435..437 "to"
+ WHITESPACE@437..438 " "
+ WORD@438..448 "party-list"
+ WHITESPACE@448..449 " "
+ WORD@449..461 "proportional"
+ WHITESPACE@461..462 " "
+ WORD@462..471 "elections"
+ R_CURLY@471..472 "}"
+ COMMA@472..473 ","
+ WHITESPACE@473..478 "\n "
+ FIELD@478..497
+ NAME@478..485 "journal"
+ WHITESPACE@485..488 " "
+ EQ@488..489 "="
+ WHITESPACE@489..490 " "
+ CURLY_GROUP@490..496
+ L_CURLY@490..491 "{"
+ WORD@491..495 "CoRR"
+ R_CURLY@495..496 "}"
+ COMMA@496..497 ","
+ WHITESPACE@497..502 "\n "
+ FIELD@502..531
+ NAME@502..508 "volume"
+ WHITESPACE@508..512 " "
+ EQ@512..513 "="
+ WHITESPACE@513..514 " "
+ CURLY_GROUP@514..530
+ L_CURLY@514..515 "{"
+ WORD@515..529 "abs/2107.11903"
+ R_CURLY@529..530 "}"
+ COMMA@530..531 ","
+ WHITESPACE@531..536 "\n "
+ FIELD@536..555
+ NAME@536..540 "year"
+ WHITESPACE@540..546 " "
+ EQ@546..547 "="
+ WHITESPACE@547..548 " "
+ CURLY_GROUP@548..554
+ L_CURLY@548..549 "{"
+ INTEGER@549..553 "2021"
+ R_CURLY@553..554 "}"
+ COMMA@554..555 ","
+ WHITESPACE@555..560 "\n "
+ FIELD@560..607
+ NAME@560..563 "url"
+ WHITESPACE@563..570 " "
+ EQ@570..571 "="
+ WHITESPACE@571..572 " "
+ CURLY_GROUP@572..606
+ L_CURLY@572..573 "{"
+ WORD@573..605 "https://arxiv.org/abs ..."
+ R_CURLY@605..606 "}"
+ COMMA@606..607 ","
+ WHITESPACE@607..612 "\n "
+ FIELD@612..633
+ NAME@612..622 "eprinttype"
+ WHITESPACE@622..623 " "
+ EQ@623..624 "="
+ WHITESPACE@624..625 " "
+ CURLY_GROUP@625..632
+ L_CURLY@625..626 "{"
+ WORD@626..631 "arXiv"
+ R_CURLY@631..632 "}"
+ COMMA@632..633 ","
+ WHITESPACE@633..638 "\n "
+ FIELD@638..663
+ NAME@638..644 "eprint"
+ WHITESPACE@644..648 " "
+ EQ@648..649 "="
+ WHITESPACE@649..650 " "
+ CURLY_GROUP@650..662
+ L_CURLY@650..651 "{"
+ WORD@651..661 "2107.11903"
+ R_CURLY@661..662 "}"
+ COMMA@662..663 ","
+ WHITESPACE@663..668 "\n "
+ FIELD@668..714
+ NAME@668..677 "timestamp"
+ WHITESPACE@677..678 " "
+ EQ@678..679 "="
+ WHITESPACE@679..680 " "
+ CURLY_GROUP@680..713
+ L_CURLY@680..681 "{"
+ WORD@681..684 "Thu"
+ COMMA@684..685 ","
+ WHITESPACE@685..686 " "
+ INTEGER@686..688 "29"
+ WHITESPACE@688..689 " "
+ WORD@689..692 "Jul"
+ WHITESPACE@692..693 " "
+ INTEGER@693..697 "2021"
+ WHITESPACE@697..698 " "
+ WORD@698..706 "16:14:15"
+ WHITESPACE@706..707 " "
+ WORD@707..712 "+0200"
+ R_CURLY@712..713 "}"
+ COMMA@713..714 ","
+ WHITESPACE@714..719 "\n "
+ FIELD@719..787
+ NAME@719..725 "biburl"
+ WHITESPACE@725..729 " "
+ EQ@729..730 "="
+ WHITESPACE@730..731 " "
+ CURLY_GROUP@731..786
+ L_CURLY@731..732 "{"
+ WORD@732..785 "https://dblp.org/rec/ ..."
+ R_CURLY@785..786 "}"
+ COMMA@786..787 ","
+ WHITESPACE@787..792 "\n "
+ FIELD@792..859
+ NAME@792..801 "bibsource"
+ WHITESPACE@801..802 " "
+ EQ@802..803 "="
+ WHITESPACE@803..804 " "
+ CURLY_GROUP@804..858
+ L_CURLY@804..805 "{"
+ WORD@805..809 "dblp"
+ WHITESPACE@809..810 " "
+ WORD@810..818 "computer"
+ WHITESPACE@818..819 " "
+ WORD@819..826 "science"
+ WHITESPACE@826..827 " "
+ WORD@827..839 "bibliography"
+ COMMA@839..840 ","
+ WHITESPACE@840..841 " "
+ WORD@841..857 "https://dblp.org"
+ R_CURLY@857..858 "}"
+ WHITESPACE@858..859 "\n"
+ R_DELIM@859..860 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__combi_2004.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__combi_2004.txt.snap
new file mode 100644
index 0000000000..82f58a700f
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__combi_2004.txt.snap
@@ -0,0 +1,251 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/combi_2004.txt
+---
+ROOT@0..674
+ ENTRY@0..674
+ TYPE@0..14 "@inproceedings"
+ L_DELIM@14..15 "{"
+ NAME@15..36 "10.1145/967900.968040"
+ COMMA@36..37 ","
+ WHITESPACE@37..42 "\n "
+ FIELD@42..86
+ NAME@42..48 "author"
+ WHITESPACE@48..49 " "
+ EQ@49..50 "="
+ WHITESPACE@50..51 " "
+ CURLY_GROUP@51..85
+ L_CURLY@51..52 "{"
+ WORD@52..57 "Combi"
+ COMMA@57..58 ","
+ WHITESPACE@58..59 " "
+ WORD@59..64 "Carlo"
+ WHITESPACE@64..65 " "
+ WORD@65..68 "and"
+ WHITESPACE@68..69 " "
+ WORD@69..74 "Pozzi"
+ COMMA@74..75 ","
+ WHITESPACE@75..76 " "
+ WORD@76..84 "Giuseppe"
+ R_CURLY@84..85 "}"
+ COMMA@85..86 ","
+ WHITESPACE@86..91 "\n "
+ FIELD@91..157
+ NAME@91..96 "title"
+ WHITESPACE@96..97 " "
+ EQ@97..98 "="
+ WHITESPACE@98..99 " "
+ CURLY_GROUP@99..156
+ L_CURLY@99..100 "{"
+ WORD@100..113 "Architectures"
+ WHITESPACE@113..114 " "
+ WORD@114..117 "for"
+ WHITESPACE@117..118 " "
+ WORD@118..119 "a"
+ WHITESPACE@119..120 " "
+ WORD@120..128 "Temporal"
+ WHITESPACE@128..129 " "
+ WORD@129..137 "Workflow"
+ WHITESPACE@137..138 " "
+ WORD@138..148 "Management"
+ WHITESPACE@148..149 " "
+ WORD@149..155 "System"
+ R_CURLY@155..156 "}"
+ COMMA@156..157 ","
+ WHITESPACE@157..162 "\n "
+ FIELD@162..176
+ NAME@162..166 "year"
+ WHITESPACE@166..167 " "
+ EQ@167..168 "="
+ WHITESPACE@168..169 " "
+ CURLY_GROUP@169..175
+ L_CURLY@169..170 "{"
+ INTEGER@170..174 "2004"
+ R_CURLY@174..175 "}"
+ COMMA@175..176 ","
+ WHITESPACE@176..181 "\n "
+ FIELD@181..201
+ NAME@181..185 "isbn"
+ WHITESPACE@185..186 " "
+ EQ@186..187 "="
+ WHITESPACE@187..188 " "
+ CURLY_GROUP@188..200
+ L_CURLY@188..189 "{"
+ INTEGER@189..199 "1581138121"
+ R_CURLY@199..200 "}"
+ COMMA@200..201 ","
+ WHITESPACE@201..206 "\n "
+ FIELD@206..256
+ NAME@206..215 "publisher"
+ WHITESPACE@215..216 " "
+ EQ@216..217 "="
+ WHITESPACE@217..218 " "
+ CURLY_GROUP@218..255
+ L_CURLY@218..219 "{"
+ WORD@219..230 "Association"
+ WHITESPACE@230..231 " "
+ WORD@231..234 "for"
+ WHITESPACE@234..235 " "
+ WORD@235..244 "Computing"
+ WHITESPACE@244..245 " "
+ WORD@245..254 "Machinery"
+ R_CURLY@254..255 "}"
+ COMMA@255..256 ","
+ WHITESPACE@256..261 "\n "
+ FIELD@261..291
+ NAME@261..268 "address"
+ WHITESPACE@268..269 " "
+ EQ@269..270 "="
+ WHITESPACE@270..271 " "
+ CURLY_GROUP@271..290
+ L_CURLY@271..272 "{"
+ WORD@272..275 "New"
+ WHITESPACE@275..276 " "
+ WORD@276..280 "York"
+ COMMA@280..281 ","
+ WHITESPACE@281..282 " "
+ WORD@282..284 "NY"
+ COMMA@284..285 ","
+ WHITESPACE@285..286 " "
+ WORD@286..289 "USA"
+ R_CURLY@289..290 "}"
+ COMMA@290..291 ","
+ WHITESPACE@291..296 "\n "
+ FIELD@296..342
+ NAME@296..299 "url"
+ WHITESPACE@299..300 " "
+ EQ@300..301 "="
+ WHITESPACE@301..302 " "
+ CURLY_GROUP@302..341
+ L_CURLY@302..303 "{"
+ WORD@303..340 "https://doi.org/10.11 ..."
+ R_CURLY@340..341 "}"
+ COMMA@341..342 ","
+ WHITESPACE@342..347 "\n "
+ FIELD@347..377
+ NAME@347..350 "doi"
+ WHITESPACE@350..351 " "
+ EQ@351..352 "="
+ WHITESPACE@352..353 " "
+ CURLY_GROUP@353..376
+ L_CURLY@353..354 "{"
+ WORD@354..375 "10.1145/967900.968040"
+ R_CURLY@375..376 "}"
+ COMMA@376..377 ","
+ WHITESPACE@377..382 "\n "
+ FIELD@382..455
+ NAME@382..391 "booktitle"
+ WHITESPACE@391..392 " "
+ EQ@392..393 "="
+ WHITESPACE@393..394 " "
+ CURLY_GROUP@394..454
+ L_CURLY@394..395 "{"
+ WORD@395..406 "Proceedings"
+ WHITESPACE@406..407 " "
+ WORD@407..409 "of"
+ WHITESPACE@409..410 " "
+ WORD@410..413 "the"
+ WHITESPACE@413..414 " "
+ INTEGER@414..418 "2004"
+ WHITESPACE@418..419 " "
+ WORD@419..422 "ACM"
+ WHITESPACE@422..423 " "
+ WORD@423..432 "Symposium"
+ WHITESPACE@432..433 " "
+ WORD@433..435 "on"
+ WHITESPACE@435..436 " "
+ WORD@436..443 "Applied"
+ WHITESPACE@443..444 " "
+ WORD@444..453 "Computing"
+ R_CURLY@453..454 "}"
+ COMMA@454..455 ","
+ WHITESPACE@455..460 "\n "
+ FIELD@460..478
+ NAME@460..465 "pages"
+ WHITESPACE@465..466 " "
+ EQ@466..467 "="
+ WHITESPACE@467..468 " "
+ CURLY_GROUP@468..477
+ L_CURLY@468..469 "{"
+ WORD@469..476 "659-666"
+ R_CURLY@476..477 "}"
+ COMMA@477..478 ","
+ WHITESPACE@478..483 "\n "
+ FIELD@483..498
+ NAME@483..491 "numpages"
+ WHITESPACE@491..492 " "
+ EQ@492..493 "="
+ WHITESPACE@493..494 " "
+ CURLY_GROUP@494..497
+ L_CURLY@494..495 "{"
+ INTEGER@495..496 "8"
+ R_CURLY@496..497 "}"
+ COMMA@497..498 ","
+ WHITESPACE@498..503 "\n "
+ FIELD@503..615
+ NAME@503..511 "keywords"
+ WHITESPACE@511..512 " "
+ EQ@512..513 "="
+ WHITESPACE@513..514 " "
+ CURLY_GROUP@514..614
+ L_CURLY@514..515 "{"
+ WORD@515..521 "active"
+ WHITESPACE@521..522 " "
+ WORD@522..526 "DBMS"
+ COMMA@526..527 ","
+ WHITESPACE@527..528 " "
+ WORD@528..536 "temporal"
+ WHITESPACE@536..537 " "
+ WORD@537..541 "DBMS"
+ COMMA@541..542 ","
+ WHITESPACE@542..543 " "
+ WORD@543..551 "workflow"
+ WHITESPACE@551..552 " "
+ WORD@552..562 "management"
+ WHITESPACE@562..563 " "
+ WORD@563..569 "system"
+ WHITESPACE@569..570 " "
+ WORD@570..571 "-"
+ WHITESPACE@571..572 " "
+ WORD@572..576 "WfMS"
+ COMMA@576..577 ","
+ WHITESPACE@577..578 " "
+ WORD@578..586 "temporal"
+ WHITESPACE@586..587 " "
+ WORD@587..595 "workflow"
+ WHITESPACE@595..596 " "
+ WORD@596..606 "management"
+ WHITESPACE@606..607 " "
+ WORD@607..613 "system"
+ R_CURLY@613..614 "}"
+ COMMA@614..615 ","
+ WHITESPACE@615..620 "\n "
+ FIELD@620..649
+ NAME@620..628 "location"
+ WHITESPACE@628..629 " "
+ EQ@629..630 "="
+ WHITESPACE@630..631 " "
+ CURLY_GROUP@631..648
+ L_CURLY@631..632 "{"
+ WORD@632..639 "Nicosia"
+ COMMA@639..640 ","
+ WHITESPACE@640..641 " "
+ WORD@641..647 "Cyprus"
+ R_CURLY@647..648 "}"
+ COMMA@648..649 ","
+ WHITESPACE@649..654 "\n "
+ FIELD@654..673
+ NAME@654..660 "series"
+ WHITESPACE@660..661 " "
+ EQ@661..662 "="
+ WHITESPACE@662..663 " "
+ CURLY_GROUP@663..672
+ L_CURLY@663..664 "{"
+ WORD@664..667 "SAC"
+ WHITESPACE@667..668 " "
+ WORD@668..671 "'04"
+ R_CURLY@671..672 "}"
+ WHITESPACE@672..673 "\n"
+ R_DELIM@673..674 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__erwin_2007.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__erwin_2007.txt.snap
new file mode 100644
index 0000000000..58094d2f42
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__erwin_2007.txt.snap
@@ -0,0 +1,239 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/erwin_2007.txt
+---
+ROOT@0..615
+ ENTRY@0..615
+ TYPE@0..14 "@inproceedings"
+ L_DELIM@14..15 "{"
+ NAME@15..38 "10.5555/1386993.1386994"
+ COMMA@38..39 ","
+ WHITESPACE@39..44 "\n "
+ FIELD@44..107
+ NAME@44..50 "author"
+ WHITESPACE@50..51 " "
+ EQ@51..52 "="
+ WHITESPACE@52..53 " "
+ CURLY_GROUP@53..106
+ L_CURLY@53..54 "{"
+ WORD@54..59 "Erwin"
+ COMMA@59..60 ","
+ WHITESPACE@60..61 " "
+ WORD@61..65 "Alva"
+ WHITESPACE@65..66 " "
+ WORD@66..69 "and"
+ WHITESPACE@69..70 " "
+ WORD@70..77 "Gopalan"
+ COMMA@77..78 ","
+ WHITESPACE@78..79 " "
+ WORD@79..82 "Raj"
+ WHITESPACE@82..83 " "
+ WORD@83..85 "P."
+ WHITESPACE@85..86 " "
+ WORD@86..89 "and"
+ WHITESPACE@89..90 " "
+ WORD@90..98 "Achuthan"
+ COMMA@98..99 ","
+ WHITESPACE@99..100 " "
+ WORD@100..102 "N."
+ WHITESPACE@102..103 " "
+ WORD@103..105 "R."
+ R_CURLY@105..106 "}"
+ COMMA@106..107 ","
+ WHITESPACE@107..112 "\n "
+ FIELD@112..194
+ NAME@112..117 "title"
+ WHITESPACE@117..118 " "
+ EQ@118..119 "="
+ WHITESPACE@119..120 " "
+ CURLY_GROUP@120..193
+ L_CURLY@120..121 "{"
+ WORD@121..122 "A"
+ WHITESPACE@122..123 " "
+ WORD@123..132 "Bottom-up"
+ WHITESPACE@132..133 " "
+ WORD@133..143 "Projection"
+ WHITESPACE@143..144 " "
+ WORD@144..149 "Based"
+ WHITESPACE@149..150 " "
+ WORD@150..159 "Algorithm"
+ WHITESPACE@159..160 " "
+ WORD@160..163 "for"
+ WHITESPACE@163..164 " "
+ WORD@164..170 "Mining"
+ WHITESPACE@170..171 " "
+ WORD@171..175 "High"
+ WHITESPACE@175..176 " "
+ WORD@176..183 "Utility"
+ WHITESPACE@183..184 " "
+ WORD@184..192 "Itemsets"
+ R_CURLY@192..193 "}"
+ COMMA@193..194 ","
+ WHITESPACE@194..199 "\n "
+ FIELD@199..213
+ NAME@199..203 "year"
+ WHITESPACE@203..204 " "
+ EQ@204..205 "="
+ WHITESPACE@205..206 " "
+ CURLY_GROUP@206..212
+ L_CURLY@206..207 "{"
+ INTEGER@207..211 "2007"
+ R_CURLY@211..212 "}"
+ COMMA@212..213 ","
+ WHITESPACE@213..218 "\n "
+ FIELD@218..241
+ NAME@218..222 "isbn"
+ WHITESPACE@222..223 " "
+ EQ@223..224 "="
+ WHITESPACE@224..225 " "
+ CURLY_GROUP@225..240
+ L_CURLY@225..226 "{"
+ INTEGER@226..239 "9781920682651"
+ R_CURLY@239..240 "}"
+ COMMA@240..241 ","
+ WHITESPACE@241..246 "\n "
+ FIELD@246..294
+ NAME@246..255 "publisher"
+ WHITESPACE@255..256 " "
+ EQ@256..257 "="
+ WHITESPACE@257..258 " "
+ CURLY_GROUP@258..293
+ L_CURLY@258..259 "{"
+ WORD@259..269 "Australian"
+ WHITESPACE@269..270 " "
+ WORD@270..278 "Computer"
+ WHITESPACE@278..279 " "
+ WORD@279..286 "Society"
+ COMMA@286..287 ","
+ WHITESPACE@287..288 " "
+ WORD@288..292 "Inc."
+ R_CURLY@292..293 "}"
+ COMMA@293..294 ","
+ WHITESPACE@294..299 "\n "
+ FIELD@299..315
+ NAME@299..306 "address"
+ WHITESPACE@306..307 " "
+ EQ@307..308 "="
+ WHITESPACE@308..309 " "
+ CURLY_GROUP@309..314
+ L_CURLY@309..310 "{"
+ WORD@310..313 "AUS"
+ R_CURLY@313..314 "}"
+ COMMA@314..315 ","
+ WHITESPACE@315..320 "\n "
+ FIELD@320..447
+ NAME@320..329 "booktitle"
+ WHITESPACE@329..330 " "
+ EQ@330..331 "="
+ WHITESPACE@331..332 " "
+ CURLY_GROUP@332..446
+ L_CURLY@332..333 "{"
+ WORD@333..344 "Proceedings"
+ WHITESPACE@344..345 " "
+ WORD@345..347 "of"
+ WHITESPACE@347..348 " "
+ WORD@348..351 "the"
+ WHITESPACE@351..352 " "
+ WORD@352..355 "2nd"
+ WHITESPACE@355..356 " "
+ WORD@356..369 "International"
+ WHITESPACE@369..370 " "
+ WORD@370..378 "Workshop"
+ WHITESPACE@378..379 " "
+ WORD@379..381 "on"
+ WHITESPACE@381..382 " "
+ WORD@382..393 "Integrating"
+ WHITESPACE@393..394 " "
+ WORD@394..404 "Artificial"
+ WHITESPACE@404..405 " "
+ WORD@405..417 "Intelligence"
+ WHITESPACE@417..418 " "
+ WORD@418..421 "and"
+ WHITESPACE@421..422 " "
+ WORD@422..426 "Data"
+ WHITESPACE@426..427 " "
+ WORD@427..433 "Mining"
+ WHITESPACE@433..434 " "
+ WORD@434..435 "-"
+ WHITESPACE@435..436 " "
+ WORD@436..442 "Volume"
+ WHITESPACE@442..443 " "
+ INTEGER@443..445 "84"
+ R_CURLY@445..446 "}"
+ COMMA@446..447 ","
+ WHITESPACE@447..452 "\n "
+ FIELD@452..467
+ NAME@452..457 "pages"
+ WHITESPACE@457..458 " "
+ EQ@458..459 "="
+ WHITESPACE@459..460 " "
+ CURLY_GROUP@460..466
+ L_CURLY@460..461 "{"
+ WORD@461..465 "3-11"
+ R_CURLY@465..466 "}"
+ COMMA@466..467 ","
+ WHITESPACE@467..472 "\n "
+ FIELD@472..487
+ NAME@472..480 "numpages"
+ WHITESPACE@480..481 " "
+ EQ@481..482 "="
+ WHITESPACE@482..483 " "
+ CURLY_GROUP@483..486
+ L_CURLY@483..484 "{"
+ INTEGER@484..485 "9"
+ R_CURLY@485..486 "}"
+ COMMA@486..487 ","
+ WHITESPACE@487..492 "\n "
+ FIELD@492..549
+ NAME@492..500 "keywords"
+ WHITESPACE@500..501 " "
+ EQ@501..502 "="
+ WHITESPACE@502..503 " "
+ CURLY_GROUP@503..548
+ L_CURLY@503..504 "{"
+ WORD@504..511 "pattern"
+ WHITESPACE@511..512 " "
+ WORD@512..518 "growth"
+ COMMA@518..519 ","
+ WHITESPACE@519..520 " "
+ WORD@520..524 "high"
+ WHITESPACE@524..525 " "
+ WORD@525..532 "utility"
+ WHITESPACE@532..533 " "
+ WORD@533..540 "itemset"
+ WHITESPACE@540..541 " "
+ WORD@541..547 "mining"
+ R_CURLY@547..548 "}"
+ COMMA@548..549 ","
+ WHITESPACE@549..554 "\n "
+ FIELD@554..589
+ NAME@554..562 "location"
+ WHITESPACE@562..563 " "
+ EQ@563..564 "="
+ WHITESPACE@564..565 " "
+ CURLY_GROUP@565..588
+ L_CURLY@565..566 "{"
+ WORD@566..570 "Gold"
+ WHITESPACE@570..571 " "
+ WORD@571..576 "Coast"
+ COMMA@576..577 ","
+ WHITESPACE@577..578 " "
+ WORD@578..587 "Australia"
+ R_CURLY@587..588 "}"
+ COMMA@588..589 ","
+ WHITESPACE@589..594 "\n "
+ FIELD@594..614
+ NAME@594..600 "series"
+ WHITESPACE@600..601 " "
+ EQ@601..602 "="
+ WHITESPACE@602..603 " "
+ CURLY_GROUP@603..613
+ L_CURLY@603..604 "{"
+ WORD@604..608 "AIDM"
+ WHITESPACE@608..609 " "
+ WORD@609..612 "'07"
+ R_CURLY@612..613 "}"
+ WHITESPACE@613..614 "\n"
+ R_DELIM@614..615 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__jain_1999.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__jain_1999.txt.snap
new file mode 100644
index 0000000000..d9f83b339a
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__jain_1999.txt.snap
@@ -0,0 +1,267 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/jain_1999.txt
+---
+ROOT@0..674
+ ENTRY@0..674
+ TYPE@0..8 "@article"
+ L_DELIM@8..9 "{"
+ NAME@9..30 "10.1145/331499.331504"
+ COMMA@30..31 ","
+ WHITESPACE@31..36 "\n "
+ FIELD@36..93
+ NAME@36..42 "author"
+ WHITESPACE@42..43 " "
+ EQ@43..44 "="
+ WHITESPACE@44..45 " "
+ CURLY_GROUP@45..92
+ L_CURLY@45..46 "{"
+ WORD@46..50 "Jain"
+ COMMA@50..51 ","
+ WHITESPACE@51..52 " "
+ WORD@52..54 "A."
+ WHITESPACE@54..55 " "
+ WORD@55..57 "K."
+ WHITESPACE@57..58 " "
+ WORD@58..61 "and"
+ WHITESPACE@61..62 " "
+ WORD@62..67 "Murty"
+ COMMA@67..68 ","
+ WHITESPACE@68..69 " "
+ WORD@69..71 "M."
+ WHITESPACE@71..72 " "
+ WORD@72..74 "N."
+ WHITESPACE@74..75 " "
+ WORD@75..78 "and"
+ WHITESPACE@78..79 " "
+ WORD@79..84 "Flynn"
+ COMMA@84..85 ","
+ WHITESPACE@85..86 " "
+ WORD@86..88 "P."
+ WHITESPACE@88..89 " "
+ WORD@89..91 "J."
+ R_CURLY@91..92 "}"
+ COMMA@92..93 ","
+ WHITESPACE@93..98 "\n "
+ FIELD@98..134
+ NAME@98..103 "title"
+ WHITESPACE@103..104 " "
+ EQ@104..105 "="
+ WHITESPACE@105..106 " "
+ CURLY_GROUP@106..133
+ L_CURLY@106..107 "{"
+ WORD@107..111 "Data"
+ WHITESPACE@111..112 " "
+ WORD@112..123 "Clustering:"
+ WHITESPACE@123..124 " "
+ WORD@124..125 "A"
+ WHITESPACE@125..126 " "
+ WORD@126..132 "Review"
+ R_CURLY@132..133 "}"
+ COMMA@133..134 ","
+ WHITESPACE@134..139 "\n "
+ FIELD@139..153
+ NAME@139..143 "year"
+ WHITESPACE@143..144 " "
+ EQ@144..145 "="
+ WHITESPACE@145..146 " "
+ CURLY_GROUP@146..152
+ L_CURLY@146..147 "{"
+ INTEGER@147..151 "1999"
+ R_CURLY@151..152 "}"
+ COMMA@152..153 ","
+ WHITESPACE@153..158 "\n "
+ FIELD@158..184
+ NAME@158..168 "issue_date"
+ WHITESPACE@168..169 " "
+ EQ@169..170 "="
+ WHITESPACE@170..171 " "
+ CURLY_GROUP@171..183
+ L_CURLY@171..172 "{"
+ WORD@172..177 "Sept."
+ WHITESPACE@177..178 " "
+ INTEGER@178..182 "1999"
+ R_CURLY@182..183 "}"
+ COMMA@183..184 ","
+ WHITESPACE@184..189 "\n "
+ FIELD@189..239
+ NAME@189..198 "publisher"
+ WHITESPACE@198..199 " "
+ EQ@199..200 "="
+ WHITESPACE@200..201 " "
+ CURLY_GROUP@201..238
+ L_CURLY@201..202 "{"
+ WORD@202..213 "Association"
+ WHITESPACE@213..214 " "
+ WORD@214..217 "for"
+ WHITESPACE@217..218 " "
+ WORD@218..227 "Computing"
+ WHITESPACE@227..228 " "
+ WORD@228..237 "Machinery"
+ R_CURLY@237..238 "}"
+ COMMA@238..239 ","
+ WHITESPACE@239..244 "\n "
+ FIELD@244..274
+ NAME@244..251 "address"
+ WHITESPACE@251..252 " "
+ EQ@252..253 "="
+ WHITESPACE@253..254 " "
+ CURLY_GROUP@254..273
+ L_CURLY@254..255 "{"
+ WORD@255..258 "New"
+ WHITESPACE@258..259 " "
+ WORD@259..263 "York"
+ COMMA@263..264 ","
+ WHITESPACE@264..265 " "
+ WORD@265..267 "NY"
+ COMMA@267..268 ","
+ WHITESPACE@268..269 " "
+ WORD@269..272 "USA"
+ R_CURLY@272..273 "}"
+ COMMA@273..274 ","
+ WHITESPACE@274..279 "\n "
+ FIELD@279..293
+ NAME@279..285 "volume"
+ WHITESPACE@285..286 " "
+ EQ@286..287 "="
+ WHITESPACE@287..288 " "
+ CURLY_GROUP@288..292
+ L_CURLY@288..289 "{"
+ INTEGER@289..291 "31"
+ R_CURLY@291..292 "}"
+ COMMA@292..293 ","
+ WHITESPACE@293..298 "\n "
+ FIELD@298..311
+ NAME@298..304 "number"
+ WHITESPACE@304..305 " "
+ EQ@305..306 "="
+ WHITESPACE@306..307 " "
+ CURLY_GROUP@307..310
+ L_CURLY@307..308 "{"
+ INTEGER@308..309 "3"
+ R_CURLY@309..310 "}"
+ COMMA@310..311 ","
+ WHITESPACE@311..316 "\n "
+ FIELD@316..335
+ NAME@316..320 "issn"
+ WHITESPACE@320..321 " "
+ EQ@321..322 "="
+ WHITESPACE@322..323 " "
+ CURLY_GROUP@323..334
+ L_CURLY@323..324 "{"
+ WORD@324..333 "0360-0300"
+ R_CURLY@333..334 "}"
+ COMMA@334..335 ","
+ WHITESPACE@335..340 "\n "
+ FIELD@340..386
+ NAME@340..343 "url"
+ WHITESPACE@343..344 " "
+ EQ@344..345 "="
+ WHITESPACE@345..346 " "
+ CURLY_GROUP@346..385
+ L_CURLY@346..347 "{"
+ WORD@347..384 "https://doi.org/10.11 ..."
+ R_CURLY@384..385 "}"
+ COMMA@385..386 ","
+ WHITESPACE@386..391 "\n "
+ FIELD@391..421
+ NAME@391..394 "doi"
+ WHITESPACE@394..395 " "
+ EQ@395..396 "="
+ WHITESPACE@396..397 " "
+ CURLY_GROUP@397..420
+ L_CURLY@397..398 "{"
+ WORD@398..419 "10.1145/331499.331504"
+ R_CURLY@419..420 "}"
+ COMMA@420..421 ","
+ WHITESPACE@421..426 "\n "
+ FIELD@426..456
+ NAME@426..433 "journal"
+ WHITESPACE@433..434 " "
+ EQ@434..435 "="
+ WHITESPACE@435..436 " "
+ CURLY_GROUP@436..455
+ L_CURLY@436..437 "{"
+ WORD@437..440 "ACM"
+ WHITESPACE@440..441 " "
+ WORD@441..448 "Comput."
+ WHITESPACE@448..449 " "
+ WORD@449..454 "Surv."
+ R_CURLY@454..455 "}"
+ COMMA@455..456 ","
+ WHITESPACE@456..461 "\n "
+ FIELD@461..475
+ NAME@461..466 "month"
+ WHITESPACE@466..467 " "
+ EQ@467..468 "="
+ WHITESPACE@468..469 " "
+ CURLY_GROUP@469..474
+ L_CURLY@469..470 "{"
+ WORD@470..473 "sep"
+ R_CURLY@473..474 "}"
+ COMMA@474..475 ","
+ WHITESPACE@475..480 "\n "
+ FIELD@480..498
+ NAME@480..485 "pages"
+ WHITESPACE@485..486 " "
+ EQ@486..487 "="
+ WHITESPACE@487..488 " "
+ CURLY_GROUP@488..497
+ L_CURLY@488..489 "{"
+ WORD@489..496 "264-323"
+ R_CURLY@496..497 "}"
+ COMMA@497..498 ","
+ WHITESPACE@498..503 "\n "
+ FIELD@503..519
+ NAME@503..511 "numpages"
+ WHITESPACE@511..512 " "
+ EQ@512..513 "="
+ WHITESPACE@513..514 " "
+ CURLY_GROUP@514..518
+ L_CURLY@514..515 "{"
+ INTEGER@515..517 "60"
+ R_CURLY@517..518 "}"
+ COMMA@518..519 ","
+ WHITESPACE@519..524 "\n "
+ FIELD@524..673
+ NAME@524..532 "keywords"
+ WHITESPACE@532..533 " "
+ EQ@533..534 "="
+ WHITESPACE@534..535 " "
+ CURLY_GROUP@535..672
+ L_CURLY@535..536 "{"
+ WORD@536..547 "incremental"
+ WHITESPACE@547..548 " "
+ WORD@548..558 "clustering"
+ COMMA@558..559 ","
+ WHITESPACE@559..560 " "
+ WORD@560..570 "clustering"
+ WHITESPACE@570..571 " "
+ WORD@571..583 "applications"
+ COMMA@583..584 ","
+ WHITESPACE@584..585 " "
+ WORD@585..596 "exploratory"
+ WHITESPACE@596..597 " "
+ WORD@597..601 "data"
+ WHITESPACE@601..602 " "
+ WORD@602..610 "analysis"
+ COMMA@610..611 ","
+ WHITESPACE@611..612 " "
+ WORD@612..619 "cluster"
+ WHITESPACE@619..620 " "
+ WORD@620..628 "analysis"
+ COMMA@628..629 ","
+ WHITESPACE@629..630 " "
+ WORD@630..640 "similarity"
+ WHITESPACE@640..641 " "
+ WORD@641..648 "indices"
+ COMMA@648..649 ","
+ WHITESPACE@649..650 " "
+ WORD@650..662 "unsupervised"
+ WHITESPACE@662..663 " "
+ WORD@663..671 "learning"
+ R_CURLY@671..672 "}"
+ WHITESPACE@672..673 "\n"
+ R_DELIM@673..674 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__kastenholz_2006.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__kastenholz_2006.txt.snap
new file mode 100644
index 0000000000..8d67f95096
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__kastenholz_2006.txt.snap
@@ -0,0 +1,302 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/kastenholz_2006.txt
+---
+ROOT@0..898
+ STRING@0..35
+ TYPE@0..7 "@string"
+ L_DELIM@7..8 "{"
+ NAME@8..13 "jchph"
+ WHITESPACE@13..16 " "
+ EQ@16..17 "="
+ WHITESPACE@17..18 " "
+ CURLY_GROUP@18..34
+ L_CURLY@18..19 "{"
+ WORD@19..21 "J."
+ NBSP@21..22 "~"
+ WORD@22..27 "Chem."
+ WHITESPACE@27..28 " "
+ WORD@28..33 "Phys."
+ R_CURLY@33..34 "}"
+ R_DELIM@34..35 "}"
+ JUNK@35..37 "\n\n"
+ ENTRY@37..898
+ TYPE@37..45 "@article"
+ L_DELIM@45..46 "{"
+ NAME@46..56 "kastenholz"
+ COMMA@56..57 ","
+ WHITESPACE@57..62 "\n "
+ FIELD@62..130
+ NAME@62..68 "author"
+ WHITESPACE@68..75 " "
+ EQ@75..76 "="
+ WHITESPACE@76..77 " "
+ CURLY_GROUP@77..129
+ L_CURLY@77..78 "{"
+ WORD@78..88 "Kastenholz"
+ COMMA@88..89 ","
+ WHITESPACE@89..90 " "
+ WORD@90..92 "M."
+ WHITESPACE@92..93 " "
+ WORD@93..95 "A."
+ WHITESPACE@95..96 " "
+ WORD@96..99 "and"
+ WHITESPACE@99..100 " "
+ WORD@100..101 "H"
+ CURLY_GROUP@101..106
+ L_CURLY@101..102 "{"
+ ACCENT@102..105
+ ACCENT_NAME@102..104 "\\\""
+ WORD@104..105 "u"
+ R_CURLY@105..106 "}"
+ WORD@106..115 "nenberger"
+ COMMA@115..116 ","
+ WHITESPACE@116..117 " "
+ WORD@117..125 "Philippe"
+ WHITESPACE@125..126 " "
+ WORD@126..128 "H."
+ R_CURLY@128..129 "}"
+ COMMA@129..130 ","
+ WHITESPACE@130..135 "\n "
+ FIELD@135..275
+ NAME@135..140 "title"
+ WHITESPACE@140..148 " "
+ EQ@148..149 "="
+ WHITESPACE@149..150 " "
+ CURLY_GROUP@150..274
+ L_CURLY@150..151 "{"
+ WORD@151..162 "Computation"
+ WHITESPACE@162..163 " "
+ WORD@163..165 "of"
+ WHITESPACE@165..166 " "
+ WORD@166..177 "methodology"
+ COMMAND@177..184
+ COMMAND_NAME@177..184 "\\hyphen"
+ WHITESPACE@184..185 " "
+ WORD@185..196 "independent"
+ WHITESPACE@196..197 " "
+ WORD@197..202 "ionic"
+ WHITESPACE@202..203 " "
+ WORD@203..212 "solvation"
+ WHITESPACE@212..233 "\n "
+ WORD@233..237 "free"
+ WHITESPACE@237..238 " "
+ WORD@238..246 "energies"
+ WHITESPACE@246..247 " "
+ WORD@247..251 "from"
+ WHITESPACE@251..252 " "
+ WORD@252..261 "molecular"
+ WHITESPACE@261..262 " "
+ WORD@262..273 "simulations"
+ R_CURLY@273..274 "}"
+ COMMA@274..275 ","
+ WHITESPACE@275..280 "\n "
+ FIELD@280..301
+ NAME@280..292 "journaltitle"
+ WHITESPACE@292..293 " "
+ EQ@293..294 "="
+ WHITESPACE@294..295 " "
+ LITERAL@295..300
+ NAME@295..300 "jchph"
+ COMMA@300..301 ","
+ WHITESPACE@301..306 "\n "
+ FIELD@306..326
+ NAME@306..310 "date"
+ WHITESPACE@310..319 " "
+ EQ@319..320 "="
+ WHITESPACE@320..321 " "
+ LITERAL@321..325
+ INTEGER@321..325 "2006"
+ COMMA@325..326 ","
+ WHITESPACE@326..331 "\n "
+ FIELD@331..404
+ NAME@331..339 "subtitle"
+ WHITESPACE@339..344 " "
+ EQ@344..345 "="
+ WHITESPACE@345..346 " "
+ CURLY_GROUP@346..403
+ L_CURLY@346..347 "{"
+ CURLY_GROUP@347..350
+ L_CURLY@347..348 "{"
+ WORD@348..349 "I"
+ R_CURLY@349..350 "}"
+ WORD@350..351 "."
+ WHITESPACE@351..352 " "
+ CURLY_GROUP@352..357
+ L_CURLY@352..353 "{"
+ WORD@353..356 "The"
+ R_CURLY@356..357 "}"
+ WHITESPACE@357..358 " "
+ WORD@358..371 "electrostatic"
+ WHITESPACE@371..372 " "
+ WORD@372..381 "potential"
+ WHITESPACE@381..382 " "
+ WORD@382..384 "in"
+ WHITESPACE@384..385 " "
+ WORD@385..394 "molecular"
+ WHITESPACE@394..395 " "
+ WORD@395..402 "liquids"
+ R_CURLY@402..403 "}"
+ COMMA@403..404 ","
+ WHITESPACE@404..409 "\n "
+ FIELD@409..428
+ NAME@409..415 "volume"
+ WHITESPACE@415..422 " "
+ EQ@422..423 "="
+ WHITESPACE@423..424 " "
+ LITERAL@424..427
+ INTEGER@424..427 "124"
+ COMMA@427..428 ","
+ WHITESPACE@428..433 "\n "
+ FIELD@433..455
+ NAME@433..436 "eid"
+ WHITESPACE@436..446 " "
+ EQ@446..447 "="
+ WHITESPACE@447..448 " "
+ LITERAL@448..454
+ INTEGER@448..454 "124106"
+ COMMA@454..455 ","
+ WHITESPACE@455..460 "\n "
+ FIELD@460..495
+ NAME@460..463 "doi"
+ WHITESPACE@463..473 " "
+ EQ@473..474 "="
+ WHITESPACE@474..475 " "
+ CURLY_GROUP@475..494
+ L_CURLY@475..476 "{"
+ WORD@476..493 "10.1063/1.2172593"
+ R_CURLY@493..494 "}"
+ COMMA@494..495 ","
+ WHITESPACE@495..500 "\n "
+ FIELD@500..525
+ NAME@500..506 "langid"
+ WHITESPACE@506..513 " "
+ EQ@513..514 "="
+ WHITESPACE@514..515 " "
+ CURLY_GROUP@515..524
+ L_CURLY@515..516 "{"
+ WORD@516..523 "english"
+ R_CURLY@523..524 "}"
+ COMMA@524..525 ","
+ WHITESPACE@525..530 "\n "
+ FIELD@530..564
+ NAME@530..540 "langidopts"
+ WHITESPACE@540..543 " "
+ EQ@543..544 "="
+ WHITESPACE@544..545 " "
+ CURLY_GROUP@545..563
+ L_CURLY@545..546 "{"
+ WORD@546..562 "variant=american"
+ R_CURLY@562..563 "}"
+ COMMA@563..564 ","
+ WHITESPACE@564..569 "\n "
+ FIELD@569..631
+ NAME@569..579 "indextitle"
+ WHITESPACE@579..582 " "
+ EQ@582..583 "="
+ WHITESPACE@583..584 " "
+ CURLY_GROUP@584..630
+ L_CURLY@584..585 "{"
+ WORD@585..596 "Computation"
+ WHITESPACE@596..597 " "
+ WORD@597..599 "of"
+ WHITESPACE@599..600 " "
+ WORD@600..605 "ionic"
+ WHITESPACE@605..606 " "
+ WORD@606..615 "solvation"
+ WHITESPACE@615..616 " "
+ WORD@616..620 "free"
+ WHITESPACE@620..621 " "
+ WORD@621..629 "energies"
+ R_CURLY@629..630 "}"
+ COMMA@630..631 ","
+ WHITESPACE@631..636 "\n "
+ FIELD@636..896
+ NAME@636..646 "annotation"
+ WHITESPACE@646..649 " "
+ EQ@649..650 "="
+ WHITESPACE@650..651 " "
+ CURLY_GROUP@651..895
+ L_CURLY@651..652 "{"
+ WORD@652..654 "An"
+ WHITESPACE@654..655 " "
+ COMMAND@655..662
+ COMMAND_NAME@655..662 "\\texttt"
+ CURLY_GROUP@662..671
+ L_CURLY@662..663 "{"
+ WORD@663..670 "article"
+ R_CURLY@670..671 "}"
+ WHITESPACE@671..672 " "
+ WORD@672..677 "entry"
+ WHITESPACE@677..678 " "
+ WORD@678..682 "with"
+ WHITESPACE@682..683 " "
+ WORD@683..685 "an"
+ WHITESPACE@685..686 " "
+ COMMAND@686..693
+ COMMAND_NAME@686..693 "\\texttt"
+ CURLY_GROUP@693..698
+ L_CURLY@693..694 "{"
+ WORD@694..697 "eid"
+ R_CURLY@697..698 "}"
+ WHITESPACE@698..699 " "
+ WORD@699..702 "and"
+ WHITESPACE@702..703 " "
+ WORD@703..704 "a"
+ WHITESPACE@704..725 "\n "
+ COMMAND@725..732
+ COMMAND_NAME@725..732 "\\texttt"
+ CURLY_GROUP@732..737
+ L_CURLY@732..733 "{"
+ WORD@733..736 "doi"
+ R_CURLY@736..737 "}"
+ WHITESPACE@737..738 " "
+ WORD@738..744 "field."
+ WHITESPACE@744..745 " "
+ WORD@745..749 "Note"
+ WHITESPACE@749..750 " "
+ WORD@750..754 "that"
+ WHITESPACE@754..755 " "
+ WORD@755..758 "the"
+ WHITESPACE@758..759 " "
+ COMMAND@759..766
+ COMMAND_NAME@759..766 "\\textsc"
+ CURLY_GROUP@766..771
+ L_CURLY@766..767 "{"
+ WORD@767..770 "doi"
+ R_CURLY@770..771 "}"
+ WHITESPACE@771..772 " "
+ WORD@772..774 "is"
+ WHITESPACE@774..775 " "
+ WORD@775..786 "transformed"
+ WHITESPACE@786..807 "\n "
+ WORD@807..811 "into"
+ WHITESPACE@811..812 " "
+ WORD@812..813 "a"
+ WHITESPACE@813..814 " "
+ WORD@814..823 "clickable"
+ WHITESPACE@823..824 " "
+ WORD@824..828 "link"
+ WHITESPACE@828..829 " "
+ WORD@829..831 "if"
+ WHITESPACE@831..832 " "
+ COMMAND@832..839
+ COMMAND_NAME@832..839 "\\texttt"
+ CURLY_GROUP@839..849
+ L_CURLY@839..840 "{"
+ WORD@840..848 "hyperref"
+ R_CURLY@848..849 "}"
+ WHITESPACE@849..850 " "
+ WORD@850..857 "support"
+ WHITESPACE@857..858 " "
+ WORD@858..861 "has"
+ WHITESPACE@861..862 " "
+ WORD@862..866 "been"
+ WHITESPACE@866..887 "\n "
+ WORD@887..894 "enabled"
+ R_CURLY@894..895 "}"
+ COMMA@895..896 ","
+ WHITESPACE@896..897 "\n"
+ R_DELIM@897..898 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__knuth_1984.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__knuth_1984.txt.snap
new file mode 100644
index 0000000000..2be9233f95
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__knuth_1984.txt.snap
@@ -0,0 +1,320 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/knuth_1984.txt
+---
+ROOT@0..993
+ ENTRY@0..993
+ TYPE@0..5 "@book"
+ L_DELIM@5..6 "{"
+ NAME@6..16 "knuth:ct:a"
+ COMMA@16..17 ","
+ WHITESPACE@17..22 "\n "
+ FIELD@22..56
+ NAME@22..28 "author"
+ WHITESPACE@28..35 " "
+ EQ@35..36 "="
+ WHITESPACE@36..37 " "
+ CURLY_GROUP@37..55
+ L_CURLY@37..38 "{"
+ WORD@38..43 "Knuth"
+ COMMA@43..44 ","
+ WHITESPACE@44..45 " "
+ WORD@45..51 "Donald"
+ WHITESPACE@51..52 " "
+ WORD@52..54 "E."
+ R_CURLY@54..55 "}"
+ COMMA@55..56 ","
+ WHITESPACE@56..61 "\n "
+ FIELD@61..94
+ NAME@61..66 "title"
+ WHITESPACE@66..74 " "
+ EQ@74..75 "="
+ WHITESPACE@75..76 " "
+ CURLY_GROUP@76..93
+ L_CURLY@76..77 "{"
+ WORD@77..80 "The"
+ WHITESPACE@80..81 " "
+ CURLY_GROUP@81..92
+ L_CURLY@81..82 "{"
+ COMMAND@82..86
+ COMMAND_NAME@82..86 "\\TeX"
+ WHITESPACE@86..87 " "
+ WORD@87..91 "book"
+ R_CURLY@91..92 "}"
+ R_CURLY@92..93 "}"
+ COMMA@93..94 ","
+ WHITESPACE@94..99 "\n "
+ FIELD@99..119
+ NAME@99..103 "date"
+ WHITESPACE@103..112 " "
+ EQ@112..113 "="
+ WHITESPACE@113..114 " "
+ LITERAL@114..118
+ INTEGER@114..118 "1984"
+ COMMA@118..119 ","
+ WHITESPACE@119..124 "\n "
+ FIELD@124..166
+ NAME@124..133 "maintitle"
+ WHITESPACE@133..137 " "
+ EQ@137..138 "="
+ WHITESPACE@138..139 " "
+ CURLY_GROUP@139..165
+ L_CURLY@139..140 "{"
+ WORD@140..149 "Computers"
+ WHITESPACE@149..150 " "
+ COMMAND@150..152
+ COMMAND_NAME@150..152 "\\&"
+ WHITESPACE@152..153 " "
+ WORD@153..164 "Typesetting"
+ R_CURLY@164..165 "}"
+ COMMA@165..166 ","
+ WHITESPACE@166..171 "\n "
+ FIELD@171..190
+ NAME@171..177 "volume"
+ WHITESPACE@177..184 " "
+ EQ@184..185 "="
+ WHITESPACE@185..186 " "
+ CURLY_GROUP@186..189
+ L_CURLY@186..187 "{"
+ WORD@187..188 "A"
+ R_CURLY@188..189 "}"
+ COMMA@189..190 ","
+ WHITESPACE@190..195 "\n "
+ FIELD@195..227
+ NAME@195..204 "publisher"
+ WHITESPACE@204..208 " "
+ EQ@208..209 "="
+ WHITESPACE@209..210 " "
+ CURLY_GROUP@210..226
+ L_CURLY@210..211 "{"
+ WORD@211..225 "Addison-Wesley"
+ R_CURLY@225..226 "}"
+ COMMA@226..227 ","
+ WHITESPACE@227..232 "\n "
+ FIELD@232..264
+ NAME@232..240 "location"
+ WHITESPACE@240..245 " "
+ EQ@245..246 "="
+ WHITESPACE@246..247 " "
+ CURLY_GROUP@247..263
+ L_CURLY@247..248 "{"
+ WORD@248..255 "Reading"
+ COMMA@255..256 ","
+ WHITESPACE@256..257 " "
+ WORD@257..262 "Mass."
+ R_CURLY@262..263 "}"
+ COMMA@263..264 ","
+ WHITESPACE@264..269 "\n "
+ FIELD@269..294
+ NAME@269..275 "langid"
+ WHITESPACE@275..282 " "
+ EQ@282..283 "="
+ WHITESPACE@283..284 " "
+ CURLY_GROUP@284..293
+ L_CURLY@284..285 "{"
+ WORD@285..292 "english"
+ R_CURLY@292..293 "}"
+ COMMA@293..294 ","
+ WHITESPACE@294..299 "\n "
+ FIELD@299..333
+ NAME@299..309 "langidopts"
+ WHITESPACE@309..312 " "
+ EQ@312..313 "="
+ WHITESPACE@313..314 " "
+ CURLY_GROUP@314..332
+ L_CURLY@314..315 "{"
+ WORD@315..331 "variant=american"
+ R_CURLY@331..332 "}"
+ COMMA@332..333 ","
+ WHITESPACE@333..338 "\n "
+ FIELD@338..381
+ NAME@338..347 "sorttitle"
+ WHITESPACE@347..351 " "
+ EQ@351..352 "="
+ WHITESPACE@352..353 " "
+ CURLY_GROUP@353..380
+ L_CURLY@353..354 "{"
+ WORD@354..363 "Computers"
+ WHITESPACE@363..364 " "
+ WORD@364..365 "&"
+ WHITESPACE@365..366 " "
+ WORD@366..377 "Typesetting"
+ WHITESPACE@377..378 " "
+ WORD@378..379 "A"
+ R_CURLY@379..380 "}"
+ COMMA@380..381 ","
+ WHITESPACE@381..386 "\n "
+ FIELD@386..416
+ NAME@386..400 "indexsorttitle"
+ EQ@400..401 "="
+ WHITESPACE@401..402 " "
+ CURLY_GROUP@402..415
+ L_CURLY@402..403 "{"
+ WORD@403..406 "The"
+ WHITESPACE@406..407 " "
+ WORD@407..414 "TeXbook"
+ R_CURLY@414..415 "}"
+ COMMA@415..416 ","
+ WHITESPACE@416..421 "\n "
+ FIELD@421..461
+ NAME@421..431 "indextitle"
+ WHITESPACE@431..434 " "
+ EQ@434..435 "="
+ WHITESPACE@435..436 " "
+ CURLY_GROUP@436..460
+ L_CURLY@436..437 "{"
+ COMMAND@437..445
+ COMMAND_NAME@437..445 "\\protect"
+ COMMAND@445..449
+ COMMAND_NAME@445..449 "\\TeX"
+ WHITESPACE@449..450 " "
+ WORD@450..454 "book"
+ COMMA@454..455 ","
+ WHITESPACE@455..456 " "
+ WORD@456..459 "The"
+ R_CURLY@459..460 "}"
+ COMMA@460..461 ","
+ WHITESPACE@461..466 "\n "
+ FIELD@466..493
+ NAME@466..476 "shorttitle"
+ WHITESPACE@476..479 " "
+ EQ@479..480 "="
+ WHITESPACE@480..481 " "
+ CURLY_GROUP@481..492
+ L_CURLY@481..482 "{"
+ COMMAND@482..486
+ COMMAND_NAME@482..486 "\\TeX"
+ WHITESPACE@486..487 " "
+ WORD@487..491 "book"
+ R_CURLY@491..492 "}"
+ COMMA@492..493 ","
+ WHITESPACE@493..498 "\n "
+ FIELD@498..991
+ NAME@498..508 "annotation"
+ WHITESPACE@508..511 " "
+ EQ@511..512 "="
+ WHITESPACE@512..513 " "
+ CURLY_GROUP@513..990
+ L_CURLY@513..514 "{"
+ WORD@514..517 "The"
+ WHITESPACE@517..518 " "
+ WORD@518..523 "first"
+ WHITESPACE@523..524 " "
+ WORD@524..530 "volume"
+ WHITESPACE@530..531 " "
+ WORD@531..533 "of"
+ WHITESPACE@533..534 " "
+ WORD@534..535 "a"
+ WHITESPACE@535..536 " "
+ WORD@536..547 "five-volume"
+ WHITESPACE@547..548 " "
+ WORD@548..553 "book."
+ WHITESPACE@553..554 " "
+ WORD@554..558 "Note"
+ WHITESPACE@558..559 " "
+ WORD@559..562 "the"
+ WHITESPACE@562..583 "\n "
+ COMMAND@583..590
+ COMMAND_NAME@583..590 "\\texttt"
+ CURLY_GROUP@590..601
+ L_CURLY@590..591 "{"
+ WORD@591..600 "sorttitle"
+ R_CURLY@600..601 "}"
+ WHITESPACE@601..602 " "
+ WORD@602..608 "field."
+ WHITESPACE@608..609 " "
+ WORD@609..611 "We"
+ WHITESPACE@611..612 " "
+ WORD@612..616 "want"
+ WHITESPACE@616..617 " "
+ WORD@617..621 "this"
+ WHITESPACE@621..642 "\n "
+ WORD@642..648 "volume"
+ WHITESPACE@648..649 " "
+ WORD@649..651 "to"
+ WHITESPACE@651..652 " "
+ WORD@652..654 "be"
+ WHITESPACE@654..655 " "
+ WORD@655..661 "listed"
+ WHITESPACE@661..662 " "
+ WORD@662..667 "after"
+ WHITESPACE@667..668 " "
+ WORD@668..671 "the"
+ WHITESPACE@671..672 " "
+ WORD@672..677 "entry"
+ WHITESPACE@677..678 " "
+ WORD@678..687 "referring"
+ WHITESPACE@687..688 " "
+ WORD@688..690 "to"
+ WHITESPACE@690..691 " "
+ WORD@691..694 "the"
+ WHITESPACE@694..695 " "
+ WORD@695..701 "entire"
+ WHITESPACE@701..722 "\n "
+ WORD@722..733 "five-volume"
+ WHITESPACE@733..734 " "
+ WORD@734..738 "set."
+ WHITESPACE@738..739 " "
+ WORD@739..743 "Also"
+ WHITESPACE@743..744 " "
+ WORD@744..748 "note"
+ WHITESPACE@748..749 " "
+ WORD@749..752 "the"
+ WHITESPACE@752..753 " "
+ COMMAND@753..760
+ COMMAND_NAME@753..760 "\\texttt"
+ CURLY_GROUP@760..772
+ L_CURLY@760..761 "{"
+ WORD@761..771 "indextitle"
+ R_CURLY@771..772 "}"
+ WHITESPACE@772..773 " "
+ WORD@773..776 "and"
+ WHITESPACE@776..797 "\n "
+ COMMAND@797..804
+ COMMAND_NAME@797..804 "\\texttt"
+ CURLY_GROUP@804..820
+ L_CURLY@804..805 "{"
+ WORD@805..819 "indexsorttitle"
+ R_CURLY@819..820 "}"
+ WHITESPACE@820..821 " "
+ WORD@821..828 "fields."
+ WHITESPACE@828..829 " "
+ WORD@829..837 "Indexing"
+ WHITESPACE@837..838 " "
+ WORD@838..846 "packages"
+ WHITESPACE@846..847 " "
+ WORD@847..851 "that"
+ WHITESPACE@851..852 " "
+ WORD@852..857 "don't"
+ WHITESPACE@857..878 "\n "
+ WORD@878..886 "generate"
+ WHITESPACE@886..887 " "
+ WORD@887..893 "robust"
+ WHITESPACE@893..894 " "
+ WORD@894..899 "index"
+ WHITESPACE@899..900 " "
+ WORD@900..907 "entries"
+ WHITESPACE@907..908 " "
+ WORD@908..915 "require"
+ WHITESPACE@915..916 " "
+ WORD@916..920 "some"
+ WHITESPACE@920..921 " "
+ WORD@921..928 "control"
+ WHITESPACE@928..929 " "
+ WORD@929..938 "sequences"
+ WHITESPACE@938..959 "\n "
+ WORD@959..961 "to"
+ WHITESPACE@961..962 " "
+ WORD@962..964 "be"
+ WHITESPACE@964..965 " "
+ WORD@965..974 "protected"
+ WHITESPACE@974..975 " "
+ WORD@975..979 "from"
+ WHITESPACE@979..980 " "
+ WORD@980..989 "expansion"
+ R_CURLY@989..990 "}"
+ COMMA@990..991 ","
+ WHITESPACE@991..992 "\n"
+ R_DELIM@992..993 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__matuz_1990.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__matuz_1990.txt.snap
new file mode 100644
index 0000000000..1e2b67f36c
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__matuz_1990.txt.snap
@@ -0,0 +1,176 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/matuz_1990.txt
+---
+ROOT@0..517
+ ENTRY@0..517
+ TYPE@0..11 "@collection"
+ L_DELIM@11..12 "{"
+ NAME@12..23 "matuz:doody"
+ COMMA@23..24 ","
+ WHITESPACE@24..29 "\n "
+ FIELD@29..59
+ NAME@29..35 "editor"
+ WHITESPACE@35..42 " "
+ EQ@42..43 "="
+ WHITESPACE@43..44 " "
+ CURLY_GROUP@44..58
+ L_CURLY@44..45 "{"
+ WORD@45..50 "Matuz"
+ COMMA@50..51 ","
+ WHITESPACE@51..52 " "
+ WORD@52..57 "Roger"
+ R_CURLY@57..58 "}"
+ COMMA@58..59 ","
+ WHITESPACE@59..64 "\n "
+ FIELD@64..113
+ NAME@64..69 "title"
+ WHITESPACE@69..77 " "
+ EQ@77..78 "="
+ WHITESPACE@78..79 " "
+ CURLY_GROUP@79..112
+ L_CURLY@79..80 "{"
+ WORD@80..92 "Contemporary"
+ WHITESPACE@92..93 " "
+ WORD@93..101 "Literary"
+ WHITESPACE@101..102 " "
+ WORD@102..111 "Criticism"
+ R_CURLY@111..112 "}"
+ COMMA@112..113 ","
+ WHITESPACE@113..118 "\n "
+ FIELD@118..138
+ NAME@118..122 "year"
+ WHITESPACE@122..131 " "
+ EQ@131..132 "="
+ WHITESPACE@132..133 " "
+ LITERAL@133..137
+ INTEGER@133..137 "1990"
+ COMMA@137..138 ","
+ WHITESPACE@138..143 "\n "
+ FIELD@143..161
+ NAME@143..149 "volume"
+ WHITESPACE@149..156 " "
+ EQ@156..157 "="
+ WHITESPACE@157..158 " "
+ LITERAL@158..160
+ INTEGER@158..160 "61"
+ COMMA@160..161 ","
+ WHITESPACE@161..166 "\n "
+ FIELD@166..188
+ NAME@166..175 "publisher"
+ WHITESPACE@175..179 " "
+ EQ@179..180 "="
+ WHITESPACE@180..181 " "
+ CURLY_GROUP@181..187
+ L_CURLY@181..182 "{"
+ WORD@182..186 "Gale"
+ R_CURLY@186..187 "}"
+ COMMA@187..188 ","
+ WHITESPACE@188..193 "\n "
+ FIELD@193..218
+ NAME@193..201 "location"
+ WHITESPACE@201..206 " "
+ EQ@206..207 "="
+ WHITESPACE@207..208 " "
+ CURLY_GROUP@208..217
+ L_CURLY@208..209 "{"
+ WORD@209..216 "Detroit"
+ R_CURLY@216..217 "}"
+ COMMA@217..218 ","
+ WHITESPACE@218..223 "\n "
+ FIELD@223..248
+ NAME@223..228 "pages"
+ WHITESPACE@228..236 " "
+ EQ@236..237 "="
+ WHITESPACE@237..238 " "
+ CURLY_GROUP@238..247
+ L_CURLY@238..239 "{"
+ WORD@239..246 "204-208"
+ R_CURLY@246..247 "}"
+ COMMA@247..248 ","
+ WHITESPACE@248..253 "\n "
+ FIELD@253..278
+ NAME@253..259 "langid"
+ WHITESPACE@259..266 " "
+ EQ@266..267 "="
+ WHITESPACE@267..268 " "
+ CURLY_GROUP@268..277
+ L_CURLY@268..269 "{"
+ WORD@269..276 "english"
+ R_CURLY@276..277 "}"
+ COMMA@277..278 ","
+ WHITESPACE@278..283 "\n "
+ FIELD@283..317
+ NAME@283..293 "langidopts"
+ WHITESPACE@293..296 " "
+ EQ@296..297 "="
+ WHITESPACE@297..298 " "
+ CURLY_GROUP@298..316
+ L_CURLY@298..299 "{"
+ WORD@299..315 "variant=american"
+ R_CURLY@315..316 "}"
+ COMMA@316..317 ","
+ WHITESPACE@317..322 "\n "
+ FIELD@322..515
+ NAME@322..332 "annotation"
+ WHITESPACE@332..335 " "
+ EQ@335..336 "="
+ WHITESPACE@336..337 " "
+ CURLY_GROUP@337..514
+ L_CURLY@337..338 "{"
+ WORD@338..339 "A"
+ WHITESPACE@339..340 " "
+ COMMAND@340..347
+ COMMAND_NAME@340..347 "\\texttt"
+ CURLY_GROUP@347..359
+ L_CURLY@347..348 "{"
+ WORD@348..358 "collection"
+ R_CURLY@358..359 "}"
+ WHITESPACE@359..360 " "
+ WORD@360..365 "entry"
+ WHITESPACE@365..366 " "
+ WORD@366..375 "providing"
+ WHITESPACE@375..376 " "
+ WORD@376..379 "the"
+ WHITESPACE@379..380 " "
+ WORD@380..387 "excerpt"
+ WHITESPACE@387..388 " "
+ WORD@388..399 "information"
+ WHITESPACE@399..420 "\n "
+ WORD@420..423 "for"
+ WHITESPACE@423..424 " "
+ WORD@424..427 "the"
+ WHITESPACE@427..428 " "
+ COMMAND@428..435
+ COMMAND_NAME@428..435 "\\texttt"
+ CURLY_GROUP@435..442
+ L_CURLY@435..436 "{"
+ WORD@436..441 "doody"
+ R_CURLY@441..442 "}"
+ WHITESPACE@442..443 " "
+ WORD@443..449 "entry."
+ WHITESPACE@449..450 " "
+ WORD@450..454 "Note"
+ WHITESPACE@454..455 " "
+ WORD@455..458 "the"
+ WHITESPACE@458..459 " "
+ WORD@459..465 "format"
+ WHITESPACE@465..466 " "
+ WORD@466..468 "of"
+ WHITESPACE@468..469 " "
+ WORD@469..472 "the"
+ WHITESPACE@472..493 "\n "
+ COMMAND@493..500
+ COMMAND_NAME@493..500 "\\texttt"
+ CURLY_GROUP@500..507
+ L_CURLY@500..501 "{"
+ WORD@501..506 "pages"
+ R_CURLY@506..507 "}"
+ WHITESPACE@507..508 " "
+ WORD@508..513 "field"
+ R_CURLY@513..514 "}"
+ COMMA@514..515 ","
+ WHITESPACE@515..516 "\n"
+ R_DELIM@516..517 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__nietzsche_1998.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__nietzsche_1998.txt.snap
new file mode 100644
index 0000000000..40d1f230af
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__nietzsche_1998.txt.snap
@@ -0,0 +1,368 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/nietzsche_1998.txt
+---
+ROOT@0..1112
+ STRING@0..49
+ TYPE@0..7 "@string"
+ L_DELIM@7..8 "{"
+ NAME@8..11 "dtv"
+ WHITESPACE@11..16 " "
+ EQ@16..17 "="
+ WHITESPACE@17..18 " "
+ CURLY_GROUP@18..48
+ L_CURLY@18..19 "{"
+ WORD@19..28 "Deutscher"
+ WHITESPACE@28..29 " "
+ WORD@29..47 "Taschenbuch-Verlag"
+ R_CURLY@47..48 "}"
+ R_DELIM@48..49 "}"
+ JUNK@49..51 "\n\n"
+ ENTRY@51..1112
+ TYPE@51..58 "@mvbook"
+ L_DELIM@58..59 "{"
+ NAME@59..72 "nietzsche:ksa"
+ COMMA@72..73 ","
+ WHITESPACE@73..78 "\n "
+ FIELD@78..116
+ NAME@78..84 "author"
+ WHITESPACE@84..91 " "
+ EQ@91..92 "="
+ WHITESPACE@92..93 " "
+ CURLY_GROUP@93..115
+ L_CURLY@93..94 "{"
+ WORD@94..103 "Nietzsche"
+ COMMA@103..104 ","
+ WHITESPACE@104..105 " "
+ WORD@105..114 "Friedrich"
+ R_CURLY@114..115 "}"
+ COMMA@115..116 ","
+ WHITESPACE@116..121 "\n "
+ FIELD@121..158
+ NAME@121..126 "title"
+ WHITESPACE@126..134 " "
+ EQ@134..135 "="
+ WHITESPACE@135..136 " "
+ CURLY_GROUP@136..157
+ L_CURLY@136..137 "{"
+ WORD@137..138 "S"
+ CURLY_GROUP@138..143
+ L_CURLY@138..139 "{"
+ ACCENT@139..142
+ ACCENT_NAME@139..141 "\\\""
+ WORD@141..142 "a"
+ R_CURLY@142..143 "}"
+ WORD@143..150 "mtliche"
+ WHITESPACE@150..151 " "
+ WORD@151..156 "Werke"
+ R_CURLY@156..157 "}"
+ COMMA@157..158 ","
+ WHITESPACE@158..163 "\n "
+ FIELD@163..183
+ NAME@163..167 "date"
+ WHITESPACE@167..176 " "
+ EQ@176..177 "="
+ WHITESPACE@177..178 " "
+ LITERAL@178..182
+ INTEGER@178..182 "1988"
+ COMMA@182..183 ","
+ WHITESPACE@183..188 "\n "
+ FIELD@188..243
+ NAME@188..194 "editor"
+ WHITESPACE@194..201 " "
+ EQ@201..202 "="
+ WHITESPACE@202..203 " "
+ CURLY_GROUP@203..242
+ L_CURLY@203..204 "{"
+ WORD@204..209 "Colli"
+ COMMA@209..210 ","
+ WHITESPACE@210..211 " "
+ WORD@211..218 "Giorgio"
+ WHITESPACE@218..219 " "
+ WORD@219..222 "and"
+ WHITESPACE@222..223 " "
+ WORD@223..232 "Montinari"
+ COMMA@232..233 ","
+ WHITESPACE@233..234 " "
+ WORD@234..241 "Mazzino"
+ R_CURLY@241..242 "}"
+ COMMA@242..243 ","
+ WHITESPACE@243..248 "\n "
+ FIELD@248..265
+ NAME@248..255 "edition"
+ WHITESPACE@255..261 " "
+ EQ@261..262 "="
+ WHITESPACE@262..263 " "
+ LITERAL@263..264
+ INTEGER@263..264 "2"
+ COMMA@264..265 ","
+ WHITESPACE@265..270 "\n "
+ FIELD@270..288
+ NAME@270..277 "volumes"
+ WHITESPACE@277..283 " "
+ EQ@283..284 "="
+ WHITESPACE@284..285 " "
+ LITERAL@285..287
+ INTEGER@285..287 "15"
+ COMMA@287..288 ","
+ WHITESPACE@288..293 "\n "
+ FIELD@293..339
+ NAME@293..302 "publisher"
+ WHITESPACE@302..306 " "
+ EQ@306..307 "="
+ WHITESPACE@307..308 " "
+ JOIN@308..338
+ LITERAL@308..311
+ NAME@308..311 "dtv"
+ WHITESPACE@311..312 " "
+ POUND@312..313 "#"
+ WHITESPACE@313..314 " "
+ CURLY_GROUP@314..338
+ L_CURLY@314..315 "{"
+ WHITESPACE@315..316 " "
+ WORD@316..319 "and"
+ WHITESPACE@319..320 " "
+ WORD@320..326 "Walter"
+ WHITESPACE@326..327 " "
+ WORD@327..329 "de"
+ WHITESPACE@329..330 " "
+ WORD@330..337 "Gruyter"
+ R_CURLY@337..338 "}"
+ COMMA@338..339 ","
+ WHITESPACE@339..344 "\n "
+ FIELD@344..397
+ NAME@344..352 "location"
+ WHITESPACE@352..357 " "
+ EQ@357..358 "="
+ WHITESPACE@358..359 " "
+ CURLY_GROUP@359..396
+ L_CURLY@359..360 "{"
+ WORD@360..361 "M"
+ CURLY_GROUP@361..366
+ L_CURLY@361..362 "{"
+ ACCENT@362..365
+ ACCENT_NAME@362..364 "\\\""
+ WORD@364..365 "u"
+ R_CURLY@365..366 "}"
+ WORD@366..371 "nchen"
+ WHITESPACE@371..372 " "
+ WORD@372..375 "and"
+ WHITESPACE@375..376 " "
+ WORD@376..382 "Berlin"
+ WHITESPACE@382..383 " "
+ WORD@383..386 "and"
+ WHITESPACE@386..387 " "
+ WORD@387..390 "New"
+ WHITESPACE@390..391 " "
+ WORD@391..395 "York"
+ R_CURLY@395..396 "}"
+ COMMA@396..397 ","
+ WHITESPACE@397..402 "\n "
+ FIELD@402..426
+ NAME@402..408 "langid"
+ WHITESPACE@408..415 " "
+ EQ@415..416 "="
+ WHITESPACE@416..417 " "
+ CURLY_GROUP@417..425
+ L_CURLY@417..418 "{"
+ WORD@418..424 "german"
+ R_CURLY@424..425 "}"
+ COMMA@425..426 ","
+ WHITESPACE@426..431 "\n "
+ FIELD@431..461
+ NAME@431..440 "sorttitle"
+ WHITESPACE@440..444 " "
+ EQ@444..445 "="
+ WHITESPACE@445..446 " "
+ CURLY_GROUP@446..460
+ L_CURLY@446..447 "{"
+ WORD@447..459 "Werke-00-000"
+ R_CURLY@459..460 "}"
+ COMMA@460..461 ","
+ WHITESPACE@461..466 "\n "
+ FIELD@466..500
+ NAME@466..480 "indexsorttitle"
+ EQ@480..481 "="
+ WHITESPACE@481..482 " "
+ CURLY_GROUP@482..499
+ L_CURLY@482..483 "{"
+ WORD@483..492 "Samtliche"
+ WHITESPACE@492..493 " "
+ WORD@493..498 "Werke"
+ R_CURLY@498..499 "}"
+ COMMA@499..500 ","
+ WHITESPACE@500..505 "\n "
+ FIELD@505..547
+ NAME@505..513 "subtitle"
+ WHITESPACE@513..518 " "
+ EQ@518..519 "="
+ WHITESPACE@519..520 " "
+ CURLY_GROUP@520..546
+ L_CURLY@520..521 "{"
+ WORD@521..530 "Kritische"
+ WHITESPACE@530..531 " "
+ WORD@531..545 "Studienausgabe"
+ R_CURLY@545..546 "}"
+ COMMA@546..547 ","
+ WHITESPACE@547..552 "\n "
+ FIELD@552..1110
+ NAME@552..562 "annotation"
+ WHITESPACE@562..565 " "
+ EQ@565..566 "="
+ WHITESPACE@566..567 " "
+ CURLY_GROUP@567..1109
+ L_CURLY@567..568 "{"
+ WORD@568..571 "The"
+ WHITESPACE@571..572 " "
+ WORD@572..580 "critical"
+ WHITESPACE@580..581 " "
+ WORD@581..588 "edition"
+ WHITESPACE@588..589 " "
+ WORD@589..591 "of"
+ WHITESPACE@591..592 " "
+ WORD@592..603 "Nietzsche's"
+ WHITESPACE@603..604 " "
+ WORD@604..610 "works."
+ WHITESPACE@610..611 " "
+ WORD@611..615 "This"
+ WHITESPACE@615..616 " "
+ WORD@616..618 "is"
+ WHITESPACE@618..619 " "
+ WORD@619..620 "a"
+ WHITESPACE@620..641 "\n "
+ COMMAND@641..648
+ COMMAND_NAME@641..648 "\\texttt"
+ CURLY_GROUP@648..656
+ L_CURLY@648..649 "{"
+ WORD@649..655 "mvbook"
+ R_CURLY@655..656 "}"
+ WHITESPACE@656..657 " "
+ WORD@657..662 "entry"
+ WHITESPACE@662..663 " "
+ WORD@663..672 "referring"
+ WHITESPACE@672..673 " "
+ WORD@673..675 "to"
+ WHITESPACE@675..676 " "
+ WORD@676..677 "a"
+ WHITESPACE@677..678 " "
+ WORD@678..687 "15-volume"
+ WHITESPACE@687..688 " "
+ WORD@688..692 "work"
+ WHITESPACE@692..693 " "
+ WORD@693..695 "as"
+ WHITESPACE@695..696 " "
+ WORD@696..697 "a"
+ WHITESPACE@697..718 "\n "
+ WORD@718..724 "whole."
+ WHITESPACE@724..725 " "
+ WORD@725..729 "Note"
+ WHITESPACE@729..730 " "
+ WORD@730..733 "the"
+ WHITESPACE@733..734 " "
+ COMMAND@734..741
+ COMMAND_NAME@734..741 "\\texttt"
+ CURLY_GROUP@741..750
+ L_CURLY@741..742 "{"
+ WORD@742..749 "volumes"
+ R_CURLY@749..750 "}"
+ WHITESPACE@750..751 " "
+ WORD@751..756 "field"
+ WHITESPACE@756..757 " "
+ WORD@757..760 "and"
+ WHITESPACE@760..761 " "
+ WORD@761..764 "the"
+ WHITESPACE@764..765 " "
+ WORD@765..771 "format"
+ WHITESPACE@771..772 " "
+ WORD@772..774 "of"
+ WHITESPACE@774..775 " "
+ WORD@775..778 "the"
+ WHITESPACE@778..799 "\n "
+ COMMAND@799..806
+ COMMAND_NAME@799..806 "\\texttt"
+ CURLY_GROUP@806..817
+ L_CURLY@806..807 "{"
+ WORD@807..816 "publisher"
+ R_CURLY@816..817 "}"
+ WHITESPACE@817..818 " "
+ WORD@818..821 "and"
+ WHITESPACE@821..822 " "
+ COMMAND@822..829
+ COMMAND_NAME@822..829 "\\texttt"
+ CURLY_GROUP@829..839
+ L_CURLY@829..830 "{"
+ WORD@830..838 "location"
+ R_CURLY@838..839 "}"
+ WHITESPACE@839..840 " "
+ WORD@840..846 "fields"
+ WHITESPACE@846..847 " "
+ WORD@847..849 "in"
+ WHITESPACE@849..850 " "
+ WORD@850..853 "the"
+ WHITESPACE@853..874 "\n "
+ WORD@874..882 "database"
+ WHITESPACE@882..883 " "
+ WORD@883..888 "file."
+ WHITESPACE@888..889 " "
+ WORD@889..893 "Also"
+ WHITESPACE@893..894 " "
+ WORD@894..898 "note"
+ WHITESPACE@898..899 " "
+ WORD@899..902 "the"
+ WHITESPACE@902..903 " "
+ COMMAND@903..910
+ COMMAND_NAME@903..910 "\\texttt"
+ CURLY_GROUP@910..921
+ L_CURLY@910..911 "{"
+ WORD@911..920 "sorttitle"
+ R_CURLY@920..921 "}"
+ WHITESPACE@921..922 " "
+ WORD@922..925 "and"
+ WHITESPACE@925..946 "\n "
+ WORD@946..951 "field"
+ WHITESPACE@951..952 " "
+ WORD@952..957 "which"
+ WHITESPACE@957..958 " "
+ WORD@958..960 "is"
+ WHITESPACE@960..961 " "
+ WORD@961..965 "used"
+ WHITESPACE@965..966 " "
+ WORD@966..968 "to"
+ WHITESPACE@968..969 " "
+ WORD@969..978 "fine-tune"
+ WHITESPACE@978..979 " "
+ WORD@979..982 "the"
+ WHITESPACE@982..1003 "\n "
+ WORD@1003..1010 "sorting"
+ WHITESPACE@1010..1011 " "
+ WORD@1011..1016 "order"
+ WHITESPACE@1016..1017 " "
+ WORD@1017..1019 "of"
+ WHITESPACE@1019..1020 " "
+ WORD@1020..1023 "the"
+ WHITESPACE@1023..1024 " "
+ WORD@1024..1037 "bibliography."
+ WHITESPACE@1037..1038 " "
+ WORD@1038..1040 "We"
+ WHITESPACE@1040..1041 " "
+ WORD@1041..1045 "want"
+ WHITESPACE@1045..1046 " "
+ WORD@1046..1050 "this"
+ WHITESPACE@1050..1051 " "
+ WORD@1051..1055 "item"
+ WHITESPACE@1055..1056 " "
+ WORD@1056..1062 "listed"
+ WHITESPACE@1062..1083 "\n "
+ WORD@1083..1088 "first"
+ WHITESPACE@1088..1089 " "
+ WORD@1089..1091 "in"
+ WHITESPACE@1091..1092 " "
+ WORD@1092..1095 "the"
+ WHITESPACE@1095..1096 " "
+ WORD@1096..1108 "bibliography"
+ R_CURLY@1108..1109 "}"
+ COMMA@1109..1110 ","
+ WHITESPACE@1110..1111 "\n"
+ R_DELIM@1111..1112 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__rivest_1978.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__rivest_1978.txt.snap
new file mode 100644
index 0000000000..b296d9cd4b
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__bibtex__tests__parse@samples__rivest_1978.txt.snap
@@ -0,0 +1,232 @@
+---
+source: src/parser/bibtex.rs
+expression: root
+input_file: src/parser/test_data/bibtex/samples/rivest_1978.txt
+---
+ROOT@0..557
+ ENTRY@0..557
+ TYPE@0..8 "@article"
+ L_DELIM@8..9 "{"
+ NAME@9..30 "10.1145/359340.359342"
+ COMMA@30..31 ","
+ WHITESPACE@31..36 "\n "
+ FIELD@36..92
+ NAME@36..42 "author"
+ WHITESPACE@42..43 " "
+ EQ@43..44 "="
+ WHITESPACE@44..45 " "
+ CURLY_GROUP@45..91
+ L_CURLY@45..46 "{"
+ WORD@46..52 "Rivest"
+ COMMA@52..53 ","
+ WHITESPACE@53..54 " "
+ WORD@54..56 "R."
+ WHITESPACE@56..57 " "
+ WORD@57..59 "L."
+ WHITESPACE@59..60 " "
+ WORD@60..63 "and"
+ WHITESPACE@63..64 " "
+ WORD@64..70 "Shamir"
+ COMMA@70..71 ","
+ WHITESPACE@71..72 " "
+ WORD@72..74 "A."
+ WHITESPACE@74..75 " "
+ WORD@75..78 "and"
+ WHITESPACE@78..79 " "
+ WORD@79..86 "Adleman"
+ COMMA@86..87 ","
+ WHITESPACE@87..88 " "
+ WORD@88..90 "L."
+ R_CURLY@90..91 "}"
+ COMMA@91..92 ","
+ WHITESPACE@92..97 "\n "
+ FIELD@97..178
+ NAME@97..102 "title"
+ WHITESPACE@102..103 " "
+ EQ@103..104 "="
+ WHITESPACE@104..105 " "
+ CURLY_GROUP@105..177
+ L_CURLY@105..106 "{"
+ WORD@106..107 "A"
+ WHITESPACE@107..108 " "
+ WORD@108..114 "Method"
+ WHITESPACE@114..115 " "
+ WORD@115..118 "for"
+ WHITESPACE@118..119 " "
+ WORD@119..128 "Obtaining"
+ WHITESPACE@128..129 " "
+ WORD@129..136 "Digital"
+ WHITESPACE@136..137 " "
+ WORD@137..147 "Signatures"
+ WHITESPACE@147..148 " "
+ WORD@148..151 "and"
+ WHITESPACE@151..152 " "
+ WORD@152..162 "Public-Key"
+ WHITESPACE@162..163 " "
+ WORD@163..176 "Cryptosystems"
+ R_CURLY@176..177 "}"
+ COMMA@177..178 ","
+ WHITESPACE@178..183 "\n "
+ FIELD@183..197
+ NAME@183..187 "year"
+ WHITESPACE@187..188 " "
+ EQ@188..189 "="
+ WHITESPACE@189..190 " "
+ CURLY_GROUP@190..196
+ L_CURLY@190..191 "{"
+ INTEGER@191..195 "1978"
+ R_CURLY@195..196 "}"
+ COMMA@196..197 ","
+ WHITESPACE@197..202 "\n "
+ FIELD@202..227
+ NAME@202..212 "issue_date"
+ WHITESPACE@212..213 " "
+ EQ@213..214 "="
+ WHITESPACE@214..215 " "
+ CURLY_GROUP@215..226
+ L_CURLY@215..216 "{"
+ WORD@216..220 "Feb."
+ WHITESPACE@220..221 " "
+ INTEGER@221..225 "1978"
+ R_CURLY@225..226 "}"
+ COMMA@226..227 ","
+ WHITESPACE@227..232 "\n "
+ FIELD@232..282
+ NAME@232..241 "publisher"
+ WHITESPACE@241..242 " "
+ EQ@242..243 "="
+ WHITESPACE@243..244 " "
+ CURLY_GROUP@244..281
+ L_CURLY@244..245 "{"
+ WORD@245..256 "Association"
+ WHITESPACE@256..257 " "
+ WORD@257..260 "for"
+ WHITESPACE@260..261 " "
+ WORD@261..270 "Computing"
+ WHITESPACE@270..271 " "
+ WORD@271..280 "Machinery"
+ R_CURLY@280..281 "}"
+ COMMA@281..282 ","
+ WHITESPACE@282..287 "\n "
+ FIELD@287..317
+ NAME@287..294 "address"
+ WHITESPACE@294..295 " "
+ EQ@295..296 "="
+ WHITESPACE@296..297 " "
+ CURLY_GROUP@297..316
+ L_CURLY@297..298 "{"
+ WORD@298..301 "New"
+ WHITESPACE@301..302 " "
+ WORD@302..306 "York"
+ COMMA@306..307 ","
+ WHITESPACE@307..308 " "
+ WORD@308..310 "NY"
+ COMMA@310..311 ","
+ WHITESPACE@311..312 " "
+ WORD@312..315 "USA"
+ R_CURLY@315..316 "}"
+ COMMA@316..317 ","
+ WHITESPACE@317..322 "\n "
+ FIELD@322..336
+ NAME@322..328 "volume"
+ WHITESPACE@328..329 " "
+ EQ@329..330 "="
+ WHITESPACE@330..331 " "
+ CURLY_GROUP@331..335
+ L_CURLY@331..332 "{"
+ INTEGER@332..334 "21"
+ R_CURLY@334..335 "}"
+ COMMA@335..336 ","
+ WHITESPACE@336..341 "\n "
+ FIELD@341..354
+ NAME@341..347 "number"
+ WHITESPACE@347..348 " "
+ EQ@348..349 "="
+ WHITESPACE@349..350 " "
+ CURLY_GROUP@350..353
+ L_CURLY@350..351 "{"
+ INTEGER@351..352 "2"
+ R_CURLY@352..353 "}"
+ COMMA@353..354 ","
+ WHITESPACE@354..359 "\n "
+ FIELD@359..378
+ NAME@359..363 "issn"
+ WHITESPACE@363..364 " "
+ EQ@364..365 "="
+ WHITESPACE@365..366 " "
+ CURLY_GROUP@366..377
+ L_CURLY@366..367 "{"
+ WORD@367..376 "0001-0782"
+ R_CURLY@376..377 "}"
+ COMMA@377..378 ","
+ WHITESPACE@378..383 "\n "
+ FIELD@383..429
+ NAME@383..386 "url"
+ WHITESPACE@386..387 " "
+ EQ@387..388 "="
+ WHITESPACE@388..389 " "
+ CURLY_GROUP@389..428
+ L_CURLY@389..390 "{"
+ WORD@390..427 "https://doi.org/10.11 ..."
+ R_CURLY@427..428 "}"
+ COMMA@428..429 ","
+ WHITESPACE@429..434 "\n "
+ FIELD@434..464
+ NAME@434..437 "doi"
+ WHITESPACE@437..438 " "
+ EQ@438..439 "="
+ WHITESPACE@439..440 " "
+ CURLY_GROUP@440..463
+ L_CURLY@440..441 "{"
+ WORD@441..462 "10.1145/359340.359342"
+ R_CURLY@462..463 "}"
+ COMMA@463..464 ","
+ WHITESPACE@464..469 "\n "
+ FIELD@469..493
+ NAME@469..476 "journal"
+ WHITESPACE@476..477 " "
+ EQ@477..478 "="
+ WHITESPACE@478..479 " "
+ CURLY_GROUP@479..492
+ L_CURLY@479..480 "{"
+ WORD@480..487 "Commun."
+ WHITESPACE@487..488 " "
+ WORD@488..491 "ACM"
+ R_CURLY@491..492 "}"
+ COMMA@492..493 ","
+ WHITESPACE@493..498 "\n "
+ FIELD@498..512
+ NAME@498..503 "month"
+ WHITESPACE@503..504 " "
+ EQ@504..505 "="
+ WHITESPACE@505..506 " "
+ CURLY_GROUP@506..511
+ L_CURLY@506..507 "{"
+ WORD@507..510 "feb"
+ R_CURLY@510..511 "}"
+ COMMA@511..512 ","
+ WHITESPACE@512..517 "\n "
+ FIELD@517..535
+ NAME@517..522 "pages"
+ WHITESPACE@522..523 " "
+ EQ@523..524 "="
+ WHITESPACE@524..525 " "
+ CURLY_GROUP@525..534
+ L_CURLY@525..526 "{"
+ WORD@526..533 "120-126"
+ R_CURLY@533..534 "}"
+ COMMA@534..535 ","
+ WHITESPACE@535..540 "\n "
+ FIELD@540..555
+ NAME@540..548 "numpages"
+ WHITESPACE@548..549 " "
+ EQ@549..550 "="
+ WHITESPACE@550..551 " "
+ CURLY_GROUP@551..554
+ L_CURLY@551..552 "{"
+ INTEGER@552..553 "7"
+ R_CURLY@553..554 "}"
+ COMMA@554..555 ","
+ WHITESPACE@555..556 "\n"
+ R_DELIM@556..557 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@001.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@001.txt.snap
new file mode 100644
index 0000000000..1d91ff7fff
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@001.txt.snap
@@ -0,0 +1,27 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+input_file: src/parser/test_data/build_log/001.txt
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "Overfull \\hbox (200.00162pt too wide) in paragraph at lines 8--9",
+ hint: None,
+ line: Some(
+ 7,
+ ),
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "Overfull \\vbox (3.19998pt too high) detected at line 23",
+ hint: None,
+ line: Some(
+ 22,
+ ),
+ },
+ ],
+}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@002.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@002.txt.snap
new file mode 100644
index 0000000000..91fa71d636
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@002.txt.snap
@@ -0,0 +1,20 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+input_file: src/parser/test_data/build_log/002.txt
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "./child.tex",
+ level: Error,
+ message: "Undefined control sequence.",
+ hint: Some(
+ "\\foo",
+ ),
+ line: Some(
+ 0,
+ ),
+ },
+ ],
+}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@003.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@003.txt.snap
new file mode 100644
index 0000000000..d2024a8cd3
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@003.txt.snap
@@ -0,0 +1,23 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+input_file: src/parser/test_data/build_log/003.txt
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "Citation `foo' on page 1 undefined on input line 6.",
+ hint: None,
+ line: None,
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "There were undefined references.",
+ hint: None,
+ line: None,
+ },
+ ],
+}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@004.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@004.txt.snap
new file mode 100644
index 0000000000..83219b5fff
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@004.txt.snap
@@ -0,0 +1,31 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+input_file: src/parser/test_data/build_log/004.txt
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty",
+ level: Error,
+ message: "Package babel Error: Unknown option `foo'. Either you misspelled it or the language definition file foo.ldf was not found.",
+ hint: Some(
+ "\\ProcessOptions*",
+ ),
+ line: Some(
+ 392,
+ ),
+ },
+ BuildError {
+ relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty",
+ level: Error,
+ message: "Package babel Error: You haven't specified a language option.",
+ hint: Some(
+ "ry to proceed from here, type x to quit.}",
+ ),
+ line: Some(
+ 425,
+ ),
+ },
+ ],
+}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@005.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@005.txt.snap
new file mode 100644
index 0000000000..e4857d00d8
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@005.txt.snap
@@ -0,0 +1,30 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+input_file: src/parser/test_data/build_log/005.txt
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "'babel/polyglossia' detected but 'csquotes' missing. Loading 'csquotes' recommended.",
+ hint: None,
+ line: None,
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "There were undefined references.",
+ hint: None,
+ line: None,
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Warning,
+ message: "Please (re)run Biber on the file: parent and rerun LaTeX afterwards.",
+ hint: None,
+ line: None,
+ },
+ ],
+}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@006.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@006.txt.snap
new file mode 100644
index 0000000000..837aa9c295
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@006.txt.snap
@@ -0,0 +1,69 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+input_file: src/parser/test_data/build_log/006.txt
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Error,
+ message: "Undefined control sequence.",
+ hint: Some(
+ "\\foo",
+ ),
+ line: Some(
+ 6,
+ ),
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Error,
+ message: "Missing $ inserted.",
+ hint: Some(
+ "\\bar",
+ ),
+ line: Some(
+ 7,
+ ),
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Error,
+ message: "Undefined control sequence.",
+ hint: Some(
+ "\\baz",
+ ),
+ line: Some(
+ 8,
+ ),
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Error,
+ message: "Missing { inserted.",
+ hint: None,
+ line: Some(
+ 9,
+ ),
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Error,
+ message: "Missing $ inserted.",
+ hint: None,
+ line: Some(
+ 9,
+ ),
+ },
+ BuildError {
+ relative_path: "./parent.tex",
+ level: Error,
+ message: "Missing } inserted.",
+ hint: None,
+ line: Some(
+ 9,
+ ),
+ },
+ ],
+}
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@007.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@007.txt.snap
new file mode 100644
index 0000000000..d509a01bd4
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__build_log__tests__parse@007.txt.snap
@@ -0,0 +1,19 @@
+---
+source: src/parser/build_log.rs
+expression: parse_build_log(&text)
+---
+BuildLog {
+ errors: [
+ BuildError {
+ relative_path: "/some/folder/a.tex",
+ level: Error,
+ message: "Undefined control sequence.",
+ hint: Some(
+ "\\lsdkfjlskdfj",
+ ),
+ line: Some(
+ 3,
+ ),
+ },
+ ],
+}
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__block_comments.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@block_comments.txt.snap
index 355a93ebad..14c73e7da5 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__block_comments.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@block_comments.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1814
-expression: "setup(r#\"Foo\n\\iffalse\nTest1\n\\fi\nBar\n\\iffalse\n\\fii\n\\fi\nBaz\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/block_comments.txt
---
ROOT@0..48
PREAMBLE@0..48
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_default.txt.snap
index f598a8f7e9..33e119c524 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_default.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\caption[qux]{Foo \\Bar Baz}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/caption/caption_default.txt
---
ROOT@0..27
PREAMBLE@0..27
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_default_error.txt.snap
index 13697e882b..a016dcebed 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_default_error.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\caption[qux]{Foo \\Bar Baz\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/caption/caption_default_error.txt
---
ROOT@0..26
PREAMBLE@0..26
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_figure.txt.snap
index b3bf7589d8..9303bd3ebe 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_figure.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{figure}\\caption{Foo}\\end{figure}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/caption/caption_figure.txt
---
ROOT@0..39
PREAMBLE@0..39
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_minimal.txt.snap
index bf9e795e18..6af368cc21 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_minimal.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\caption{Foo \\Bar Baz}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/caption/caption_minimal.txt
---
ROOT@0..22
PREAMBLE@0..22
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_minimal_error.txt.snap
index 6c932ce883..68fc629613 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@caption__caption_minimal_error.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\caption{Foo \\Bar Baz\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/caption/caption_minimal_error.txt
---
ROOT@0..21
PREAMBLE@0..21
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_empty.txt.snap
index faad4c0039..fbf2b5123e 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_empty.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite{}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_empty.txt
---
ROOT@0..7
PREAMBLE@0..7
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_missing_brace.txt.snap
index 61cd6d8f0b..72bd618594 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_missing_brace.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite{foo\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_missing_brace.txt
---
ROOT@0..9
PREAMBLE@0..9
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_multiple_keys.txt.snap
index 7195e8992e..81716cf8b1 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_multiple_keys.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite{foo, bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_multiple_keys.txt
---
ROOT@0..15
PREAMBLE@0..15
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_prenote.txt.snap
index b5d92886aa..f5afdca536 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_prenote.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite[foo]{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_prenote.txt
---
ROOT@0..15
PREAMBLE@0..15
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_prenote_postnote.txt.snap
index ba99b66e35..feecca0435 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_prenote_postnote.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite[foo][bar]{baz}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_prenote_postnote.txt
---
ROOT@0..20
PREAMBLE@0..20
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_redundant_comma.txt.snap
index 9062479f7a..2a8acd1173 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_redundant_comma.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite{,foo,}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_redundant_comma.txt
---
ROOT@0..12
PREAMBLE@0..12
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_simple.txt.snap
index 335b9d461c..faa2e07155 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\cite{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_simple.txt
---
ROOT@0..10
PREAMBLE@0..10
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_star.txt.snap
index e6d5dad128..3bcd74701c 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@citation__citation_star.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\nocite{*}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/citation/citation_star.txt
---
ROOT@0..10
PREAMBLE@0..10
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_definition_simple.txt.snap
index 64eee4351b..163ea09071 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_definition_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\definecolor{foo}{rgb}{255,168,0}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_definition_simple.txt
---
ROOT@0..33
PREAMBLE@0..33
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_reference_simple.txt.snap
index cb0ee151b9..0c68b5eae0 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_reference_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\color{black}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_reference_simple.txt
---
ROOT@0..13
PREAMBLE@0..13
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error1.txt.snap
index 59688f0902..2e3be43ecb 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error1.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\definecolorset[ty]{rgb,HTML}{foo}{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_set_definition_error1.txt
---
ROOT@0..39
PREAMBLE@0..39
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error2.txt.snap
index cbbeacb649..ee15174688 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error2.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\definecolorset{rgb,HTML}{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_set_definition_error2.txt
---
ROOT@0..30
PREAMBLE@0..30
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error3.txt.snap
index 826bea28b6..3416786dda 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error3.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\definecolorset{rgb,HTML}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_set_definition_error3.txt
---
ROOT@0..25
PREAMBLE@0..25
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error4.txt.snap
index 3cafa81bae..5c16d9d7a5 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_error4.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\definecolorset\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_set_definition_error4.txt
---
ROOT@0..15
PREAMBLE@0..15
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_simple.txt.snap
index 9d32e127ef..a4cff9c7a1 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@color__color_set_definition_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/color/color_set_definition_simple.txt
---
ROOT@0..44
PREAMBLE@0..44
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_argc.txt.snap
index 2ff3896080..a10287b262 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_argc.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newcommand{\\foo}{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/command_definition_no_argc.txt
---
ROOT@0..22
PREAMBLE@0..22
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_impl.txt.snap
index 4602a7a688..ae9b54e0c8 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_impl.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newcommand{\\foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/command_definition_no_impl.txt
---
ROOT@0..17
PREAMBLE@0..17
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_impl_error.txt.snap
index 4a20be7d7c..2e1f2f2956 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_no_impl_error.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newcommand{\\foo\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/command_definition_no_impl_error.txt
---
ROOT@0..16
PREAMBLE@0..16
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_optional.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_optional.txt.snap
index 66c263e49a..fe1a3ad6ec 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_optional.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_optional.txt.snap
@@ -1,8 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1579
-expression: "setup(r#\"\\newcommand{\\foo}[1][def]{#1}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/command_definition_optional.txt
---
ROOT@0..29
PREAMBLE@0..29
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_simple.txt.snap
index b6b8380155..422fdd0d5b 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newcommand[1]{\\id}{#1}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/command_definition_simple.txt
---
ROOT@0..23
PREAMBLE@0..23
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_with_begin.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_with_begin.txt.snap
index 6b7724c0aa..9ee38a1796 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_with_begin.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__command_definition_with_begin.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1723
-expression: "setup(r#\"\\newcommand{\\CVSubHeadingListStart}{\\begin{itemize}[leftmargin=0.5cm, label={}]}\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/command_definition_with_begin.txt
---
ROOT@0..80
PREAMBLE@0..80
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__math_operator_no_impl.txt.snap
index 2e39ee899c..371d1272c4 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__math_operator_no_impl.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\DeclareMathOperator{\\foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/math_operator_no_impl.txt
---
ROOT@0..26
PREAMBLE@0..26
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__math_operator_simple.txt.snap
index bb44005153..004e6918e4 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@command_definition__math_operator_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\DeclareMathOperator{\\foo}{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/command_definition/math_operator_simple.txt
---
ROOT@0..31
PREAMBLE@0..31
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__asymptote.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_asymptote.txt.snap
index a735d71e73..abbc71e6ed 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__asymptote.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_asymptote.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1829
-expression: "setup(r#\"\\begin{asy}\n printf(\"Hello World\\n\");\n\\end{asy}\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/environment/environment_asymptote.txt
---
ROOT@0..50
PREAMBLE@0..50
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_definition.txt.snap
index 5d977db814..fb45f5d35b 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_definition.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1792
-expression: "setup(r#\"\\newenvironment{bar}[1]{\\begin{foo}}{\\end{foo}}\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/environment/environment_definition.txt
---
ROOT@0..47
PREAMBLE@0..47
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition_optional_arg.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_definition_optional_arg.txt.snap
index 1a064fefe3..10009e851b 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition_optional_arg.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_definition_optional_arg.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1797
-expression: "setup(r#\"\\newenvironment{foo}[1][default]{begin}{end}\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/environment/environment_definition_optional_arg.txt
---
ROOT@0..44
PREAMBLE@0..44
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_nested.txt.snap
index 3a65af3a2b..e3274ef70e 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_nested.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{foo} \\begin{qux} \\end{baz} \\end{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/environment/environment_nested.txt
---
ROOT@0..43
PREAMBLE@0..43
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_nested_missing_braces.txt.snap
index 973e6e9a9f..698be0eae7 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_nested_missing_braces.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{foo \\begin{qux Hello World \\end{baz} \\end{bar\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/environment/environment_nested_missing_braces.txt
---
ROOT@0..52
PREAMBLE@0..52
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_simple.txt.snap
index b8b85f1348..db57d39580 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@environment__environment_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{foo} Hello World \\end{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/environment/environment_simple.txt
---
ROOT@0..33
PREAMBLE@0..33
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@equation.txt.snap
index e146fcaee6..0974c69449 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@equation.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\[ foo bar \\]\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/equation.txt
---
ROOT@0..13
PREAMBLE@0..13
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@equation_missing_begin.txt.snap
index 9a2af78417..c649fc6a6a 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@equation_missing_begin.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{a} foo bar \\] \\end{b}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/equation_missing_begin.txt
---
ROOT@0..28
PREAMBLE@0..28
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_args.txt.snap
index b62c8c2559..9fee38a855 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_args.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\foo{bar}[qux]\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/generic_command_args.txt
---
ROOT@0..14
PREAMBLE@0..14
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_empty.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_empty.txt.snap
new file mode 100644
index 0000000000..dcd2382d02
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_empty.txt.snap
@@ -0,0 +1,10 @@
+---
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/generic_command_empty.txt
+---
+ROOT@0..4
+ PREAMBLE@0..4
+ GENERIC_COMMAND@0..4
+ GENERIC_COMMAND_NAME@0..4 "\\foo"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_escape.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_escape.txt.snap
new file mode 100644
index 0000000000..d7d88a32ce
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@generic_command_escape.txt.snap
@@ -0,0 +1,10 @@
+---
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/generic_command_escape.txt
+---
+ROOT@0..2
+ PREAMBLE@0..2
+ GENERIC_COMMAND@0..2
+ GENERIC_COMMAND_NAME@0..2 "\\#"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_declaration.txt.snap
index a92180efb3..c1298e50fc 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_declaration.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/acronym_declaration.txt
---
ROOT@0..64
PREAMBLE@0..64
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_definition_options.txt.snap
index 80043c18e7..eac9b6e3ba 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_definition_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/acronym_definition_options.txt
---
ROOT@0..76
PREAMBLE@0..76
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_definition_simple.txt.snap
index 74ae2219de..e6bfe2a9d5 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_definition_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newacronym{fpsLabel}{FPS}{Frame per Second}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/acronym_definition_simple.txt
---
ROOT@0..44
PREAMBLE@0..44
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_package.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_package.txt.snap
new file mode 100644
index 0000000000..9babc04165
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_package.txt.snap
@@ -0,0 +1,30 @@
+---
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/acronym_package.txt
+---
+ROOT@0..35
+ PREAMBLE@0..35
+ ACRONYM_DEFINITION@0..35
+ ACRONYM_DEFINITION_NAME@0..5 "\\acro"
+ CURLY_GROUP_WORD@5..10
+ L_CURLY@5..6 "{"
+ KEY@6..9
+ WORD@6..9 "fps"
+ R_CURLY@9..10 "}"
+ BRACK_GROUP@10..15
+ L_BRACK@10..11 "["
+ TEXT@11..14
+ WORD@11..14 "FPS"
+ R_BRACK@14..15 "]"
+ CURLY_GROUP@15..35
+ L_CURLY@15..16 "{"
+ TEXT@16..33
+ WORD@16..22 "Frames"
+ WHITESPACE@22..23 " "
+ WORD@23..26 "per"
+ WHITESPACE@26..27 " "
+ WORD@27..33 "Second"
+ R_CURLY@33..34 "}"
+ LINE_BREAK@34..35 "\n"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_reference_options.txt.snap
index 6a1c2cdcbe..99a8fbc562 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_reference_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\acrshort[foo=bar,baz]{fpsLabel}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/acronym_reference_options.txt
---
ROOT@0..32
PREAMBLE@0..32
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_reference_simple.txt.snap
index 9924c7175c..08effadf7e 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__acronym_reference_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\acrshort{fpsLabel}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/acronym_reference_simple.txt
---
ROOT@0..19
PREAMBLE@0..19
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_definition_simple.txt.snap
index 519abf75e8..4ab51a0db3 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_definition_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newglossaryentry{foo}{bar = baz, qux,}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/glossary_entry_definition_simple.txt
---
ROOT@0..39
PREAMBLE@0..39
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_reference_options.txt.snap
index 494c0be0a0..d97e984b77 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_reference_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\gls[foo = bar, qux]{baz}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/glossary_entry_reference_options.txt
---
ROOT@0..25
PREAMBLE@0..25
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_reference_simple.txt.snap
index 94af3796fc..8c8fb0c1e2 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@glossary__glossary_entry_reference_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\gls{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/glossary/glossary_entry_reference_simple.txt
---
ROOT@0..9
PREAMBLE@0..9
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path.txt.snap
new file mode 100644
index 0000000000..9067d36c7d
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path.txt.snap
@@ -0,0 +1,18 @@
+---
+source: src/parser/latex.rs
+assertion_line: 1361
+expression: root
+---
+ROOT@0..28
+ PREAMBLE@0..28
+ GRAPHICS_PATH@0..28
+ GRAPHICS_PATH_NAME@0..13 "\\graphicspath"
+ CURLY_GROUP@13..28
+ L_CURLY@13..14 "{"
+ CURLY_GROUP_WORD@14..27
+ L_CURLY@14..15 "{"
+ KEY@15..26
+ WORD@15..26 "../figures/"
+ R_CURLY@26..27 "}"
+ R_CURLY@27..28 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_command.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_command.txt.snap
new file mode 100644
index 0000000000..4092b2e8e9
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_command.txt.snap
@@ -0,0 +1,23 @@
+---
+source: src/parser/latex.rs
+assertion_line: 1348
+expression: root
+---
+ROOT@0..32
+ PREAMBLE@0..32
+ GRAPHICS_PATH@0..32
+ GRAPHICS_PATH_NAME@0..13 "\\graphicspath"
+ CURLY_GROUP@13..32
+ L_CURLY@13..14 "{"
+ CURLY_GROUP_WORD@14..31
+ L_CURLY@14..15 "{"
+ KEY@15..30
+ GENERIC_COMMAND_NAME@15..22 "\\subfix"
+ CURLY_GROUP_WORD@22..30
+ L_CURLY@22..23 "{"
+ KEY@23..29
+ WORD@23..29 "./img/"
+ R_CURLY@29..30 "}"
+ R_CURLY@30..31 "}"
+ R_CURLY@31..32 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_options.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_options.txt.snap
new file mode 100644
index 0000000000..0eb034bbb3
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@graphics_path_options.txt.snap
@@ -0,0 +1,23 @@
+---
+source: src/parser/latex.rs
+assertion_line: 1348
+expression: root
+---
+ROOT@0..33
+ PREAMBLE@0..33
+ GRAPHICS_PATH@0..13
+ GRAPHICS_PATH_NAME@0..13 "\\graphicspath"
+ MIXED_GROUP@13..18
+ L_BRACK@13..14 "["
+ TEXT@14..17
+ WORD@14..17 "foo"
+ R_BRACK@17..18 "]"
+ CURLY_GROUP@18..33
+ L_CURLY@18..19 "{"
+ CURLY_GROUP@19..32
+ L_CURLY@19..20 "{"
+ TEXT@20..31
+ WORD@20..31 "../figures/"
+ R_CURLY@31..32 "}"
+ R_CURLY@32..33 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__curly_group_missing_end.txt.snap
index c5fc46ec4d..68969f9c11 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__curly_group_missing_end.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"{hello world\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/curly_group_missing_end.txt
---
ROOT@0..12
PREAMBLE@0..12
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__curly_group_simple.txt.snap
index 10ebc4418e..156ec2e0a2 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__curly_group_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"{hello world}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/curly_group_simple.txt
---
ROOT@0..13
PREAMBLE@0..13
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__equation_missing_end.txt.snap
index 3085037fac..eae04433d1 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__equation_missing_end.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{a} \\[ foo bar \\end{b}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/equation_missing_end.txt
---
ROOT@0..28
PREAMBLE@0..28
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__escaped_brackets.txt.snap
index c595828583..a396d2e984 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__escaped_brackets.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"{[}{]}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/escaped_brackets.txt
---
ROOT@0..6
PREAMBLE@0..6
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_braces.txt.snap
index df463f501a..82d7e9db45 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_braces.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"}{\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/unmatched_braces.txt
---
ROOT@0..2
PREAMBLE@0..2
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_brackets.txt.snap
index 578cbf6099..7dd82d48bc 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_brackets.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"][\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/unmatched_brackets.txt
---
ROOT@0..2
PREAMBLE@0..2
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_brackets_with_group.txt.snap
index d929c98c46..3c45c0c14c 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@group__unmatched_brackets_with_group.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"{][}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/group/unmatched_brackets_with_group.txt
---
ROOT@0..4
PREAMBLE@0..4
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@hello_world.txt.snap
index ee25cb5453..7c1ff02b81 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@hello_world.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"Hello World!\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/hello_world.txt
---
ROOT@0..12
PREAMBLE@0..12
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__biblatex_include_options.txt.snap
index 3189cabd46..897d693fd0 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__biblatex_include_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\addbibresource[foo=bar, baz]{foo/bar.bib}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/biblatex_include_options.txt
---
ROOT@0..42
PREAMBLE@0..42
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__biblatex_include_simple.txt.snap
index 3166d13aff..a4be9c38cb 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__biblatex_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\addbibresource{foo/bar.bib}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/biblatex_include_simple.txt
---
ROOT@0..28
PREAMBLE@0..28
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__bibtex_include_simple.txt.snap
index db58621c75..0a8f9cd3f6 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__bibtex_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\bibliography{foo/bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/bibtex_include_simple.txt
---
ROOT@0..22
PREAMBLE@0..22
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_empty.txt.snap
index 7d84653225..37ea745308 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_empty.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\documentclass{}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/class_include_empty.txt
---
ROOT@0..16
PREAMBLE@0..16
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_options.txt.snap
index ac20c4b8ac..ff95f8358f 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\documentclass[foo = bar, baz, qux]{article}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/class_include_options.txt
---
ROOT@0..44
PREAMBLE@0..44
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_simple.txt.snap
index 07c426101e..670fa2b902 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__class_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\documentclass{article}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/class_include_simple.txt
---
ROOT@0..23
PREAMBLE@0..23
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_command.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_command.txt.snap
index d279eda7f7..0332499c66 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_command.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_command.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1639
-expression: "setup(r#\"\\includegraphics[width=0.5\\textwidth]{\\foo.\\bar.pdf}\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/graphics_include_command.txt
---
ROOT@0..52
PREAMBLE@0..52
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_complicated_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_complicated_options.txt.snap
index a79c7f925e..5259eb5072 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_complicated_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_complicated_options.txt.snap
@@ -1,8 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1547
-expression: "setup(r#\"\\includegraphics[width=0.5\\textwidth]{}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/graphics_include_complicated_options.txt
---
ROOT@0..39
PREAMBLE@0..39
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_options.txt.snap
index 14e2413323..5ed7df9cfc 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\includegraphics[scale=.5]{foo/bar.pdf}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/graphics_include_options.txt
---
ROOT@0..39
PREAMBLE@0..39
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_simple.txt.snap
index f4c640bca8..cabb065d24 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__graphics_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\includegraphics{foo/bar.pdf}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/graphics_include_simple.txt
---
ROOT@0..29
PREAMBLE@0..29
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__import_incomplete.txt.snap
index c8634d6269..dad78222c2 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__import_incomplete.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\import{foo\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/import_incomplete.txt
---
ROOT@0..11
PREAMBLE@0..11
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__import_simple.txt.snap
index c94f34eb3f..9e4fdeb284 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__import_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\import{foo}{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/import_simple.txt
---
ROOT@0..17
PREAMBLE@0..17
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__inkscape_include_options.txt.snap
index 8b95509be6..d0e8321692 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__inkscape_include_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\includesvg[scale=.5]{foo/bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/inkscape_include_options.txt
---
ROOT@0..30
PREAMBLE@0..30
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__inkscape_include_simple.txt.snap
index e3d1f9bdfd..1d1e68f33c 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__inkscape_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\includesvg{foo/bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/inkscape_include_simple.txt
---
ROOT@0..20
PREAMBLE@0..20
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_equality_sign.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_include_equality_sign.txt.snap
index c6cfb5dda3..abf7865a42 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_equality_sign.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_include_equality_sign.txt.snap
@@ -1,8 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1504
-expression: "setup(r#\"\\include{foo=bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/latex_include_equality_sign.txt
---
ROOT@0..17
PREAMBLE@0..17
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_include_simple.txt.snap
index dbdeaad2f7..2315a2ad7c 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\include{foo/bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/latex_include_simple.txt
---
ROOT@0..17
PREAMBLE@0..17
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_path_with_brackets.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_input_path_brackets.txt.snap
index cb99f39728..0ba79f0c41 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_path_with_brackets.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__latex_input_path_brackets.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1591
-expression: "setup(r#\"\\input{foo[bar].tex}\"#)"
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/latex_input_path_brackets.txt
---
ROOT@0..20
PREAMBLE@0..20
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_empty.txt.snap
index b8851b6e1d..36e2ab5aeb 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_empty.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\usepackage{}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/package_include_empty.txt
---
ROOT@0..13
PREAMBLE@0..13
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_multiple.txt.snap
index 2df92fd909..876f7313d7 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_multiple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\usepackage{amsmath, lipsum}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/package_include_multiple.txt
---
ROOT@0..28
PREAMBLE@0..28
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_options.txt.snap
index 0f87570023..32a624f769 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\usepackage[foo = bar, baz, qux]{amsmath}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/package_include_options.txt
---
ROOT@0..41
PREAMBLE@0..41
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_simple.txt.snap
index a28dd39a13..014ed983dd 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__package_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\usepackage{amsmath}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/package_include_simple.txt
---
ROOT@0..20
PREAMBLE@0..20
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__pgf_library_import_simple.txt.snap
index c36bf857bb..7f5f78415e 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__pgf_library_import_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\usepgflibrary{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/pgf_library_import_simple.txt
---
ROOT@0..19
PREAMBLE@0..19
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__svg_include_options.txt.snap
index 3140282630..2410aeaa5a 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__svg_include_options.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\includesvg[scale=.5]{foo/bar.svg}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/svg_include_options.txt
---
ROOT@0..34
PREAMBLE@0..34
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__svg_include_simple.txt.snap
index 2cf133b4d0..61dd22f045 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__svg_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\includesvg{foo/bar.svg}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/svg_include_simple.txt
---
ROOT@0..24
PREAMBLE@0..24
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__tikz_library_import_simple.txt.snap
index 4cd63e6728..c68ff02964 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__tikz_library_import_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\usetikzlibrary{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/tikz_library_import_simple.txt
---
ROOT@0..20
PREAMBLE@0..20
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__verbatim_include_simple.txt.snap
index 48d6b816f3..3c35fddf18 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@include__verbatim_include_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\verbatiminput{foo/bar.txt}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/include/verbatim_include_simple.txt
---
ROOT@0..27
PREAMBLE@0..27
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@inline.txt.snap
index b430de766e..c055d183bd 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@inline.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"$x \\in [0, \\infty)$\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/inline.txt
---
ROOT@0..19
PREAMBLE@0..19
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@inline_double_dollar.txt.snap
index 8da22f3ecb..7084156fce 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@inline_double_dollar.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"$$x \\in [0, \\infty)$$\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/inline_double_dollar.txt
---
ROOT@0..21
PREAMBLE@0..21
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_745.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_745.txt.snap
new file mode 100644
index 0000000000..8530953285
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_745.txt.snap
@@ -0,0 +1,120 @@
+---
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/issue_745.txt
+---
+ROOT@0..271
+ PREAMBLE@0..271
+ CLASS_INCLUDE@0..24
+ CLASS_INCLUDE_NAME@0..14 "\\documentclass"
+ CURLY_GROUP_WORD_LIST@14..24
+ L_CURLY@14..15 "{"
+ KEY@15..22
+ WORD@15..22 "article"
+ R_CURLY@22..23 "}"
+ LINE_BREAK@23..24 "\n"
+ PACKAGE_INCLUDE@24..50
+ PACKAGE_INCLUDE_NAME@24..35 "\\usepackage"
+ CURLY_GROUP_WORD_LIST@35..50
+ L_CURLY@35..36 "{"
+ KEY@36..46
+ WORD@36..46 "tabularray"
+ R_CURLY@46..47 "}"
+ WHITESPACE@47..50 " \n\n"
+ GENERIC_COMMAND@50..64
+ GENERIC_COMMAND_NAME@50..63 "\\ExplSyntaxOn"
+ LINE_BREAK@63..64 "\n"
+ GENERIC_COMMAND@64..223
+ GENERIC_COMMAND_NAME@64..87 "\\NewDocumentEnvironment"
+ CURLY_GROUP@87..96
+ L_CURLY@87..88 "{"
+ TEXT@88..95
+ WORD@88..95 "exptblr"
+ R_CURLY@95..96 "}"
+ CURLY_GROUP@96..107
+ L_CURLY@96..97 "{"
+ TEXT@97..98
+ WORD@97..98 "O"
+ CURLY_GROUP@98..100
+ L_CURLY@98..99 "{"
+ R_CURLY@99..100 "}"
+ TEXT@100..101
+ WORD@100..101 "m"
+ R_CURLY@101..102 "}"
+ WHITESPACE@102..107 "\n "
+ CURLY_GROUP@107..200
+ L_CURLY@107..108 "{"
+ WHITESPACE@108..113 "\n "
+ GENERIC_COMMAND@113..194
+ GENERIC_COMMAND_NAME@113..119 "\\use:x"
+ WHITESPACE@119..124 "\n "
+ CURLY_GROUP@124..194
+ L_CURLY@124..125 "{"
+ WHITESPACE@125..130 "\n "
+ GENERIC_COMMAND@130..141
+ GENERIC_COMMAND_NAME@130..140 "\\exp_not:N"
+ WHITESPACE@140..141 " "
+ ENVIRONMENT@141..188
+ BEGIN@141..179
+ BEGIN_ENVIRONMENT_NAME@141..147 "\\begin"
+ CURLY_GROUP_WORD@147..158
+ L_CURLY@147..148 "{"
+ KEY@148..152
+ WORD@148..152 "tblr"
+ R_CURLY@152..153 "}"
+ WHITESPACE@153..158 "\n "
+ BRACK_GROUP@158..179
+ L_BRACK@158..159 "["
+ GENERIC_COMMAND@159..173
+ GENERIC_COMMAND_NAME@159..169 "\\exp_not:n"
+ CURLY_GROUP@169..173
+ L_CURLY@169..170 "{"
+ TEXT@170..172
+ WORD@170..172 "#1"
+ R_CURLY@172..173 "}"
+ R_BRACK@173..174 "]"
+ WHITESPACE@174..179 "\n "
+ CURLY_GROUP@179..188
+ L_CURLY@179..180 "{"
+ TEXT@180..182
+ WORD@180..182 "#2"
+ R_CURLY@182..183 "}"
+ WHITESPACE@183..188 "\n "
+ MISSING@188..188 ""
+ R_CURLY@188..189 "}"
+ WHITESPACE@189..194 "\n "
+ R_CURLY@194..195 "}"
+ WHITESPACE@195..200 "\n "
+ CURLY_GROUP@200..223
+ L_CURLY@200..201 "{"
+ WHITESPACE@201..206 "\n "
+ GENERIC_COMMAND@206..221
+ END_ENVIRONMENT_NAME@206..210 "\\end"
+ CURLY_GROUP@210..221
+ L_CURLY@210..211 "{"
+ TEXT@211..215
+ WORD@211..215 "tblr"
+ R_CURLY@215..216 "}"
+ WHITESPACE@216..221 "\n "
+ R_CURLY@221..222 "}"
+ LINE_BREAK@222..223 "\n"
+ GENERIC_COMMAND@223..239
+ GENERIC_COMMAND_NAME@223..237 "\\ExplSyntaxOff"
+ LINE_BREAK@237..239 "\n\n"
+ ENVIRONMENT@239..271
+ BEGIN@239..257
+ BEGIN_ENVIRONMENT_NAME@239..245 "\\begin"
+ CURLY_GROUP_WORD@245..257
+ L_CURLY@245..246 "{"
+ KEY@246..254
+ WORD@246..254 "document"
+ R_CURLY@254..255 "}"
+ LINE_BREAK@255..257 "\n\n"
+ END@257..271
+ END_ENVIRONMENT_NAME@257..261 "\\end"
+ CURLY_GROUP_WORD@261..271
+ L_CURLY@261..262 "{"
+ KEY@262..270
+ WORD@262..270 "document"
+ R_CURLY@270..271 "}"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_789.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_789.txt.snap
new file mode 100644
index 0000000000..ff20c01008
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@issue_789.txt.snap
@@ -0,0 +1,15 @@
+---
+source: src/parser/latex.rs
+assertion_line: 1361
+expression: root
+---
+ROOT@0..19
+ PREAMBLE@0..19
+ GRAPHICS_PATH@0..19
+ GRAPHICS_PATH_NAME@0..13 "\\graphicspath"
+ CURLY_GROUP_WORD@13..19
+ L_CURLY@13..14 "{"
+ KEY@14..18
+ WORD@14..18 "test"
+ R_CURLY@18..19 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_line_break.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_definition_line_break.txt.snap
index 47187fabcd..7f2ff377b8 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_line_break.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_definition_line_break.txt.snap
@@ -1,8 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-assertion_line: 1719
-expression: "setup(\"\\\\label{hello\\nworld}\")"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_definition_line_break.txt
---
ROOT@0..19
PREAMBLE@0..19
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_definition_simple.txt.snap
index 2d15533f87..a2e8b85b55 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_definition_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\label{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_definition_simple.txt
---
ROOT@0..11
PREAMBLE@0..11
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_number.txt.snap
index a63752d80e..9afdf9e88e 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_number.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newlabel{foo}{{1.1}}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_number.txt
---
ROOT@0..21
PREAMBLE@0..21
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_equation.txt.snap
index 9123fafcf2..93580e846e 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_equation.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\eqref{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_equation.txt
---
ROOT@0..11
PREAMBLE@0..11
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_incomplete.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_incomplete.txt.snap
index 047eea657d..64e2d9af44 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_incomplete.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_incomplete.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"Equation \\eqref{eq is a \\emph{useful} identity.\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_incomplete.txt
---
ROOT@0..47
PREAMBLE@0..47
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_multiple.txt.snap
index 0176c770e8..27d56c6789 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_multiple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\ref{foo, bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_multiple.txt
---
ROOT@0..14
PREAMBLE@0..14
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_error.txt.snap
index ee65f8a02f..ce5e6a49b5 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_error.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\crefrange{foo{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_range_error.txt
---
ROOT@0..19
PREAMBLE@0..19
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_incomplete.txt.snap
index 3dbe4bbab6..b682f40ebc 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_incomplete.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\crefrange{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_range_incomplete.txt
---
ROOT@0..15
PREAMBLE@0..15
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_simple.txt.snap
index 094d0e2870..e76708bf43 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_range_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\crefrange{foo}{bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_range_simple.txt
---
ROOT@0..20
PREAMBLE@0..20
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_simple.txt.snap
index 9b6e622ef9..398a6c1682 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@label__label_reference_simple.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\ref{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/label/label_reference_simple.txt
---
ROOT@0..9
PREAMBLE@0..9
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter.txt.snap
new file mode 100644
index 0000000000..333db9df78
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter.txt.snap
@@ -0,0 +1,10 @@
+---
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/parameter.txt
+---
+ROOT@0..2
+ PREAMBLE@0..2
+ TEXT@0..2
+ WORD@0..2 "#1"
+
diff --git a/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter_error.txt.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter_error.txt.snap
new file mode 100644
index 0000000000..8e3b45ad67
--- /dev/null
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@parameter_error.txt.snap
@@ -0,0 +1,10 @@
+---
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/parameter_error.txt
+---
+ROOT@0..1
+ PREAMBLE@0..1
+ TEXT@0..1
+ WORD@0..1 "#"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_enum_item.txt.snap
index 0ca7421ac6..4c6222045c 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_enum_item.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\begin{enumerate} \\item 1 \\item[2] 2 \\item 3 \\end{enumerate}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/structure/structure_enum_item.txt
---
ROOT@0..60
PREAMBLE@0..60
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_invalid_nesting.txt.snap
index f999a50e9a..c5f7aede51 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_invalid_nesting.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\section{Foo} \\chapter{Bar}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/structure/structure_invalid_nesting.txt
---
ROOT@0..27
PREAMBLE@0..27
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_nested.txt.snap
index db5602e591..1f93b8c11f 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_nested.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\part{1}\\chapter{2}\\section{3}\\subsection{4}\\subsubsection{5}\\paragraph{6}\\subparagraph{7}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/structure/structure_nested.txt
---
ROOT@0..90
PREAMBLE@0..90
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_siblings.txt.snap
index 94d2ef40bd..03a29cb4bd 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@structure__structure_siblings.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\section{Foo} Foo \\section{Bar} Bar\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/structure/structure_siblings.txt
---
ROOT@0..35
PREAMBLE@0..35
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_full.txt.snap
index 7f21c0ef52..81b9c92758 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_full.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newtheorem{foo}[bar]{Foo}[baz]\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/theorem_definition/theorem_definition_full.txt
---
ROOT@0..31
PREAMBLE@0..31
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_counter.txt.snap
index d7bb29bf58..b422a2619f 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_counter.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newtheorem{foo}[bar]\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_counter.txt
---
ROOT@0..21
PREAMBLE@0..21
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_description.txt.snap
index 7c8b49efa1..0b1a3f3e48 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_description.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newtheorem{foo}{Foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description.txt
---
ROOT@0..21
PREAMBLE@0..21
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_description_and_counter.txt.snap
index 3322a9a116..1f84c653bd 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_name_with_description_and_counter.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newtheorem{foo}[bar]{Foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description_and_counter.txt
---
ROOT@0..26
PREAMBLE@0..26
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_only_name.txt.snap
index 0ba62df76c..8c85579665 100644
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap
+++ b/support/texlab/src/parser/snapshots/texlab__parser__latex__tests__parse@theorem_definition__theorem_definition_only_name.txt.snap
@@ -1,7 +1,7 @@
---
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\newtheorem{foo}\"#)"
-
+source: src/parser/latex.rs
+expression: root
+input_file: src/parser/test_data/latex/theorem_definition/theorem_definition_only_name.txt
---
ROOT@0..16
PREAMBLE@0..16
diff --git a/support/texlab/src/parser/test_data/bibtex/comment.txt b/support/texlab/src/parser/test_data/bibtex/comment.txt
new file mode 100644
index 0000000000..f2ff586ab7
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/comment.txt
@@ -0,0 +1,6 @@
+Some junk here
+
+@comment{foo,
+ author = {Foo Bar},
+ title = {Some Title},
+}
diff --git a/support/texlab/src/parser/test_data/bibtex/issue_809.txt b/support/texlab/src/parser/test_data/bibtex/issue_809.txt
new file mode 100644
index 0000000000..c94b808fd5
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/issue_809.txt
@@ -0,0 +1,3 @@
+@article{issue_809,
+ title = {foo (" bar) baz},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/preamble.txt b/support/texlab/src/parser/test_data/bibtex/preamble.txt
new file mode 100644
index 0000000000..266b5ce228
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/preamble.txt
@@ -0,0 +1 @@
+@preamble{"Hello World!"} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/aho_2006.txt b/support/texlab/src/parser/test_data/bibtex/samples/aho_2006.txt
new file mode 100644
index 0000000000..ff829b40d1
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/aho_2006.txt
@@ -0,0 +1,8 @@
+@book{10.5555/1177220,
+ author = {Aho, Alfred V. and Lam, Monica S. and Sethi, Ravi and Ullman, Jeffrey D.},
+ title = {Compilers: Principles, Techniques, and Tools (2nd Edition)},
+ year = {2006},
+ isbn = {0321486811},
+ publisher = {Addison-Wesley Longman Publishing Co., Inc.},
+ address = {USA}
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/aksin_2006.txt b/support/texlab/src/parser/test_data/bibtex/samples/aksin_2006.txt
new file mode 100644
index 0000000000..88ea701c22
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/aksin_2006.txt
@@ -0,0 +1,16 @@
+@string{jomch = {J.~Organomet. Chem.}}
+
+@article{aksin,
+ author = {Aks{\i}n, {\"O}zge and T{\"u}rkmen, Hayati and Artok, Levent
+ and {\c{C}}etinkaya, Bekir and Ni, Chaoying and
+ B{\"u}y{\"u}kg{\"u}ng{\"o}r, Orhan and {\"O}zkal, Erhan},
+ title = {Effect of immobilization on catalytic characteristics of
+ saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}
+ reactions},
+ journaltitle = jomch,
+ date = 2006,
+ volume = 691,
+ number = 13,
+ pages = {3027-3036},
+ indextitle = {Effect of immobilization on catalytic characteristics},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/almendro_1998.txt b/support/texlab/src/parser/test_data/bibtex/samples/almendro_1998.txt
new file mode 100644
index 0000000000..ed16d33c67
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/almendro_1998.txt
@@ -0,0 +1,14 @@
+@patent{almendro,
+ author = {Almendro, Jos{\'e} L. and Mart{\'i}n, Jacinto and S{\'a}nchez,
+ Alberto and Nozal, Fernando},
+ title = {Elektromagnetisches Signalhorn},
+ number = {EU-29702195U},
+ date = 1998,
+ location = {countryfr and countryuk and countryde},
+ langid = {german},
+ annotation = {This is a \texttt{patent} entry with a \texttt{location}
+ field. The number is given in the \texttt{number} field. Note
+ the format of the \texttt{location} field in the database
+ file. Compare \texttt{laufenberg}, \texttt{sorace}, and
+ \texttt{kowalik}},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/averroes_1998.txt b/support/texlab/src/parser/test_data/bibtex/samples/averroes_1998.txt
new file mode 100644
index 0000000000..9f224353cc
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/averroes_1998.txt
@@ -0,0 +1,21 @@
+@book{averroes/bland,
+ author = {Averroes},
+ title = {The Epistle on the Possibility of Conjunction with the Active
+ Intellect by {Ibn Rushd} with the Commentary of {Moses Narboni}},
+ date = 1982,
+ editor = {Bland, Kalman P.},
+ translator = {Bland, Kalman P.},
+ series = {Moreshet: Studies in {Jewish} History, Literature and Thought},
+ number = 7,
+ publisher = {Jewish Theological Seminary of America},
+ location = {New York},
+ keywords = {primary},
+ langid = {english},
+ langidopts = {variant=american},
+ indextitle = {Epistle on the Possibility of Conjunction, The},
+ shorttitle = {Possibility of Conjunction},
+ annotation = {A \texttt{book} entry with a \texttt{series} and a
+ \texttt{number}. Note the concatenation of the \texttt{editor}
+ and \texttt{translator} fields as well as the
+ \texttt{indextitle} field},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/betram_1996.txt b/support/texlab/src/parser/test_data/bibtex/samples/betram_1996.txt
new file mode 100644
index 0000000000..b2d77e216f
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/betram_1996.txt
@@ -0,0 +1,16 @@
+@string{jams = {J.~Amer. Math. Soc.}}
+
+@article{bertram,
+ author = {Bertram, Aaron and Wentworth, Richard},
+ title = {Gromov invariants for holomorphic maps on {Riemann} surfaces},
+ journaltitle = jams,
+ date = 1996,
+ volume = 9,
+ number = 2,
+ pages = {529-571},
+ langid = {english},
+ langidopts = {variant=american},
+ shorttitle = {Gromov invariants},
+ annotation = {An \texttt{article} entry with a \texttt{volume} and a
+ \texttt{number} field},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/blom_2021.txt b/support/texlab/src/parser/test_data/bibtex/samples/blom_2021.txt
new file mode 100644
index 0000000000..a502e53e03
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/blom_2021.txt
@@ -0,0 +1,20 @@
+@article{DBLP:journals/corr/abs-2107-11903,
+ author = {Michelle L. Blom and
+ Jurlind Budurushi and
+ Ronald L. Rivest and
+ Philip B. Stark and
+ Peter J. Stuckey and
+ Vanessa Teague and
+ Damjan Vukcevic},
+ title = {Assertion-based Approaches to Auditing Complex Elections, with application
+ to party-list proportional elections},
+ journal = {CoRR},
+ volume = {abs/2107.11903},
+ year = {2021},
+ url = {https://arxiv.org/abs/2107.11903},
+ eprinttype = {arXiv},
+ eprint = {2107.11903},
+ timestamp = {Thu, 29 Jul 2021 16:14:15 +0200},
+ biburl = {https://dblp.org/rec/journals/corr/abs-2107-11903.bib},
+ bibsource = {dblp computer science bibliography, https://dblp.org}
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/combi_2004.txt b/support/texlab/src/parser/test_data/bibtex/samples/combi_2004.txt
new file mode 100644
index 0000000000..7d8545577d
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/combi_2004.txt
@@ -0,0 +1,16 @@
+@inproceedings{10.1145/967900.968040,
+ author = {Combi, Carlo and Pozzi, Giuseppe},
+ title = {Architectures for a Temporal Workflow Management System},
+ year = {2004},
+ isbn = {1581138121},
+ publisher = {Association for Computing Machinery},
+ address = {New York, NY, USA},
+ url = {https://doi.org/10.1145/967900.968040},
+ doi = {10.1145/967900.968040},
+ booktitle = {Proceedings of the 2004 ACM Symposium on Applied Computing},
+ pages = {659-666},
+ numpages = {8},
+ keywords = {active DBMS, temporal DBMS, workflow management system - WfMS, temporal workflow management system},
+ location = {Nicosia, Cyprus},
+ series = {SAC '04}
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/erwin_2007.txt b/support/texlab/src/parser/test_data/bibtex/samples/erwin_2007.txt
new file mode 100644
index 0000000000..116f5e7098
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/erwin_2007.txt
@@ -0,0 +1,14 @@
+@inproceedings{10.5555/1386993.1386994,
+ author = {Erwin, Alva and Gopalan, Raj P. and Achuthan, N. R.},
+ title = {A Bottom-up Projection Based Algorithm for Mining High Utility Itemsets},
+ year = {2007},
+ isbn = {9781920682651},
+ publisher = {Australian Computer Society, Inc.},
+ address = {AUS},
+ booktitle = {Proceedings of the 2nd International Workshop on Integrating Artificial Intelligence and Data Mining - Volume 84},
+ pages = {3-11},
+ numpages = {9},
+ keywords = {pattern growth, high utility itemset mining},
+ location = {Gold Coast, Australia},
+ series = {AIDM '07}
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/jain_1999.txt b/support/texlab/src/parser/test_data/bibtex/samples/jain_1999.txt
new file mode 100644
index 0000000000..c367ef12ee
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/jain_1999.txt
@@ -0,0 +1,18 @@
+@article{10.1145/331499.331504,
+ author = {Jain, A. K. and Murty, M. N. and Flynn, P. J.},
+ title = {Data Clustering: A Review},
+ year = {1999},
+ issue_date = {Sept. 1999},
+ publisher = {Association for Computing Machinery},
+ address = {New York, NY, USA},
+ volume = {31},
+ number = {3},
+ issn = {0360-0300},
+ url = {https://doi.org/10.1145/331499.331504},
+ doi = {10.1145/331499.331504},
+ journal = {ACM Comput. Surv.},
+ month = {sep},
+ pages = {264-323},
+ numpages = {60},
+ keywords = {incremental clustering, clustering applications, exploratory data analysis, cluster analysis, similarity indices, unsupervised learning}
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/kastenholz_2006.txt b/support/texlab/src/parser/test_data/bibtex/samples/kastenholz_2006.txt
new file mode 100644
index 0000000000..457f871491
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/kastenholz_2006.txt
@@ -0,0 +1,20 @@
+@string{jchph = {J.~Chem. Phys.}}
+
+@article{kastenholz,
+ author = {Kastenholz, M. A. and H{\"u}nenberger, Philippe H.},
+ title = {Computation of methodology\hyphen independent ionic solvation
+ free energies from molecular simulations},
+ journaltitle = jchph,
+ date = 2006,
+ subtitle = {{I}. {The} electrostatic potential in molecular liquids},
+ volume = 124,
+ eid = 124106,
+ doi = {10.1063/1.2172593},
+ langid = {english},
+ langidopts = {variant=american},
+ indextitle = {Computation of ionic solvation free energies},
+ annotation = {An \texttt{article} entry with an \texttt{eid} and a
+ \texttt{doi} field. Note that the \textsc{doi} is transformed
+ into a clickable link if \texttt{hyperref} support has been
+ enabled},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/knuth_1984.txt b/support/texlab/src/parser/test_data/bibtex/samples/knuth_1984.txt
new file mode 100644
index 0000000000..98c595e35c
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/knuth_1984.txt
@@ -0,0 +1,22 @@
+@book{knuth:ct:a,
+ author = {Knuth, Donald E.},
+ title = {The {\TeX book}},
+ date = 1984,
+ maintitle = {Computers \& Typesetting},
+ volume = {A},
+ publisher = {Addison-Wesley},
+ location = {Reading, Mass.},
+ langid = {english},
+ langidopts = {variant=american},
+ sorttitle = {Computers & Typesetting A},
+ indexsorttitle= {The TeXbook},
+ indextitle = {\protect\TeX book, The},
+ shorttitle = {\TeX book},
+ annotation = {The first volume of a five-volume book. Note the
+ \texttt{sorttitle} field. We want this
+ volume to be listed after the entry referring to the entire
+ five-volume set. Also note the \texttt{indextitle} and
+ \texttt{indexsorttitle} fields. Indexing packages that don't
+ generate robust index entries require some control sequences
+ to be protected from expansion},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/matuz_1990.txt b/support/texlab/src/parser/test_data/bibtex/samples/matuz_1990.txt
new file mode 100644
index 0000000000..cb803aab51
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/matuz_1990.txt
@@ -0,0 +1,14 @@
+@collection{matuz:doody,
+ editor = {Matuz, Roger},
+ title = {Contemporary Literary Criticism},
+ year = 1990,
+ volume = 61,
+ publisher = {Gale},
+ location = {Detroit},
+ pages = {204-208},
+ langid = {english},
+ langidopts = {variant=american},
+ annotation = {A \texttt{collection} entry providing the excerpt information
+ for the \texttt{doody} entry. Note the format of the
+ \texttt{pages} field},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/nietzsche_1998.txt b/support/texlab/src/parser/test_data/bibtex/samples/nietzsche_1998.txt
new file mode 100644
index 0000000000..e245be39bb
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/nietzsche_1998.txt
@@ -0,0 +1,24 @@
+@string{dtv = {Deutscher Taschenbuch-Verlag}}
+
+@mvbook{nietzsche:ksa,
+ author = {Nietzsche, Friedrich},
+ title = {S{\"a}mtliche Werke},
+ date = 1988,
+ editor = {Colli, Giorgio and Montinari, Mazzino},
+ edition = 2,
+ volumes = 15,
+ publisher = dtv # { and Walter de Gruyter},
+ location = {M{\"u}nchen and Berlin and New York},
+ langid = {german},
+ sorttitle = {Werke-00-000},
+ indexsorttitle= {Samtliche Werke},
+ subtitle = {Kritische Studienausgabe},
+ annotation = {The critical edition of Nietzsche's works. This is a
+ \texttt{mvbook} entry referring to a 15-volume work as a
+ whole. Note the \texttt{volumes} field and the format of the
+ \texttt{publisher} and \texttt{location} fields in the
+ database file. Also note the \texttt{sorttitle} and
+ field which is used to fine-tune the
+ sorting order of the bibliography. We want this item listed
+ first in the bibliography},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/bibtex/samples/rivest_1978.txt b/support/texlab/src/parser/test_data/bibtex/samples/rivest_1978.txt
new file mode 100644
index 0000000000..e1076145ea
--- /dev/null
+++ b/support/texlab/src/parser/test_data/bibtex/samples/rivest_1978.txt
@@ -0,0 +1,17 @@
+@article{10.1145/359340.359342,
+ author = {Rivest, R. L. and Shamir, A. and Adleman, L.},
+ title = {A Method for Obtaining Digital Signatures and Public-Key Cryptosystems},
+ year = {1978},
+ issue_date = {Feb. 1978},
+ publisher = {Association for Computing Machinery},
+ address = {New York, NY, USA},
+ volume = {21},
+ number = {2},
+ issn = {0001-0782},
+ url = {https://doi.org/10.1145/359340.359342},
+ doi = {10.1145/359340.359342},
+ journal = {Commun. ACM},
+ month = {feb},
+ pages = {120-126},
+ numpages = {7},
+} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/001.txt b/support/texlab/src/parser/test_data/build_log/001.txt
new file mode 100644
index 0000000000..fc9968af0f
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/001.txt
@@ -0,0 +1,83 @@
+This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:50
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**./parent.tex
+(./parent.tex
+LaTeX2e <2017-04-15>
+Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+(/TexLive/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+(/TexLive/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+)
+\c@part=\count79
+\c@section=\count80
+\c@subsection=\count81
+\c@subsubsection=\count82
+\c@paragraph=\count83
+\c@subparagraph=\count84
+\c@figure=\count85
+\c@table=\count86
+\abovecaptionskip=\skip41
+\belowcaptionskip=\skip42
+\bibindent=\dimen102
+)
+(/TexLive/texmf-dist/tex/latex/multirow/bigstrut.sty
+Package: bigstrut 2016/11/25 v2.2 Provide larger struts in tabulars
+\bigstrutjot=\dimen103
+)
+(/TexLive/texmf-dist/tex/latex/multirow/multirow.sty
+Package: multirow 2016/11/25 v2.2 Span multiple rows of a table
+\multirow@colwidth=\skip43
+\multirow@cntb=\count87
+\multirow@dima=\skip44
+) (./parent.aux)
+\openout1 = `parent.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+
+Overfull \hbox (200.00162pt too wide) in paragraph at lines 8--9
+[]\OT1/cmr/m/n/10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+[]
+
+LaTeX Font Info: External font `cmex10' loaded for size
+(Font) <7> on input line 11.
+LaTeX Font Info: External font `cmex10' loaded for size
+(Font) <5> on input line 11.
+
+Overfull \vbox (3.19998pt too high) detected at line 23
+[]
+
+[1
+
+{/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux) )
+Here is how much of TeX's memory you used:
+265 strings out of 492995
+3121 string characters out of 6138727
+55074 words of memory out of 5000000
+3896 multiletter control sequences out of 15000+600000
+3640 words of font info for 14 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+23i,20n,20p,124b,282s stack positions out of 5000i,500n,10000p,200000b,80000s
+</
+TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></TexLive/texmf-d
+ist/fonts/type1/public/amsfonts/cm/cmr7.pfb>
+Output written on parent.pdf (1 page, 17505 bytes).
+PDF statistics:
+16 PDF objects out of 1000 (max. 8388607)
+10 compressed objects within 1 object stream
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/002.txt b/support/texlab/src/parser/test_data/build_log/002.txt
new file mode 100644
index 0000000000..8d438d1f12
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/002.txt
@@ -0,0 +1,64 @@
+This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:52
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**./parent.tex
+(./parent.tex
+LaTeX2e <2017-04-15>
+Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+(/TexLive/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+(/TexLive/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+)
+\c@part=\count79
+\c@section=\count80
+\c@subsection=\count81
+\c@subsubsection=\count82
+\c@paragraph=\count83
+\c@subparagraph=\count84
+\c@figure=\count85
+\c@table=\count86
+\abovecaptionskip=\skip41
+\belowcaptionskip=\skip42
+\bibindent=\dimen102
+) (./parent.aux (./child.aux))
+\openout1 = `parent.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+(./child.tex
+! Undefined control sequence.
+l.1 \foo
+
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+
+) (./parent.aux) )
+Here is how much of TeX's memory you used:
+205 strings out of 492995
+2149 string characters out of 6138727
+54074 words of memory out of 5000000
+3841 multiletter control sequences out of 15000+600000
+3640 words of font info for 14 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+No pages of output.
+PDF statistics:
+0 PDF objects out of 1000 (max. 8388607)
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/003.txt b/support/texlab/src/parser/test_data/build_log/003.txt
new file mode 100644
index 0000000000..220614b91c
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/003.txt
@@ -0,0 +1,65 @@
+This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:51
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**./parent.tex
+(./parent.tex
+LaTeX2e <2017-04-15>
+Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+(/TexLive/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+(/TexLive/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+)
+\c@part=\count79
+\c@section=\count80
+\c@subsection=\count81
+\c@subsubsection=\count82
+\c@paragraph=\count83
+\c@subparagraph=\count84
+\c@figure=\count85
+\c@table=\count86
+\abovecaptionskip=\skip41
+\belowcaptionskip=\skip42
+\bibindent=\dimen102
+) (./parent.aux)
+\openout1 = `parent.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4.
+LaTeX Font Info: ... okay on input line 4.
+
+LaTeX Warning: Citation `foo' on page 1 undefined on input line 6.
+
+[1
+
+{/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux)
+
+LaTeX Warning: There were undefined references.
+
+)
+Here is how much of TeX's memory you used:
+204 strings out of 492995
+2142 string characters out of 6138727
+54074 words of memory out of 5000000
+3842 multiletter control sequences out of 15000+600000
+3948 words of font info for 15 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+23i,4n,21p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s
+</TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></TexLive/
+texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb>
+Output written on parent.pdf (1 page, 17339 bytes).
+PDF statistics:
+16 PDF objects out of 1000 (max. 8388607)
+10 compressed objects within 1 object stream
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/004.txt b/support/texlab/src/parser/test_data/build_log/004.txt
new file mode 100644
index 0000000000..4ab379cd53
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/004.txt
@@ -0,0 +1,83 @@
+This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:40
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**./parent.tex
+(./parent.tex
+LaTeX2e <2017-04-15>
+Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+(/TexLive/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+(/TexLive/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+)
+\c@part=\count79
+\c@section=\count80
+\c@subsection=\count81
+\c@subsubsection=\count82
+\c@paragraph=\count83
+\c@subparagraph=\count84
+\c@figure=\count85
+\c@table=\count86
+\abovecaptionskip=\skip41
+\belowcaptionskip=\skip42
+\bibindent=\dimen102
+)
+(/TexLive/texmf-dist/tex/generic/babel/babel.sty
+Package: babel 2017/05/19 3.10 The Babel package
+
+! Package babel Error: Unknown option `foo'. Either you misspelled it
+(babel) or the language definition file foo.ldf was not found.
+
+See the babel package documentation for explanation.
+Type H <return> for immediate help.
+...
+
+l.393 \ProcessOptions*
+
+Valid options are: shorthands=, KeepShorthandsActive,
+activeacute, activegrave, noconfigs, safe=, main=, math=
+headfoot=, strings=, config=, hyphenmap=, or a language name.
+
+! Package babel Error: You haven't specified a language option.
+
+See the babel package documentation for explanation.
+Type H <return> for immediate help.
+...
+
+l.426 ...ry to proceed from here, type x to quit.}
+
+You need to specify a language, either as a global option
+or as an optional argument to the \usepackage command;
+You shouldn't try to proceed from here, type x to quit.
+
+) (./parent.aux)
+\openout1 = `parent.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5.
+LaTeX Font Info: ... okay on input line 5.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5.
+LaTeX Font Info: ... okay on input line 5.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5.
+LaTeX Font Info: ... okay on input line 5.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5.
+LaTeX Font Info: ... okay on input line 5.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5.
+LaTeX Font Info: ... okay on input line 5.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5.
+LaTeX Font Info: ... okay on input line 5.
+(./parent.aux) )
+Here is how much of TeX's memory you used:
+304 strings out of 492995
+3421 string characters out of 6138727
+56035 words of memory out of 5000000
+3938 multiletter control sequences out of 15000+600000
+3640 words of font info for 14 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+No pages of output.
+PDF statistics:
+0 PDF objects out of 1000 (max. 8388607)
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/005.txt b/support/texlab/src/parser/test_data/build_log/005.txt
new file mode 100644
index 0000000000..07c95bfc6b
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/005.txt
@@ -0,0 +1,385 @@
+This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:45
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**./parent.tex
+(./parent.tex
+LaTeX2e <2017-04-15>
+Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+(/TexLive/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+(/TexLive/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+)
+\c@part=\count79
+\c@section=\count80
+\c@subsection=\count81
+\c@subsubsection=\count82
+\c@paragraph=\count83
+\c@subparagraph=\count84
+\c@figure=\count85
+\c@table=\count86
+\abovecaptionskip=\skip41
+\belowcaptionskip=\skip42
+\bibindent=\dimen102
+)
+(/TexLive/texmf-dist/tex/generic/babel/babel.sty
+Package: babel 2017/05/19 3.10 The Babel package
+
+(/TexLive/texmf-dist/tex/generic/babel-spanish/spanish.ldf
+Language: spanish.ldf 2016/03/03 v5.0p Spanish support from the babel system
+
+(/TexLive/texmf-dist/tex/generic/babel/babel.def
+File: babel.def 2017/05/19 3.10 Babel common definitions
+\babel@savecnt=\count87
+\U@D=\dimen103
+)
+\es@quottoks=\toks14
+\es@quotdepth=\count88
+Package babel Info: Making " an active character on input line 561.
+Package babel Info: Making . an active character on input line 662.
+Package babel Info: Making < an active character on input line 707.
+Package babel Info: Making > an active character on input line 707.
+))
+(/TexLive/texmf-dist/tex/latex/biblatex/biblatex.sty
+Package: biblatex 2016/12/05 v3.7 programmable bibliographies (PK/JW/AB)
+
+(/TexLive/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty
+Package: pdftexcmds 2017/03/19 v0.25 Utility functions of pdfTeX for LuaTeX (HO
+)
+
+(/TexLive/texmf-dist/tex/generic/oberdiek/infwarerr.sty
+Package: infwarerr 2016/05/16 v1.4 Providing info/warning/error messages (HO)
+)
+(/TexLive/texmf-dist/tex/generic/oberdiek/ifluatex.sty
+Package: ifluatex 2016/05/16 v1.4 Provides the ifluatex switch (HO)
+Package ifluatex Info: LuaTeX not detected.
+)
+(/TexLive/texmf-dist/tex/generic/oberdiek/ltxcmds.sty
+Package: ltxcmds 2016/05/16 v1.23 LaTeX kernel commands for general use (HO)
+)
+(/TexLive/texmf-dist/tex/generic/oberdiek/ifpdf.sty
+Package: ifpdf 2017/03/15 v3.2 Provides the ifpdf switch
+)
+Package pdftexcmds Info: LuaTeX not detected.
+Package pdftexcmds Info: \pdf@primitive is available.
+Package pdftexcmds Info: \pdf@ifprimitive is available.
+Package pdftexcmds Info: \pdfdraftmode found.
+)
+(/TexLive/texmf-dist/tex/latex/etoolbox/etoolbox.sty
+Package: etoolbox 2017/01/02 v2.4 e-TeX tools for LaTeX (JAW)
+\etb@tempcnta=\count89
+)
+(/TexLive/texmf-dist/tex/latex/graphics/keyval.sty
+Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
+\KV@toks@=\toks15
+)
+(/TexLive/texmf-dist/tex/latex/oberdiek/kvoptions.sty
+Package: kvoptions 2016/05/16 v3.12 Key value format for package options (HO)
+
+(/TexLive/texmf-dist/tex/generic/oberdiek/kvsetkeys.sty
+Package: kvsetkeys 2016/05/16 v1.17 Key value parser (HO)
+
+(/TexLive/texmf-dist/tex/generic/oberdiek/etexcmds.sty
+Package: etexcmds 2016/05/16 v1.6 Avoid name clashes with e-TeX commands (HO)
+Package etexcmds Info: Could not find \expanded.
+(etexcmds) That can mean that you are not using pdfTeX 1.50 or
+(etexcmds) that some package has redefined \expanded.
+(etexcmds) In the latter case, load this package earlier.
+)))
+(/TexLive/texmf-dist/tex/latex/logreq/logreq.sty
+Package: logreq 2010/08/04 v1.0 xml request logger
+\lrq@indent=\count90
+
+(/TexLive/texmf-dist/tex/latex/logreq/logreq.def
+File: logreq.def 2010/08/04 v1.0 logreq spec v1.0
+))
+(/TexLive/texmf-dist/tex/latex/base/ifthen.sty
+Package: ifthen 2014/09/29 v1.1c Standard LaTeX ifthen package (DPC)
+)
+(/TexLive/texmf-dist/tex/latex/url/url.sty
+\Urlmuskip=\muskip10
+Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc.
+)
+(/TexLive/texmf-dist/tex/generic/xstring/xstring.sty
+(/TexLive/texmf-dist/tex/generic/xstring/xstring.tex
+\@xs@message=\write3
+\integerpart=\count91
+\decimalpart=\count92
+)
+Package: xstring 2013/10/13 v1.7c String manipulations (C Tellechea)
+)
+\c@tabx@nest=\count93
+\c@listtotal=\count94
+\c@listcount=\count95
+\c@liststart=\count96
+\c@liststop=\count97
+\c@citecount=\count98
+\c@citetotal=\count99
+\c@multicitecount=\count100
+\c@multicitetotal=\count101
+\c@instcount=\count102
+\c@maxnames=\count103
+\c@minnames=\count104
+\c@maxitems=\count105
+\c@minitems=\count106
+\c@citecounter=\count107
+\c@savedcitecounter=\count108
+\c@uniquelist=\count109
+\c@uniquename=\count110
+\c@refsection=\count111
+\c@refsegment=\count112
+\c@maxextratitle=\count113
+\c@maxextratitleyear=\count114
+\c@maxextrayear=\count115
+\c@maxextraalpha=\count116
+\c@abbrvpenalty=\count117
+\c@highnamepenalty=\count118
+\c@lownamepenalty=\count119
+\c@maxparens=\count120
+\c@parenlevel=\count121
+\blx@tempcnta=\count122
+\blx@tempcntb=\count123
+\blx@tempcntc=\count124
+\blx@maxsection=\count125
+\blx@maxsegment@0=\count126
+\blx@notetype=\count127
+\blx@parenlevel@text=\count128
+\blx@parenlevel@foot=\count129
+\blx@sectionciteorder@0=\count130
+\labelnumberwidth=\skip43
+\labelalphawidth=\skip44
+\biblabelsep=\skip45
+\bibitemsep=\skip46
+\bibnamesep=\skip47
+\bibinitsep=\skip48
+\bibparsep=\skip49
+\bibhang=\skip50
+\blx@bcfin=\read1
+\blx@bcfout=\write4
+\c@mincomprange=\count131
+\c@maxcomprange=\count132
+\c@mincompwidth=\count133
+Package biblatex Info: Trying to load biblatex default data model...
+Package biblatex Info: ... file 'blx-dm.def' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/blx-dm.def)
+Package biblatex Info: Trying to load biblatex style data model...
+Package biblatex Info: ... file 'ieee.dbx' not found.
+Package biblatex Info: Trying to load biblatex custom data model...
+Package biblatex Info: ... file 'biblatex-dm.cfg' not found.
+\c@afterword=\count134
+\c@savedafterword=\count135
+\c@annotator=\count136
+\c@savedannotator=\count137
+\c@author=\count138
+\c@savedauthor=\count139
+\c@bookauthor=\count140
+\c@savedbookauthor=\count141
+\c@commentator=\count142
+\c@savedcommentator=\count143
+\c@editor=\count144
+\c@savededitor=\count145
+\c@editora=\count146
+\c@savededitora=\count147
+\c@editorb=\count148
+\c@savededitorb=\count149
+\c@editorc=\count150
+\c@savededitorc=\count151
+\c@foreword=\count152
+\c@savedforeword=\count153
+\c@holder=\count154
+\c@savedholder=\count155
+\c@introduction=\count156
+\c@savedintroduction=\count157
+\c@namea=\count158
+\c@savednamea=\count159
+\c@nameb=\count160
+\c@savednameb=\count161
+\c@namec=\count162
+\c@savednamec=\count163
+\c@translator=\count164
+\c@savedtranslator=\count165
+\c@shortauthor=\count166
+\c@savedshortauthor=\count167
+\c@shorteditor=\count168
+\c@savedshorteditor=\count169
+\c@labelname=\count170
+\c@savedlabelname=\count171
+\c@institution=\count172
+\c@savedinstitution=\count173
+\c@lista=\count174
+\c@savedlista=\count175
+\c@listb=\count176
+\c@savedlistb=\count177
+\c@listc=\count178
+\c@savedlistc=\count179
+\c@listd=\count180
+\c@savedlistd=\count181
+\c@liste=\count182
+\c@savedliste=\count183
+\c@listf=\count184
+\c@savedlistf=\count185
+\c@location=\count186
+\c@savedlocation=\count187
+\c@organization=\count188
+\c@savedorganization=\count189
+\c@origlocation=\count190
+\c@savedoriglocation=\count191
+\c@origpublisher=\count192
+\c@savedorigpublisher=\count193
+\c@publisher=\count194
+\c@savedpublisher=\count195
+\c@language=\count196
+\c@savedlanguage=\count197
+\c@pageref=\count198
+\c@savedpageref=\count199
+\shorthandwidth=\skip51
+\shortjournalwidth=\skip52
+\shortserieswidth=\skip53
+\shorttitlewidth=\skip54
+\shortauthorwidth=\skip55
+\shorteditorwidth=\skip56
+Package biblatex Info: Trying to load compatibility code...
+Package biblatex Info: ... file 'blx-compat.def' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/blx-compat.def
+File: blx-compat.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB)
+)
+Package biblatex Info: Trying to load generic definitions...
+Package biblatex Info: ... file 'biblatex.def' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/biblatex.def
+File: biblatex.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB)
+\c@textcitecount=\count266
+\c@textcitetotal=\count267
+\c@textcitemaxnames=\count268
+\c@biburlnumpenalty=\count269
+\c@biburlucpenalty=\count270
+\c@biburllcpenalty=\count271
+\c@smartand=\count272
+)
+Package biblatex Info: Trying to load bibliography style 'ieee'...
+Package biblatex Info: ... file 'ieee.bbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.bbx
+File: ieee.bbx 2017/03/27 v1.2d biblatex bibliography style
+Package biblatex Info: Trying to load bibliography style 'numeric-comp'...
+Package biblatex Info: ... file 'numeric-comp.bbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric-comp.bbx
+File: numeric-comp.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
+Package biblatex Info: Trying to load bibliography style 'numeric'...
+Package biblatex Info: ... file 'numeric.bbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx
+File: numeric.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
+Package biblatex Info: Trying to load bibliography style 'standard'...
+Package biblatex Info: ... file 'standard.bbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/bbx/standard.bbx
+File: standard.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
+\c@bbx:relatedcount=\count273
+\c@bbx:relatedtotal=\count274
+))))
+Package biblatex Info: Trying to load citation style 'ieee'...
+Package biblatex Info: ... file 'ieee.cbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.cbx
+File: ieee.cbx 2017/03/27 v1.2d biblatex citation style
+Package biblatex Info: Trying to load citation style 'numeric-comp'...
+Package biblatex Info: ... file 'numeric-comp.cbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/cbx/numeric-comp.cbx
+File: numeric-comp.cbx 2016/12/05 v3.7 biblatex citation style (PK/JW/AB)
+\c@cbx@tempcnta=\count275
+\c@cbx@tempcntb=\count276
+Package biblatex Info: Redefining '\cite'.
+Package biblatex Info: Redefining '\parencite'.
+Package biblatex Info: Redefining '\footcite'.
+Package biblatex Info: Redefining '\footcitetext'.
+Package biblatex Info: Redefining '\smartcite'.
+Package biblatex Info: Redefining '\supercite'.
+Package biblatex Info: Redefining '\textcite'.
+Package biblatex Info: Redefining '\textcites'.
+Package biblatex Info: Redefining '\cites'.
+Package biblatex Info: Redefining '\parencites'.
+Package biblatex Info: Redefining '\smartcites'.
+)
+Package biblatex Info: Redefining '\cite'.
+Package biblatex Info: Redefining '\cites'.
+)
+Package biblatex Info: Trying to load configuration file...
+Package biblatex Info: ... file 'biblatex.cfg' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/biblatex.cfg
+File: biblatex.cfg
+))
+Package biblatex Info: Trying to load language 'spanish'...
+Package biblatex Info: ... file 'spanish.lbx' found.
+
+(/TexLive/texmf-dist/tex/latex/biblatex/lbx/spanish.lbx
+File: spanish.lbx 2016/12/05 v3.7 biblatex localization (PK/JW/AB)
+)
+
+Package biblatex Warning: 'babel/polyglossia' detected but 'csquotes' missing.
+(biblatex) Loading 'csquotes' recommended.
+
+\@quotelevel=\count277
+\@quotereset=\count278
+(./parent.aux)
+\openout1 = `parent.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6.
+LaTeX Font Info: ... okay on input line 6.
+LaTeX Info: Redefining \sptext on input line 6.
+LaTeX Info: Redefining \. on input line 6.
+LaTeX Info: Redefining \% on input line 6.
+Package biblatex Info: No input encoding detected.
+(biblatex) Assuming 'ascii'.
+Package biblatex Info: Automatic encoding selection.
+(biblatex) Assuming data encoding 'ascii'.
+\openout4 = `parent.bcf'.
+
+Package biblatex Info: Trying to load bibliographic data...
+Package biblatex Info: ... file 'parent.bbl' not found.
+
+No file parent.bbl.
+Package biblatex Info: Reference section=0 on input line 6.
+Package biblatex Info: Reference segment=0 on input line 6.
+(./parent.aux)
+
+LaTeX Warning: There were undefined references.
+
+Package biblatex Warning: Please (re)run Biber on the file:
+(biblatex) parent
+(biblatex) and rerun LaTeX afterwards.
+
+Package logreq Info: Writing requests to 'parent.run.xml'.
+\openout1 = `parent.run.xml'.
+
+)
+Here is how much of TeX's memory you used:
+7717 strings out of 492995
+133301 string characters out of 6138727
+557258 words of memory out of 5000000
+11248 multiletter control sequences out of 15000+600000
+3640 words of font info for 14 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+35i,1n,30p,856b,700s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+No pages of output.
+PDF statistics:
+0 PDF objects out of 1000 (max. 8388607)
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/006.txt b/support/texlab/src/parser/test_data/build_log/006.txt
new file mode 100644
index 0000000000..3edb18b8c7
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/006.txt
@@ -0,0 +1,122 @@
+This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:33
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**./parent.tex
+(./parent.tex
+LaTeX2e <2017-04-15>
+Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+(/TexLive/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+(/TexLive/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+)
+\c@part=\count79
+\c@section=\count80
+\c@subsection=\count81
+\c@subsubsection=\count82
+\c@paragraph=\count83
+\c@subparagraph=\count84
+\c@figure=\count85
+\c@table=\count86
+\abovecaptionskip=\skip41
+\belowcaptionskip=\skip42
+\bibindent=\dimen102
+) (./parent.aux
+(./child.tex.aux))
+\openout1 = `parent.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+\openout2 = `child.aux'.
+
+(./child.tex)
+! Undefined control sequence.
+l.7 \foo
+
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+
+! Missing $ inserted.
+<inserted text>
+ $
+l.8 \bar
+
+I've inserted a begin-math/end-math symbol since I think
+you left one out. Proceed, with fingers crossed.
+
+LaTeX Font Info: External font `cmex10' loaded for size
+(Font) <7> on input line 8.
+LaTeX Font Info: External font `cmex10' loaded for size
+(Font) <5> on input line 8.
+! Undefined control sequence.
+l.9 \baz
+
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+
+! Missing { inserted.
+<to be read again>
+ \par
+l.10
+
+A left brace was mandatory here, so I've put one in.
+You might want to delete and/or insert some corrections
+so that I will find a matching right brace soon.
+(If you're confused by all this, try typing `I}' now.)
+
+! Missing $ inserted.
+<inserted text>
+ $
+l.10
+
+I've inserted a begin-math/end-math symbol since I think
+you left one out. Proceed, with fingers crossed.
+
+! Missing } inserted.
+<inserted text>
+ }
+l.10
+
+I've inserted something that you may have forgotten.
+(See the <inserted text> above.)
+With luck, this will get me unwedged. But if you
+really didn't forget anything, try typing `2' now; then
+my insertion and my current dilemma will both disappear.
+
+[1
+
+{/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux
+(./child.aux)) )
+Here is how much of TeX's memory you used:
+212 strings out of 492995
+2238 string characters out of 6138727
+54074 words of memory out of 5000000
+3843 multiletter control sequences out of 15000+600000
+3640 words of font info for 14 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+23i,4n,17p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s
+</TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb
+>
+Output written on parent.pdf (1 page, 8329 bytes).
+PDF statistics:
+12 PDF objects out of 1000 (max. 8388607)
+7 compressed objects within 1 object stream
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/build_log/007.txt b/support/texlab/src/parser/test_data/build_log/007.txt
new file mode 100644
index 0000000000..4c86405c0d
--- /dev/null
+++ b/support/texlab/src/parser/test_data/build_log/007.txt
@@ -0,0 +1,176 @@
+This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2021/W32TeX) (preloaded format=pdflatex 2021.6.19) 5 NOV 2021 19:10
+entering extended mode
+restricted \write18 enabled.
+%&-line parsing enabled.
+**/some/folder/a.tex
+(/some/folder/a.tex
+LaTeX2e <2021-06-01> patch level 1
+L3 programming layer <2021-06-18>
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrartcl.cls
+Document Class: scrartcl 2021/03/17 v3.33 KOMA-Script document class (article)
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrkbase.sty
+Package: scrkbase 2021/03/17 v3.33 KOMA-Script package (KOMA-Script-dependent b
+asics and keyval usage)
+
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrbase.sty
+Package: scrbase 2021/03/17 v3.33 KOMA-Script package (KOMA-Script-independent
+basics and keyval usage)
+
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrlfile.sty
+Package: scrlfile 2021/03/17 v3.33 KOMA-Script package (file load hooks)
+
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrlfile-hook.sty
+Package: scrlfile-hook 2021/03/17 v3.33 KOMA-Script package (using LaTeX hooks)
+
+
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrlogo.sty
+Package: scrlogo 2021/03/17 v3.33 KOMA-Script package (logo)
+)))
+(/texlive/2021/texmf-dist/tex/latex/graphics/keyval.sty
+Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
+\KV@toks@=\toks16
+)
+Applying: [2021/05/01] Usage of raw option list on input line 252.
+Already applied: [0000/00/00] compatibility for LaTeX before 2021/05/01 on inpu
+t line 337.
+))
+==> First Aid for scrkbase.sty no longer applied!
+(/texlive/2021/texmf-dist/tex/latex/koma-script/tocbasic.sty
+Package: tocbasic 2021/03/17 v3.33 KOMA-Script package (handling toc-files)
+\scr@dte@tocline@numberwidth=\skip47
+\scr@dte@tocline@numbox=\box50
+)
+Package tocbasic Info: babel extension for `toc' omitted
+(tocbasic) because of missing \bbl@set@language on input line 135.
+Class scrartcl Info: File `scrsize11pt.clo' used instead of
+(scrartcl) file `scrsize11.clo' to setup font sizes on input line 223
+9.
+
+(/texlive/2021/texmf-dist/tex/latex/koma-script/scrsize11pt.clo
+File: scrsize11pt.clo 2021/03/17 v3.33 KOMA-Script font size class option (11pt
+)
+)
+(/texlive/2021/texmf-dist/tex/latex/koma-script/typearea.sty
+Package: typearea 2021/03/17 v3.33 KOMA-Script package (type area)
+\ta@bcor=\skip48
+\ta@div=\count182
+\ta@hblk=\skip49
+\ta@vblk=\skip50
+\ta@temp=\skip51
+\footheight=\skip52
+Package typearea Info: These are the values describing the layout:
+(typearea) DIV = 10
+(typearea) BCOR = 0.0pt
+(typearea) \paperwidth = 597.50793pt
+(typearea) \textwidth = 418.25555pt
+(typearea) DIV departure = -6%
+(typearea) \evensidemargin = 17.3562pt
+(typearea) \oddsidemargin = 17.3562pt
+(typearea) \paperheight = 845.04694pt
+(typearea) \textheight = 595.80026pt
+(typearea) \topmargin = -25.16531pt
+(typearea) \headheight = 17.0pt
+(typearea) \headsep = 20.40001pt
+(typearea) \topskip = 11.0pt
+(typearea) \footskip = 47.6pt
+(typearea) \baselineskip = 13.6pt
+(typearea) on input line 1741.
+)
+\c@part=\count183
+\c@section=\count184
+\c@subsection=\count185
+\c@subsubsection=\count186
+\c@paragraph=\count187
+\c@subparagraph=\count188
+\scr@dte@section@maxnumwidth=\skip53
+Class scrartcl Info: using compatibility default `runin=bysign'
+(scrartcl) for `\section on input line 4846.
+Class scrartcl Info: using compatibility default `afterindent=bysign'
+(scrartcl) for `\section on input line 4846.
+\scr@dte@part@maxnumwidth=\skip54
+Class scrartcl Info: using compatibility default `afterindent=false'
+(scrartcl) for `\part on input line 4854.
+\scr@dte@subsection@maxnumwidth=\skip55
+Class scrartcl Info: using compatibility default `runin=bysign'
+(scrartcl) for `\subsection on input line 4864.
+Class scrartcl Info: using compatibility default `afterindent=bysign'
+(scrartcl) for `\subsection on input line 4864.
+\scr@dte@subsubsection@maxnumwidth=\skip56
+Class scrartcl Info: using compatibility default `runin=bysign'
+(scrartcl) for `\subsubsection on input line 4874.
+Class scrartcl Info: using compatibility default `afterindent=bysign'
+(scrartcl) for `\subsubsection on input line 4874.
+\scr@dte@paragraph@maxnumwidth=\skip57
+Class scrartcl Info: using compatibility default `runin=bysign'
+(scrartcl) for `\paragraph on input line 4885.
+Class scrartcl Info: using compatibility default `afterindent=bysign'
+(scrartcl) for `\paragraph on input line 4885.
+\scr@dte@subparagraph@maxnumwidth=\skip58
+Class scrartcl Info: using compatibility default `runin=bysign'
+(scrartcl) for `\subparagraph on input line 4895.
+Class scrartcl Info: using compatibility default `afterindent=bysign'
+(scrartcl) for `\subparagraph on input line 4895.
+\abovecaptionskip=\skip59
+\belowcaptionskip=\skip60
+\c@pti@nb@sid@b@x=\box51
+Package tocbasic Info: babel extension for `lof' omitted
+(tocbasic) because of missing \bbl@set@language on input line 6127.
+
+\scr@dte@figure@maxnumwidth=\skip61
+\c@figure=\count189
+Package tocbasic Info: babel extension for `lot' omitted
+(tocbasic) because of missing \bbl@set@language on input line 6139.
+
+\scr@dte@table@maxnumwidth=\skip62
+\c@table=\count190
+Class scrartcl Info: Redefining `\numberline' on input line 6303.
+\bibindent=\dimen138
+)
+(/texlive/2021/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
+File: l3backend-pdftex.def 2021-05-07 L3 backend support: PDF output (pdfTeX)
+\l__color_backend_stack_int=\count191
+\l__pdf_internal_box=\box52
+) (./a.aux)
+\openout1 = `a.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
+LaTeX Font Info: ... okay on input line 3.
+Package scrbase Info: activating english \contentsname on input line 3.
+Package scrbase Info: activating english \listfigurename on input line 3.
+Package scrbase Info: activating english \listtablename on input line 3.
+! Undefined control sequence.
+l.4 \lsdkfjlskdfj
+
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+
+(./a.aux) )
+Here is how much of TeX's memory you used:
+3199 strings out of 478510
+74549 string characters out of 5853586
+510929 words of memory out of 5000000
+21227 multiletter control sequences out of 15000+600000
+403730 words of font info for 28 fonts, out of 8000000 for 9000
+1141 hyphenation exceptions out of 8191
+108i,1n,108p,10625b,270s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+No pages of output.
+PDF statistics:
+0 PDF objects out of 1000 (max. 8388607)
+0 named destinations out of 1000 (max. 500000)
+1 words of extra memory for PDF output out of 10000 (max. 10000000) \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/block_comments.txt b/support/texlab/src/parser/test_data/latex/block_comments.txt
new file mode 100644
index 0000000000..d4e63ab1c8
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/block_comments.txt
@@ -0,0 +1,9 @@
+Foo
+\iffalse
+Test1
+\fi
+Bar
+\iffalse
+\fii
+\fi
+Baz \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/caption/caption_default.txt b/support/texlab/src/parser/test_data/latex/caption/caption_default.txt
new file mode 100644
index 0000000000..9084f143bf
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/caption/caption_default.txt
@@ -0,0 +1 @@
+\caption[qux]{Foo \Bar Baz} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/caption/caption_default_error.txt b/support/texlab/src/parser/test_data/latex/caption/caption_default_error.txt
new file mode 100644
index 0000000000..62e5962816
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/caption/caption_default_error.txt
@@ -0,0 +1 @@
+\caption[qux]{Foo \Bar Baz \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/caption/caption_figure.txt b/support/texlab/src/parser/test_data/latex/caption/caption_figure.txt
new file mode 100644
index 0000000000..8a1d3067d8
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/caption/caption_figure.txt
@@ -0,0 +1 @@
+\begin{figure}\caption{Foo}\end{figure} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/caption/caption_minimal.txt b/support/texlab/src/parser/test_data/latex/caption/caption_minimal.txt
new file mode 100644
index 0000000000..07af0af8ca
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/caption/caption_minimal.txt
@@ -0,0 +1 @@
+\caption{Foo \Bar Baz} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/caption/caption_minimal_error.txt b/support/texlab/src/parser/test_data/latex/caption/caption_minimal_error.txt
new file mode 100644
index 0000000000..747db731c1
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/caption/caption_minimal_error.txt
@@ -0,0 +1 @@
+\caption{Foo \Bar Baz \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_empty.txt b/support/texlab/src/parser/test_data/latex/citation/citation_empty.txt
new file mode 100644
index 0000000000..06704b89a4
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_empty.txt
@@ -0,0 +1 @@
+\cite{} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_missing_brace.txt b/support/texlab/src/parser/test_data/latex/citation/citation_missing_brace.txt
new file mode 100644
index 0000000000..1f8e4c9310
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_missing_brace.txt
@@ -0,0 +1 @@
+\cite{foo \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_multiple_keys.txt b/support/texlab/src/parser/test_data/latex/citation/citation_multiple_keys.txt
new file mode 100644
index 0000000000..be5da02c62
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_multiple_keys.txt
@@ -0,0 +1 @@
+\cite{foo, bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_prenote.txt b/support/texlab/src/parser/test_data/latex/citation/citation_prenote.txt
new file mode 100644
index 0000000000..5ae28c28ec
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_prenote.txt
@@ -0,0 +1 @@
+\cite[foo]{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_prenote_postnote.txt b/support/texlab/src/parser/test_data/latex/citation/citation_prenote_postnote.txt
new file mode 100644
index 0000000000..23103b2476
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_prenote_postnote.txt
@@ -0,0 +1 @@
+\cite[foo][bar]{baz} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_redundant_comma.txt b/support/texlab/src/parser/test_data/latex/citation/citation_redundant_comma.txt
new file mode 100644
index 0000000000..68e3c2d36e
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_redundant_comma.txt
@@ -0,0 +1 @@
+\cite{,foo,} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_simple.txt b/support/texlab/src/parser/test_data/latex/citation/citation_simple.txt
new file mode 100644
index 0000000000..cd5809ef25
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_simple.txt
@@ -0,0 +1 @@
+\cite{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/citation/citation_star.txt b/support/texlab/src/parser/test_data/latex/citation/citation_star.txt
new file mode 100644
index 0000000000..d1a2677021
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/citation/citation_star.txt
@@ -0,0 +1 @@
+\nocite{*} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_definition_simple.txt b/support/texlab/src/parser/test_data/latex/color/color_definition_simple.txt
new file mode 100644
index 0000000000..e8b58c8162
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_definition_simple.txt
@@ -0,0 +1 @@
+\definecolor{foo}{rgb}{255,168,0} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_reference_simple.txt b/support/texlab/src/parser/test_data/latex/color/color_reference_simple.txt
new file mode 100644
index 0000000000..4fea2b24ec
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_reference_simple.txt
@@ -0,0 +1 @@
+\color{black} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_set_definition_error1.txt b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error1.txt
new file mode 100644
index 0000000000..f377ffcf2f
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error1.txt
@@ -0,0 +1 @@
+\definecolorset[ty]{rgb,HTML}{foo}{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_set_definition_error2.txt b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error2.txt
new file mode 100644
index 0000000000..0b5fa35868
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error2.txt
@@ -0,0 +1 @@
+\definecolorset{rgb,HTML}{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_set_definition_error3.txt b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error3.txt
new file mode 100644
index 0000000000..fb695b4530
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error3.txt
@@ -0,0 +1 @@
+\definecolorset{rgb,HTML} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_set_definition_error4.txt b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error4.txt
new file mode 100644
index 0000000000..7c494133bf
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_set_definition_error4.txt
@@ -0,0 +1 @@
+\definecolorset \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/color/color_set_definition_simple.txt b/support/texlab/src/parser/test_data/latex/color/color_set_definition_simple.txt
new file mode 100644
index 0000000000..c812e61041
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/color/color_set_definition_simple.txt
@@ -0,0 +1 @@
+\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_argc.txt b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_argc.txt
new file mode 100644
index 0000000000..ef63ef62cb
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_argc.txt
@@ -0,0 +1 @@
+\newcommand{\foo}{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl.txt b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl.txt
new file mode 100644
index 0000000000..5ffc7e97c2
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl.txt
@@ -0,0 +1 @@
+\newcommand{\foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl_error.txt b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl_error.txt
new file mode 100644
index 0000000000..9323f5a313
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_no_impl_error.txt
@@ -0,0 +1 @@
+\newcommand{\foo \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/command_definition_optional.txt b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_optional.txt
new file mode 100644
index 0000000000..c3fb7225f3
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_optional.txt
@@ -0,0 +1 @@
+\newcommand{\foo}[1][def]{#1} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/command_definition_simple.txt b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_simple.txt
new file mode 100644
index 0000000000..ffce86f21c
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_simple.txt
@@ -0,0 +1 @@
+\newcommand[1]{\id}{#1} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/command_definition_with_begin.txt b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_with_begin.txt
new file mode 100644
index 0000000000..e7ce7984cb
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/command_definition_with_begin.txt
@@ -0,0 +1 @@
+\newcommand{\CVSubHeadingListStart}{\begin{itemize}[leftmargin=0.5cm, label={}]} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/math_operator_no_impl.txt b/support/texlab/src/parser/test_data/latex/command_definition/math_operator_no_impl.txt
new file mode 100644
index 0000000000..685be22ce6
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/math_operator_no_impl.txt
@@ -0,0 +1 @@
+\DeclareMathOperator{\foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/command_definition/math_operator_simple.txt b/support/texlab/src/parser/test_data/latex/command_definition/math_operator_simple.txt
new file mode 100644
index 0000000000..ffbf671f74
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/command_definition/math_operator_simple.txt
@@ -0,0 +1 @@
+\DeclareMathOperator{\foo}{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/environment/environment_asymptote.txt b/support/texlab/src/parser/test_data/latex/environment/environment_asymptote.txt
new file mode 100644
index 0000000000..76646075ea
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/environment/environment_asymptote.txt
@@ -0,0 +1,3 @@
+\begin{asy}
+ printf("Hello World\n");
+\end{asy} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/environment/environment_definition.txt b/support/texlab/src/parser/test_data/latex/environment/environment_definition.txt
new file mode 100644
index 0000000000..d3a5291dca
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/environment/environment_definition.txt
@@ -0,0 +1 @@
+\newenvironment{bar}[1]{\begin{foo}}{\end{foo}} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/environment/environment_definition_optional_arg.txt b/support/texlab/src/parser/test_data/latex/environment/environment_definition_optional_arg.txt
new file mode 100644
index 0000000000..bead3228a4
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/environment/environment_definition_optional_arg.txt
@@ -0,0 +1 @@
+\newenvironment{foo}[1][default]{begin}{end} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/environment/environment_nested.txt b/support/texlab/src/parser/test_data/latex/environment/environment_nested.txt
new file mode 100644
index 0000000000..8d84019ac7
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/environment/environment_nested.txt
@@ -0,0 +1 @@
+\begin{foo} \begin{qux} \end{baz} \end{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/environment/environment_nested_missing_braces.txt b/support/texlab/src/parser/test_data/latex/environment/environment_nested_missing_braces.txt
new file mode 100644
index 0000000000..35ce901a83
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/environment/environment_nested_missing_braces.txt
@@ -0,0 +1 @@
+\begin{foo \begin{qux Hello World \end{baz} \end{bar \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/environment/environment_simple.txt b/support/texlab/src/parser/test_data/latex/environment/environment_simple.txt
new file mode 100644
index 0000000000..309b559957
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/environment/environment_simple.txt
@@ -0,0 +1 @@
+\begin{foo} Hello World \end{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/equation.txt b/support/texlab/src/parser/test_data/latex/equation.txt
new file mode 100644
index 0000000000..7f85196961
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/equation.txt
@@ -0,0 +1 @@
+\[ foo bar \] \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/equation_missing_begin.txt b/support/texlab/src/parser/test_data/latex/equation_missing_begin.txt
new file mode 100644
index 0000000000..9e5707b196
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/equation_missing_begin.txt
@@ -0,0 +1 @@
+\begin{a} foo bar \] \end{b} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/generic_command_args.txt b/support/texlab/src/parser/test_data/latex/generic_command_args.txt
new file mode 100644
index 0000000000..2d62314c5c
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/generic_command_args.txt
@@ -0,0 +1 @@
+\foo{bar}[qux] \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/generic_command_empty.txt b/support/texlab/src/parser/test_data/latex/generic_command_empty.txt
new file mode 100644
index 0000000000..eeb4a48255
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/generic_command_empty.txt
@@ -0,0 +1 @@
+\foo \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/generic_command_escape.txt b/support/texlab/src/parser/test_data/latex/generic_command_escape.txt
new file mode 100644
index 0000000000..133d674d1e
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/generic_command_escape.txt
@@ -0,0 +1 @@
+\# \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/acronym_declaration.txt b/support/texlab/src/parser/test_data/latex/glossary/acronym_declaration.txt
new file mode 100644
index 0000000000..abf12b6cae
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/acronym_declaration.txt
@@ -0,0 +1 @@
+\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/acronym_definition_options.txt b/support/texlab/src/parser/test_data/latex/glossary/acronym_definition_options.txt
new file mode 100644
index 0000000000..47a69067ca
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/acronym_definition_options.txt
@@ -0,0 +1 @@
+\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/acronym_definition_simple.txt b/support/texlab/src/parser/test_data/latex/glossary/acronym_definition_simple.txt
new file mode 100644
index 0000000000..4fbf285927
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/acronym_definition_simple.txt
@@ -0,0 +1 @@
+\newacronym{fpsLabel}{FPS}{Frame per Second} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/acronym_package.txt b/support/texlab/src/parser/test_data/latex/glossary/acronym_package.txt
new file mode 100644
index 0000000000..86e60c4584
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/acronym_package.txt
@@ -0,0 +1 @@
+\acro{fps}[FPS]{Frames per Second}
diff --git a/support/texlab/src/parser/test_data/latex/glossary/acronym_reference_options.txt b/support/texlab/src/parser/test_data/latex/glossary/acronym_reference_options.txt
new file mode 100644
index 0000000000..43fd03f12b
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/acronym_reference_options.txt
@@ -0,0 +1 @@
+\acrshort[foo=bar,baz]{fpsLabel} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/acronym_reference_simple.txt b/support/texlab/src/parser/test_data/latex/glossary/acronym_reference_simple.txt
new file mode 100644
index 0000000000..d9fb5b9a25
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/acronym_reference_simple.txt
@@ -0,0 +1 @@
+\acrshort{fpsLabel} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_definition_simple.txt b/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_definition_simple.txt
new file mode 100644
index 0000000000..fb1e12899a
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_definition_simple.txt
@@ -0,0 +1 @@
+\newglossaryentry{foo}{bar = baz, qux,} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_options.txt b/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_options.txt
new file mode 100644
index 0000000000..fbae075e1d
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_options.txt
@@ -0,0 +1 @@
+\gls[foo = bar, qux]{baz} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_simple.txt b/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_simple.txt
new file mode 100644
index 0000000000..a603355f0f
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/glossary/glossary_entry_reference_simple.txt
@@ -0,0 +1 @@
+\gls{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/graphics_path.txt b/support/texlab/src/parser/test_data/latex/graphics_path.txt
new file mode 100644
index 0000000000..0e9f431c36
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/graphics_path.txt
@@ -0,0 +1 @@
+\graphicspath{{../figures/}} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/graphics_path_command.txt b/support/texlab/src/parser/test_data/latex/graphics_path_command.txt
new file mode 100644
index 0000000000..729ee51690
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/graphics_path_command.txt
@@ -0,0 +1 @@
+\graphicspath{{\subfix{./img/}}} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/graphics_path_options.txt b/support/texlab/src/parser/test_data/latex/graphics_path_options.txt
new file mode 100644
index 0000000000..ae8aa84d86
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/graphics_path_options.txt
@@ -0,0 +1 @@
+\graphicspath[foo]{{../figures/}} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/curly_group_missing_end.txt b/support/texlab/src/parser/test_data/latex/group/curly_group_missing_end.txt
new file mode 100644
index 0000000000..6c7b76c0f8
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/curly_group_missing_end.txt
@@ -0,0 +1 @@
+{hello world \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/curly_group_simple.txt b/support/texlab/src/parser/test_data/latex/group/curly_group_simple.txt
new file mode 100644
index 0000000000..5084aae70a
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/curly_group_simple.txt
@@ -0,0 +1 @@
+{hello world} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/equation_missing_end.txt b/support/texlab/src/parser/test_data/latex/group/equation_missing_end.txt
new file mode 100644
index 0000000000..fcb715926a
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/equation_missing_end.txt
@@ -0,0 +1 @@
+\begin{a} \[ foo bar \end{b} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/escaped_brackets.txt b/support/texlab/src/parser/test_data/latex/group/escaped_brackets.txt
new file mode 100644
index 0000000000..144a5fdd6d
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/escaped_brackets.txt
@@ -0,0 +1 @@
+{[}{]} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/unmatched_braces.txt b/support/texlab/src/parser/test_data/latex/group/unmatched_braces.txt
new file mode 100644
index 0000000000..8f606b7dbe
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/unmatched_braces.txt
@@ -0,0 +1 @@
+}{ \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/unmatched_brackets.txt b/support/texlab/src/parser/test_data/latex/group/unmatched_brackets.txt
new file mode 100644
index 0000000000..94ea4d5c96
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/unmatched_brackets.txt
@@ -0,0 +1 @@
+][ \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/group/unmatched_brackets_with_group.txt b/support/texlab/src/parser/test_data/latex/group/unmatched_brackets_with_group.txt
new file mode 100644
index 0000000000..ebf6229429
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/group/unmatched_brackets_with_group.txt
@@ -0,0 +1 @@
+{][} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/hello_world.txt b/support/texlab/src/parser/test_data/latex/hello_world.txt
new file mode 100644
index 0000000000..c57eff55eb
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/hello_world.txt
@@ -0,0 +1 @@
+Hello World! \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/biblatex_include_options.txt b/support/texlab/src/parser/test_data/latex/include/biblatex_include_options.txt
new file mode 100644
index 0000000000..216e7ea165
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/biblatex_include_options.txt
@@ -0,0 +1 @@
+\addbibresource[foo=bar, baz]{foo/bar.bib} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/biblatex_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/biblatex_include_simple.txt
new file mode 100644
index 0000000000..fe10ce257d
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/biblatex_include_simple.txt
@@ -0,0 +1 @@
+\addbibresource{foo/bar.bib} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/bibtex_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/bibtex_include_simple.txt
new file mode 100644
index 0000000000..955353f690
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/bibtex_include_simple.txt
@@ -0,0 +1 @@
+\bibliography{foo/bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/class_include_empty.txt b/support/texlab/src/parser/test_data/latex/include/class_include_empty.txt
new file mode 100644
index 0000000000..144b418175
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/class_include_empty.txt
@@ -0,0 +1 @@
+\documentclass{} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/class_include_options.txt b/support/texlab/src/parser/test_data/latex/include/class_include_options.txt
new file mode 100644
index 0000000000..32dc4279d7
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/class_include_options.txt
@@ -0,0 +1 @@
+\documentclass[foo = bar, baz, qux]{article} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/class_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/class_include_simple.txt
new file mode 100644
index 0000000000..15f3d6ea0a
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/class_include_simple.txt
@@ -0,0 +1 @@
+\documentclass{article} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/graphics_include_command.txt b/support/texlab/src/parser/test_data/latex/include/graphics_include_command.txt
new file mode 100644
index 0000000000..14a4a96216
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/graphics_include_command.txt
@@ -0,0 +1 @@
+\includegraphics[width=0.5\textwidth]{\foo.\bar.pdf} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/graphics_include_complicated_options.txt b/support/texlab/src/parser/test_data/latex/include/graphics_include_complicated_options.txt
new file mode 100644
index 0000000000..d6b6910c14
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/graphics_include_complicated_options.txt
@@ -0,0 +1 @@
+\includegraphics[width=0.5\textwidth]{} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/graphics_include_options.txt b/support/texlab/src/parser/test_data/latex/include/graphics_include_options.txt
new file mode 100644
index 0000000000..8d3c45f2a5
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/graphics_include_options.txt
@@ -0,0 +1 @@
+\includegraphics[scale=.5]{foo/bar.pdf} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/graphics_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/graphics_include_simple.txt
new file mode 100644
index 0000000000..629cf3e8a9
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/graphics_include_simple.txt
@@ -0,0 +1 @@
+\includegraphics{foo/bar.pdf} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/import_incomplete.txt b/support/texlab/src/parser/test_data/latex/include/import_incomplete.txt
new file mode 100644
index 0000000000..3d6bc5f043
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/import_incomplete.txt
@@ -0,0 +1 @@
+\import{foo \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/import_simple.txt b/support/texlab/src/parser/test_data/latex/include/import_simple.txt
new file mode 100644
index 0000000000..f38059d6c1
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/import_simple.txt
@@ -0,0 +1 @@
+\import{foo}{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/inkscape_include_options.txt b/support/texlab/src/parser/test_data/latex/include/inkscape_include_options.txt
new file mode 100644
index 0000000000..e6dd431c7c
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/inkscape_include_options.txt
@@ -0,0 +1 @@
+\includesvg[scale=.5]{foo/bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/inkscape_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/inkscape_include_simple.txt
new file mode 100644
index 0000000000..4e3a4ea9ba
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/inkscape_include_simple.txt
@@ -0,0 +1 @@
+\includesvg{foo/bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/latex_include_equality_sign.txt b/support/texlab/src/parser/test_data/latex/include/latex_include_equality_sign.txt
new file mode 100644
index 0000000000..5a763b4e29
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/latex_include_equality_sign.txt
@@ -0,0 +1 @@
+\include{foo=bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/latex_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/latex_include_simple.txt
new file mode 100644
index 0000000000..c0d3d390df
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/latex_include_simple.txt
@@ -0,0 +1 @@
+\include{foo/bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/latex_input_path_brackets.txt b/support/texlab/src/parser/test_data/latex/include/latex_input_path_brackets.txt
new file mode 100644
index 0000000000..6585f50d52
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/latex_input_path_brackets.txt
@@ -0,0 +1 @@
+\input{foo[bar].tex} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/package_include_empty.txt b/support/texlab/src/parser/test_data/latex/include/package_include_empty.txt
new file mode 100644
index 0000000000..09e02cc1cc
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/package_include_empty.txt
@@ -0,0 +1 @@
+\usepackage{} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/package_include_multiple.txt b/support/texlab/src/parser/test_data/latex/include/package_include_multiple.txt
new file mode 100644
index 0000000000..c1080aaf66
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/package_include_multiple.txt
@@ -0,0 +1 @@
+\usepackage{amsmath, lipsum} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/package_include_options.txt b/support/texlab/src/parser/test_data/latex/include/package_include_options.txt
new file mode 100644
index 0000000000..83a50c12cf
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/package_include_options.txt
@@ -0,0 +1 @@
+\usepackage[foo = bar, baz, qux]{amsmath} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/package_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/package_include_simple.txt
new file mode 100644
index 0000000000..3cf1b4edc0
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/package_include_simple.txt
@@ -0,0 +1 @@
+\usepackage{amsmath} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/pgf_library_import_simple.txt b/support/texlab/src/parser/test_data/latex/include/pgf_library_import_simple.txt
new file mode 100644
index 0000000000..25a2b24720
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/pgf_library_import_simple.txt
@@ -0,0 +1 @@
+\usepgflibrary{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/svg_include_options.txt b/support/texlab/src/parser/test_data/latex/include/svg_include_options.txt
new file mode 100644
index 0000000000..3eb90568ab
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/svg_include_options.txt
@@ -0,0 +1 @@
+\includesvg[scale=.5]{foo/bar.svg} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/svg_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/svg_include_simple.txt
new file mode 100644
index 0000000000..964a3adcd6
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/svg_include_simple.txt
@@ -0,0 +1 @@
+\includesvg{foo/bar.svg} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/tikz_library_import_simple.txt b/support/texlab/src/parser/test_data/latex/include/tikz_library_import_simple.txt
new file mode 100644
index 0000000000..ff616ebd11
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/tikz_library_import_simple.txt
@@ -0,0 +1 @@
+\usetikzlibrary{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/include/verbatim_include_simple.txt b/support/texlab/src/parser/test_data/latex/include/verbatim_include_simple.txt
new file mode 100644
index 0000000000..a0b3fec843
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/include/verbatim_include_simple.txt
@@ -0,0 +1 @@
+\verbatiminput{foo/bar.txt} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/inline.txt b/support/texlab/src/parser/test_data/latex/inline.txt
new file mode 100644
index 0000000000..81a429dc26
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/inline.txt
@@ -0,0 +1 @@
+$x \in [0, \infty)$ \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/inline_double_dollar.txt b/support/texlab/src/parser/test_data/latex/inline_double_dollar.txt
new file mode 100644
index 0000000000..c3a7fee9f6
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/inline_double_dollar.txt
@@ -0,0 +1 @@
+$$x \in [0, \infty)$$ \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/issue_745.txt b/support/texlab/src/parser/test_data/latex/issue_745.txt
new file mode 100644
index 0000000000..4cbebeb2d8
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/issue_745.txt
@@ -0,0 +1,21 @@
+\documentclass{article}
+\usepackage{tabularray}
+
+\ExplSyntaxOn
+\NewDocumentEnvironment{exptblr}{O{}m}
+ {
+ \use:x
+ {
+ \exp_not:N \begin{tblr}
+ [\exp_not:n{#1}]
+ {#2}
+ }
+ }
+ {
+ \end{tblr}
+ }
+\ExplSyntaxOff
+
+\begin{document}
+
+\end{document} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/issue_789.txt b/support/texlab/src/parser/test_data/latex/issue_789.txt
new file mode 100644
index 0000000000..612c6de786
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/issue_789.txt
@@ -0,0 +1 @@
+\graphicspath{test} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_definition_line_break.txt b/support/texlab/src/parser/test_data/latex/label/label_definition_line_break.txt
new file mode 100644
index 0000000000..d567c6d25a
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_definition_line_break.txt
@@ -0,0 +1,2 @@
+\label{hello
+world} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_definition_simple.txt b/support/texlab/src/parser/test_data/latex/label/label_definition_simple.txt
new file mode 100644
index 0000000000..43ebaf1cec
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_definition_simple.txt
@@ -0,0 +1 @@
+\label{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_number.txt b/support/texlab/src/parser/test_data/latex/label/label_number.txt
new file mode 100644
index 0000000000..6e994af500
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_number.txt
@@ -0,0 +1 @@
+\newlabel{foo}{{1.1}} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_equation.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_equation.txt
new file mode 100644
index 0000000000..ab386645df
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_equation.txt
@@ -0,0 +1 @@
+\eqref{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_incomplete.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_incomplete.txt
new file mode 100644
index 0000000000..1d0f79ec8e
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_incomplete.txt
@@ -0,0 +1 @@
+Equation \eqref{eq is a \emph{useful} identity. \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_multiple.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_multiple.txt
new file mode 100644
index 0000000000..e8bb38ee9e
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_multiple.txt
@@ -0,0 +1 @@
+\ref{foo, bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_range_error.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_range_error.txt
new file mode 100644
index 0000000000..9213da0460
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_range_error.txt
@@ -0,0 +1 @@
+\crefrange{foo{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_range_incomplete.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_range_incomplete.txt
new file mode 100644
index 0000000000..841133d73a
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_range_incomplete.txt
@@ -0,0 +1 @@
+\crefrange{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_range_simple.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_range_simple.txt
new file mode 100644
index 0000000000..a0e985ea88
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_range_simple.txt
@@ -0,0 +1 @@
+\crefrange{foo}{bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/label/label_reference_simple.txt b/support/texlab/src/parser/test_data/latex/label/label_reference_simple.txt
new file mode 100644
index 0000000000..dde4f444f4
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/label/label_reference_simple.txt
@@ -0,0 +1 @@
+\ref{foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/parameter.txt b/support/texlab/src/parser/test_data/latex/parameter.txt
new file mode 100644
index 0000000000..d44ac7b947
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/parameter.txt
@@ -0,0 +1 @@
+#1 \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/parameter_error.txt b/support/texlab/src/parser/test_data/latex/parameter_error.txt
new file mode 100644
index 0000000000..4287ca8617
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/parameter_error.txt
@@ -0,0 +1 @@
+# \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/structure/structure_enum_item.txt b/support/texlab/src/parser/test_data/latex/structure/structure_enum_item.txt
new file mode 100644
index 0000000000..3288ec259b
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/structure/structure_enum_item.txt
@@ -0,0 +1 @@
+\begin{enumerate} \item 1 \item[2] 2 \item 3 \end{enumerate} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/structure/structure_invalid_nesting.txt b/support/texlab/src/parser/test_data/latex/structure/structure_invalid_nesting.txt
new file mode 100644
index 0000000000..7203dd2d24
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/structure/structure_invalid_nesting.txt
@@ -0,0 +1 @@
+\section{Foo} \chapter{Bar} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/structure/structure_nested.txt b/support/texlab/src/parser/test_data/latex/structure/structure_nested.txt
new file mode 100644
index 0000000000..082f10765b
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/structure/structure_nested.txt
@@ -0,0 +1 @@
+\part{1}\chapter{2}\section{3}\subsection{4}\subsubsection{5}\paragraph{6}\subparagraph{7} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/structure/structure_siblings.txt b/support/texlab/src/parser/test_data/latex/structure/structure_siblings.txt
new file mode 100644
index 0000000000..d8e347c429
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/structure/structure_siblings.txt
@@ -0,0 +1 @@
+\section{Foo} Foo \section{Bar} Bar \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_full.txt b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_full.txt
new file mode 100644
index 0000000000..286d111ad1
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_full.txt
@@ -0,0 +1 @@
+\newtheorem{foo}[bar]{Foo}[baz] \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_counter.txt b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_counter.txt
new file mode 100644
index 0000000000..af1b6903a5
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_counter.txt
@@ -0,0 +1 @@
+\newtheorem{foo}[bar] \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description.txt b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description.txt
new file mode 100644
index 0000000000..e5d6551666
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description.txt
@@ -0,0 +1 @@
+\newtheorem{foo}{Foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description_and_counter.txt b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description_and_counter.txt
new file mode 100644
index 0000000000..89c37d9497
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_name_with_description_and_counter.txt
@@ -0,0 +1 @@
+\newtheorem{foo}[bar]{Foo} \ No newline at end of file
diff --git a/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_only_name.txt b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_only_name.txt
new file mode 100644
index 0000000000..62adb2c4b9
--- /dev/null
+++ b/support/texlab/src/parser/test_data/latex/theorem_definition/theorem_definition_only_name.txt
@@ -0,0 +1 @@
+\newtheorem{foo} \ No newline at end of file
diff --git a/support/texlab/src/range.rs b/support/texlab/src/range.rs
deleted file mode 100644
index c70a573f39..0000000000
--- a/support/texlab/src/range.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use lsp_types::{Position, Range};
-
-pub trait RangeExt {
- fn new_simple(start_line: u32, start_character: u32, end_line: u32, end_character: u32)
- -> Self;
-}
-
-impl RangeExt for Range {
- fn new_simple(
- start_line: u32,
- start_character: u32,
- end_line: u32,
- end_character: u32,
- ) -> Self {
- Self {
- start: Position::new(start_line, start_character),
- end: Position::new(end_line, end_character),
- }
- }
-}
diff --git a/support/texlab/src/req_queue.rs b/support/texlab/src/req_queue.rs
deleted file mode 100644
index c1eb7b9594..0000000000
--- a/support/texlab/src/req_queue.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use crossbeam_channel::Sender;
-use lsp_server::ResponseError;
-
-pub struct IncomingData;
-
-pub struct OutgoingData {
- pub(crate) sender: Sender<Result<serde_json::Value, ResponseError>>,
-}
-
-pub type ReqQueue = lsp_server::ReqQueue<IncomingData, OutgoingData>;
diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs
index 0d5d1f8fe1..93b35c69c6 100644
--- a/support/texlab/src/server.rs
+++ b/support/texlab/src/server.rs
@@ -1,83 +1,89 @@
+mod dispatch;
+mod query;
+
use std::{
path::PathBuf,
sync::{Arc, Mutex},
- time::Duration,
};
use anyhow::Result;
use crossbeam_channel::{Receiver, Sender};
-use log::{error, info, warn};
-use lsp_server::{Connection, Message, RequestId};
+use log::{error, info};
+use lsp_server::{Connection, ErrorCode, Message, RequestId};
use lsp_types::{notification::*, request::*, *};
-use serde::Serialize;
+use once_cell::sync::Lazy;
+use rowan::{ast::AstNode, TextSize};
+use rustc_hash::FxHashSet;
+use serde::{Deserialize, Serialize};
+use serde_repr::{Deserialize_repr, Serialize_repr};
use threadpool::ThreadPool;
use crate::{
- client::{send_notification, send_request},
- debouncer,
- diagnostics::DiagnosticManager,
- dispatch::{NotificationDispatcher, RequestDispatcher},
- distro::Distribution,
+ citation,
+ client::LspClient,
+ db::{self, discover_dependencies, Document, Language, Owner, Workspace},
+ distro::Distro,
features::{
- execute_command, find_all_references, find_document_highlights, find_document_links,
- find_document_symbols, find_foldings, find_hover, find_workspace_symbols,
- format_source_code, goto_definition, prepare_rename_all, rename_all, BuildEngine,
- BuildParams, BuildResult, BuildStatus, FeatureRequest, ForwardSearchResult,
- ForwardSearchStatus,
+ build::{self, BuildParams, BuildResult, BuildStatus},
+ completion::{self, builder::CompletionItemData},
+ definition, folding, formatting, forward_search, highlight, hover, inlay_hint, link,
+ reference, rename, symbol, workspace_command,
+ },
+ normalize_uri,
+ syntax::bibtex,
+ util::{
+ self, capabilities::ClientCapabilitiesExt, components::COMPONENT_DATABASE,
+ line_index_ext::LineIndexExt,
},
- req_queue::{IncomingData, ReqQueue},
- ClientCapabilitiesExt, Document, DocumentData, DocumentLanguage, Environment, LineIndex,
- LineIndexExt, Options, Workspace, WorkspaceEvent,
+ Db, Options, StartupOptions,
};
#[derive(Debug)]
enum InternalMessage {
- SetDistro(Distribution),
- SetOptions(Arc<Options>),
+ SetDistro(Distro),
+ SetOptions(Options),
+ FileEvent(notify::Event),
+ ForwardSearch(Url),
+ Diagnostics,
+ ChktexResult(Url, Vec<db::diagnostics::Diagnostic>),
}
-#[derive(Clone)]
pub struct Server {
connection: Arc<Connection>,
internal_tx: Sender<InternalMessage>,
internal_rx: Receiver<InternalMessage>,
- req_queue: Arc<Mutex<ReqQueue>>,
- workspace: Workspace,
- diagnostic_tx: debouncer::Sender<Workspace>,
- diagnostic_manager: DiagnosticManager,
- pool: Arc<Mutex<ThreadPool>>,
- load_resolver: bool,
- build_engine: Arc<BuildEngine>,
+ client: LspClient,
+ engine: query::Engine,
+ watcher: FileWatcher,
+ pool: ThreadPool,
}
impl Server {
- pub fn with_connection(
- connection: Connection,
- current_dir: PathBuf,
- load_resolver: bool,
- ) -> Self {
- let req_queue = Arc::default();
- let workspace = Workspace::new(Environment::new(Arc::new(current_dir)));
+ pub fn new(connection: Connection) -> Self {
+ let client = LspClient::new(connection.sender.clone());
let (internal_tx, internal_rx) = crossbeam_channel::unbounded();
- let diagnostic_manager = DiagnosticManager::default();
- let diagnostic_tx = create_debouncer(connection.sender.clone(), diagnostic_manager.clone());
+ let watcher = FileWatcher::new(internal_tx.clone()).expect("init file watcher");
Self {
connection: Arc::new(connection),
internal_tx,
internal_rx,
- req_queue,
- workspace,
- diagnostic_tx,
- diagnostic_manager,
- pool: Arc::new(Mutex::new(threadpool::Builder::new().build())),
- load_resolver,
- build_engine: Arc::default(),
+ client,
+ engine: query::Engine::default(),
+ watcher,
+ pool: threadpool::Builder::new().build(),
}
}
- fn spawn(&self, job: impl FnOnce(Self) + Send + 'static) {
- let server = self.clone();
- self.pool.lock().unwrap().execute(move || job(server));
+ fn run_with_db<R, Q>(&self, id: RequestId, query: Q)
+ where
+ R: Serialize,
+ Q: FnOnce(&dyn Db) -> R + Send + 'static,
+ {
+ let client = self.client.clone();
+ self.engine.fork(move |db| {
+ let response = lsp_server::Response::new_ok(id, query(db));
+ client.send_response(response).unwrap();
+ });
}
fn capabilities(&self) -> ServerCapabilities {
@@ -101,7 +107,6 @@ impl Server {
definition_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
- #[cfg(feature = "completion")]
completion_provider: Some(CompletionOptions {
resolve_provider: Some(true),
trigger_characters: Some(vec![
@@ -129,6 +134,7 @@ impl Server {
],
..Default::default()
}),
+ inlay_hint_provider: Some(OneOf::Left(true)),
..ServerCapabilities::default()
}
}
@@ -137,8 +143,29 @@ impl Server {
let (id, params) = self.connection.initialize_start()?;
let params: InitializeParams = serde_json::from_value(params)?;
- self.workspace.environment.client_capabilities = Arc::new(params.capabilities);
- self.workspace.environment.client_info = params.client_info.map(Arc::new);
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
+ workspace
+ .set_client_capabilities(db)
+ .with_durability(salsa::Durability::HIGH)
+ .to(params.capabilities);
+
+ workspace
+ .set_client_info(db)
+ .with_durability(salsa::Durability::HIGH)
+ .to(params.client_info);
+
+ let root_dirs = params
+ .workspace_folders
+ .unwrap_or_default()
+ .into_iter()
+ .map(|folder| db::Location::new(db, folder.uri))
+ .collect();
+
+ workspace
+ .set_root_dirs(db)
+ .with_durability(salsa::Durability::HIGH)
+ .to(root_dirs);
let result = InitializeResult {
capabilities: self.capabilities(),
@@ -146,132 +173,109 @@ impl Server {
name: "TexLab".to_owned(),
version: Some(env!("CARGO_PKG_VERSION").to_owned()),
}),
+ offset_encoding: None,
};
self.connection
.initialize_finish(id, serde_json::to_value(result)?)?;
- if self.load_resolver {
- self.spawn(move |server| {
- let distro = Distribution::detect();
- info!("Detected distribution: {}", distro.kind);
+ let StartupOptions { skip_distro } =
+ serde_json::from_value(params.initialization_options.unwrap_or_default())
+ .unwrap_or_default();
- server
- .internal_tx
- .send(InternalMessage::SetDistro(distro))
- .unwrap();
+ if !skip_distro {
+ let sender = self.internal_tx.clone();
+ self.pool.execute(move || {
+ let distro = Distro::detect().unwrap_or_else(|why| {
+ log::warn!("Unable to load distro files: {}", why);
+ Distro::default()
+ });
+
+ info!("Detected distribution: {:?}", distro.kind);
+ sender.send(InternalMessage::SetDistro(distro)).unwrap();
});
}
- self.register_diagnostics_handler();
-
- self.spawn(move |server| {
- server.register_config_capability();
- server.register_file_watching();
- let _ = server.pull_config();
- });
-
+ self.register_configuration();
+ self.pull_options();
Ok(())
}
- fn register_file_watching(&self) {
- if self
- .workspace
- .environment
- .client_capabilities
- .has_file_watching_support()
- {
- let options = DidChangeWatchedFilesRegistrationOptions {
- watchers: vec![FileSystemWatcher {
- glob_pattern: "**/*.{aux,log}".into(),
- kind: Some(WatchKind::Create | WatchKind::Change | WatchKind::Delete),
- }],
- };
-
- let reg = Registration {
- id: "build-watch".to_string(),
- method: DidChangeWatchedFiles::METHOD.to_string(),
- register_options: Some(serde_json::to_value(options).unwrap()),
- };
-
- let params = RegistrationParams {
- registrations: vec![reg],
- };
-
- if let Err(why) =
- send_request::<RegisterCapability>(&self.req_queue, &self.connection.sender, params)
- {
- error!(
- "Failed to register \"{}\" notification: {}",
- DidChangeWatchedFiles::METHOD,
- why
- );
- }
- }
- }
+ fn register_configuration(&mut self) {
+ let db = self.engine.read();
- fn register_config_capability(&self) {
- if self
- .workspace
- .environment
- .client_capabilities
+ if Workspace::get(db)
+ .client_capabilities(db)
.has_push_configuration_support()
{
- let reg = Registration {
+ let registration = Registration {
id: "pull-config".to_string(),
method: DidChangeConfiguration::METHOD.to_string(),
register_options: None,
};
let params = RegistrationParams {
- registrations: vec![reg],
+ registrations: vec![registration],
};
- if let Err(why) =
- send_request::<RegisterCapability>(&self.req_queue, &self.connection.sender, params)
- {
- error!(
- "Failed to register \"{}\" notification: {}",
- DidChangeConfiguration::METHOD,
- why
- );
- }
+ let client = self.client.clone();
+ self.pool.execute(move || {
+ if let Err(why) = client.send_request::<RegisterCapability>(params) {
+ log::error!(
+ "Failed to register \"{}\" notification: {}",
+ DidChangeConfiguration::METHOD,
+ why
+ );
+ }
+ });
}
}
- fn register_diagnostics_handler(&mut self) {
- let (event_sender, event_receiver) = crossbeam_channel::unbounded();
- let diagnostic_tx = self.diagnostic_tx.clone();
- let diagnostic_manager = self.diagnostic_manager.clone();
- std::thread::spawn(move || {
- for event in event_receiver {
- match event {
- WorkspaceEvent::Changed(workspace, document) => {
- diagnostic_manager.push_syntax(&workspace, &document.uri);
- let delay = workspace.environment.options.diagnostics_delay;
- diagnostic_tx
- .send(workspace, Duration::from_millis(delay))
- .unwrap();
- }
- };
- }
- });
+ fn update_workspace(&mut self) {
+ let db = self.engine.write();
+ discover_dependencies(db);
+ self.watcher.watch(db);
+ self.publish_diagnostics_with_delay();
+ }
+
+ fn publish_diagnostics(&mut self) -> Result<()> {
+ let db = self.engine.read();
+
+ let all_diagnostics = db::diagnostics::collect_filtered(db, Workspace::get(db));
+
+ for (document, diagnostics) in all_diagnostics {
+ let uri = document.location(db).uri(db).clone();
+ let version = None;
+ let params = PublishDiagnosticsParams {
+ uri,
+ diagnostics,
+ version,
+ };
+
+ self.client
+ .send_notification::<PublishDiagnostics>(params)?;
+ }
- self.workspace.listeners.push(event_sender);
+ Ok(())
}
- fn register_incoming_request(&self, id: RequestId) {
- let mut req_queue = self.req_queue.lock().unwrap();
- req_queue.incoming.register(id, IncomingData);
+ fn publish_diagnostics_with_delay(&mut self) {
+ let db = self.engine.read();
+ let sender = self.internal_tx.clone();
+ let delay = Workspace::get(db).options(db).diagnostics_delay.0;
+ self.pool.execute(move || {
+ std::thread::sleep(delay);
+ sender.send(InternalMessage::Diagnostics).unwrap();
+ });
}
- fn pull_config(&self) -> Result<()> {
- if !self
- .workspace
- .environment
- .client_capabilities
+ fn pull_options(&mut self) {
+ let db = self.engine.read();
+ let workspace = Workspace::get(db);
+ if !workspace
+ .client_capabilities(db)
.has_pull_configuration_support()
{
- return Ok(());
+ return;
}
let params = ConfigurationParams {
@@ -281,108 +285,81 @@ impl Server {
}],
};
- match send_request::<WorkspaceConfiguration>(
- &self.req_queue,
- &self.connection.sender,
- params,
- ) {
- Ok(mut json) => {
- let value = json.pop().expect("invalid configuration request");
- let options = self.parse_options(value)?;
- self.internal_tx
- .send(InternalMessage::SetOptions(Arc::new(options)))
- .unwrap();
- }
- Err(why) => {
- error!("Retrieving configuration failed: {}", why);
- }
- };
+ let client = self.client.clone();
+ let sender = self.internal_tx.clone();
+ self.pool.execute(move || {
+ match client.send_request::<WorkspaceConfiguration>(params) {
+ Ok(mut json) => {
+ let options = client
+ .parse_options(json.pop().expect("invalid configuration request"))
+ .unwrap();
- Ok(())
+ sender.send(InternalMessage::SetOptions(options)).unwrap();
+ }
+ Err(why) => {
+ error!("Retrieving configuration failed: {}", why);
+ }
+ };
+ });
}
- fn parse_options(&self, value: serde_json::Value) -> Result<Options> {
- let options = match serde_json::from_value(value) {
- Ok(new_options) => new_options,
- Err(why) => {
- send_notification::<ShowMessage>(
- &self.connection.sender,
- ShowMessageParams {
- message: format!(
- "The texlab configuration is invalid; using the default settings instead.\nDetails: {why}"
- ),
- typ: MessageType::WARNING,
- },
- )?;
-
- Options::default()
- }
- };
+ fn update_options(&mut self, options: Options) {
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
+ workspace
+ .set_options(db)
+ .with_durability(salsa::Durability::MEDIUM)
+ .to(options);
- Ok(options)
+ self.watcher.watch(db);
}
- fn cancel(&self, params: CancelParams) -> Result<()> {
- let id = match params.id {
- NumberOrString::Number(id) => RequestId::from(id),
- NumberOrString::String(id) => RequestId::from(id),
- };
-
- let mut req_queue = self.req_queue.lock().unwrap();
- req_queue.incoming.complete(id);
-
+ fn cancel(&self, _params: CancelParams) -> Result<()> {
Ok(())
}
- fn did_change_watched_files(&mut self, params: DidChangeWatchedFilesParams) -> Result<()> {
- for change in params.changes {
- if let Ok(path) = change.uri.to_file_path() {
- match change.typ {
- FileChangeType::CREATED | FileChangeType::CHANGED => {
- self.workspace.reload(path)?;
- }
- FileChangeType::DELETED => {
- self.workspace.documents_by_uri.remove(&change.uri);
- }
- _ => {}
- }
- }
- }
-
+ fn did_change_watched_files(&mut self, _params: DidChangeWatchedFilesParams) -> Result<()> {
Ok(())
}
fn did_change_configuration(&mut self, params: DidChangeConfigurationParams) -> Result<()> {
- if self
- .workspace
- .environment
- .client_capabilities
+ let db = self.engine.read();
+ let workspace = Workspace::get(db);
+ if workspace
+ .client_capabilities(db)
.has_pull_configuration_support()
{
- self.spawn(move |server| {
- let _ = server.pull_config();
- });
+ self.pull_options();
} else {
- let options = self.parse_options(params.settings)?;
- self.workspace.environment.options = Arc::new(options);
- self.reparse_all()?;
+ let options = self.client.parse_options(params.settings)?;
+ self.update_options(options);
}
Ok(())
}
- fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> {
+ fn did_open(&mut self, mut params: DidOpenTextDocumentParams) -> Result<()> {
+ normalize_uri(&mut params.text_document.uri);
+
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
let language_id = &params.text_document.language_id;
- let language = DocumentLanguage::by_language_id(language_id);
- let document = self.workspace.open(
- Arc::new(params.text_document.uri),
- Arc::new(params.text_document.text),
- language.unwrap_or(DocumentLanguage::Latex),
- )?;
+ let language = Language::from_id(language_id).unwrap_or(Language::Tex);
+ let document = workspace.open(
+ db,
+ params.text_document.uri,
+ params.text_document.text,
+ language,
+ Owner::Client,
+ );
- self.workspace.viewport.insert(Arc::clone(&document.uri));
+ self.update_workspace();
- if self.workspace.environment.options.chktex.on_open_and_save {
+ if workspace
+ .options(self.engine.read())
+ .chktex
+ .on_open_and_save
+ {
self.run_chktex(document);
}
@@ -390,315 +367,299 @@ impl Server {
}
fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri);
- match self.workspace.documents_by_uri.get(&uri).cloned() {
- Some(old_document) => {
- let mut text = old_document.text.to_string();
- apply_document_edit(&mut text, params.content_changes);
- let language = old_document.data.language();
- let new_document =
- self.workspace
- .open(Arc::clone(&uri), Arc::new(text), language)?;
- self.workspace
- .viewport
- .insert(Arc::clone(&new_document.uri));
-
- self.build_engine.positions_by_uri.insert(
- Arc::clone(&uri),
- Position::new(
- old_document
- .text
- .lines()
- .zip(new_document.text.lines())
- .position(|(a, b)| a != b)
- .unwrap_or_default() as u32,
- 0,
- ),
- );
-
- if self.workspace.environment.options.chktex.on_edit {
- self.run_chktex(new_document);
- };
- }
- None => match uri.to_file_path() {
- Ok(path) => {
- self.workspace.load(path)?;
- }
- Err(_) => return Ok(()),
- },
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
+ let document = match workspace.lookup_uri(db, &uri) {
+ Some(document) => document,
+ None => return Ok(()),
};
+ for change in params.content_changes {
+ match change.range {
+ Some(range) => {
+ let range = document.contents(db).line_index(db).offset_lsp_range(range);
+ document.edit(db, range, &change.text);
+ }
+ None => {
+ document
+ .contents(db)
+ .set_text(db)
+ .with_durability(salsa::Durability::LOW)
+ .to(change.text);
+
+ document
+ .set_cursor(db)
+ .with_durability(salsa::Durability::LOW)
+ .to(TextSize::from(0));
+ }
+ };
+ }
+
+ self.update_workspace();
+
+ if workspace.options(self.engine.read()).chktex.on_edit {
+ self.run_chktex(document);
+ }
+
Ok(())
}
fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Result<()> {
- let uri = params.text_document.uri;
-
- if let Some(request) = self
- .workspace
- .documents_by_uri
- .get(&uri)
- .filter(|_| self.workspace.environment.options.build.on_save)
- .map(|document| {
- self.feature_request(
- Arc::clone(&document.uri),
- BuildParams {
- text_document: TextDocumentIdentifier::new(uri.clone()),
- },
- )
- })
- {
- self.spawn(move |server| {
- server
- .build_engine
- .build(request, &server.req_queue, &server.connection.sender)
- .unwrap_or_else(|why| {
- error!("Build failed: {}", why);
- BuildResult {
- status: BuildStatus::FAILURE,
- }
- });
- });
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+
+ let db = self.engine.read();
+ let workspace = Workspace::get(db);
+ if workspace.options(db).build.on_save {
+ self.build_internal(uri.clone(), |_| ())?;
}
- if let Some(document) = self
- .workspace
- .documents_by_uri
- .get(&uri)
- .filter(|_| self.workspace.environment.options.chktex.on_open_and_save)
- .cloned()
- {
- self.run_chktex(document);
+ self.publish_diagnostics_with_delay();
+
+ let db = self.engine.read();
+ if let Some(document) = workspace.lookup_uri(db, &uri) {
+ if workspace.options(db).chktex.on_open_and_save {
+ self.run_chktex(document);
+ }
}
Ok(())
}
fn did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> {
- self.workspace.close(&params.text_document.uri);
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+
+ let db = self.engine.write();
+ if let Some(document) = Workspace::get(db).lookup_uri(db, &uri) {
+ document
+ .set_owner(db)
+ .with_durability(salsa::Durability::LOW)
+ .to(Owner::Server);
+ }
+
+ self.publish_diagnostics_with_delay();
Ok(())
}
fn run_chktex(&mut self, document: Document) {
- self.spawn(move |server| {
- server
- .diagnostic_manager
- .push_chktex(&server.workspace, &document.uri);
-
- let delay = server.workspace.environment.options.diagnostics_delay;
- server
- .diagnostic_tx
- .send(server.workspace.clone(), Duration::from_millis(delay))
- .unwrap();
- });
- }
-
- fn feature_request<P>(&self, uri: Arc<Url>, params: P) -> FeatureRequest<P> {
- FeatureRequest {
- params,
- workspace: self.workspace.slice(&uri),
- uri,
- }
- }
-
- fn handle_feature_request<P, R, H>(
- &self,
- id: RequestId,
- params: P,
- uri: Arc<Url>,
- handler: H,
- ) -> Result<()>
- where
- P: Send + 'static,
- R: Serialize,
- H: FnOnce(FeatureRequest<P>) -> R + Send + 'static,
- {
- self.spawn(move |server| {
- let request = server.feature_request(uri, params);
- if request.workspace.documents_by_uri.is_empty() {
- let code = lsp_server::ErrorCode::InvalidRequest as i32;
- let message = "unknown document".to_string();
- let response = lsp_server::Response::new_err(id, code, message);
- server.connection.sender.send(response.into()).unwrap();
- } else {
- let result = handler(request);
- server
- .connection
- .sender
- .send(lsp_server::Response::new_ok(id, result).into())
+ let db = self.engine.read();
+ if let Some(command) = util::chktex::Command::new(db, document) {
+ let sender = self.internal_tx.clone();
+ let uri = document.location(db).uri(db).clone();
+ self.pool.execute(move || {
+ let diagnostics = command.run().unwrap_or_default();
+ sender
+ .send(InternalMessage::ChktexResult(uri, diagnostics))
.unwrap();
- }
- });
-
- Ok(())
+ });
+ }
}
fn document_link(&self, id: RequestId, params: DocumentLinkParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, find_document_links)?;
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+ self.run_with_db(id, move |db| link::find_all(db, &uri).unwrap_or_default());
Ok(())
}
fn document_symbols(&self, id: RequestId, params: DocumentSymbolParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, find_document_symbols)?;
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+ self.run_with_db(id, move |db| symbol::find_document_symbols(db, &uri));
Ok(())
}
fn workspace_symbols(&self, id: RequestId, params: WorkspaceSymbolParams) -> Result<()> {
- self.spawn(move |server| {
- let result = find_workspace_symbols(&server.workspace, &params);
- server
- .connection
- .sender
- .send(lsp_server::Response::new_ok(id, result).into())
- .unwrap();
- });
+ self.run_with_db(id, move |db| symbol::find_workspace_symbols(db, &params));
Ok(())
}
- #[cfg(feature = "completion")]
- fn completion(&self, id: RequestId, params: CompletionParams) -> Result<()> {
- let uri = Arc::new(params.text_document_position.text_document.uri.clone());
-
- self.build_engine
- .positions_by_uri
- .insert(Arc::clone(&uri), params.text_document_position.position);
-
- self.handle_feature_request(id, params, uri, crate::features::complete)?;
+ fn completion(&mut self, id: RequestId, params: CompletionParams) -> Result<()> {
+ let mut uri = params.text_document_position.text_document.uri;
+ normalize_uri(&mut uri);
+ let position = params.text_document_position.position;
+ self.run_with_db(id, move |db| completion::complete(db, &uri, position));
Ok(())
}
- #[cfg(feature = "completion")]
fn completion_resolve(&self, id: RequestId, mut item: CompletionItem) -> Result<()> {
- use rowan::ast::AstNode;
-
- use crate::{
- citation, component_db::COMPONENT_DATABASE, features::CompletionItemData,
- syntax::bibtex,
- };
-
- self.spawn(move |server| {
- match serde_json::from_value(item.data.clone().unwrap()).unwrap() {
- CompletionItemData::Package | CompletionItemData::Class => {
+ self.run_with_db(id, move |db| {
+ match item
+ .data
+ .clone()
+ .map(|data| serde_json::from_value(data).unwrap())
+ {
+ Some(CompletionItemData::Package | CompletionItemData::Class) => {
item.documentation = COMPONENT_DATABASE
.documentation(&item.label)
.map(Documentation::MarkupContent);
}
- CompletionItemData::Citation { uri, key } => {
- if let Some(document) = server.workspace.documents_by_uri.get(&uri) {
- if let Some(data) = document.data.as_bibtex() {
- let root = bibtex::SyntaxNode::new_root(data.green.clone());
- item.documentation = bibtex::Root::cast(root)
- .and_then(|root| root.find_entry(&key))
- .and_then(|entry| citation::render(&entry))
- .map(|value| {
- Documentation::MarkupContent(MarkupContent {
- kind: MarkupKind::Markdown,
- value,
- })
- });
- }
+ Some(CompletionItemData::Citation { uri, key }) => {
+ if let Some(root) = Workspace::get(db)
+ .lookup_uri(db, &uri)
+ .and_then(|document| document.parse(db).as_bib().map(|data| data.root(db)))
+ {
+ item.documentation = bibtex::Root::cast(root)
+ .and_then(|root| root.find_entry(&key))
+ .and_then(|entry| citation::render(&entry))
+ .map(|value| {
+ Documentation::MarkupContent(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value,
+ })
+ });
}
}
- _ => {}
+ None => {}
};
- server
- .connection
- .sender
- .send(lsp_server::Response::new_ok(id, item).into())
- .unwrap();
+ item
});
+
Ok(())
}
fn folding_range(&self, id: RequestId, params: FoldingRangeParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, find_foldings)?;
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+ self.run_with_db(id, move |db| {
+ folding::find_all(db, &uri).unwrap_or_default()
+ });
Ok(())
}
fn references(&self, id: RequestId, params: ReferenceParams) -> Result<()> {
- let uri = Arc::new(params.text_document_position.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, find_all_references)?;
+ let mut uri = params.text_document_position.text_document.uri;
+ normalize_uri(&mut uri);
+ let position = params.text_document_position.position;
+ self.run_with_db(id, move |db| {
+ reference::find_all(db, &uri, position, &params.context).unwrap_or_default()
+ });
+
Ok(())
}
- fn hover(&self, id: RequestId, params: HoverParams) -> Result<()> {
- let uri = Arc::new(
- params
- .text_document_position_params
- .text_document
- .uri
- .clone(),
- );
- self.build_engine.positions_by_uri.insert(
- Arc::clone(&uri),
- params.text_document_position_params.position,
- );
+ fn hover(&mut self, id: RequestId, params: HoverParams) -> Result<()> {
+ let mut uri = params.text_document_position_params.text_document.uri;
+ normalize_uri(&mut uri);
- self.handle_feature_request(id, params, uri, find_hover)?;
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
+ if let Some(document) = workspace.lookup_uri(db, &uri) {
+ let position = document
+ .contents(db)
+ .line_index(db)
+ .offset_lsp(params.text_document_position_params.position);
+
+ document
+ .set_cursor(db)
+ .with_durability(salsa::Durability::LOW)
+ .to(position);
+ }
+
+ let position = params.text_document_position_params.position;
+ self.run_with_db(id, move |db| hover::find(db, &uri, position));
Ok(())
}
fn goto_definition(&self, id: RequestId, params: GotoDefinitionParams) -> Result<()> {
- let uri = Arc::new(
- params
- .text_document_position_params
- .text_document
- .uri
- .clone(),
- );
- self.handle_feature_request(id, params, uri, goto_definition)?;
+ let mut uri = params.text_document_position_params.text_document.uri;
+ normalize_uri(&mut uri);
+ let position = params.text_document_position_params.position;
+ self.run_with_db(id, move |db| {
+ definition::goto_definition(db, &uri, position)
+ });
+
Ok(())
}
fn prepare_rename(&self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, prepare_rename_all)?;
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+ self.run_with_db(id, move |db| {
+ rename::prepare_rename_all(db, &uri, params.position)
+ });
+
Ok(())
}
fn rename(&self, id: RequestId, params: RenameParams) -> Result<()> {
- let uri = Arc::new(params.text_document_position.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, rename_all)?;
+ let mut uri = params.text_document_position.text_document.uri;
+ normalize_uri(&mut uri);
+ let position = params.text_document_position.position;
+ self.run_with_db(id, move |db| {
+ rename::rename_all(db, &uri, position, params.new_name)
+ });
+
Ok(())
}
fn document_highlight(&self, id: RequestId, params: DocumentHighlightParams) -> Result<()> {
- let uri = Arc::new(
- params
- .text_document_position_params
- .text_document
- .uri
- .clone(),
- );
- self.handle_feature_request(id, params, uri, find_document_highlights)?;
+ let mut uri = params.text_document_position_params.text_document.uri;
+ normalize_uri(&mut uri);
+ let position = params.text_document_position_params.position;
+ self.run_with_db(id, move |db| {
+ highlight::find_all(db, &uri, position).unwrap_or_default()
+ });
Ok(())
}
fn formatting(&self, id: RequestId, params: DocumentFormattingParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, format_source_code)?;
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+ self.run_with_db(id, move |db| {
+ formatting::format_source_code(db, &uri, &params.options)
+ });
+
Ok(())
}
- fn execute_command(&self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
- self.spawn(move |server| {
- let result = execute_command(&server.workspace, &params.command, params.arguments);
- let response = match result {
- Ok(()) => lsp_server::Response::new_ok(id, ()),
- Err(why) => lsp_server::Response::new_err(
- id,
- lsp_server::ErrorCode::InternalError as i32,
- why.to_string(),
- ),
- };
+ fn execute_command(&mut self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
+ let db = self.engine.read();
+ match workspace_command::select(db, &params.command, params.arguments) {
+ Ok(command) => {
+ let client = self.client.clone();
+ self.pool.execute(move || {
+ match command.run() {
+ Ok(()) => {
+ client
+ .send_response(lsp_server::Response::new_ok(id, ()))
+ .unwrap();
+ }
+ Err(why) => {
+ client
+ .send_error(id, ErrorCode::InternalError, why.to_string())
+ .unwrap();
+ }
+ };
+ });
+ }
+ Err(why) => {
+ self.client
+ .send_error(id, ErrorCode::InvalidParams, why.to_string())
+ .unwrap();
+ }
+ };
+
+ Ok(())
+ }
- server.connection.sender.send(response.into()).unwrap();
+ fn inlay_hints(&self, id: RequestId, params: InlayHintParams) -> Result<()> {
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+ self.run_with_db(id, move |db| {
+ inlay_hint::find_all(db, &uri, params.range).unwrap_or_default()
});
+ Ok(())
+ }
+ fn inlay_hint_resolve(&self, id: RequestId, hint: InlayHint) -> Result<()> {
+ let response = lsp_server::Response::new_ok(id, hint);
+ self.connection.sender.send(response.into()).unwrap();
Ok(())
}
@@ -710,52 +671,144 @@ impl Server {
Ok(())
}
- fn build(&self, id: RequestId, params: BuildParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- let lsp_sender = self.connection.sender.clone();
- let req_queue = Arc::clone(&self.req_queue);
- let build_engine = Arc::clone(&self.build_engine);
- self.handle_feature_request(id, params, uri, move |request| {
- build_engine
- .build(request, &req_queue, &lsp_sender)
- .unwrap_or_else(|why| {
- error!("Build failed: {}", why);
- BuildResult {
- status: BuildStatus::FAILURE,
- }
- })
+ fn build(&mut self, id: RequestId, params: BuildParams) -> Result<()> {
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+
+ let client = self.client.clone();
+ self.build_internal(uri, move |status| {
+ let result = BuildResult { status };
+ client
+ .send_response(lsp_server::Response::new_ok(id, result))
+ .unwrap();
})?;
+
+ Ok(())
+ }
+
+ fn build_internal(
+ &mut self,
+ uri: Url,
+ callback: impl FnOnce(BuildStatus) + Send + 'static,
+ ) -> Result<()> {
+ static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+
+ let db = self.engine.read();
+ let compiler = match build::Command::new(db, uri.clone(), self.client.clone()) {
+ Some(compiler) => compiler,
+ None => {
+ callback(BuildStatus::FAILURE);
+ return Ok(());
+ }
+ };
+
+ let forward_search_after = Workspace::get(db).options(db).build.forward_search_after;
+
+ let sender = self.internal_tx.clone();
+ self.pool.execute(move || {
+ let guard = LOCK.lock().unwrap();
+
+ let status = compiler.run();
+ if forward_search_after {
+ sender.send(InternalMessage::ForwardSearch(uri)).unwrap();
+ }
+
+ drop(guard);
+ callback(status);
+ });
+
Ok(())
}
- fn forward_search(&self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> {
- let uri = Arc::new(params.text_document.uri.clone());
- self.handle_feature_request(id, params, uri, |req| {
- crate::features::execute_forward_search(req).unwrap_or(ForwardSearchResult {
- status: ForwardSearchStatus::ERROR,
- })
+ fn forward_search(&mut self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> {
+ let mut uri = params.text_document.uri;
+ normalize_uri(&mut uri);
+
+ let client = self.client.clone();
+ self.forward_search_internal(uri, Some(params.position), move |status| {
+ let result = ForwardSearchResult { status };
+ client
+ .send_response(lsp_server::Response::new_ok(id, result))
+ .unwrap();
})?;
+
Ok(())
}
- fn reparse_all(&mut self) -> Result<()> {
- for document in self
- .workspace
- .documents_by_uri
- .values()
- .cloned()
- .collect::<Vec<_>>()
- {
- self.workspace.open(
- Arc::clone(&document.uri),
- document.text.clone(),
- document.data.language(),
- )?;
- }
+ fn forward_search_internal(
+ &mut self,
+ uri: Url,
+ position: Option<Position>,
+ callback: impl FnOnce(ForwardSearchStatus) + Send + 'static,
+ ) -> Result<()> {
+ let db = self.engine.read();
+ let command = match forward_search::Command::configure(db, &uri, position) {
+ Ok(command) => command,
+ Err(why) => {
+ log::error!("Forward search failed: {}", why);
+ callback(why.into());
+ return Ok(());
+ }
+ };
+
+ self.pool.execute(move || {
+ let status = command
+ .run()
+ .map_or_else(ForwardSearchStatus::from, |()| ForwardSearchStatus::SUCCESS);
+
+ callback(status);
+ });
Ok(())
}
+ fn handle_file_event(&mut self, event: notify::Event) {
+ let mut changed = false;
+
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
+ match event.kind {
+ notify::EventKind::Create(_) | notify::EventKind::Modify(_) => {
+ for path in event.paths {
+ if workspace
+ .lookup_path(db, &path)
+ .map_or(true, |document| document.owner(db) == Owner::Server)
+ {
+ if let Some(language) = Language::from_path(&path) {
+ workspace.load(db, &path, language, Owner::Server);
+ changed = true;
+ }
+ }
+ }
+ }
+ notify::EventKind::Remove(_) => {
+ for path in event.paths {
+ if let Some(document) = workspace.lookup_path(db, &path) {
+ if document.owner(db) == Owner::Server {
+ let mut documents = workspace
+ .set_documents(db)
+ .with_durability(salsa::Durability::LOW)
+ .to(FxHashSet::default());
+
+ documents.remove(&document);
+ workspace
+ .set_documents(db)
+ .with_durability(salsa::Durability::MEDIUM)
+ .to(documents);
+
+ changed = true;
+ }
+ }
+ }
+ }
+ notify::EventKind::Any | notify::EventKind::Access(_) | notify::EventKind::Other => {}
+ };
+
+ if changed {
+ self.publish_diagnostics_with_delay();
+ }
+ }
+
fn process_messages(&mut self) -> Result<()> {
loop {
crossbeam_channel::select! {
@@ -766,8 +819,7 @@ impl Server {
return Ok(());
}
- self.register_incoming_request(request.id.clone());
- if let Some(response) = RequestDispatcher::new(request)
+ if let Some(response) = dispatch::RequestDispatcher::new(request)
.on::<DocumentLinkRequest, _>(|id, params| self.document_link(id, params))?
.on::<FoldingRangeRequest, _>(|id, params| self.folding_range(id, params))?
.on::<References, _>(|id, params| self.references(id, params))?
@@ -777,12 +829,10 @@ impl Server {
})?
.on::<WorkspaceSymbol, _>(|id, params| self.workspace_symbols(id, params))?
.on::<Completion, _>(|id, params| {
- #[cfg(feature = "completion")]
self.completion(id, params)?;
Ok(())
})?
.on::<ResolveCompletionItem, _>(|id, params| {
- #[cfg(feature = "completion")]
self.completion_resolve(id, params)?;
Ok(())
})?
@@ -803,13 +853,19 @@ impl Server {
.on::<SemanticTokensRangeRequest, _>(|id, params| {
self.semantic_tokens_range(id, params)
})?
+ .on::<InlayHintRequest, _>(|id,params| {
+ self.inlay_hints(id, params)
+ })?
+ .on::<InlayHintResolveRequest,_>(|id, params| {
+ self.inlay_hint_resolve(id, params)
+ })?
.default()
{
self.connection.sender.send(response.into())?;
}
}
Message::Notification(notification) => {
- NotificationDispatcher::new(notification)
+ dispatch::NotificationDispatcher::new(notification)
.on::<Cancel, _>(|params| self.cancel(params))?
.on::<DidChangeConfiguration, _>(|params| {
self.did_change_configuration(params)
@@ -824,26 +880,39 @@ impl Server {
.default();
}
Message::Response(response) => {
- let mut req_queue = self.req_queue.lock().unwrap();
- if let Some(data) = req_queue.outgoing.complete(response.id) {
- let result = match response.error {
- Some(error) => Err(error),
- None => Ok(response.result.unwrap_or_default()),
- };
- data.sender.send(result)?;
- }
+ self.client.recv_response(response)?;
}
};
},
recv(&self.internal_rx) -> msg => {
match msg? {
InternalMessage::SetDistro(distro) => {
- self.workspace.environment.resolver = Arc::new(distro.resolver);
- self.reparse_all()?;
+ let db = self.engine.write();
+ Workspace::get(db)
+ .set_file_name_db(db)
+ .with_durability(salsa::Durability::HIGH)
+ .to(distro.file_name_db);
}
InternalMessage::SetOptions(options) => {
- self.workspace.environment.options = options;
- self.reparse_all()?;
+ self.update_options(options);
+ }
+ InternalMessage::FileEvent(event) => {
+ self.handle_file_event(event);
+ }
+ InternalMessage::ForwardSearch(uri) => {
+ self.forward_search_internal(uri, None, |_| ())?;
+ }
+ InternalMessage::Diagnostics => {
+ self.publish_diagnostics()?;
+ }
+ InternalMessage::ChktexResult(uri, diagnostics) => {
+ let db = self.engine.write();
+ let workspace = Workspace::get(db);
+ if let Some(document) = workspace.lookup_uri(db, &uri) {
+ document.linter(db).set_chktex(db).to(diagnostics);
+ }
+
+ self.publish_diagnostics()?;
}
};
}
@@ -854,63 +923,34 @@ impl Server {
pub fn run(mut self) -> Result<()> {
self.initialize()?;
self.process_messages()?;
- self.pool.lock().unwrap().join();
+ self.pool.join();
+ self.engine.finish();
Ok(())
}
}
-fn create_debouncer(
- lsp_sender: Sender<Message>,
- diagnostic_manager: DiagnosticManager,
-) -> debouncer::Sender<Workspace> {
- let (tx, rx) = debouncer::unbounded();
- std::thread::spawn(move || {
- while let Ok(workspace) = rx.recv() {
- if let Err(why) = publish_diagnostics(&lsp_sender, &diagnostic_manager, &workspace) {
- warn!("Failed to publish diagnostics: {}", why);
- }
- }
- });
-
- tx
+struct FileWatcher {
+ watcher: notify::RecommendedWatcher,
+ watched_dirs: FxHashSet<PathBuf>,
}
-fn publish_diagnostics(
- lsp_sender: &Sender<lsp_server::Message>,
- diagnostic_manager: &DiagnosticManager,
- workspace: &Workspace,
-) -> Result<()> {
- for document in workspace.documents_by_uri.values() {
- if matches!(document.data, DocumentData::BuildLog(_)) {
- continue;
- }
+impl FileWatcher {
+ pub fn new(sender: Sender<InternalMessage>) -> Result<Self> {
+ let handle = move |event| {
+ if let Ok(event) = event {
+ sender.send(InternalMessage::FileEvent(event)).unwrap();
+ }
+ };
- let diagnostics = diagnostic_manager.publish(workspace, &document.uri);
- send_notification::<PublishDiagnostics>(
- lsp_sender,
- PublishDiagnosticsParams {
- uri: document.uri.as_ref().clone(),
- version: None,
- diagnostics,
- },
- )?;
+ Ok(Self {
+ watcher: notify::recommended_watcher(handle)?,
+ watched_dirs: FxHashSet::default(),
+ })
}
- Ok(())
-}
-
-fn apply_document_edit(old_text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) {
- for change in changes {
- let line_index = LineIndex::new(old_text);
- match change.range {
- Some(range) => {
- let range = std::ops::Range::<usize>::from(line_index.offset_lsp_range(range));
- old_text.replace_range(range, &change.text);
- }
- None => {
- *old_text = change.text;
- }
- };
+ pub fn watch(&mut self, db: &dyn Db) {
+ let workspace = Workspace::get(db);
+ workspace.watch(db, &mut self.watcher, &mut self.watched_dirs);
}
}
@@ -933,3 +973,29 @@ impl lsp_types::request::Request for ForwardSearchRequest {
const METHOD: &'static str = "textDocument/forwardSearch";
}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum ForwardSearchStatus {
+ SUCCESS = 0,
+ ERROR = 1,
+ FAILURE = 2,
+ UNCONFIGURED = 3,
+}
+
+impl From<forward_search::Error> for ForwardSearchStatus {
+ fn from(err: forward_search::Error) -> Self {
+ match err {
+ forward_search::Error::TexNotFound(_) => ForwardSearchStatus::FAILURE,
+ forward_search::Error::PdfNotFound(_) => ForwardSearchStatus::ERROR,
+ forward_search::Error::NoLocalFile(_) => ForwardSearchStatus::FAILURE,
+ forward_search::Error::Unconfigured => ForwardSearchStatus::UNCONFIGURED,
+ forward_search::Error::Spawn(_) => ForwardSearchStatus::ERROR,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct ForwardSearchResult {
+ pub status: ForwardSearchStatus,
+}
diff --git a/support/texlab/src/dispatch.rs b/support/texlab/src/server/dispatch.rs
index f5659ee3b4..f5659ee3b4 100644
--- a/support/texlab/src/dispatch.rs
+++ b/support/texlab/src/server/dispatch.rs
diff --git a/support/texlab/src/server/query.rs b/support/texlab/src/server/query.rs
new file mode 100644
index 0000000000..f19d15f85b
--- /dev/null
+++ b/support/texlab/src/server/query.rs
@@ -0,0 +1,32 @@
+use salsa::{DbWithJar, ParallelDatabase};
+use threadpool::ThreadPool;
+
+use crate::{Database, Db};
+
+#[derive(Default)]
+pub struct Engine {
+ db: Database,
+ pool: ThreadPool,
+}
+
+impl Engine {
+ pub fn read(&self) -> &dyn Db {
+ &self.db
+ }
+
+ pub fn write(&mut self) -> &mut dyn Db {
+ self.pool.join();
+ &mut self.db
+ }
+
+ pub fn fork<F: FnOnce(&dyn Db) + Send + 'static>(&self, action: F) {
+ let snapshot = self.db.snapshot();
+ self.pool.execute(move || {
+ action(snapshot.as_jar_db());
+ });
+ }
+
+ pub fn finish(self) {
+ self.pool.join();
+ }
+}
diff --git a/support/texlab/src/syntax.rs b/support/texlab/src/syntax.rs
index a0ae96089c..dc8761b73d 100644
--- a/support/texlab/src/syntax.rs
+++ b/support/texlab/src/syntax.rs
@@ -1,3 +1,22 @@
pub mod bibtex;
-pub mod build_log;
pub mod latex;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
+pub enum BuildErrorLevel {
+ Error,
+ Warning,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub struct BuildError {
+ pub relative_path: std::path::PathBuf,
+ pub level: BuildErrorLevel,
+ pub message: String,
+ pub hint: Option<String>,
+ pub line: Option<u32>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub struct BuildLog {
+ pub errors: Vec<BuildError>,
+}
diff --git a/support/texlab/src/syntax/bibtex.rs b/support/texlab/src/syntax/bibtex.rs
index a35399419a..9b4b1ab77e 100644
--- a/support/texlab/src/syntax/bibtex.rs
+++ b/support/texlab/src/syntax/bibtex.rs
@@ -1,10 +1,4 @@
-mod ast;
-mod lexer;
-mod parser;
-#[cfg(test)]
-mod tests;
-
-pub use self::{ast::*, parser::parse, SyntaxKind::*};
+use rowan::{ast::AstNode, NodeOrToken};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
#[allow(non_camel_case_types)]
@@ -42,6 +36,8 @@ pub enum SyntaxKind {
ROOT,
}
+pub use SyntaxKind::*;
+
impl From<SyntaxKind> for rowan::SyntaxKind {
fn from(kind: SyntaxKind) -> Self {
Self(kind as u16)
@@ -69,3 +65,240 @@ pub type SyntaxNode = rowan::SyntaxNode<Lang>;
pub type SyntaxToken = rowan::SyntaxToken<Lang>;
pub type SyntaxElement = rowan::SyntaxElement<Lang>;
+
+macro_rules! ast_node {
+ (name: $name:ident, kinds: [$($kind:pat),+], traits: [$($trait: ident),*]) => {
+ #[derive(Clone)]
+ pub struct $name {
+ node: SyntaxNode,
+ }
+
+ impl AstNode for $name {
+ type Language = Lang;
+
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ $($kind => true,)+
+ _ => false,
+ }
+ }
+
+ fn cast(node: SyntaxNode) -> Option<Self>
+ where
+ Self: Sized,
+ {
+ match node.kind() {
+ $($kind => Some(Self { node}),)+
+ _ => None,
+ }
+ }
+
+ fn syntax(&self) -> &SyntaxNode {
+ &self.node
+ }
+ }
+
+ $(
+ impl $trait for $name { }
+ )*
+ };
+}
+
+macro_rules! ast_node_enum {
+ (name: $name:ident, variants: [$($variant:ident),+]) => {
+ #[derive(Clone)]
+ pub enum $name {
+ $($variant($variant),)*
+ }
+
+ impl AstNode for $name {
+ type Language = Lang;
+
+ fn can_cast(kind: SyntaxKind) -> bool {
+ false $(|| $variant::can_cast(kind))+
+ }
+
+ fn cast(node: SyntaxNode) -> Option<Self>
+ where
+ Self: Sized,
+ {
+ None $(.or_else(|| $variant::cast(node.clone()).map(Self::$variant)))*
+ }
+
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ $(Self::$variant(node) => node.syntax(),)*
+ }
+ }
+ }
+
+ $(
+ impl From<$variant> for $name {
+ fn from(node: $variant) -> Self {
+ Self::$variant(node)
+ }
+ }
+ )*
+ };
+}
+
+pub trait HasType: AstNode<Language = Lang> {
+ fn type_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == TYPE)
+ }
+}
+
+pub trait HasDelims: AstNode<Language = Lang> {
+ fn left_delim_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == L_DELIM)
+ }
+
+ fn right_delim_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == R_DELIM)
+ }
+}
+
+pub trait HasName: AstNode<Language = Lang> {
+ fn name_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == NAME)
+ }
+}
+
+pub trait HasEq: AstNode<Language = Lang> {
+ fn eq_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == NAME)
+ }
+}
+
+pub trait HasComma: AstNode<Language = Lang> {
+ fn comma_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == COMMA)
+ }
+}
+
+pub trait HasPound: AstNode<Language = Lang> {
+ fn pound_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == POUND)
+ }
+}
+
+pub trait HasInteger: AstNode<Language = Lang> {
+ fn integer_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == INTEGER)
+ }
+}
+
+pub trait HasCommandName: AstNode<Language = Lang> {
+ fn command_name_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == COMMAND_NAME)
+ }
+}
+
+pub trait HasAccentName: AstNode<Language = Lang> {
+ fn accent_name_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == ACCENT_NAME)
+ }
+}
+
+pub trait HasWord: AstNode<Language = Lang> {
+ fn word_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|token| token.kind() == WORD)
+ }
+}
+
+pub trait HasValue: AstNode<Language = Lang> {
+ fn value(&self) -> Option<Value> {
+ self.syntax().children().find_map(Value::cast)
+ }
+}
+
+ast_node!(name: Root, kinds: [ROOT], traits: []);
+
+impl Root {
+ pub fn strings(&self) -> impl Iterator<Item = StringDef> {
+ self.syntax().children().filter_map(StringDef::cast)
+ }
+
+ pub fn entries(&self) -> impl Iterator<Item = Entry> {
+ self.syntax().children().filter_map(Entry::cast)
+ }
+
+ pub fn find_entry(&self, name: &str) -> Option<Entry> {
+ self.entries().find(|entry| {
+ entry
+ .name_token()
+ .map_or(false, |token| token.text() == name)
+ })
+ }
+}
+
+ast_node!(name: Preamble, kinds: [PREAMBLE], traits: [HasType, HasDelims, HasValue]);
+
+ast_node!(name: StringDef, kinds: [STRING], traits: [HasType, HasDelims, HasName, HasEq, HasValue]);
+
+ast_node!(name: Entry, kinds: [ENTRY], traits: [HasType, HasDelims, HasName, HasComma]);
+
+impl Entry {
+ pub fn fields(&self) -> impl Iterator<Item = Field> {
+ self.syntax().children().filter_map(Field::cast)
+ }
+}
+
+ast_node!(name: Field, kinds: [FIELD], traits: [HasName, HasEq, HasValue, HasComma]);
+
+ast_node_enum!(name: Value, variants: [Literal, CurlyGroup, QuoteGroup, Join, Accent, Command]);
+
+ast_node!(name: Literal, kinds: [LITERAL], traits: [HasName, HasInteger]);
+
+ast_node!(name: CurlyGroup, kinds: [CURLY_GROUP], traits: []);
+
+ast_node!(name: QuoteGroup, kinds: [QUOTE_GROUP], traits: []);
+
+ast_node!(name: Join, kinds: [JOIN], traits: [HasPound]);
+
+impl Join {
+ pub fn left_value(&self) -> Option<Value> {
+ self.syntax().children().find_map(Value::cast)
+ }
+
+ pub fn right_value(&self) -> Option<Value> {
+ self.syntax().children().filter_map(Value::cast).nth(1)
+ }
+}
+
+ast_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]);
+
+ast_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]);
diff --git a/support/texlab/src/syntax/bibtex/ast.rs b/support/texlab/src/syntax/bibtex/ast.rs
deleted file mode 100644
index 10ac571dd2..0000000000
--- a/support/texlab/src/syntax/bibtex/ast.rs
+++ /dev/null
@@ -1,244 +0,0 @@
-use rowan::{ast::AstNode, NodeOrToken};
-
-use super::{
- Lang,
- SyntaxKind::{self, *},
- SyntaxNode, SyntaxToken,
-};
-
-macro_rules! ast_node {
- (name: $name:ident, kinds: [$($kind:pat),+], traits: [$($trait: ident),*]) => {
- #[derive(Clone)]
- pub struct $name {
- node: SyntaxNode,
- }
-
- impl AstNode for $name {
- type Language = Lang;
-
- fn can_cast(kind: SyntaxKind) -> bool {
- match kind {
- $($kind => true,)+
- _ => false,
- }
- }
-
- fn cast(node: SyntaxNode) -> Option<Self>
- where
- Self: Sized,
- {
- match node.kind() {
- $($kind => Some(Self { node}),)+
- _ => None,
- }
- }
-
- fn syntax(&self) -> &SyntaxNode {
- &self.node
- }
- }
-
- $(
- impl $trait for $name { }
- )*
- };
-}
-
-macro_rules! ast_node_enum {
- (name: $name:ident, variants: [$($variant:ident),+]) => {
- #[derive(Clone)]
- pub enum $name {
- $($variant($variant),)*
- }
-
- impl AstNode for $name {
- type Language = Lang;
-
- fn can_cast(kind: SyntaxKind) -> bool {
- false $(|| $variant::can_cast(kind))+
- }
-
- fn cast(node: SyntaxNode) -> Option<Self>
- where
- Self: Sized,
- {
- None $(.or_else(|| $variant::cast(node.clone()).map(Self::$variant)))*
- }
-
- fn syntax(&self) -> &SyntaxNode {
- match self {
- $(Self::$variant(node) => node.syntax(),)*
- }
- }
- }
-
- $(
- impl From<$variant> for $name {
- fn from(node: $variant) -> Self {
- Self::$variant(node)
- }
- }
- )*
- };
-}
-
-pub trait HasType: AstNode<Language = Lang> {
- fn type_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == TYPE)
- }
-}
-
-pub trait HasDelims: AstNode<Language = Lang> {
- fn left_delim_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == L_DELIM)
- }
-
- fn right_delim_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == R_DELIM)
- }
-}
-
-pub trait HasName: AstNode<Language = Lang> {
- fn name_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == NAME)
- }
-}
-
-pub trait HasEq: AstNode<Language = Lang> {
- fn eq_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == NAME)
- }
-}
-
-pub trait HasComma: AstNode<Language = Lang> {
- fn comma_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == COMMA)
- }
-}
-
-pub trait HasPound: AstNode<Language = Lang> {
- fn pound_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == POUND)
- }
-}
-
-pub trait HasInteger: AstNode<Language = Lang> {
- fn integer_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == INTEGER)
- }
-}
-
-pub trait HasCommandName: AstNode<Language = Lang> {
- fn command_name_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == COMMAND_NAME)
- }
-}
-
-pub trait HasAccentName: AstNode<Language = Lang> {
- fn accent_name_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == ACCENT_NAME)
- }
-}
-
-pub trait HasWord: AstNode<Language = Lang> {
- fn word_token(&self) -> Option<SyntaxToken> {
- self.syntax()
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|token| token.kind() == WORD)
- }
-}
-
-pub trait HasValue: AstNode<Language = Lang> {
- fn value(&self) -> Option<Value> {
- self.syntax().children().find_map(Value::cast)
- }
-}
-
-ast_node!(name: Root, kinds: [ROOT], traits: []);
-
-impl Root {
- pub fn strings(&self) -> impl Iterator<Item = StringDef> {
- self.syntax().children().filter_map(StringDef::cast)
- }
-
- pub fn entries(&self) -> impl Iterator<Item = Entry> {
- self.syntax().children().filter_map(Entry::cast)
- }
-
- pub fn find_entry(&self, name: &str) -> Option<Entry> {
- self.entries().find(|entry| {
- entry
- .name_token()
- .map_or(false, |token| token.text() == name)
- })
- }
-}
-
-ast_node!(name: Preamble, kinds: [PREAMBLE], traits: [HasType, HasDelims, HasValue]);
-
-ast_node!(name: StringDef, kinds: [STRING], traits: [HasType, HasDelims, HasName, HasEq, HasValue]);
-
-ast_node!(name: Entry, kinds: [ENTRY], traits: [HasType, HasDelims, HasName, HasComma]);
-
-impl Entry {
- pub fn fields(&self) -> impl Iterator<Item = Field> {
- self.syntax().children().filter_map(Field::cast)
- }
-}
-
-ast_node!(name: Field, kinds: [FIELD], traits: [HasName, HasEq, HasValue, HasComma]);
-
-ast_node_enum!(name: Value, variants: [Literal, CurlyGroup, QuoteGroup, Join, Accent, Command]);
-
-ast_node!(name: Literal, kinds: [LITERAL], traits: [HasName, HasInteger]);
-
-ast_node!(name: CurlyGroup, kinds: [CURLY_GROUP], traits: []);
-
-ast_node!(name: QuoteGroup, kinds: [QUOTE_GROUP], traits: []);
-
-ast_node!(name: Join, kinds: [JOIN], traits: [HasPound]);
-
-impl Join {
- pub fn left_value(&self) -> Option<Value> {
- self.syntax().children().find_map(Value::cast)
- }
-
- pub fn right_value(&self) -> Option<Value> {
- self.syntax().children().filter_map(Value::cast).nth(1)
- }
-}
-
-ast_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]);
-
-ast_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]);
diff --git a/support/texlab/src/syntax/bibtex/lexer.rs b/support/texlab/src/syntax/bibtex/lexer.rs
deleted file mode 100644
index 3ea8e9f8d2..0000000000
--- a/support/texlab/src/syntax/bibtex/lexer.rs
+++ /dev/null
@@ -1,180 +0,0 @@
-use logos::Logos;
-
-use super::SyntaxKind::{self, *};
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
-pub enum RootToken {
- #[token(r"@preamble", ignore(ascii_case))]
- Preamble,
-
- #[token(r"@string", ignore(ascii_case))]
- String,
-
- #[token(r"@comment", ignore(ascii_case))]
- Comment,
-
- #[regex(r"@[a-zA-Z]*")]
- Entry,
-
- #[regex(r"[^@]+")]
- #[error]
- Junk,
-}
-
-impl From<RootToken> for SyntaxKind {
- fn from(token: RootToken) -> Self {
- match token {
- RootToken::Preamble | RootToken::String | RootToken::Comment | RootToken::Entry => TYPE,
- RootToken::Junk => JUNK,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
-pub enum BodyToken {
- #[regex(r"\s+")]
- Whitespace,
-
- #[token("{")]
- #[token("(")]
- LDelim,
-
- #[token("}")]
- #[token(")")]
- RDelim,
-
- #[token(",")]
- Comma,
-
- #[token("=")]
- Eq,
-
- #[regex(r"[^\s\(\)\{\}@,=]+")]
- Name,
-
- #[error]
- Error,
-}
-
-impl From<BodyToken> for SyntaxKind {
- fn from(token: BodyToken) -> Self {
- match token {
- BodyToken::Whitespace => WHITESPACE,
- BodyToken::LDelim => L_DELIM,
- BodyToken::RDelim => R_DELIM,
- BodyToken::Comma => COMMA,
- BodyToken::Eq => EQ,
- BodyToken::Name => NAME,
- BodyToken::Error => unreachable!(),
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
-pub enum ValueToken {
- #[regex(r"\s+")]
- Whitespace,
-
- #[token("#")]
- Pound,
-
- #[token(",")]
- Comma,
-
- #[token("{")]
- LCurly,
-
- #[token("}")]
- RCurly,
-
- #[token("\"")]
- Quote,
-
- #[regex(r"\d+", priority = 2)]
- Integer,
-
- #[regex(r#"[^\s"\{\},]+"#)]
- #[error]
- Name,
-}
-
-impl From<ValueToken> for SyntaxKind {
- fn from(token: ValueToken) -> Self {
- match token {
- ValueToken::Whitespace => WHITESPACE,
- ValueToken::Pound => POUND,
- ValueToken::Comma => COMMA,
- ValueToken::LCurly => L_CURLY,
- ValueToken::RCurly => R_CURLY,
- ValueToken::Quote => QUOTE,
- ValueToken::Integer => INTEGER,
- ValueToken::Name => NAME,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Logos)]
-pub enum ContentToken {
- #[regex(r"\s+")]
- Whitespace,
-
- #[token(",")]
- Comma,
-
- #[token("{")]
- LCurly,
-
- #[token("}")]
- RCurly,
-
- #[token("\"")]
- Quote,
-
- #[regex(r"\d+", priority = 2)]
- Integer,
-
- #[token(r#"~"#)]
- Nbsp,
-
- #[token(r#"\`"#)]
- #[token(r#"\'"#)]
- #[token(r#"\^"#)]
- #[token(r#"\""#)]
- #[token(r#"\H"#)]
- #[token(r#"\~"#)]
- #[token(r#"\c"#)]
- #[token(r#"\k"#)]
- #[token(r#"\="#)]
- #[token(r#"\b"#)]
- #[token(r#"\."#)]
- #[token(r#"\d"#)]
- #[token(r#"\r"#)]
- #[token(r#"\u"#)]
- #[token(r#"\v"#)]
- #[token(r#"\t"#)]
- AccentName,
-
- #[regex(r"\\([^\r\n]|[@a-zA-Z:_]+\*?)?")]
- CommandName,
-
- #[regex(r#"[^\s"\{\}\\~,]+"#)]
- #[error]
- Word,
-}
-
-impl From<ContentToken> for SyntaxKind {
- fn from(token: ContentToken) -> Self {
- match token {
- ContentToken::Whitespace => WHITESPACE,
- ContentToken::Comma => COMMA,
- ContentToken::LCurly => L_CURLY,
- ContentToken::RCurly => R_CURLY,
- ContentToken::Quote => QUOTE,
- ContentToken::Integer => INTEGER,
- ContentToken::Nbsp => NBSP,
- ContentToken::AccentName => ACCENT_NAME,
- ContentToken::CommandName => COMMAND_NAME,
- ContentToken::Word => WORD,
- }
- }
-}
diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_001.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_001.snap
deleted file mode 100644
index 4fbec161d2..0000000000
--- a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_001.snap
+++ /dev/null
@@ -1,430 +0,0 @@
----
-source: src/syntax/bibtex/tests.rs
-assertion_line: 47
-expression: "setup(r#\"@incollection{westfahl:space,\n author = {Westfahl, Gary},\n title = {The True Frontier},\n subtitle = {Confronting and Avoiding the Realities of Space in {American}\n Science Fiction Films},\n pages = {55-65},\n crossref = {westfahl:frontier},\n langid = {english},\n langidopts = {variant=american},\n indextitle = {True Frontier, The},\n annotation = {A cross-referenced article from a \\texttt{collection}. This is\n an \\texttt{incollection} entry with a \\texttt{crossref}\n field. Note the \\texttt{subtitle} and \\texttt{indextitle}\n fields},\n}\n\n% booktitle and booksubtitle are only needed for BibTeX's less sophisticated\n% inheritance set-up to make sure westfahl:space shows correctly.\n% With Biber they are not needed.\n@collection{westfahl:frontier,\n editor = {Westfahl, Gary},\n title = {Space and Beyond},\n date = 2000,\n subtitle = {The Frontier Theme in Science Fiction},\n publisher = {Greenwood},\n location = {Westport, Conn. and London},\n langid = {english},\n langidopts = {variant=american},\n booktitle = {Space and Beyond},\n booksubtitle = {The Frontier Theme in Science Fiction},\n annotation = {This is a \\texttt{collection} entry. Note the format of the\n \\texttt{location} field as well as the \\texttt{subtitle}\n field},\n}\"#)"
----
-ROOT@0..1484
- ENTRY@0..678
- TYPE@0..13 "@incollection"
- L_DELIM@13..14 "{"
- NAME@14..28 "westfahl:space"
- COMMA@28..29 ","
- WHITESPACE@29..34 "\n "
- FIELD@34..66
- NAME@34..40 "author"
- WHITESPACE@40..47 " "
- EQ@47..48 "="
- WHITESPACE@48..49 " "
- CURLY_GROUP@49..65
- L_CURLY@49..50 "{"
- WORD@50..58 "Westfahl"
- COMMA@58..59 ","
- WHITESPACE@59..60 " "
- WORD@60..64 "Gary"
- R_CURLY@64..65 "}"
- COMMA@65..66 ","
- WHITESPACE@66..71 "\n "
- FIELD@71..106
- NAME@71..76 "title"
- WHITESPACE@76..84 " "
- EQ@84..85 "="
- WHITESPACE@85..86 " "
- CURLY_GROUP@86..105
- L_CURLY@86..87 "{"
- WORD@87..90 "The"
- WHITESPACE@90..91 " "
- WORD@91..95 "True"
- WHITESPACE@95..96 " "
- WORD@96..104 "Frontier"
- R_CURLY@104..105 "}"
- COMMA@105..106 ","
- WHITESPACE@106..111 "\n "
- FIELD@111..232
- NAME@111..119 "subtitle"
- WHITESPACE@119..124 " "
- EQ@124..125 "="
- WHITESPACE@125..126 " "
- CURLY_GROUP@126..231
- L_CURLY@126..127 "{"
- WORD@127..138 "Confronting"
- WHITESPACE@138..139 " "
- WORD@139..142 "and"
- WHITESPACE@142..143 " "
- WORD@143..151 "Avoiding"
- WHITESPACE@151..152 " "
- WORD@152..155 "the"
- WHITESPACE@155..156 " "
- WORD@156..165 "Realities"
- WHITESPACE@165..166 " "
- WORD@166..168 "of"
- WHITESPACE@168..169 " "
- WORD@169..174 "Space"
- WHITESPACE@174..175 " "
- WORD@175..177 "in"
- WHITESPACE@177..178 " "
- CURLY_GROUP@178..188
- L_CURLY@178..179 "{"
- WORD@179..187 "American"
- R_CURLY@187..188 "}"
- WHITESPACE@188..209 "\n "
- WORD@209..216 "Science"
- WHITESPACE@216..217 " "
- WORD@217..224 "Fiction"
- WHITESPACE@224..225 " "
- WORD@225..230 "Films"
- R_CURLY@230..231 "}"
- COMMA@231..232 ","
- WHITESPACE@232..237 "\n "
- FIELD@237..260
- NAME@237..242 "pages"
- WHITESPACE@242..250 " "
- EQ@250..251 "="
- WHITESPACE@251..252 " "
- CURLY_GROUP@252..259
- L_CURLY@252..253 "{"
- WORD@253..258 "55-65"
- R_CURLY@258..259 "}"
- COMMA@259..260 ","
- WHITESPACE@260..265 "\n "
- FIELD@265..300
- NAME@265..273 "crossref"
- WHITESPACE@273..278 " "
- EQ@278..279 "="
- WHITESPACE@279..280 " "
- CURLY_GROUP@280..299
- L_CURLY@280..281 "{"
- WORD@281..298 "westfahl:frontier"
- R_CURLY@298..299 "}"
- COMMA@299..300 ","
- WHITESPACE@300..305 "\n "
- FIELD@305..330
- NAME@305..311 "langid"
- WHITESPACE@311..318 " "
- EQ@318..319 "="
- WHITESPACE@319..320 " "
- CURLY_GROUP@320..329
- L_CURLY@320..321 "{"
- WORD@321..328 "english"
- R_CURLY@328..329 "}"
- COMMA@329..330 ","
- WHITESPACE@330..335 "\n "
- FIELD@335..369
- NAME@335..345 "langidopts"
- WHITESPACE@345..348 " "
- EQ@348..349 "="
- WHITESPACE@349..350 " "
- CURLY_GROUP@350..368
- L_CURLY@350..351 "{"
- WORD@351..367 "variant=american"
- R_CURLY@367..368 "}"
- COMMA@368..369 ","
- WHITESPACE@369..374 "\n "
- FIELD@374..410
- NAME@374..384 "indextitle"
- WHITESPACE@384..387 " "
- EQ@387..388 "="
- WHITESPACE@388..389 " "
- CURLY_GROUP@389..409
- L_CURLY@389..390 "{"
- WORD@390..394 "True"
- WHITESPACE@394..395 " "
- WORD@395..403 "Frontier"
- COMMA@403..404 ","
- WHITESPACE@404..405 " "
- WORD@405..408 "The"
- R_CURLY@408..409 "}"
- COMMA@409..410 ","
- WHITESPACE@410..415 "\n "
- FIELD@415..676
- NAME@415..425 "annotation"
- WHITESPACE@425..428 " "
- EQ@428..429 "="
- WHITESPACE@429..430 " "
- CURLY_GROUP@430..675
- L_CURLY@430..431 "{"
- WORD@431..432 "A"
- WHITESPACE@432..433 " "
- WORD@433..449 "cross-referenced"
- WHITESPACE@449..450 " "
- WORD@450..457 "article"
- WHITESPACE@457..458 " "
- WORD@458..462 "from"
- WHITESPACE@462..463 " "
- WORD@463..464 "a"
- WHITESPACE@464..465 " "
- COMMAND@465..472
- COMMAND_NAME@465..472 "\\texttt"
- CURLY_GROUP@472..484
- L_CURLY@472..473 "{"
- WORD@473..483 "collection"
- R_CURLY@483..484 "}"
- WORD@484..485 "."
- WHITESPACE@485..486 " "
- WORD@486..490 "This"
- WHITESPACE@490..491 " "
- WORD@491..493 "is"
- WHITESPACE@493..514 "\n "
- WORD@514..516 "an"
- WHITESPACE@516..517 " "
- COMMAND@517..524
- COMMAND_NAME@517..524 "\\texttt"
- CURLY_GROUP@524..538
- L_CURLY@524..525 "{"
- WORD@525..537 "incollection"
- R_CURLY@537..538 "}"
- WHITESPACE@538..539 " "
- WORD@539..544 "entry"
- WHITESPACE@544..545 " "
- WORD@545..549 "with"
- WHITESPACE@549..550 " "
- WORD@550..551 "a"
- WHITESPACE@551..552 " "
- COMMAND@552..559
- COMMAND_NAME@552..559 "\\texttt"
- CURLY_GROUP@559..569
- L_CURLY@559..560 "{"
- WORD@560..568 "crossref"
- R_CURLY@568..569 "}"
- WHITESPACE@569..590 "\n "
- WORD@590..596 "field."
- WHITESPACE@596..597 " "
- WORD@597..601 "Note"
- WHITESPACE@601..602 " "
- WORD@602..605 "the"
- WHITESPACE@605..606 " "
- COMMAND@606..613
- COMMAND_NAME@606..613 "\\texttt"
- CURLY_GROUP@613..623
- L_CURLY@613..614 "{"
- WORD@614..622 "subtitle"
- R_CURLY@622..623 "}"
- WHITESPACE@623..624 " "
- WORD@624..627 "and"
- WHITESPACE@627..628 " "
- COMMAND@628..635
- COMMAND_NAME@628..635 "\\texttt"
- CURLY_GROUP@635..647
- L_CURLY@635..636 "{"
- WORD@636..646 "indextitle"
- R_CURLY@646..647 "}"
- WHITESPACE@647..668 "\n "
- WORD@668..674 "fields"
- R_CURLY@674..675 "}"
- COMMA@675..676 ","
- WHITESPACE@676..677 "\n"
- R_DELIM@677..678 "}"
- JUNK@678..857 "\n\n% booktitle and boo ..."
- ENTRY@857..1484
- TYPE@857..868 "@collection"
- L_DELIM@868..869 "{"
- NAME@869..886 "westfahl:frontier"
- COMMA@886..887 ","
- WHITESPACE@887..892 "\n "
- FIELD@892..924
- NAME@892..898 "editor"
- WHITESPACE@898..905 " "
- EQ@905..906 "="
- WHITESPACE@906..907 " "
- CURLY_GROUP@907..923
- L_CURLY@907..908 "{"
- WORD@908..916 "Westfahl"
- COMMA@916..917 ","
- WHITESPACE@917..918 " "
- WORD@918..922 "Gary"
- R_CURLY@922..923 "}"
- COMMA@923..924 ","
- WHITESPACE@924..929 "\n "
- FIELD@929..963
- NAME@929..934 "title"
- WHITESPACE@934..942 " "
- EQ@942..943 "="
- WHITESPACE@943..944 " "
- CURLY_GROUP@944..962
- L_CURLY@944..945 "{"
- WORD@945..950 "Space"
- WHITESPACE@950..951 " "
- WORD@951..954 "and"
- WHITESPACE@954..955 " "
- WORD@955..961 "Beyond"
- R_CURLY@961..962 "}"
- COMMA@962..963 ","
- WHITESPACE@963..968 "\n "
- FIELD@968..988
- NAME@968..972 "date"
- WHITESPACE@972..981 " "
- EQ@981..982 "="
- WHITESPACE@982..983 " "
- LITERAL@983..987
- INTEGER@983..987 "2000"
- COMMA@987..988 ","
- WHITESPACE@988..993 "\n "
- FIELD@993..1048
- NAME@993..1001 "subtitle"
- WHITESPACE@1001..1006 " "
- EQ@1006..1007 "="
- WHITESPACE@1007..1008 " "
- CURLY_GROUP@1008..1047
- L_CURLY@1008..1009 "{"
- WORD@1009..1012 "The"
- WHITESPACE@1012..1013 " "
- WORD@1013..1021 "Frontier"
- WHITESPACE@1021..1022 " "
- WORD@1022..1027 "Theme"
- WHITESPACE@1027..1028 " "
- WORD@1028..1030 "in"
- WHITESPACE@1030..1031 " "
- WORD@1031..1038 "Science"
- WHITESPACE@1038..1039 " "
- WORD@1039..1046 "Fiction"
- R_CURLY@1046..1047 "}"
- COMMA@1047..1048 ","
- WHITESPACE@1048..1053 "\n "
- FIELD@1053..1080
- NAME@1053..1062 "publisher"
- WHITESPACE@1062..1066 " "
- EQ@1066..1067 "="
- WHITESPACE@1067..1068 " "
- CURLY_GROUP@1068..1079
- L_CURLY@1068..1069 "{"
- WORD@1069..1078 "Greenwood"
- R_CURLY@1078..1079 "}"
- COMMA@1079..1080 ","
- WHITESPACE@1080..1085 "\n "
- FIELD@1085..1129
- NAME@1085..1093 "location"
- WHITESPACE@1093..1098 " "
- EQ@1098..1099 "="
- WHITESPACE@1099..1100 " "
- CURLY_GROUP@1100..1128
- L_CURLY@1100..1101 "{"
- WORD@1101..1109 "Westport"
- COMMA@1109..1110 ","
- WHITESPACE@1110..1111 " "
- WORD@1111..1116 "Conn."
- WHITESPACE@1116..1117 " "
- WORD@1117..1120 "and"
- WHITESPACE@1120..1121 " "
- WORD@1121..1127 "London"
- R_CURLY@1127..1128 "}"
- COMMA@1128..1129 ","
- WHITESPACE@1129..1134 "\n "
- FIELD@1134..1159
- NAME@1134..1140 "langid"
- WHITESPACE@1140..1147 " "
- EQ@1147..1148 "="
- WHITESPACE@1148..1149 " "
- CURLY_GROUP@1149..1158
- L_CURLY@1149..1150 "{"
- WORD@1150..1157 "english"
- R_CURLY@1157..1158 "}"
- COMMA@1158..1159 ","
- WHITESPACE@1159..1164 "\n "
- FIELD@1164..1198
- NAME@1164..1174 "langidopts"
- WHITESPACE@1174..1177 " "
- EQ@1177..1178 "="
- WHITESPACE@1178..1179 " "
- CURLY_GROUP@1179..1197
- L_CURLY@1179..1180 "{"
- WORD@1180..1196 "variant=american"
- R_CURLY@1196..1197 "}"
- COMMA@1197..1198 ","
- WHITESPACE@1198..1203 "\n "
- FIELD@1203..1237
- NAME@1203..1212 "booktitle"
- WHITESPACE@1212..1216 " "
- EQ@1216..1217 "="
- WHITESPACE@1217..1218 " "
- CURLY_GROUP@1218..1236
- L_CURLY@1218..1219 "{"
- WORD@1219..1224 "Space"
- WHITESPACE@1224..1225 " "
- WORD@1225..1228 "and"
- WHITESPACE@1228..1229 " "
- WORD@1229..1235 "Beyond"
- R_CURLY@1235..1236 "}"
- COMMA@1236..1237 ","
- WHITESPACE@1237..1242 "\n "
- FIELD@1242..1297
- NAME@1242..1254 "booksubtitle"
- WHITESPACE@1254..1255 " "
- EQ@1255..1256 "="
- WHITESPACE@1256..1257 " "
- CURLY_GROUP@1257..1296
- L_CURLY@1257..1258 "{"
- WORD@1258..1261 "The"
- WHITESPACE@1261..1262 " "
- WORD@1262..1270 "Frontier"
- WHITESPACE@1270..1271 " "
- WORD@1271..1276 "Theme"
- WHITESPACE@1276..1277 " "
- WORD@1277..1279 "in"
- WHITESPACE@1279..1280 " "
- WORD@1280..1287 "Science"
- WHITESPACE@1287..1288 " "
- WORD@1288..1295 "Fiction"
- R_CURLY@1295..1296 "}"
- COMMA@1296..1297 ","
- WHITESPACE@1297..1302 "\n "
- FIELD@1302..1482
- NAME@1302..1312 "annotation"
- WHITESPACE@1312..1315 " "
- EQ@1315..1316 "="
- WHITESPACE@1316..1317 " "
- CURLY_GROUP@1317..1481
- L_CURLY@1317..1318 "{"
- WORD@1318..1322 "This"
- WHITESPACE@1322..1323 " "
- WORD@1323..1325 "is"
- WHITESPACE@1325..1326 " "
- WORD@1326..1327 "a"
- WHITESPACE@1327..1328 " "
- COMMAND@1328..1335
- COMMAND_NAME@1328..1335 "\\texttt"
- CURLY_GROUP@1335..1347
- L_CURLY@1335..1336 "{"
- WORD@1336..1346 "collection"
- R_CURLY@1346..1347 "}"
- WHITESPACE@1347..1348 " "
- WORD@1348..1354 "entry."
- WHITESPACE@1354..1355 " "
- WORD@1355..1359 "Note"
- WHITESPACE@1359..1360 " "
- WORD@1360..1363 "the"
- WHITESPACE@1363..1364 " "
- WORD@1364..1370 "format"
- WHITESPACE@1370..1371 " "
- WORD@1371..1373 "of"
- WHITESPACE@1373..1374 " "
- WORD@1374..1377 "the"
- WHITESPACE@1377..1398 "\n "
- COMMAND@1398..1405
- COMMAND_NAME@1398..1405 "\\texttt"
- CURLY_GROUP@1405..1415
- L_CURLY@1405..1406 "{"
- WORD@1406..1414 "location"
- R_CURLY@1414..1415 "}"
- WHITESPACE@1415..1416 " "
- WORD@1416..1421 "field"
- WHITESPACE@1421..1422 " "
- WORD@1422..1424 "as"
- WHITESPACE@1424..1425 " "
- WORD@1425..1429 "well"
- WHITESPACE@1429..1430 " "
- WORD@1430..1432 "as"
- WHITESPACE@1432..1433 " "
- WORD@1433..1436 "the"
- WHITESPACE@1436..1437 " "
- COMMAND@1437..1444
- COMMAND_NAME@1437..1444 "\\texttt"
- CURLY_GROUP@1444..1454
- L_CURLY@1444..1445 "{"
- WORD@1445..1453 "subtitle"
- R_CURLY@1453..1454 "}"
- WHITESPACE@1454..1475 "\n "
- WORD@1475..1480 "field"
- R_CURLY@1480..1481 "}"
- COMMA@1481..1482 ","
- WHITESPACE@1482..1483 "\n"
- R_DELIM@1483..1484 "}"
-
diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_003.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_003.snap
deleted file mode 100644
index 360b133a8e..0000000000
--- a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__biblatex_examples_003.snap
+++ /dev/null
@@ -1,208 +0,0 @@
----
-source: src/syntax/bibtex/tests.rs
-assertion_line: 109
-expression: "setup(r#\"@article{angenendt,\n author = {Angenendt, Arnold},\n title = {In Honore Salvatoris~-- Vom Sinn und Unsinn der\n Patrozinienkunde},\n journaltitle = {Revue d'Histoire Eccl{\\'e}siastique},\n date = 2002,\n volume = 97,\n pages = {431--456, 791--823},\n langid = {german},\n indextitle = {In Honore Salvatoris},\n shorttitle = {In Honore Salvatoris},\n annotation = {A German article in a French journal. Apart from that, a\n typical \\texttt{article} entry. Note the \\texttt{indextitle}\n field},\n}\"#)"
----
-ROOT@0..616
- ENTRY@0..616
- TYPE@0..8 "@article"
- L_DELIM@8..9 "{"
- NAME@9..18 "angenendt"
- COMMA@18..19 ","
- WHITESPACE@19..24 "\n "
- FIELD@24..59
- NAME@24..30 "author"
- WHITESPACE@30..37 " "
- EQ@37..38 "="
- WHITESPACE@38..39 " "
- CURLY_GROUP@39..58
- L_CURLY@39..40 "{"
- WORD@40..49 "Angenendt"
- COMMA@49..50 ","
- WHITESPACE@50..51 " "
- WORD@51..57 "Arnold"
- R_CURLY@57..58 "}"
- COMMA@58..59 ","
- WHITESPACE@59..64 "\n "
- FIELD@64..166
- NAME@64..69 "title"
- WHITESPACE@69..77 " "
- EQ@77..78 "="
- WHITESPACE@78..79 " "
- CURLY_GROUP@79..165
- L_CURLY@79..80 "{"
- WORD@80..82 "In"
- WHITESPACE@82..83 " "
- WORD@83..89 "Honore"
- WHITESPACE@89..90 " "
- WORD@90..100 "Salvatoris"
- NBSP@100..101 "~"
- WORD@101..103 "--"
- WHITESPACE@103..104 " "
- WORD@104..107 "Vom"
- WHITESPACE@107..108 " "
- WORD@108..112 "Sinn"
- WHITESPACE@112..113 " "
- WORD@113..116 "und"
- WHITESPACE@116..117 " "
- WORD@117..123 "Unsinn"
- WHITESPACE@123..124 " "
- WORD@124..127 "der"
- WHITESPACE@127..148 "\n "
- WORD@148..164 "Patrozinienkunde"
- R_CURLY@164..165 "}"
- COMMA@165..166 ","
- WHITESPACE@166..171 "\n "
- FIELD@171..224
- NAME@171..183 "journaltitle"
- WHITESPACE@183..184 " "
- EQ@184..185 "="
- WHITESPACE@185..186 " "
- CURLY_GROUP@186..223
- L_CURLY@186..187 "{"
- WORD@187..192 "Revue"
- WHITESPACE@192..193 " "
- WORD@193..203 "d'Histoire"
- WHITESPACE@203..204 " "
- WORD@204..208 "Eccl"
- CURLY_GROUP@208..213
- L_CURLY@208..209 "{"
- ACCENT@209..212
- ACCENT_NAME@209..211 "\\'"
- WORD@211..212 "e"
- R_CURLY@212..213 "}"
- WORD@213..222 "siastique"
- R_CURLY@222..223 "}"
- COMMA@223..224 ","
- WHITESPACE@224..229 "\n "
- FIELD@229..249
- NAME@229..233 "date"
- WHITESPACE@233..242 " "
- EQ@242..243 "="
- WHITESPACE@243..244 " "
- LITERAL@244..248
- INTEGER@244..248 "2002"
- COMMA@248..249 ","
- WHITESPACE@249..254 "\n "
- FIELD@254..272
- NAME@254..260 "volume"
- WHITESPACE@260..267 " "
- EQ@267..268 "="
- WHITESPACE@268..269 " "
- LITERAL@269..271
- INTEGER@269..271 "97"
- COMMA@271..272 ","
- WHITESPACE@272..277 "\n "
- FIELD@277..313
- NAME@277..282 "pages"
- WHITESPACE@282..290 " "
- EQ@290..291 "="
- WHITESPACE@291..292 " "
- CURLY_GROUP@292..312
- L_CURLY@292..293 "{"
- WORD@293..301 "431--456"
- COMMA@301..302 ","
- WHITESPACE@302..303 " "
- WORD@303..311 "791--823"
- R_CURLY@311..312 "}"
- COMMA@312..313 ","
- WHITESPACE@313..318 "\n "
- FIELD@318..342
- NAME@318..324 "langid"
- WHITESPACE@324..331 " "
- EQ@331..332 "="
- WHITESPACE@332..333 " "
- CURLY_GROUP@333..341
- L_CURLY@333..334 "{"
- WORD@334..340 "german"
- R_CURLY@340..341 "}"
- COMMA@341..342 ","
- WHITESPACE@342..347 "\n "
- FIELD@347..385
- NAME@347..357 "indextitle"
- WHITESPACE@357..360 " "
- EQ@360..361 "="
- WHITESPACE@361..362 " "
- CURLY_GROUP@362..384
- L_CURLY@362..363 "{"
- WORD@363..365 "In"
- WHITESPACE@365..366 " "
- WORD@366..372 "Honore"
- WHITESPACE@372..373 " "
- WORD@373..383 "Salvatoris"
- R_CURLY@383..384 "}"
- COMMA@384..385 ","
- WHITESPACE@385..390 "\n "
- FIELD@390..428
- NAME@390..400 "shorttitle"
- WHITESPACE@400..403 " "
- EQ@403..404 "="
- WHITESPACE@404..405 " "
- CURLY_GROUP@405..427
- L_CURLY@405..406 "{"
- WORD@406..408 "In"
- WHITESPACE@408..409 " "
- WORD@409..415 "Honore"
- WHITESPACE@415..416 " "
- WORD@416..426 "Salvatoris"
- R_CURLY@426..427 "}"
- COMMA@427..428 ","
- WHITESPACE@428..433 "\n "
- FIELD@433..614
- NAME@433..443 "annotation"
- WHITESPACE@443..446 " "
- EQ@446..447 "="
- WHITESPACE@447..448 " "
- CURLY_GROUP@448..613
- L_CURLY@448..449 "{"
- WORD@449..450 "A"
- WHITESPACE@450..451 " "
- WORD@451..457 "German"
- WHITESPACE@457..458 " "
- WORD@458..465 "article"
- WHITESPACE@465..466 " "
- WORD@466..468 "in"
- WHITESPACE@468..469 " "
- WORD@469..470 "a"
- WHITESPACE@470..471 " "
- WORD@471..477 "French"
- WHITESPACE@477..478 " "
- WORD@478..486 "journal."
- WHITESPACE@486..487 " "
- WORD@487..492 "Apart"
- WHITESPACE@492..493 " "
- WORD@493..497 "from"
- WHITESPACE@497..498 " "
- WORD@498..502 "that"
- COMMA@502..503 ","
- WHITESPACE@503..504 " "
- WORD@504..505 "a"
- WHITESPACE@505..526 "\n "
- WORD@526..533 "typical"
- WHITESPACE@533..534 " "
- COMMAND@534..541
- COMMAND_NAME@534..541 "\\texttt"
- CURLY_GROUP@541..550
- L_CURLY@541..542 "{"
- WORD@542..549 "article"
- R_CURLY@549..550 "}"
- WHITESPACE@550..551 " "
- WORD@551..557 "entry."
- WHITESPACE@557..558 " "
- WORD@558..562 "Note"
- WHITESPACE@562..563 " "
- WORD@563..566 "the"
- WHITESPACE@566..567 " "
- COMMAND@567..574
- COMMAND_NAME@567..574 "\\texttt"
- CURLY_GROUP@574..586
- L_CURLY@574..575 "{"
- WORD@575..585 "indextitle"
- R_CURLY@585..586 "}"
- WHITESPACE@586..607 "\n "
- WORD@607..612 "field"
- R_CURLY@612..613 "}"
- COMMA@613..614 ","
- WHITESPACE@614..615 "\n"
- R_DELIM@615..616 "}"
-
diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__comment.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__comment.snap
deleted file mode 100644
index 3267517c49..0000000000
--- a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__comment.snap
+++ /dev/null
@@ -1,9 +0,0 @@
----
-source: src/syntax/bibtex/tests.rs
-assertion_line: 16
-expression: "setup(r#\"@comment{aksin,\n author = {Aks{\\i}n, {\\\"O}zge and T{\\\"u}rkmen, Hayati and Artok, Levent\n and {\\c{C}}etinkaya, Bekir and Ni, Chaoying and\n B{\\\"u}y{\\\"u}kg{\\\"u}ng{\\\"o}r, Orhan and {\\\"O}zkal, Erhan},\n title = {Effect of immobilization on catalytic characteristics of\n saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}\n reactions},\n journaltitle = jomch,\n date = 2006,\n volume = 691,\n number = 13,\n pages = {3027-3036},\n indextitle = {Effect of immobilization on catalytic characteristics},\n}\"#)"
----
-ROOT@0..637
- TYPE@0..8 "@comment"
- JUNK@8..637 "{aksin,\n author ..."
-
diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__entry_colon.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__entry_colon.snap
deleted file mode 100644
index bff11a8d5a..0000000000
--- a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__tests__entry_colon.snap
+++ /dev/null
@@ -1,51 +0,0 @@
----
-source: src/syntax/bibtex/tests.rs
-assertion_line: 36
-expression: "setup(r#\"@article{foo:2019,\n author = {Foo Bar},\n title = {Baz Qux},\n year = {2019},\n}\"#)"
----
-ROOT@0..86
- ENTRY@0..86
- TYPE@0..8 "@article"
- L_DELIM@8..9 "{"
- NAME@9..17 "foo:2019"
- COMMA@17..18 ","
- WHITESPACE@18..23 "\n "
- FIELD@23..42
- NAME@23..29 "author"
- WHITESPACE@29..30 " "
- EQ@30..31 "="
- WHITESPACE@31..32 " "
- CURLY_GROUP@32..41
- L_CURLY@32..33 "{"
- WORD@33..36 "Foo"
- WHITESPACE@36..37 " "
- WORD@37..40 "Bar"
- R_CURLY@40..41 "}"
- COMMA@41..42 ","
- WHITESPACE@42..47 "\n "
- FIELD@47..65
- NAME@47..52 "title"
- WHITESPACE@52..53 " "
- EQ@53..54 "="
- WHITESPACE@54..55 " "
- CURLY_GROUP@55..64
- L_CURLY@55..56 "{"
- WORD@56..59 "Baz"
- WHITESPACE@59..60 " "
- WORD@60..63 "Qux"
- R_CURLY@63..64 "}"
- COMMA@64..65 ","
- WHITESPACE@65..70 "\n "
- FIELD@70..84
- NAME@70..74 "year"
- WHITESPACE@74..75 " "
- EQ@75..76 "="
- WHITESPACE@76..77 " "
- CURLY_GROUP@77..83
- L_CURLY@77..78 "{"
- INTEGER@78..82 "2019"
- R_CURLY@82..83 "}"
- COMMA@83..84 ","
- WHITESPACE@84..85 "\n"
- R_DELIM@85..86 "}"
-
diff --git a/support/texlab/src/syntax/bibtex/tests.rs b/support/texlab/src/syntax/bibtex/tests.rs
deleted file mode 100644
index 8dce0e2df9..0000000000
--- a/support/texlab/src/syntax/bibtex/tests.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-use insta::assert_debug_snapshot;
-
-use super::{parse, SyntaxNode};
-
-fn setup(input: &str) -> SyntaxNode {
- SyntaxNode::new_root(parse(input))
-}
-
-#[test]
-fn test_preamble() {
- assert_debug_snapshot!(setup(r#"@preamble{"Hello World!"}"#));
-}
-
-#[test]
-fn test_comment() {
- assert_debug_snapshot!(setup(
- r#"@comment{aksin,
- author = {Aks{\i}n, {\"O}zge and T{\"u}rkmen, Hayati and Artok, Levent
- and {\c{C}}etinkaya, Bekir and Ni, Chaoying and
- B{\"u}y{\"u}kg{\"u}ng{\"o}r, Orhan and {\"O}zkal, Erhan},
- title = {Effect of immobilization on catalytic characteristics of
- saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}
- reactions},
- journaltitle = jomch,
- date = 2006,
- volume = 691,
- number = 13,
- pages = {3027-3036},
- indextitle = {Effect of immobilization on catalytic characteristics},
-}"#
- ));
-}
-
-#[test]
-fn test_entry_colon() {
- assert_debug_snapshot!(setup(
- r#"@article{foo:2019,
- author = {Foo Bar},
- title = {Baz Qux},
- year = {2019},
-}"#
- ));
-}
-
-#[test]
-fn test_biblatex_examples_001() {
- assert_debug_snapshot!(setup(
- r#"@incollection{westfahl:space,
- author = {Westfahl, Gary},
- title = {The True Frontier},
- subtitle = {Confronting and Avoiding the Realities of Space in {American}
- Science Fiction Films},
- pages = {55-65},
- crossref = {westfahl:frontier},
- langid = {english},
- langidopts = {variant=american},
- indextitle = {True Frontier, The},
- annotation = {A cross-referenced article from a \texttt{collection}. This is
- an \texttt{incollection} entry with a \texttt{crossref}
- field. Note the \texttt{subtitle} and \texttt{indextitle}
- fields},
-}
-
-% booktitle and booksubtitle are only needed for BibTeX's less sophisticated
-% inheritance set-up to make sure westfahl:space shows correctly.
-% With Biber they are not needed.
-@collection{westfahl:frontier,
- editor = {Westfahl, Gary},
- title = {Space and Beyond},
- date = 2000,
- subtitle = {The Frontier Theme in Science Fiction},
- publisher = {Greenwood},
- location = {Westport, Conn. and London},
- langid = {english},
- langidopts = {variant=american},
- booktitle = {Space and Beyond},
- booksubtitle = {The Frontier Theme in Science Fiction},
- annotation = {This is a \texttt{collection} entry. Note the format of the
- \texttt{location} field as well as the \texttt{subtitle}
- field},
-}"#
- ));
-}
-
-#[test]
-fn test_biblatex_examples_002() {
- assert_debug_snapshot!(setup(
- r#"@string{jomch = {J.~Organomet. Chem.}}
-
-@article{aksin,
- author = {Aks{\i}n, {\"O}zge and T{\"u}rkmen, Hayati and Artok, Levent
- and {\c{C}}etinkaya, Bekir and Ni, Chaoying and
- B{\"u}y{\"u}kg{\"u}ng{\"o}r, Orhan and {\"O}zkal, Erhan},
- title = {Effect of immobilization on catalytic characteristics of
- saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}
- reactions},
- journaltitle = jomch,
- date = 2006,
- volume = 691,
- number = 13,
- pages = {3027-3036},
- indextitle = {Effect of immobilization on catalytic characteristics},
-}"#
- ));
-}
-
-#[test]
-fn test_biblatex_examples_003() {
- assert_debug_snapshot!(setup(
- r#"@article{angenendt,
- author = {Angenendt, Arnold},
- title = {In Honore Salvatoris~-- Vom Sinn und Unsinn der
- Patrozinienkunde},
- journaltitle = {Revue d'Histoire Eccl{\'e}siastique},
- date = 2002,
- volume = 97,
- pages = {431--456, 791--823},
- langid = {german},
- indextitle = {In Honore Salvatoris},
- shorttitle = {In Honore Salvatoris},
- annotation = {A German article in a French journal. Apart from that, a
- typical \texttt{article} entry. Note the \texttt{indextitle}
- field},
-}"#
- ));
-}
diff --git a/support/texlab/src/syntax/build_log.rs b/support/texlab/src/syntax/build_log.rs
deleted file mode 100644
index af03e51d11..0000000000
--- a/support/texlab/src/syntax/build_log.rs
+++ /dev/null
@@ -1,1332 +0,0 @@
-use std::{cmp::Ordering, path::PathBuf};
-
-use once_cell::sync::Lazy;
-use regex::{Match, Regex};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
-pub enum BuildErrorLevel {
- Error,
- Warning,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Hash)]
-pub struct BuildError {
- pub relative_path: PathBuf,
- pub level: BuildErrorLevel,
- pub message: String,
- pub line: Option<u32>,
-}
-
-const MAX_LINE_LENGTH: usize = 79;
-
-pub static PACKAGE_MESSAGE_REGEX: Lazy<Regex> =
- Lazy::new(|| Regex::new("^\\([a-zA-Z_\\-]+\\)\\s*(?P<msg>.*)$").unwrap());
-
-pub static FILE_REGEX: Lazy<Regex> =
- Lazy::new(|| Regex::new("\\((?P<file>[^\r\n()]+\\.(tex|sty|cls))").unwrap());
-
-pub static TEX_ERROR_REGEX: Lazy<Regex> = Lazy::new(|| {
- Regex::new("(?m)^! ((?P<msg1>(.|\r|\n)*?)\r?\nl\\.(?P<line>\\d+)|(?P<msg2>[^\r\n]*))").unwrap()
-});
-
-pub static WARNING_REGEX: Lazy<Regex> =
- Lazy::new(|| Regex::new("(LaTeX|Package [a-zA-Z_\\-]+) Warning: (?P<msg>[^\r\n]*)").unwrap());
-
-pub static BAD_BOX_REGEX: Lazy<Regex> = Lazy::new(|| {
- Regex::new("(?P<msg>(Ov|Und)erfull \\\\[hv]box[^\r\n]*lines? (?P<line>\\d+)[^\r\n]*)").unwrap()
-});
-
-#[derive(Debug, PartialEq, Eq, Clone, Hash)]
-pub struct Parse {
- pub errors: Vec<BuildError>,
-}
-
-pub fn parse(log: &str) -> Parse {
- let log = prepare_log(log);
- let mut ranges: Vec<FileRange> = FILE_REGEX
- .find_iter(&log)
- .map(|result| FileRange::create(&log, result))
- .collect();
- ranges.sort();
-
- let tex_errors = extract_matches(&log, &ranges, &TEX_ERROR_REGEX, BuildErrorLevel::Error);
- let warnings = extract_matches(&log, &ranges, &WARNING_REGEX, BuildErrorLevel::Warning);
- let bad_boxes = extract_matches(&log, &ranges, &BAD_BOX_REGEX, BuildErrorLevel::Warning);
-
- Parse {
- errors: vec![tex_errors, warnings, bad_boxes].concat(),
- }
-}
-
-fn extract_matches(
- log: &str,
- ranges: &[FileRange],
- regex: &Regex,
- level: BuildErrorLevel,
-) -> Vec<BuildError> {
- let mut errors = Vec::new();
- for result in regex.find_iter(log) {
- let captures = regex.captures(&log[result.start()..result.end()]).unwrap();
- let message = captures
- .name("msg")
- .or_else(|| captures.name("msg1"))
- .or_else(|| captures.name("msg2"))
- .unwrap()
- .as_str()
- .lines()
- .next()
- .unwrap_or_default()
- .to_owned();
-
- if let Some(range) = ranges.iter().find(|range| range.contains(result.start())) {
- let line = captures
- .name("line")
- .map(|result| result.as_str().parse::<u32>().unwrap() - 1);
-
- errors.push(BuildError {
- relative_path: range.path.clone(),
- level,
- message,
- line,
- });
- }
- }
- errors
-}
-
-fn prepare_log(log: &str) -> String {
- let mut old_lines = log.lines();
- let mut new_lines: Vec<String> = Vec::new();
- while let Some(line) = old_lines.next() {
- if PACKAGE_MESSAGE_REGEX.is_match(line) {
- let captures = PACKAGE_MESSAGE_REGEX.captures(line).unwrap();
- if let Some(last_line) = new_lines.last_mut() {
- last_line.push(' ');
- last_line.push_str(captures.name("msg").unwrap().as_str());
- }
- } else if line.ends_with("...") {
- let mut new_line = line[line.len() - 3..].to_owned();
- if let Some(old_line) = old_lines.next() {
- new_line.push_str(old_line);
- }
- new_lines.push(new_line);
- } else if line.chars().count() == MAX_LINE_LENGTH {
- let mut new_line = String::new();
- new_line.push_str(line);
- if let Some(old_line) = old_lines.next() {
- new_line.push_str(old_line);
- }
- new_lines.push(new_line);
- } else {
- new_lines.push(line.to_owned());
- }
- }
- new_lines.join("\n")
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-struct FileRange {
- pub path: PathBuf,
- pub start: usize,
- pub end: usize,
-}
-
-impl FileRange {
- fn create(log: &str, result: Match) -> Self {
- let mut balance = 1;
- let mut end = result.start() + 1;
- let chars = (&log[result.start() + 1..]).chars();
- for c in chars {
- if balance <= 0 {
- break;
- }
-
- if c == '(' {
- balance += 1;
- } else if c == ')' {
- balance -= 1;
- }
- end += c.len_utf8();
- }
-
- let captures = FILE_REGEX.captures(result.as_str()).unwrap();
- let path = PathBuf::from(captures.name("file").unwrap().as_str());
- Self {
- path,
- start: result.start(),
- end,
- }
- }
-
- fn len(&self) -> usize {
- self.end - self.start + 1
- }
-
- fn contains(&self, index: usize) -> bool {
- index >= self.start && index <= self.end
- }
-}
-
-impl Ord for FileRange {
- fn cmp(&self, other: &Self) -> Ordering {
- self.len().cmp(&other.len())
- }
-}
-
-impl PartialOrd for FileRange {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use insta::assert_debug_snapshot;
-
- #[test]
- fn parse_001() {
- let log = r#"
-This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:50
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**./parent.tex
-(./parent.tex
-LaTeX2e <2017-04-15>
-Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
-(/TexLive/texmf-dist/tex/latex/base/article.cls
-Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
-(/TexLive/texmf-dist/tex/latex/base/size10.clo
-File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
-)
-\c@part=\count79
-\c@section=\count80
-\c@subsection=\count81
-\c@subsubsection=\count82
-\c@paragraph=\count83
-\c@subparagraph=\count84
-\c@figure=\count85
-\c@table=\count86
-\abovecaptionskip=\skip41
-\belowcaptionskip=\skip42
-\bibindent=\dimen102
-)
-(/TexLive/texmf-dist/tex/latex/multirow/bigstrut.sty
-Package: bigstrut 2016/11/25 v2.2 Provide larger struts in tabulars
-\bigstrutjot=\dimen103
-)
-(/TexLive/texmf-dist/tex/latex/multirow/multirow.sty
-Package: multirow 2016/11/25 v2.2 Span multiple rows of a table
-\multirow@colwidth=\skip43
-\multirow@cntb=\count87
-\multirow@dima=\skip44
-) (./parent.aux)
-\openout1 = `parent.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-
-Overfull \hbox (200.00162pt too wide) in paragraph at lines 8--9
-[]\OT1/cmr/m/n/10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-[]
-
-LaTeX Font Info: External font `cmex10' loaded for size
-(Font) <7> on input line 11.
-LaTeX Font Info: External font `cmex10' loaded for size
-(Font) <5> on input line 11.
-
-Overfull \vbox (3.19998pt too high) detected at line 23
-[]
-
-[1
-
-{/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux) )
-Here is how much of TeX's memory you used:
-265 strings out of 492995
-3121 string characters out of 6138727
-55074 words of memory out of 5000000
-3896 multiletter control sequences out of 15000+600000
-3640 words of font info for 14 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-23i,20n,20p,124b,282s stack positions out of 5000i,500n,10000p,200000b,80000s
-</
-TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></TexLive/texmf-d
-ist/fonts/type1/public/amsfonts/cm/cmr7.pfb>
-Output written on parent.pdf (1 page, 17505 bytes).
-PDF statistics:
-16 PDF objects out of 1000 (max. 8388607)
-10 compressed objects within 1 object stream
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_eq!(
- parse(log).errors,
- vec![
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "Overfull \\hbox (200.00162pt too wide) in paragraph at lines 8--9"
- .into(),
- line: Some(7),
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "Overfull \\vbox (3.19998pt too high) detected at line 23".into(),
- line: Some(22),
- }
- ]
- );
- }
-
- #[test]
- fn parse_002() {
- let log = r#"
-This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:52
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**./parent.tex
-(./parent.tex
-LaTeX2e <2017-04-15>
-Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
-(/TexLive/texmf-dist/tex/latex/base/article.cls
-Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
-(/TexLive/texmf-dist/tex/latex/base/size10.clo
-File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
-)
-\c@part=\count79
-\c@section=\count80
-\c@subsection=\count81
-\c@subsubsection=\count82
-\c@paragraph=\count83
-\c@subparagraph=\count84
-\c@figure=\count85
-\c@table=\count86
-\abovecaptionskip=\skip41
-\belowcaptionskip=\skip42
-\bibindent=\dimen102
-) (./parent.aux (./child.aux))
-\openout1 = `parent.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-(./child.tex
-! Undefined control sequence.
-l.1 \foo
-
-The control sequence at the end of the top line
-of your error message was never \def'ed. If you have
-misspelled it (e.g., `\hobx'), type `I' and the correct
-spelling (e.g., `I\hbox'). Otherwise just continue,
-and I'll forget about whatever was undefined.
-
-) (./parent.aux) )
-Here is how much of TeX's memory you used:
-205 strings out of 492995
-2149 string characters out of 6138727
-54074 words of memory out of 5000000
-3841 multiletter control sequences out of 15000+600000
-3640 words of font info for 14 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s
-
-No pages of output.
-PDF statistics:
-0 PDF objects out of 1000 (max. 8388607)
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_eq!(
- parse(log).errors,
- vec![BuildError {
- relative_path: "./child.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Undefined control sequence.".into(),
- line: Some(0)
- }]
- );
- }
-
- #[test]
- fn parse_003() {
- let log = r#"
-This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:51
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**./parent.tex
-(./parent.tex
-LaTeX2e <2017-04-15>
-Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
-(/TexLive/texmf-dist/tex/latex/base/article.cls
-Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
-(/TexLive/texmf-dist/tex/latex/base/size10.clo
-File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
-)
-\c@part=\count79
-\c@section=\count80
-\c@subsection=\count81
-\c@subsubsection=\count82
-\c@paragraph=\count83
-\c@subparagraph=\count84
-\c@figure=\count85
-\c@table=\count86
-\abovecaptionskip=\skip41
-\belowcaptionskip=\skip42
-\bibindent=\dimen102
-) (./parent.aux)
-\openout1 = `parent.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4.
-LaTeX Font Info: ... okay on input line 4.
-
-LaTeX Warning: Citation `foo' on page 1 undefined on input line 6.
-
-[1
-
-{/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux)
-
-LaTeX Warning: There were undefined references.
-
-)
-Here is how much of TeX's memory you used:
-204 strings out of 492995
-2142 string characters out of 6138727
-54074 words of memory out of 5000000
-3842 multiletter control sequences out of 15000+600000
-3948 words of font info for 15 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-23i,4n,21p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s
-</TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></TexLive/
-texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb>
-Output written on parent.pdf (1 page, 17339 bytes).
-PDF statistics:
-16 PDF objects out of 1000 (max. 8388607)
-10 compressed objects within 1 object stream
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_eq!(
- parse(log).errors,
- vec![
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "Citation `foo' on page 1 undefined on input line 6.".into(),
- line: None
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "There were undefined references.".into(),
- line: None,
- }
- ]
- );
- }
-
- #[test]
- fn parse_004() {
- let log = r#"
-This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:40
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**./parent.tex
-(./parent.tex
-LaTeX2e <2017-04-15>
-Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
-(/TexLive/texmf-dist/tex/latex/base/article.cls
-Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
-(/TexLive/texmf-dist/tex/latex/base/size10.clo
-File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
-)
-\c@part=\count79
-\c@section=\count80
-\c@subsection=\count81
-\c@subsubsection=\count82
-\c@paragraph=\count83
-\c@subparagraph=\count84
-\c@figure=\count85
-\c@table=\count86
-\abovecaptionskip=\skip41
-\belowcaptionskip=\skip42
-\bibindent=\dimen102
-)
-(/TexLive/texmf-dist/tex/generic/babel/babel.sty
-Package: babel 2017/05/19 3.10 The Babel package
-
-! Package babel Error: Unknown option `foo'. Either you misspelled it
-(babel) or the language definition file foo.ldf was not found.
-
-See the babel package documentation for explanation.
-Type H <return> for immediate help.
-...
-
-l.393 \ProcessOptions*
-
-Valid options are: shorthands=, KeepShorthandsActive,
-activeacute, activegrave, noconfigs, safe=, main=, math=
-headfoot=, strings=, config=, hyphenmap=, or a language name.
-
-! Package babel Error: You haven't specified a language option.
-
-See the babel package documentation for explanation.
-Type H <return> for immediate help.
-...
-
-l.426 ...ry to proceed from here, type x to quit.}
-
-You need to specify a language, either as a global option
-or as an optional argument to the \usepackage command;
-You shouldn't try to proceed from here, type x to quit.
-
-) (./parent.aux)
-\openout1 = `parent.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5.
-LaTeX Font Info: ... okay on input line 5.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5.
-LaTeX Font Info: ... okay on input line 5.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5.
-LaTeX Font Info: ... okay on input line 5.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5.
-LaTeX Font Info: ... okay on input line 5.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5.
-LaTeX Font Info: ... okay on input line 5.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5.
-LaTeX Font Info: ... okay on input line 5.
-(./parent.aux) )
-Here is how much of TeX's memory you used:
-304 strings out of 492995
-3421 string characters out of 6138727
-56035 words of memory out of 5000000
-3938 multiletter control sequences out of 15000+600000
-3640 words of font info for 14 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s
-
-No pages of output.
-PDF statistics:
-0 PDF objects out of 1000 (max. 8388607)
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_eq!(
- parse(log).errors,
- vec![
- BuildError {
- relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty".into(),
- level: BuildErrorLevel::Error,
- message: "Package babel Error: Unknown option `foo'. Either you misspelled it or the language definition file foo.ldf was not found.".into(),
- line: Some(392)
- },
- BuildError {
- relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty".into(),
- level: BuildErrorLevel::Error,
- message: "Package babel Error: You haven't specified a language option.".into(),
- line: Some(425)
- }
- ]
- );
- }
-
- #[test]
- fn parse_005() {
- let log = r#"
-This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:45
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**./parent.tex
-(./parent.tex
-LaTeX2e <2017-04-15>
-Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
-(/TexLive/texmf-dist/tex/latex/base/article.cls
-Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
-(/TexLive/texmf-dist/tex/latex/base/size10.clo
-File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
-)
-\c@part=\count79
-\c@section=\count80
-\c@subsection=\count81
-\c@subsubsection=\count82
-\c@paragraph=\count83
-\c@subparagraph=\count84
-\c@figure=\count85
-\c@table=\count86
-\abovecaptionskip=\skip41
-\belowcaptionskip=\skip42
-\bibindent=\dimen102
-)
-(/TexLive/texmf-dist/tex/generic/babel/babel.sty
-Package: babel 2017/05/19 3.10 The Babel package
-
-(/TexLive/texmf-dist/tex/generic/babel-spanish/spanish.ldf
-Language: spanish.ldf 2016/03/03 v5.0p Spanish support from the babel system
-
-(/TexLive/texmf-dist/tex/generic/babel/babel.def
-File: babel.def 2017/05/19 3.10 Babel common definitions
-\babel@savecnt=\count87
-\U@D=\dimen103
-)
-\es@quottoks=\toks14
-\es@quotdepth=\count88
-Package babel Info: Making " an active character on input line 561.
-Package babel Info: Making . an active character on input line 662.
-Package babel Info: Making < an active character on input line 707.
-Package babel Info: Making > an active character on input line 707.
-))
-(/TexLive/texmf-dist/tex/latex/biblatex/biblatex.sty
-Package: biblatex 2016/12/05 v3.7 programmable bibliographies (PK/JW/AB)
-
-(/TexLive/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty
-Package: pdftexcmds 2017/03/19 v0.25 Utility functions of pdfTeX for LuaTeX (HO
-)
-
-(/TexLive/texmf-dist/tex/generic/oberdiek/infwarerr.sty
-Package: infwarerr 2016/05/16 v1.4 Providing info/warning/error messages (HO)
-)
-(/TexLive/texmf-dist/tex/generic/oberdiek/ifluatex.sty
-Package: ifluatex 2016/05/16 v1.4 Provides the ifluatex switch (HO)
-Package ifluatex Info: LuaTeX not detected.
-)
-(/TexLive/texmf-dist/tex/generic/oberdiek/ltxcmds.sty
-Package: ltxcmds 2016/05/16 v1.23 LaTeX kernel commands for general use (HO)
-)
-(/TexLive/texmf-dist/tex/generic/oberdiek/ifpdf.sty
-Package: ifpdf 2017/03/15 v3.2 Provides the ifpdf switch
-)
-Package pdftexcmds Info: LuaTeX not detected.
-Package pdftexcmds Info: \pdf@primitive is available.
-Package pdftexcmds Info: \pdf@ifprimitive is available.
-Package pdftexcmds Info: \pdfdraftmode found.
-)
-(/TexLive/texmf-dist/tex/latex/etoolbox/etoolbox.sty
-Package: etoolbox 2017/01/02 v2.4 e-TeX tools for LaTeX (JAW)
-\etb@tempcnta=\count89
-)
-(/TexLive/texmf-dist/tex/latex/graphics/keyval.sty
-Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
-\KV@toks@=\toks15
-)
-(/TexLive/texmf-dist/tex/latex/oberdiek/kvoptions.sty
-Package: kvoptions 2016/05/16 v3.12 Key value format for package options (HO)
-
-(/TexLive/texmf-dist/tex/generic/oberdiek/kvsetkeys.sty
-Package: kvsetkeys 2016/05/16 v1.17 Key value parser (HO)
-
-(/TexLive/texmf-dist/tex/generic/oberdiek/etexcmds.sty
-Package: etexcmds 2016/05/16 v1.6 Avoid name clashes with e-TeX commands (HO)
-Package etexcmds Info: Could not find \expanded.
-(etexcmds) That can mean that you are not using pdfTeX 1.50 or
-(etexcmds) that some package has redefined \expanded.
-(etexcmds) In the latter case, load this package earlier.
-)))
-(/TexLive/texmf-dist/tex/latex/logreq/logreq.sty
-Package: logreq 2010/08/04 v1.0 xml request logger
-\lrq@indent=\count90
-
-(/TexLive/texmf-dist/tex/latex/logreq/logreq.def
-File: logreq.def 2010/08/04 v1.0 logreq spec v1.0
-))
-(/TexLive/texmf-dist/tex/latex/base/ifthen.sty
-Package: ifthen 2014/09/29 v1.1c Standard LaTeX ifthen package (DPC)
-)
-(/TexLive/texmf-dist/tex/latex/url/url.sty
-\Urlmuskip=\muskip10
-Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc.
-)
-(/TexLive/texmf-dist/tex/generic/xstring/xstring.sty
-(/TexLive/texmf-dist/tex/generic/xstring/xstring.tex
-\@xs@message=\write3
-\integerpart=\count91
-\decimalpart=\count92
-)
-Package: xstring 2013/10/13 v1.7c String manipulations (C Tellechea)
-)
-\c@tabx@nest=\count93
-\c@listtotal=\count94
-\c@listcount=\count95
-\c@liststart=\count96
-\c@liststop=\count97
-\c@citecount=\count98
-\c@citetotal=\count99
-\c@multicitecount=\count100
-\c@multicitetotal=\count101
-\c@instcount=\count102
-\c@maxnames=\count103
-\c@minnames=\count104
-\c@maxitems=\count105
-\c@minitems=\count106
-\c@citecounter=\count107
-\c@savedcitecounter=\count108
-\c@uniquelist=\count109
-\c@uniquename=\count110
-\c@refsection=\count111
-\c@refsegment=\count112
-\c@maxextratitle=\count113
-\c@maxextratitleyear=\count114
-\c@maxextrayear=\count115
-\c@maxextraalpha=\count116
-\c@abbrvpenalty=\count117
-\c@highnamepenalty=\count118
-\c@lownamepenalty=\count119
-\c@maxparens=\count120
-\c@parenlevel=\count121
-\blx@tempcnta=\count122
-\blx@tempcntb=\count123
-\blx@tempcntc=\count124
-\blx@maxsection=\count125
-\blx@maxsegment@0=\count126
-\blx@notetype=\count127
-\blx@parenlevel@text=\count128
-\blx@parenlevel@foot=\count129
-\blx@sectionciteorder@0=\count130
-\labelnumberwidth=\skip43
-\labelalphawidth=\skip44
-\biblabelsep=\skip45
-\bibitemsep=\skip46
-\bibnamesep=\skip47
-\bibinitsep=\skip48
-\bibparsep=\skip49
-\bibhang=\skip50
-\blx@bcfin=\read1
-\blx@bcfout=\write4
-\c@mincomprange=\count131
-\c@maxcomprange=\count132
-\c@mincompwidth=\count133
-Package biblatex Info: Trying to load biblatex default data model...
-Package biblatex Info: ... file 'blx-dm.def' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/blx-dm.def)
-Package biblatex Info: Trying to load biblatex style data model...
-Package biblatex Info: ... file 'ieee.dbx' not found.
-Package biblatex Info: Trying to load biblatex custom data model...
-Package biblatex Info: ... file 'biblatex-dm.cfg' not found.
-\c@afterword=\count134
-\c@savedafterword=\count135
-\c@annotator=\count136
-\c@savedannotator=\count137
-\c@author=\count138
-\c@savedauthor=\count139
-\c@bookauthor=\count140
-\c@savedbookauthor=\count141
-\c@commentator=\count142
-\c@savedcommentator=\count143
-\c@editor=\count144
-\c@savededitor=\count145
-\c@editora=\count146
-\c@savededitora=\count147
-\c@editorb=\count148
-\c@savededitorb=\count149
-\c@editorc=\count150
-\c@savededitorc=\count151
-\c@foreword=\count152
-\c@savedforeword=\count153
-\c@holder=\count154
-\c@savedholder=\count155
-\c@introduction=\count156
-\c@savedintroduction=\count157
-\c@namea=\count158
-\c@savednamea=\count159
-\c@nameb=\count160
-\c@savednameb=\count161
-\c@namec=\count162
-\c@savednamec=\count163
-\c@translator=\count164
-\c@savedtranslator=\count165
-\c@shortauthor=\count166
-\c@savedshortauthor=\count167
-\c@shorteditor=\count168
-\c@savedshorteditor=\count169
-\c@labelname=\count170
-\c@savedlabelname=\count171
-\c@institution=\count172
-\c@savedinstitution=\count173
-\c@lista=\count174
-\c@savedlista=\count175
-\c@listb=\count176
-\c@savedlistb=\count177
-\c@listc=\count178
-\c@savedlistc=\count179
-\c@listd=\count180
-\c@savedlistd=\count181
-\c@liste=\count182
-\c@savedliste=\count183
-\c@listf=\count184
-\c@savedlistf=\count185
-\c@location=\count186
-\c@savedlocation=\count187
-\c@organization=\count188
-\c@savedorganization=\count189
-\c@origlocation=\count190
-\c@savedoriglocation=\count191
-\c@origpublisher=\count192
-\c@savedorigpublisher=\count193
-\c@publisher=\count194
-\c@savedpublisher=\count195
-\c@language=\count196
-\c@savedlanguage=\count197
-\c@pageref=\count198
-\c@savedpageref=\count199
-\shorthandwidth=\skip51
-\shortjournalwidth=\skip52
-\shortserieswidth=\skip53
-\shorttitlewidth=\skip54
-\shortauthorwidth=\skip55
-\shorteditorwidth=\skip56
-Package biblatex Info: Trying to load compatibility code...
-Package biblatex Info: ... file 'blx-compat.def' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/blx-compat.def
-File: blx-compat.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB)
-)
-Package biblatex Info: Trying to load generic definitions...
-Package biblatex Info: ... file 'biblatex.def' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/biblatex.def
-File: biblatex.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB)
-\c@textcitecount=\count266
-\c@textcitetotal=\count267
-\c@textcitemaxnames=\count268
-\c@biburlnumpenalty=\count269
-\c@biburlucpenalty=\count270
-\c@biburllcpenalty=\count271
-\c@smartand=\count272
-)
-Package biblatex Info: Trying to load bibliography style 'ieee'...
-Package biblatex Info: ... file 'ieee.bbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.bbx
-File: ieee.bbx 2017/03/27 v1.2d biblatex bibliography style
-Package biblatex Info: Trying to load bibliography style 'numeric-comp'...
-Package biblatex Info: ... file 'numeric-comp.bbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric-comp.bbx
-File: numeric-comp.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
-Package biblatex Info: Trying to load bibliography style 'numeric'...
-Package biblatex Info: ... file 'numeric.bbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx
-File: numeric.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
-Package biblatex Info: Trying to load bibliography style 'standard'...
-Package biblatex Info: ... file 'standard.bbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/bbx/standard.bbx
-File: standard.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
-\c@bbx:relatedcount=\count273
-\c@bbx:relatedtotal=\count274
-))))
-Package biblatex Info: Trying to load citation style 'ieee'...
-Package biblatex Info: ... file 'ieee.cbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.cbx
-File: ieee.cbx 2017/03/27 v1.2d biblatex citation style
-Package biblatex Info: Trying to load citation style 'numeric-comp'...
-Package biblatex Info: ... file 'numeric-comp.cbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/cbx/numeric-comp.cbx
-File: numeric-comp.cbx 2016/12/05 v3.7 biblatex citation style (PK/JW/AB)
-\c@cbx@tempcnta=\count275
-\c@cbx@tempcntb=\count276
-Package biblatex Info: Redefining '\cite'.
-Package biblatex Info: Redefining '\parencite'.
-Package biblatex Info: Redefining '\footcite'.
-Package biblatex Info: Redefining '\footcitetext'.
-Package biblatex Info: Redefining '\smartcite'.
-Package biblatex Info: Redefining '\supercite'.
-Package biblatex Info: Redefining '\textcite'.
-Package biblatex Info: Redefining '\textcites'.
-Package biblatex Info: Redefining '\cites'.
-Package biblatex Info: Redefining '\parencites'.
-Package biblatex Info: Redefining '\smartcites'.
-)
-Package biblatex Info: Redefining '\cite'.
-Package biblatex Info: Redefining '\cites'.
-)
-Package biblatex Info: Trying to load configuration file...
-Package biblatex Info: ... file 'biblatex.cfg' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/biblatex.cfg
-File: biblatex.cfg
-))
-Package biblatex Info: Trying to load language 'spanish'...
-Package biblatex Info: ... file 'spanish.lbx' found.
-
-(/TexLive/texmf-dist/tex/latex/biblatex/lbx/spanish.lbx
-File: spanish.lbx 2016/12/05 v3.7 biblatex localization (PK/JW/AB)
-)
-
-Package biblatex Warning: 'babel/polyglossia' detected but 'csquotes' missing.
-(biblatex) Loading 'csquotes' recommended.
-
-\@quotelevel=\count277
-\@quotereset=\count278
-(./parent.aux)
-\openout1 = `parent.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6.
-LaTeX Font Info: ... okay on input line 6.
-LaTeX Info: Redefining \sptext on input line 6.
-LaTeX Info: Redefining \. on input line 6.
-LaTeX Info: Redefining \% on input line 6.
-Package biblatex Info: No input encoding detected.
-(biblatex) Assuming 'ascii'.
-Package biblatex Info: Automatic encoding selection.
-(biblatex) Assuming data encoding 'ascii'.
-\openout4 = `parent.bcf'.
-
-Package biblatex Info: Trying to load bibliographic data...
-Package biblatex Info: ... file 'parent.bbl' not found.
-
-No file parent.bbl.
-Package biblatex Info: Reference section=0 on input line 6.
-Package biblatex Info: Reference segment=0 on input line 6.
-(./parent.aux)
-
-LaTeX Warning: There were undefined references.
-
-Package biblatex Warning: Please (re)run Biber on the file:
-(biblatex) parent
-(biblatex) and rerun LaTeX afterwards.
-
-Package logreq Info: Writing requests to 'parent.run.xml'.
-\openout1 = `parent.run.xml'.
-
-)
-Here is how much of TeX's memory you used:
-7717 strings out of 492995
-133301 string characters out of 6138727
-557258 words of memory out of 5000000
-11248 multiletter control sequences out of 15000+600000
-3640 words of font info for 14 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-35i,1n,30p,856b,700s stack positions out of 5000i,500n,10000p,200000b,80000s
-
-No pages of output.
-PDF statistics:
-0 PDF objects out of 1000 (max. 8388607)
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_eq!(
- parse(log).errors,
- vec![
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "'babel/polyglossia' detected but 'csquotes' missing. Loading 'csquotes' recommended.".into(),
- line: None
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "There were undefined references.".into(),
- line: None,
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Warning,
- message: "Please (re)run Biber on the file: parent and rerun LaTeX afterwards.".into(),
- line: None
- }
- ]
- );
- }
-
- #[test]
- fn parse_006() {
- let log = r#"
-This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:33
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**./parent.tex
-(./parent.tex
-LaTeX2e <2017-04-15>
-Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
-(/TexLive/texmf-dist/tex/latex/base/article.cls
-Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
-(/TexLive/texmf-dist/tex/latex/base/size10.clo
-File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
-)
-\c@part=\count79
-\c@section=\count80
-\c@subsection=\count81
-\c@subsubsection=\count82
-\c@paragraph=\count83
-\c@subparagraph=\count84
-\c@figure=\count85
-\c@table=\count86
-\abovecaptionskip=\skip41
-\belowcaptionskip=\skip42
-\bibindent=\dimen102
-) (./parent.aux
-(./child.tex.aux))
-\openout1 = `parent.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-\openout2 = `child.aux'.
-
-(./child.tex)
-! Undefined control sequence.
-l.7 \foo
-
-The control sequence at the end of the top line
-of your error message was never \def'ed. If you have
-misspelled it (e.g., `\hobx'), type `I' and the correct
-spelling (e.g., `I\hbox'). Otherwise just continue,
-and I'll forget about whatever was undefined.
-
-! Missing $ inserted.
-<inserted text>
- $
-l.8 \bar
-
-I've inserted a begin-math/end-math symbol since I think
-you left one out. Proceed, with fingers crossed.
-
-LaTeX Font Info: External font `cmex10' loaded for size
-(Font) <7> on input line 8.
-LaTeX Font Info: External font `cmex10' loaded for size
-(Font) <5> on input line 8.
-! Undefined control sequence.
-l.9 \baz
-
-The control sequence at the end of the top line
-of your error message was never \def'ed. If you have
-misspelled it (e.g., `\hobx'), type `I' and the correct
-spelling (e.g., `I\hbox'). Otherwise just continue,
-and I'll forget about whatever was undefined.
-
-! Missing { inserted.
-<to be read again>
- \par
-l.10
-
-A left brace was mandatory here, so I've put one in.
-You might want to delete and/or insert some corrections
-so that I will find a matching right brace soon.
-(If you're confused by all this, try typing `I}' now.)
-
-! Missing $ inserted.
-<inserted text>
- $
-l.10
-
-I've inserted a begin-math/end-math symbol since I think
-you left one out. Proceed, with fingers crossed.
-
-! Missing } inserted.
-<inserted text>
- }
-l.10
-
-I've inserted something that you may have forgotten.
-(See the <inserted text> above.)
-With luck, this will get me unwedged. But if you
-really didn't forget anything, try typing `2' now; then
-my insertion and my current dilemma will both disappear.
-
-[1
-
-{/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux
-(./child.aux)) )
-Here is how much of TeX's memory you used:
-212 strings out of 492995
-2238 string characters out of 6138727
-54074 words of memory out of 5000000
-3843 multiletter control sequences out of 15000+600000
-3640 words of font info for 14 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-23i,4n,17p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s
-</TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb
->
-Output written on parent.pdf (1 page, 8329 bytes).
-PDF statistics:
-12 PDF objects out of 1000 (max. 8388607)
-7 compressed objects within 1 object stream
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_eq!(
- parse(log).errors,
- vec![
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Undefined control sequence.".into(),
- line: Some(6)
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Missing $ inserted.".into(),
- line: Some(7)
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Undefined control sequence.".into(),
- line: Some(8)
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Missing { inserted.".into(),
- line: Some(9)
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Missing $ inserted.".into(),
- line: Some(9)
- },
- BuildError {
- relative_path: "./parent.tex".into(),
- level: BuildErrorLevel::Error,
- message: "Missing } inserted.".into(),
- line: Some(9)
- },
- ]
- );
- }
-
- #[test]
- fn parse_007() {
- let log = r#"
-This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2021/W32TeX) (preloaded format=pdflatex 2021.6.19) 5 NOV 2021 19:10
-entering extended mode
-restricted \write18 enabled.
-%&-line parsing enabled.
-**/some/folder/a.tex
-(/some/folder/a.tex
-LaTeX2e <2021-06-01> patch level 1
-L3 programming layer <2021-06-18>
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrartcl.cls
-Document Class: scrartcl 2021/03/17 v3.33 KOMA-Script document class (article)
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrkbase.sty
-Package: scrkbase 2021/03/17 v3.33 KOMA-Script package (KOMA-Script-dependent b
-asics and keyval usage)
-
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrbase.sty
-Package: scrbase 2021/03/17 v3.33 KOMA-Script package (KOMA-Script-independent
-basics and keyval usage)
-
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrlfile.sty
-Package: scrlfile 2021/03/17 v3.33 KOMA-Script package (file load hooks)
-
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrlfile-hook.sty
-Package: scrlfile-hook 2021/03/17 v3.33 KOMA-Script package (using LaTeX hooks)
-
-
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrlogo.sty
-Package: scrlogo 2021/03/17 v3.33 KOMA-Script package (logo)
-)))
-(/texlive/2021/texmf-dist/tex/latex/graphics/keyval.sty
-Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
-\KV@toks@=\toks16
-)
-Applying: [2021/05/01] Usage of raw option list on input line 252.
-Already applied: [0000/00/00] compatibility for LaTeX before 2021/05/01 on inpu
-t line 337.
-))
-==> First Aid for scrkbase.sty no longer applied!
-(/texlive/2021/texmf-dist/tex/latex/koma-script/tocbasic.sty
-Package: tocbasic 2021/03/17 v3.33 KOMA-Script package (handling toc-files)
-\scr@dte@tocline@numberwidth=\skip47
-\scr@dte@tocline@numbox=\box50
-)
-Package tocbasic Info: babel extension for `toc' omitted
-(tocbasic) because of missing \bbl@set@language on input line 135.
-Class scrartcl Info: File `scrsize11pt.clo' used instead of
-(scrartcl) file `scrsize11.clo' to setup font sizes on input line 223
-9.
-
-(/texlive/2021/texmf-dist/tex/latex/koma-script/scrsize11pt.clo
-File: scrsize11pt.clo 2021/03/17 v3.33 KOMA-Script font size class option (11pt
-)
-)
-(/texlive/2021/texmf-dist/tex/latex/koma-script/typearea.sty
-Package: typearea 2021/03/17 v3.33 KOMA-Script package (type area)
-\ta@bcor=\skip48
-\ta@div=\count182
-\ta@hblk=\skip49
-\ta@vblk=\skip50
-\ta@temp=\skip51
-\footheight=\skip52
-Package typearea Info: These are the values describing the layout:
-(typearea) DIV = 10
-(typearea) BCOR = 0.0pt
-(typearea) \paperwidth = 597.50793pt
-(typearea) \textwidth = 418.25555pt
-(typearea) DIV departure = -6%
-(typearea) \evensidemargin = 17.3562pt
-(typearea) \oddsidemargin = 17.3562pt
-(typearea) \paperheight = 845.04694pt
-(typearea) \textheight = 595.80026pt
-(typearea) \topmargin = -25.16531pt
-(typearea) \headheight = 17.0pt
-(typearea) \headsep = 20.40001pt
-(typearea) \topskip = 11.0pt
-(typearea) \footskip = 47.6pt
-(typearea) \baselineskip = 13.6pt
-(typearea) on input line 1741.
-)
-\c@part=\count183
-\c@section=\count184
-\c@subsection=\count185
-\c@subsubsection=\count186
-\c@paragraph=\count187
-\c@subparagraph=\count188
-\scr@dte@section@maxnumwidth=\skip53
-Class scrartcl Info: using compatibility default `runin=bysign'
-(scrartcl) for `\section on input line 4846.
-Class scrartcl Info: using compatibility default `afterindent=bysign'
-(scrartcl) for `\section on input line 4846.
-\scr@dte@part@maxnumwidth=\skip54
-Class scrartcl Info: using compatibility default `afterindent=false'
-(scrartcl) for `\part on input line 4854.
-\scr@dte@subsection@maxnumwidth=\skip55
-Class scrartcl Info: using compatibility default `runin=bysign'
-(scrartcl) for `\subsection on input line 4864.
-Class scrartcl Info: using compatibility default `afterindent=bysign'
-(scrartcl) for `\subsection on input line 4864.
-\scr@dte@subsubsection@maxnumwidth=\skip56
-Class scrartcl Info: using compatibility default `runin=bysign'
-(scrartcl) for `\subsubsection on input line 4874.
-Class scrartcl Info: using compatibility default `afterindent=bysign'
-(scrartcl) for `\subsubsection on input line 4874.
-\scr@dte@paragraph@maxnumwidth=\skip57
-Class scrartcl Info: using compatibility default `runin=bysign'
-(scrartcl) for `\paragraph on input line 4885.
-Class scrartcl Info: using compatibility default `afterindent=bysign'
-(scrartcl) for `\paragraph on input line 4885.
-\scr@dte@subparagraph@maxnumwidth=\skip58
-Class scrartcl Info: using compatibility default `runin=bysign'
-(scrartcl) for `\subparagraph on input line 4895.
-Class scrartcl Info: using compatibility default `afterindent=bysign'
-(scrartcl) for `\subparagraph on input line 4895.
-\abovecaptionskip=\skip59
-\belowcaptionskip=\skip60
-\c@pti@nb@sid@b@x=\box51
-Package tocbasic Info: babel extension for `lof' omitted
-(tocbasic) because of missing \bbl@set@language on input line 6127.
-
-\scr@dte@figure@maxnumwidth=\skip61
-\c@figure=\count189
-Package tocbasic Info: babel extension for `lot' omitted
-(tocbasic) because of missing \bbl@set@language on input line 6139.
-
-\scr@dte@table@maxnumwidth=\skip62
-\c@table=\count190
-Class scrartcl Info: Redefining `\numberline' on input line 6303.
-\bibindent=\dimen138
-)
-(/texlive/2021/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
-File: l3backend-pdftex.def 2021-05-07 L3 backend support: PDF output (pdfTeX)
-\l__color_backend_stack_int=\count191
-\l__pdf_internal_box=\box52
-) (./a.aux)
-\openout1 = `a.aux'.
-
-LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
-LaTeX Font Info: ... okay on input line 3.
-Package scrbase Info: activating english \contentsname on input line 3.
-Package scrbase Info: activating english \listfigurename on input line 3.
-Package scrbase Info: activating english \listtablename on input line 3.
-! Undefined control sequence.
-l.4 \lsdkfjlskdfj
-
-The control sequence at the end of the top line
-of your error message was never \def'ed. If you have
-misspelled it (e.g., `\hobx'), type `I' and the correct
-spelling (e.g., `I\hbox'). Otherwise just continue,
-and I'll forget about whatever was undefined.
-
-(./a.aux) )
-Here is how much of TeX's memory you used:
-3199 strings out of 478510
-74549 string characters out of 5853586
-510929 words of memory out of 5000000
-21227 multiletter control sequences out of 15000+600000
-403730 words of font info for 28 fonts, out of 8000000 for 9000
-1141 hyphenation exceptions out of 8191
-108i,1n,108p,10625b,270s stack positions out of 5000i,500n,10000p,200000b,80000s
-
-No pages of output.
-PDF statistics:
-0 PDF objects out of 1000 (max. 8388607)
-0 named destinations out of 1000 (max. 500000)
-1 words of extra memory for PDF output out of 10000 (max. 10000000)"#;
-
- assert_debug_snapshot!(parse(log).errors);
- }
-}
diff --git a/support/texlab/src/syntax/latex.rs b/support/texlab/src/syntax/latex.rs
index 2d8918e49e..ce1ca125fd 100644
--- a/support/texlab/src/syntax/latex.rs
+++ b/support/texlab/src/syntax/latex.rs
@@ -1,14 +1,9 @@
-mod analysis;
mod cst;
mod kind;
-mod lexer;
-mod parser;
pub use self::{
- analysis::*,
cst::*,
kind::SyntaxKind::{self, *},
- parser::{parse, Parse},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
diff --git a/support/texlab/src/syntax/latex/analysis.rs b/support/texlab/src/syntax/latex/analysis.rs
deleted file mode 100644
index 02d9b315ba..0000000000
--- a/support/texlab/src/syntax/latex/analysis.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-mod command;
-mod distro_file;
-mod environment;
-mod explicit_link;
-mod graphics_path;
-mod implicit_link;
-mod label_name;
-mod label_number;
-mod theorem;
-mod types;
-
-use crate::syntax::latex;
-
-pub use self::types::*;
-use self::{
- command::{analyze_command, analyze_command_definition},
- environment::analyze_begin,
- explicit_link::{analyze_import, analyze_include},
- graphics_path::analyze_graphics_path,
- implicit_link::analyze_implicit_links,
- label_name::analyze_label_name,
- label_number::analyze_label_number,
- theorem::analyze_theorem_definition,
-};
-
-pub fn analyze(context: &mut LatexAnalyzerContext, root: &latex::SyntaxNode) {
- analyze_implicit_links(context);
- for node in root.descendants() {
- analyze_command(context, node.clone())
- .or_else(|| analyze_command_definition(context, node.clone()))
- .or_else(|| analyze_begin(context, node.clone()))
- .or_else(|| analyze_include(context, node.clone()))
- .or_else(|| analyze_import(context, node.clone()))
- .or_else(|| analyze_label_name(context, node.clone()))
- .or_else(|| analyze_label_number(context, node.clone()))
- .or_else(|| analyze_theorem_definition(context, node.clone()))
- .or_else(|| analyze_graphics_path(context, node));
- }
- context.extras.has_document_environment = context.extras.environment_names.contains("document");
-}
diff --git a/support/texlab/src/syntax/latex/analysis/command.rs b/support/texlab/src/syntax/latex/analysis/command.rs
deleted file mode 100644
index 60aceef725..0000000000
--- a/support/texlab/src/syntax/latex/analysis/command.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use rowan::ast::AstNode;
-
-use crate::syntax::latex;
-
-use super::LatexAnalyzerContext;
-
-pub fn analyze_command(context: &mut LatexAnalyzerContext, node: latex::SyntaxNode) -> Option<()> {
- let command = latex::GenericCommand::cast(node)?;
- context
- .extras
- .command_names
- .insert(command.name()?.text().into());
- Some(())
-}
-
-pub fn analyze_command_definition(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let definition = latex::CommandDefinition::cast(node)?;
- context
- .extras
- .command_names
- .insert(definition.name()?.command()?.text().into());
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/distro_file.rs b/support/texlab/src/syntax/latex/analysis/distro_file.rs
deleted file mode 100644
index f278c33627..0000000000
--- a/support/texlab/src/syntax/latex/analysis/distro_file.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use lsp_types::Url;
-
-use crate::distro::Resolver;
-
-pub fn resolve_distro_file(resolver: &Resolver, stem: &str, extensions: &[&str]) -> Option<Url> {
- let mut document = resolver.files_by_name.get(stem);
- for extension in extensions {
- document = document.or_else(|| {
- let full_name = format!("{}.{}", stem, extension);
- resolver.files_by_name.get(full_name.as_str())
- });
- }
- document.and_then(|path| Url::from_file_path(path).ok())
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- #[cfg(windows)]
- fn test() {
- let mut resolver = Resolver::default();
- resolver
- .files_by_name
- .insert("foo.tex".into(), "C:/distro/foo.tex".into());
- resolver
- .files_by_name
- .insert("foo.sty".into(), "C:/distro/foo.sty".into());
- resolver
- .files_by_name
- .insert("bar.tex".into(), "C:/distro/bar.tex".into());
-
- assert_eq!(
- resolve_distro_file(&resolver, "foo", &["tex"]),
- Some(Url::from_file_path("C:/distro/foo.tex").unwrap())
- );
-
- assert_eq!(
- resolve_distro_file(&resolver, "foo", &["sty"]),
- Some(Url::from_file_path("C:/distro/foo.sty").unwrap())
- );
-
- assert_eq!(resolve_distro_file(&resolver, "foo", &["cls"]), None);
- }
-
- #[test]
- #[cfg(unix)]
- fn test() {
- let mut resolver = Resolver::default();
- resolver
- .files_by_name
- .insert("foo.tex".into(), "/distro/foo.tex".into());
- resolver
- .files_by_name
- .insert("foo.sty".into(), "/distro/foo.sty".into());
- resolver
- .files_by_name
- .insert("bar.tex".into(), "/distro/bar.tex".into());
-
- assert_eq!(
- resolve_distro_file(&resolver, "foo", &["tex"]),
- Some(Url::from_file_path("/distro/foo.tex").unwrap())
- );
-
- assert_eq!(
- resolve_distro_file(&resolver, "foo", &["sty"]),
- Some(Url::from_file_path("/distro/foo.sty").unwrap())
- );
-
- assert_eq!(resolve_distro_file(&resolver, "foo", &["cls"]), None);
- }
-}
diff --git a/support/texlab/src/syntax/latex/analysis/environment.rs b/support/texlab/src/syntax/latex/analysis/environment.rs
deleted file mode 100644
index e99483f706..0000000000
--- a/support/texlab/src/syntax/latex/analysis/environment.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use rowan::ast::AstNode;
-
-use crate::syntax::latex;
-
-use super::LatexAnalyzerContext;
-
-pub fn analyze_begin(context: &mut LatexAnalyzerContext, node: latex::SyntaxNode) -> Option<()> {
- let begin = latex::Begin::cast(node)?;
- let name = begin.name()?.key()?.to_string();
- let extras = &mut context.extras;
- extras.environment_names.insert(name);
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/explicit_link.rs b/support/texlab/src/syntax/latex/analysis/explicit_link.rs
deleted file mode 100644
index 77e2cfb696..0000000000
--- a/support/texlab/src/syntax/latex/analysis/explicit_link.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use std::sync::Arc;
-
-use rowan::ast::AstNode;
-
-use crate::syntax::latex;
-
-use super::{
- distro_file::resolve_distro_file, ExplicitLink, ExplicitLinkKind, LatexAnalyzerContext,
-};
-
-pub fn analyze_include(context: &mut LatexAnalyzerContext, node: latex::SyntaxNode) -> Option<()> {
- let include = latex::Include::cast(node)?;
- let kind = match include.syntax().kind() {
- latex::LATEX_INCLUDE => ExplicitLinkKind::Latex,
- latex::BIBLATEX_INCLUDE | latex::BIBTEX_INCLUDE => ExplicitLinkKind::Bibtex,
- latex::PACKAGE_INCLUDE => ExplicitLinkKind::Package,
- latex::CLASS_INCLUDE => ExplicitLinkKind::Class,
- _ => return None,
- };
-
- let extensions = match kind {
- ExplicitLinkKind::Latex => &["tex"],
- ExplicitLinkKind::Bibtex => &["bib"],
- ExplicitLinkKind::Package => &["sty"],
- ExplicitLinkKind::Class => &["cls"],
- };
-
- for path in include.path_list()?.keys() {
- let stem = path.to_string();
- let mut targets = vec![Arc::new(context.base_uri.join(&stem).ok()?)];
- for extension in extensions {
- let path = format!("{}.{}", stem, extension);
- targets.push(Arc::new(context.base_uri.join(&path).ok()?));
- }
-
- resolve_distro_file(&context.environment.resolver, &stem, extensions)
- .into_iter()
- .for_each(|target| targets.push(Arc::new(target)));
-
- context.extras.explicit_links.push(ExplicitLink {
- kind,
- stem: stem.into(),
- stem_range: latex::small_range(&path),
- targets,
- });
- }
-
- Some(())
-}
-
-pub fn analyze_import(context: &mut LatexAnalyzerContext, node: latex::SyntaxNode) -> Option<()> {
- let import = latex::Import::cast(node)?;
-
- let mut targets = Vec::new();
- let directory = match import.directory().and_then(|dir| dir.key()) {
- Some(dir) => Arc::new(context.base_uri.join(&dir.to_string()).ok()?),
- None => Arc::clone(&context.base_uri),
- };
-
- let file = import.file()?.key()?;
- let stem = file.to_string();
- targets.push(Arc::new(directory.join(&stem).ok()?));
- targets.push(Arc::new(directory.join(&format!("{}.tex", stem)).ok()?));
-
- context.extras.explicit_links.push(ExplicitLink {
- stem: stem.into(),
- stem_range: latex::small_range(&file),
- targets,
- kind: ExplicitLinkKind::Latex,
- });
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/graphics_path.rs b/support/texlab/src/syntax/latex/analysis/graphics_path.rs
deleted file mode 100644
index 477094b841..0000000000
--- a/support/texlab/src/syntax/latex/analysis/graphics_path.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use rowan::ast::AstNode;
-
-use crate::syntax::latex;
-
-use super::LatexAnalyzerContext;
-
-pub fn analyze_graphics_path(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let definition = latex::GraphicsPath::cast(node)?;
- for path in definition
- .path_list()
- .filter_map(|group| group.key())
- .map(|path| path.to_string())
- {
- context.extras.graphics_paths.insert(path);
- }
-
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/implicit_link.rs b/support/texlab/src/syntax/latex/analysis/implicit_link.rs
deleted file mode 100644
index 60524c623e..0000000000
--- a/support/texlab/src/syntax/latex/analysis/implicit_link.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use std::{path::Path, sync::Arc};
-
-use lsp_types::Url;
-
-use super::LatexAnalyzerContext;
-
-pub fn analyze_implicit_links(context: &mut LatexAnalyzerContext) {
- context.extras.implicit_links.aux = find_by_extension(context, "aux").unwrap_or_default();
- context.extras.implicit_links.log = find_by_extension(context, "log").unwrap_or_default();
- context.extras.implicit_links.pdf = find_by_extension(context, "pdf").unwrap_or_default();
-}
-
-fn find_by_extension(context: &LatexAnalyzerContext, extension: &str) -> Option<Vec<Arc<Url>>> {
- let mut targets = vec![Arc::new(with_extension(&context.document_uri, extension)?)];
- if context.document_uri.scheme() == "file" {
- let file_path = context.document_uri.to_file_path().ok()?;
- let file_stem = file_path.file_stem()?;
- let aux_name = format!("{}.{}", file_stem.to_str()?, extension);
-
- let options = &context.environment.options;
- if let Some(root_dir) = options.root_directory.as_ref() {
- find_inside_directory(context, root_dir, &aux_name, &mut targets)?;
- }
-
- if let Some(build_dir) = options.aux_directory.as_ref() {
- find_inside_directory(context, build_dir, &aux_name, &mut targets)?;
- }
- }
- Some(targets)
-}
-
-fn find_inside_directory(
- context: &LatexAnalyzerContext,
- dir: &Path,
- aux_name: &str,
- targets: &mut Vec<Arc<Url>>,
-) -> Option<()> {
- let path = context
- .environment
- .current_directory
- .join(dir)
- .join(aux_name);
-
- targets.push(Arc::new(Url::from_file_path(path).ok()?));
-
- targets.push(Arc::new(
- context
- .base_uri
- .join(&dir.join(aux_name).to_string_lossy())
- .ok()?,
- ));
-
- Some(())
-}
-
-fn with_extension(uri: &Url, extension: &str) -> Option<Url> {
- let file_name = uri.path_segments()?.last()?;
- let file_stem = file_name
- .rfind('.')
- .map(|i| &file_name[..i])
- .unwrap_or(file_name);
-
- uri.join(&format!("{}.{}", file_stem, extension)).ok()
-}
diff --git a/support/texlab/src/syntax/latex/analysis/label_name.rs b/support/texlab/src/syntax/latex/analysis/label_name.rs
deleted file mode 100644
index 1e3339db5e..0000000000
--- a/support/texlab/src/syntax/latex/analysis/label_name.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use latex::LabelReferenceRange;
-use rowan::ast::AstNode;
-
-use crate::syntax::latex;
-
-use super::{LabelName, LatexAnalyzerContext};
-
-pub fn analyze_label_name(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- analyze_label_definition_name(context, node.clone())
- .or_else(|| analyze_label_reference_name(context, node.clone()))
- .or_else(|| analyze_label_reference_range_name(context, node))
-}
-
-fn analyze_label_definition_name(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let label = latex::LabelDefinition::cast(node)?;
- let name = label.name()?.key()?;
- context.extras.label_names.push(LabelName {
- text: name.to_string().into(),
- range: latex::small_range(&name),
- is_definition: true,
- });
- Some(())
-}
-
-fn analyze_label_reference_name(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let label = latex::LabelReference::cast(node)?;
- for name in label.name_list()?.keys() {
- context.extras.label_names.push(LabelName {
- text: name.to_string().into(),
- range: latex::small_range(&name),
- is_definition: false,
- });
- }
- Some(())
-}
-
-fn analyze_label_reference_range_name(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let label = LabelReferenceRange::cast(node)?;
- if let Some(name1) = label.from().and_then(|name| name.key()) {
- context.extras.label_names.push(LabelName {
- text: name1.to_string().into(),
- range: latex::small_range(&name1),
- is_definition: false,
- });
- }
-
- if let Some(name2) = label.to().and_then(|name| name.key()) {
- context.extras.label_names.push(LabelName {
- text: name2.to_string().into(),
- range: latex::small_range(&name2),
- is_definition: false,
- });
- }
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/label_number.rs b/support/texlab/src/syntax/latex/analysis/label_number.rs
deleted file mode 100644
index 61ad348680..0000000000
--- a/support/texlab/src/syntax/latex/analysis/label_number.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use rowan::ast::AstNode;
-
-use crate::syntax::latex;
-
-use super::LatexAnalyzerContext;
-
-pub fn analyze_label_number(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let number = latex::LabelNumber::cast(node)?;
- let name = number.name()?.key()?.to_string();
- let text = number
- .text()?
- .syntax()
- .descendants_with_tokens()
- .filter_map(|element| element.into_node())
- .find(|node| node.kind() == latex::TEXT || node.kind() == latex::MIXED_GROUP)?
- .text()
- .to_string();
-
- context.extras.label_numbers_by_name.insert(name, text);
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/theorem.rs b/support/texlab/src/syntax/latex/analysis/theorem.rs
deleted file mode 100644
index 70047362d1..0000000000
--- a/support/texlab/src/syntax/latex/analysis/theorem.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use rowan::ast::AstNode;
-
-use crate::syntax::latex::{self, HasCurly};
-
-use super::{LatexAnalyzerContext, TheoremEnvironment};
-
-pub fn analyze_theorem_definition(
- context: &mut LatexAnalyzerContext,
- node: latex::SyntaxNode,
-) -> Option<()> {
- let theorem = latex::TheoremDefinition::cast(node)?;
- let name = theorem.name()?.key()?.to_string();
- let description = theorem.description()?;
- let description = description.content_text()?;
-
- context
- .extras
- .theorem_environments
- .push(TheoremEnvironment { name, description });
-
- Some(())
-}
diff --git a/support/texlab/src/syntax/latex/analysis/types.rs b/support/texlab/src/syntax/latex/analysis/types.rs
deleted file mode 100644
index 7fb938e3d2..0000000000
--- a/support/texlab/src/syntax/latex/analysis/types.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-use std::sync::Arc;
-
-use lsp_types::Url;
-use rowan::TextRange;
-use rustc_hash::{FxHashMap, FxHashSet};
-use smol_str::SmolStr;
-
-use crate::Environment;
-
-#[derive(Debug)]
-pub struct LatexAnalyzerContext<'a> {
- pub environment: &'a Environment,
- pub document_uri: Arc<Url>,
- pub base_uri: Arc<Url>,
- pub extras: Extras,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Extras {
- pub implicit_links: ImplicitLinks,
- pub explicit_links: Vec<ExplicitLink>,
- pub has_document_environment: bool,
- pub command_names: FxHashSet<SmolStr>,
- pub environment_names: FxHashSet<String>,
- pub label_names: Vec<LabelName>,
- pub label_numbers_by_name: FxHashMap<String, String>,
- pub theorem_environments: Vec<TheoremEnvironment>,
- pub graphics_paths: FxHashSet<String>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
-pub struct ImplicitLinks {
- pub aux: Vec<Arc<Url>>,
- pub log: Vec<Arc<Url>>,
- pub pdf: Vec<Arc<Url>>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
-pub enum ExplicitLinkKind {
- Package,
- Class,
- Latex,
- Bibtex,
-}
-
-#[derive(Debug, Clone)]
-pub struct ExplicitLink {
- pub stem: SmolStr,
- pub stem_range: TextRange,
- pub targets: Vec<Arc<Url>>,
- pub kind: ExplicitLinkKind,
-}
-
-impl ExplicitLink {
- pub fn as_component_name(&self) -> Option<String> {
- match self.kind {
- ExplicitLinkKind::Package => Some(format!("{}.sty", self.stem)),
- ExplicitLinkKind::Class => Some(format!("{}.cls", self.stem)),
- ExplicitLinkKind::Latex | ExplicitLinkKind::Bibtex => None,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
-pub struct TheoremEnvironment {
- pub name: String,
- pub description: String,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
-pub struct LabelName {
- pub text: SmolStr,
- pub range: TextRange,
- pub is_definition: bool,
-}
diff --git a/support/texlab/src/syntax/latex/cst.rs b/support/texlab/src/syntax/latex/cst.rs
index a83246571d..d7c9ec9bc7 100644
--- a/support/texlab/src/syntax/latex/cst.rs
+++ b/support/texlab/src/syntax/latex/cst.rs
@@ -683,6 +683,6 @@ impl GraphicsPath {
}
pub fn path_list(&self) -> impl Iterator<Item = CurlyGroupWord> {
- self.syntax().children().filter_map(CurlyGroupWord::cast)
+ self.syntax().descendants().filter_map(CurlyGroupWord::cast)
}
}
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__asymptote.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__asymptote.snap
deleted file mode 100644
index 12f0f3ca63..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__asymptote.snap
+++ /dev/null
@@ -1,43 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-assertion_line: 664
-expression: "verify(r#\"\\begin{asy}\n printf(\"Hello World\\n\");\n\\end{asy}\"#)"
----
-[
- (
- BEGIN_ENVIRONMENT_NAME,
- "\\begin",
- ),
- (
- L_CURLY,
- "{",
- ),
- (
- WORD,
- "asy",
- ),
- (
- R_CURLY,
- "}",
- ),
- (
- VERBATIM,
- "\n printf(\"Hello World\\n\");\n",
- ),
- (
- END_ENVIRONMENT_NAME,
- "\\end",
- ),
- (
- L_CURLY,
- "{",
- ),
- (
- WORD,
- "asy",
- ),
- (
- R_CURLY,
- "}",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__block_comment.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__block_comment.snap
deleted file mode 100644
index 5ced043aaf..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__block_comment.snap
+++ /dev/null
@@ -1,27 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-assertion_line: 601
-expression: "verify(\"Foo\\\\iffalse\\n\\\\Bar{Baz}\\n\\\\fi\\\\Qux\")"
----
-[
- (
- WORD,
- "Foo",
- ),
- (
- BEGIN_BLOCK_COMMENT_NAME,
- "\\iffalse",
- ),
- (
- VERBATIM,
- "\n\\Bar{Baz}\n",
- ),
- (
- END_BLOCK_COMMENT_NAME,
- "\\fi",
- ),
- (
- GENERIC_COMMAND_NAME,
- "\\Qux",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap
deleted file mode 100644
index a9a0e11ad7..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap
+++ /dev/null
@@ -1,47 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"\\newcommand{\\id}[1]{#1}\"#)"
-
----
-[
- (
- COMMAND_DEFINITION_NAME,
- "\\newcommand",
- ),
- (
- L_CURLY,
- "{",
- ),
- (
- GENERIC_COMMAND_NAME,
- "\\id",
- ),
- (
- R_CURLY,
- "}",
- ),
- (
- L_BRACK,
- "[",
- ),
- (
- WORD,
- "1",
- ),
- (
- R_BRACK,
- "]",
- ),
- (
- L_CURLY,
- "{",
- ),
- (
- WORD,
- "#1",
- ),
- (
- R_CURLY,
- "}",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap
deleted file mode 100644
index 41310edaa1..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap
+++ /dev/null
@@ -1,23 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"\\section*{Foo}\"#)"
-
----
-[
- (
- SECTION_NAME,
- "\\section*",
- ),
- (
- L_CURLY,
- "{",
- ),
- (
- WORD,
- "Foo",
- ),
- (
- R_CURLY,
- "}",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap
deleted file mode 100644
index 19db458200..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap
+++ /dev/null
@@ -1,36 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-assertion_line: 248
-expression: "verify(\"hello %world\\r\\ntest %test\")"
-
----
-[
- (
- WORD,
- "hello",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- COMMENT,
- "%world",
- ),
- (
- LINE_BREAK,
- "\r\n",
- ),
- (
- WORD,
- "test",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- COMMENT,
- "%test",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap
deleted file mode 100644
index 36bec0a276..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap
+++ /dev/null
@@ -1,79 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"{foo} (bar) [baz, qux = foo-bar]\"#)"
-
----
-[
- (
- L_CURLY,
- "{",
- ),
- (
- WORD,
- "foo",
- ),
- (
- R_CURLY,
- "}",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- L_PAREN,
- "(",
- ),
- (
- WORD,
- "bar",
- ),
- (
- R_PAREN,
- ")",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- L_BRACK,
- "[",
- ),
- (
- WORD,
- "baz",
- ),
- (
- COMMA,
- ",",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- WORD,
- "qux",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- EQUALITY_SIGN,
- "=",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- WORD,
- "foo-bar",
- ),
- (
- R_BRACK,
- "]",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap
deleted file mode 100644
index 61735b5087..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap
+++ /dev/null
@@ -1,6 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"\"#)"
-
----
-[]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap
deleted file mode 100644
index 4dbc1bb8f5..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap
+++ /dev/null
@@ -1,19 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"\\% hello\"#)"
-
----
-[
- (
- GENERIC_COMMAND_NAME,
- "\\%",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- WORD,
- "hello",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap
deleted file mode 100644
index 3fb7079384..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap
+++ /dev/null
@@ -1,55 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"$ f(x) = y $$\"#)"
-
----
-[
- (
- DOLLAR,
- "$",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- WORD,
- "f",
- ),
- (
- L_PAREN,
- "(",
- ),
- (
- WORD,
- "x",
- ),
- (
- R_PAREN,
- ")",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- EQUALITY_SIGN,
- "=",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- WORD,
- "y",
- ),
- (
- WHITESPACE,
- " ",
- ),
- (
- DOLLAR,
- "$$",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap
deleted file mode 100644
index e947ec3fb6..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap
+++ /dev/null
@@ -1,11 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-expression: "verify(r#\"#\"#)"
-
----
-[
- (
- WORD,
- "#",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__line_break.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__line_break.snap
deleted file mode 100644
index a8a392370e..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__line_break.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-source: src/syntax/latex/lexer.rs
-assertion_line: 258
-expression: "verify(\"hello\\nworld\")"
-
----
-[
- (
- WORD,
- "hello",
- ),
- (
- LINE_BREAK,
- "\n",
- ),
- (
- WORD,
- "world",
- ),
-]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap
deleted file mode 100644
index f01f7f9092..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap
+++ /dev/null
@@ -1,8 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\"#)"
-
----
-ROOT@0..0
- PREAMBLE@0..0
-
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap
deleted file mode 100644
index 84e62ea81d..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\foo\"#)"
-
----
-ROOT@0..4
- PREAMBLE@0..4
- GENERIC_COMMAND@0..4
- GENERIC_COMMAND_NAME@0..4 "\\foo"
-
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap
deleted file mode 100644
index a0d5d2e29b..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\#\"#)"
-
----
-ROOT@0..2
- PREAMBLE@0..2
- GENERIC_COMMAND@0..2
- GENERIC_COMMAND_NAME@0..2 "\\#"
-
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_path.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_path.snap
deleted file mode 100644
index 622a0fc869..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_path.snap
+++ /dev/null
@@ -1,17 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-assertion_line: 1871
-expression: "setup(r#\"\\graphicspath{{../figures/}}\"#)"
----
-ROOT@0..28
- PREAMBLE@0..28
- GRAPHICS_PATH@0..28
- GRAPHICS_PATH_NAME@0..13 "\\graphicspath"
- L_CURLY@13..14 "{"
- CURLY_GROUP_WORD@14..27
- L_CURLY@14..15 "{"
- KEY@15..26
- WORD@15..26 "../figures/"
- R_CURLY@26..27 "}"
- R_CURLY@27..28 "}"
-
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap
deleted file mode 100644
index efa0d9b675..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap
+++ /dev/null
@@ -1,15 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"\\input{foo/bar.tex}\"#)"
-
----
-ROOT@0..19
- PREAMBLE@0..19
- LATEX_INCLUDE@0..19
- LATEX_INCLUDE_NAME@0..6 "\\input"
- CURLY_GROUP_WORD_LIST@6..19
- L_CURLY@6..7 "{"
- KEY@7..18
- WORD@7..18 "foo/bar.tex"
- R_CURLY@18..19 "}"
-
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap
deleted file mode 100644
index 4888672c31..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"#1\"#)"
-
----
-ROOT@0..2
- PREAMBLE@0..2
- TEXT@0..2
- WORD@0..2 "#1"
-
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap
deleted file mode 100644
index 843327ab80..0000000000
--- a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: src/syntax/latex/parser.rs
-expression: "setup(r#\"#\"#)"
-
----
-ROOT@0..1
- PREAMBLE@0..1
- TEXT@0..1
- WORD@0..1 "#"
-
diff --git a/support/texlab/src/syntax/snapshots/texlab__syntax__build_log__tests__parse_007.snap b/support/texlab/src/syntax/snapshots/texlab__syntax__build_log__tests__parse_007.snap
deleted file mode 100644
index 09d99c4f21..0000000000
--- a/support/texlab/src/syntax/snapshots/texlab__syntax__build_log__tests__parse_007.snap
+++ /dev/null
@@ -1,15 +0,0 @@
----
-source: src/syntax/build_log.rs
-expression: parse(&log).errors
-
----
-[
- BuildError {
- relative_path: "/some/folder/a.tex",
- level: Error,
- message: "Undefined control sequence.",
- line: Some(
- 3,
- ),
- },
-]
diff --git a/support/texlab/src/tests.rs b/support/texlab/src/tests.rs
new file mode 100644
index 0000000000..c6afcfc20c
--- /dev/null
+++ b/support/texlab/src/tests.rs
@@ -0,0 +1,5 @@
+mod client;
+mod fixture;
+mod issues;
+mod text_document;
+mod workspace;
diff --git a/support/texlab/src/tests/client.rs b/support/texlab/src/tests/client.rs
new file mode 100644
index 0000000000..ff6b23469b
--- /dev/null
+++ b/support/texlab/src/tests/client.rs
@@ -0,0 +1,159 @@
+use std::sync::Once;
+
+use anyhow::{bail, Result};
+use crossbeam_channel::{Receiver, Sender};
+use lsp_server::{Connection, Message, Notification, Request, Response};
+use lsp_types::{
+ notification::{Exit, Initialized},
+ request::{Initialize, Shutdown},
+ ClientCapabilities, ClientInfo, DidOpenTextDocumentParams, InitializeParams, InitializeResult,
+ InitializedParams, Url, WorkspaceFolder,
+};
+use tempfile::{tempdir, TempDir};
+
+use crate::Server;
+
+static INIT_LOGGER: Once = Once::new();
+
+pub struct IncomingHandler {
+ _handle: jod_thread::JoinHandle<Result<()>>,
+ pub requests: Receiver<Request>,
+ pub notifications: Receiver<Notification>,
+ pub responses: Receiver<Response>,
+}
+
+impl IncomingHandler {
+ pub fn spawn(receiver: Receiver<Message>) -> Self {
+ let (req_sender, req_receiver) = crossbeam_channel::unbounded();
+ let (not_sender, not_receiver) = crossbeam_channel::unbounded();
+ let (res_sender, res_receiver) = crossbeam_channel::unbounded();
+
+ let _handle = jod_thread::spawn(move || {
+ for message in &receiver {
+ match message {
+ Message::Request(req) => req_sender.send(req)?,
+ Message::Response(res) => res_sender.send(res)?,
+ Message::Notification(not) => not_sender.send(not)?,
+ };
+ }
+
+ Ok(())
+ });
+
+ Self {
+ _handle,
+ requests: req_receiver,
+ notifications: not_receiver,
+ responses: res_receiver,
+ }
+ }
+}
+
+pub struct ClientResult {
+ pub directory: TempDir,
+ pub incoming: IncomingHandler,
+}
+
+pub struct Client {
+ outgoing: Sender<Message>,
+ incoming: IncomingHandler,
+ directory: TempDir,
+ request_id: i32,
+ _handle: jod_thread::JoinHandle,
+}
+
+impl Client {
+ pub fn spawn() -> Self {
+ INIT_LOGGER.call_once(|| env_logger::init());
+
+ let directory = tempdir().unwrap();
+ let (client, server) = Connection::memory();
+ let incoming = IncomingHandler::spawn(client.receiver);
+ let outgoing = client.sender;
+ let server = Server::new(server);
+ let _handle = jod_thread::spawn(move || {
+ server.run().expect("server failed to run");
+ });
+
+ Self {
+ outgoing,
+ incoming,
+ directory,
+ request_id: 0,
+ _handle,
+ }
+ }
+
+ pub fn initialize(
+ &mut self,
+ client_capabilities: ClientCapabilities,
+ client_info: Option<ClientInfo>,
+ ) -> InitializeResult {
+ let result = self
+ .request::<Initialize>(InitializeParams {
+ initialization_options: Some(serde_json::json!({ "skipDistro": true })),
+ capabilities: client_capabilities,
+ workspace_folders: Some(vec![WorkspaceFolder {
+ name: "Test".into(),
+ uri: Url::from_directory_path(self.directory.path()).unwrap(),
+ }]),
+ client_info,
+ ..InitializeParams::default()
+ })
+ .unwrap();
+
+ self.notify::<Initialized>(InitializedParams {});
+ result
+ }
+
+ pub fn request<R: lsp_types::request::Request>(
+ &mut self,
+ params: R::Params,
+ ) -> Result<R::Result> {
+ self.request_id += 1;
+
+ self.outgoing
+ .send(Request::new(self.request_id.into(), R::METHOD.into(), params).into())
+ .unwrap();
+
+ let response = self.incoming.responses.recv().unwrap();
+ assert_eq!(response.id, self.request_id.into());
+
+ let result = match response.result {
+ Some(result) => result,
+ None => bail!("request failed: {:?}", response.error),
+ };
+
+ Ok(serde_json::from_value(result)?)
+ }
+
+ pub fn notify<N: lsp_types::notification::Notification>(&mut self, params: N::Params) {
+ self.outgoing
+ .send(Notification::new(N::METHOD.into(), serde_json::to_value(params).unwrap()).into())
+ .unwrap();
+ }
+
+ pub fn open(&mut self, name: &str, language_id: &str, text: String) {
+ self.notify::<lsp_types::notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
+ text_document: lsp_types::TextDocumentItem {
+ uri: self.uri(name),
+ language_id: language_id.to_string(),
+ version: 0,
+ text,
+ },
+ });
+ }
+
+ pub fn shutdown(mut self) -> ClientResult {
+ self.request::<Shutdown>(()).unwrap();
+ self.notify::<Exit>(());
+ ClientResult {
+ directory: self.directory,
+ incoming: self.incoming,
+ }
+ }
+
+ pub fn uri(&self, name: &str) -> Url {
+ Url::from_file_path(self.directory.path().join(name)).unwrap()
+ }
+}
diff --git a/support/texlab/src/tests/fixture.rs b/support/texlab/src/tests/fixture.rs
new file mode 100644
index 0000000000..b837a9f5b0
--- /dev/null
+++ b/support/texlab/src/tests/fixture.rs
@@ -0,0 +1,124 @@
+use std::collections::BTreeMap;
+
+use lsp_types::{Position, Range, TextDocumentIdentifier, TextDocumentPositionParams};
+use rustc_hash::FxHashMap;
+
+use super::client::Client;
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+enum Line<'a> {
+ File(&'a str, &'a str),
+ Plain(&'a str),
+ Range(u32, u32, std::ops::Range<usize>),
+ Cursor(usize),
+ Empty,
+}
+
+fn parse_line(line: &str) -> Line {
+ if let Some(name) = line.strip_prefix("%TEX ") {
+ Line::File(name, "latex")
+ } else if let Some(name) = line.strip_prefix("%BIB ") {
+ Line::File(name, "bibtex")
+ } else if let Some(name) = line.strip_prefix("%LOG ") {
+ Line::File(name, "log")
+ } else if let Some(text) = line.strip_prefix("%SRC ") {
+ Line::Plain(text)
+ } else if let Some(text) = line.strip_prefix("%CUR ") {
+ let position = text.find('^').unwrap();
+ Line::Cursor(position)
+ } else if line.is_empty() {
+ Line::Empty
+ } else {
+ let key1 = line[1..2].parse().unwrap();
+ let key2 = line[3..4].parse().unwrap();
+ let line = &line[5..];
+ let range = line
+ .find('^')
+ .map_or(0..0, |start| start..(line.rfind('^').unwrap() + 1));
+ Line::Range(key1, key2, range)
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct FileRange<'a> {
+ pub name: &'a str,
+ pub range: Range,
+}
+
+#[derive(Debug, Default)]
+pub struct File<'a> {
+ pub name: &'a str,
+ pub lang: &'a str,
+ pub text: String,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+pub struct FileCursor<'a> {
+ pub name: &'a str,
+ pub position: Position,
+}
+
+impl<'a> FileCursor<'a> {
+ pub fn into_params(self, server: &Client) -> TextDocumentPositionParams {
+ let text_document = TextDocumentIdentifier::new(server.uri(self.name));
+ TextDocumentPositionParams {
+ text_document,
+ position: self.position,
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct Fixture<'a> {
+ pub files: Vec<File<'a>>,
+ pub cursor: Option<FileCursor<'a>>,
+ pub ranges: BTreeMap<u32, FxHashMap<u32, FileRange<'a>>>,
+}
+
+pub fn parse(input: &str) -> Fixture {
+ let mut fixture = Fixture::default();
+ let mut file = File::default();
+ let mut file_line_index = 0;
+ for line in input.lines().map(parse_line) {
+ match line {
+ Line::File(name, lang) => {
+ if !file.name.is_empty() {
+ fixture.files.push(file);
+ file = File::default();
+ }
+
+ file.name = name;
+ file.lang = lang;
+ file_line_index = 0;
+ }
+ Line::Plain(line) => {
+ file.text.push_str(line);
+ file.text.push('\n');
+ file_line_index += 1;
+ }
+ Line::Range(key1, key2, range) => {
+ let line = file_line_index - 1;
+ let start = Position::new(line, range.start.try_into().unwrap());
+ let end = Position::new(line, range.end.try_into().unwrap());
+ let range = Range::new(start, end);
+ fixture.ranges.entry(key1).or_default().insert(
+ key2,
+ FileRange {
+ name: file.name,
+ range,
+ },
+ );
+ }
+ Line::Cursor(position) => {
+ fixture.cursor = Some(FileCursor {
+ name: file.name,
+ position: Position::new(file_line_index - 1, position.try_into().unwrap()),
+ });
+ }
+ Line::Empty => {}
+ };
+ }
+
+ fixture.files.push(file);
+ fixture
+}
diff --git a/support/texlab/src/tests/issues.rs b/support/texlab/src/tests/issues.rs
new file mode 100644
index 0000000000..8f578f5ada
--- /dev/null
+++ b/support/texlab/src/tests/issues.rs
@@ -0,0 +1,12 @@
+use insta::assert_debug_snapshot;
+
+use crate::Options;
+
+#[test]
+fn issue_707() {
+ assert_debug_snapshot!(
+ serde_json::from_value::<Option<Options>>(serde_json::json!({}))
+ .unwrap()
+ .unwrap_or_default()
+ );
+}
diff --git a/support/texlab/src/tests/snapshots/texlab__tests__issues__issue_707.snap b/support/texlab/src/tests/snapshots/texlab__tests__issues__issue_707.snap
new file mode 100644
index 0000000000..1397a08fc0
--- /dev/null
+++ b/support/texlab/src/tests/snapshots/texlab__tests__issues__issue_707.snap
@@ -0,0 +1,45 @@
+---
+source: src/tests/issues.rs
+expression: "serde_json::from_value::<Option<Options>>(serde_json::json!({}))?.unwrap_or_default()"
+---
+Options {
+ root_directory: None,
+ aux_directory: None,
+ bibtex_formatter: Texlab,
+ latex_formatter: Latexindent,
+ formatter_line_length: None,
+ diagnostics: DiagnosticsOptions {
+ allowed_patterns: [],
+ ignored_patterns: [],
+ },
+ diagnostics_delay: DiagnosticsDelay(
+ 300ms,
+ ),
+ build: BuildOptions {
+ executable: BuildExecutable(
+ "latexmk",
+ ),
+ args: BuildArgs(
+ [
+ "-pdf",
+ "-interaction=nonstopmode",
+ "-synctex=1",
+ "%f",
+ ],
+ ),
+ on_save: false,
+ forward_search_after: false,
+ },
+ chktex: ChktexOptions {
+ on_open_and_save: false,
+ on_edit: false,
+ },
+ latexindent: LatexindentOptions {
+ local: None,
+ modify_line_breaks: false,
+ },
+ forward_search: ForwardSearchOptions {
+ executable: None,
+ args: None,
+ },
+}
diff --git a/support/texlab/src/tests/text_document.rs b/support/texlab/src/tests/text_document.rs
new file mode 100644
index 0000000000..aac0553ec4
--- /dev/null
+++ b/support/texlab/src/tests/text_document.rs
@@ -0,0 +1,12 @@
+mod completion;
+mod definition;
+mod did_change_configuration;
+mod document_highlight;
+mod document_link;
+mod document_symbol;
+mod folding_range;
+mod formatting;
+mod hover;
+mod inlay_hint;
+mod references;
+mod rename;
diff --git a/support/texlab/src/tests/text_document/completion.rs b/support/texlab/src/tests/text_document/completion.rs
new file mode 100644
index 0000000000..d4f256cdfb
--- /dev/null
+++ b/support/texlab/src/tests/text_document/completion.rs
@@ -0,0 +1,812 @@
+use insta::assert_json_snapshot;
+use lsp_types::{
+ request::{Completion, ResolveCompletionItem},
+ CompletionItem, CompletionList, CompletionParams, CompletionResponse, CompletionTextEdit,
+ Range,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn complete(fixture: &str) -> Vec<CompletionItem> {
+ let mut client = Client::spawn();
+ client.initialize(
+ serde_json::from_value(serde_json::json!({
+ "textDocument": {
+ "completion": {
+ "completionItem": {
+ "documentationFormat": ["plaintext", "markdown"]
+ }
+ }
+ }
+ }))
+ .unwrap(),
+ None,
+ );
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let range = fixture
+ .ranges
+ .values()
+ .next()
+ .and_then(|map| map.values().next())
+ .map_or_else(
+ || {
+ let position = fixture.cursor.unwrap().position;
+ Range::new(position, position)
+ },
+ |file_range| file_range.range,
+ );
+
+ let actual_list = client
+ .request::<Completion>(CompletionParams {
+ text_document_position: fixture.cursor.unwrap().into_params(&client),
+ partial_result_params: Default::default(),
+ work_done_progress_params: Default::default(),
+ context: None,
+ })
+ .unwrap()
+ .map_or(CompletionList::default(), |actual| match actual {
+ CompletionResponse::List(list) => list,
+ CompletionResponse::Array(_) => unreachable!(),
+ });
+
+ for item in &actual_list.items {
+ if let Some(CompletionTextEdit::Edit(edit)) = item.text_edit.as_ref() {
+ assert_eq!(edit.range, range);
+ }
+ }
+
+ let actual_items = actual_list
+ .items
+ .into_iter()
+ .take(5)
+ .map(|item| client.request::<ResolveCompletionItem>(item).unwrap())
+ .collect();
+
+ client.shutdown();
+ actual_items
+}
+
+macro_rules! assert_items {
+ ($items:expr) => {
+ assert_json_snapshot!($items, {
+ "[].data" => "[data]",
+ "[].sortText" => "[sortText]",
+ "[].documentation" => "[documentation]",
+ "[].textEdit.range" => "[range]",
+ "[]" => insta::sorted_redaction()
+ });
+ };
+}
+
+#[test]
+fn acronym_ref_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
+%SRC \acrshort{f}
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn acronym_ref_empty() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
+%SRC \acrshort{}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn acronym_ref_after_group() {
+ let actual_items = complete(
+ r#"
+%TEX main.tex
+%SRC \newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
+%SRC \acrshort{}
+%CUR ^
+%1.1 ^"#,
+ );
+
+ assert_eq!(actual_items, Vec::new());
+}
+
+#[test]
+fn acronym_ref_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
+%SRC \acrshort{f
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn acronym_package_ref() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \acrodef{fpsLabel}[FPS]{Frames per Second}
+%SRC \ac{f
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn glossary_ref_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
+%SRC \gls{f}
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn glossary_ref_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
+%SRC \gls{f
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn argument_empty() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{amsfonts}
+%SRC \mathbb{}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn argument_word() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{amsfonts}
+%SRC \mathbb{A}
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn argument_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{amsfonts}
+%SRC \mathbb{
+%CUR ^
+%SRC Test"#
+ ));
+}
+
+#[test]
+fn argument_open_brace_unrelated() {
+ let actual_items = complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{amsfonts}
+%SRC \mathbb{}{
+%CUR ^
+%SRC Test"#,
+ );
+
+ assert_eq!(actual_items, Vec::new());
+}
+
+#[test]
+fn begin_environment_without_snippet_support() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \beg
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn citation() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \bibliography{main}
+%SRC \begin{document}
+%SRC \cite{
+%CUR ^
+%SRC \end{document}
+
+%BIB main.bib
+%SRC @article{foo:2019,
+%SRC author = {Foo Bar},
+%SRC title = {Baz Qux},
+%SRC year = {2019},
+%SRC }
+%SRC
+%SRC @article{bar:2005,}"#
+ ));
+}
+
+#[test]
+fn citation_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \cite{
+%CUR ^
+
+%BIB main.bib
+%SRC @article{foo,}"#
+ ));
+}
+
+#[test]
+fn citation_open_brace_multiple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \cite{foo,a
+%CUR ^
+%1.1 ^
+
+%BIB main.bib
+%SRC @article{foo,}"#
+ ));
+}
+
+#[test]
+fn citation_acronym() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \DeclareAcronym{foo}{cite={}}
+%CUR ^
+
+%BIB main.bib
+%SRC @article{foo,}"#
+ ));
+}
+
+#[test]
+fn citation_after_brace() {
+ let actual_items = complete(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \bibliography{main}
+%SRC \begin{document}
+%SRC \cite{}
+%CUR ^
+%SRC \end{document}
+
+%BIB main.bib
+%SRC @article{foo,}"#,
+ );
+
+ assert_eq!(actual_items, Vec::new());
+}
+
+#[test]
+fn color_model_definition_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \definecolor{foo}{}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn color_model_definition_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \definecolor{foo}{
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn color_model_definition_set_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \definecolorset{}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn color_model_definition_set_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \definecolorset{
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn color_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \color{}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn color_word() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \color{re}
+%CUR ^
+%1.1 ^^"#
+ ));
+}
+
+#[test]
+fn color_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \color{
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn component_command_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn component_command_simple_before() {
+ let actual_items = complete(
+ r#"
+%TEX main.tex
+%SRC \
+%CUR ^"#,
+ );
+
+ assert_eq!(actual_items, Vec::new());
+}
+
+#[test]
+fn component_command_simple_package() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{lipsum}
+%SRC \lips
+%CUR ^
+%1.1 ^^^^"#
+ ));
+}
+
+#[test]
+fn component_command_bibtex() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{b,
+%SRC c = {\LaT }
+%CUR ^
+%1.1 ^^^
+%SRC }"#
+ ));
+}
+
+#[test]
+fn component_environment_simple() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \begin{doc
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn component_environment_simple_end() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \begin{document}
+%SRC \end{
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn component_environment_class() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \begin{thein}
+%CUR ^
+%1.1 ^^^^^"#
+ ));
+}
+
+#[test]
+fn component_environment_command_definition() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newcommand{\foo}{\begin{doc}
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn entry_type_at_empty() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn entry_type_before_preamble() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @preamble
+%CUR ^
+%1.1 ^^^^^^^^"#
+ ));
+}
+
+#[test]
+fn entry_type_before_string() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @string
+%CUR ^
+%1.1 ^^^^^^"#
+ ));
+}
+
+#[test]
+fn entry_type_before_article() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article
+%CUR ^
+%1.1 ^^^^^^^"#
+ ));
+}
+
+#[test]
+fn entry_type_after_preamble() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @preamble{
+%CUR ^
+%1.1 ^^^^^^^^"#
+ ));
+}
+
+#[test]
+fn entry_type_after_string() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @string{
+%CUR ^
+%1.1 ^^^^^^"#
+ ));
+}
+
+#[test]
+fn entry_type_complete_entry() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{foo, author = {foo}}
+%CUR ^
+%1.1 ^^^^^^^"#
+ ));
+}
+
+#[test]
+fn field_empty_entry_open() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{foo,
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn field_empty_entry_closed() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{foo,}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn field_entry_field_name() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{foo, a
+%CUR ^
+%1.1 ^"#
+ ));
+}
+
+#[test]
+fn field_entry_two_fields_name_open() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{foo, author = bar, edit
+%CUR ^
+%1.1 ^^^^"#
+ ));
+}
+
+#[test]
+fn field_entry_two_fields_name_closed() {
+ assert_items!(complete(
+ r#"
+%BIB main.bib
+%SRC @article{foo, author = bar, edit}
+%CUR ^
+%1.1 ^^^^"#
+ ));
+}
+
+#[test]
+fn import_package_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{lips
+%CUR ^
+%1.1 ^^^^"#
+ ));
+}
+
+#[test]
+fn import_package_closed_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepackage{lips}
+%CUR ^
+%1.1 ^^^^"#
+ ));
+}
+
+#[test]
+fn import_class_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \documentclass{art \foo
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn import_class_closed_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \documentclass{art}
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn label() {
+ assert_items!(complete(
+ r#"
+%TEX foo.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \usepackage{amsmath}
+%SRC \usepackage{caption}
+%SRC \usepackage{amsthm}
+%SRC \newtheorem{lemma}{Lemma}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \section{Foo}%
+%SRC \label{sec:foo}
+%SRC
+%SRC \begin{equation}%
+%SRC \label{eq:foo}
+%SRC 1 + 1 = 2
+%SRC \end{equation}
+%SRC
+%SRC \begin{equation}%
+%SRC \label{eq:bar}
+%SRC 1 + 1 = 2
+%SRC \end{equation}
+%SRC
+%SRC \begin{figure}%
+%SRC \LaTeX{}
+%SRC \caption{Baz}%
+%SRC \label{fig:baz}
+%SRC \end{figure}
+%SRC
+%SRC \begin{lemma}%
+%SRC \label{thm:foo}
+%SRC 1 + 1 = 2
+%SRC \end{lemma}
+%SRC
+%SRC \include{bar}
+%SRC
+%SRC \end{document}
+
+%TEX bar.tex
+%SRC \section{Bar}%
+%SRC \label{sec:bar}
+%SRC
+%SRC Lorem ipsum dolor sit amet.
+%SRC \ref{}
+%CUR ^
+
+%TEX foo.aux
+%SRC \relax
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Baz\relax }}{1}\protected@file@percent }
+%SRC \providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
+%SRC \newlabel{fig:baz}{{1}{1}}
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {1}Foo}{1}\protected@file@percent }
+%SRC \newlabel{sec:foo}{{1}{1}}
+%SRC \newlabel{eq:foo}{{1}{1}}
+%SRC \newlabel{eq:bar}{{2}{1}}
+%SRC \newlabel{thm:foo}{{1}{1}}
+%SRC \@input{bar.aux}"#
+ ));
+}
+
+#[test]
+fn theorem_begin() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newtheorem{lemma}{Lemma}
+%SRC \begin{lem
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn theorem_end() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \newtheorem{lemma}{Lemma}
+%SRC \begin{}
+%SRC \end{lem
+%CUR ^
+%1.1 ^^^"#
+ ));
+}
+
+#[test]
+fn tikz_library_open_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepgflibrary{
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn tikz_library_closed_brace() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \usepgflibrary{}
+%CUR ^"#
+ ));
+}
+
+#[test]
+fn test_user_command() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \foobar
+%SRC \fooba
+%CUR ^
+%1.1 ^^^^^
+%SRC \begin{foo}
+%SRC \end{foo}
+%SRC \begin{fo}
+"#
+ ));
+}
+
+#[test]
+fn test_user_environment() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \foobar
+%SRC \fooba
+%SRC \begin{foo}
+%SRC \end{foo}
+%SRC \begin{fo}
+%CUR ^
+%1.1 ^^
+"#
+ ));
+}
+
+#[test]
+fn test_project_resolution() {
+ assert_items!(complete(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \import{sub}{sub/sub.tex}
+%SRC \lipsu
+%CUR ^
+%1.1 ^^^^^
+
+%TEX sub/sub.tex
+%SRC \input{child.tex}
+
+%TEX sub/child.tex
+%SRC \usepackage{lipsum}
+
+"#
+ ));
+}
diff --git a/support/texlab/src/tests/text_document/definition.rs b/support/texlab/src/tests/text_document/definition.rs
new file mode 100644
index 0000000000..b1b915c6c9
--- /dev/null
+++ b/support/texlab/src/tests/text_document/definition.rs
@@ -0,0 +1,140 @@
+use assert_unordered::assert_eq_unordered;
+use lsp_types::{
+ request::GotoDefinition, ClientCapabilities, GotoDefinitionParams, GotoDefinitionResponse,
+ LocationLink,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let mut expected_links = Vec::new();
+ for ranges in fixture.ranges.values() {
+ expected_links.push(LocationLink {
+ origin_selection_range: Some(ranges[&1].range),
+ target_uri: client.uri(ranges[&2].name),
+ target_range: ranges[&2].range,
+ target_selection_range: ranges[&3].range,
+ });
+ }
+
+ let actual_links = client
+ .request::<GotoDefinition>(GotoDefinitionParams {
+ text_document_position_params: fixture.cursor.unwrap().into_params(&client),
+ partial_result_params: Default::default(),
+ work_done_progress_params: Default::default(),
+ })
+ .unwrap()
+ .map_or(Vec::new(), |actual| match actual {
+ GotoDefinitionResponse::Link(links) => links,
+ GotoDefinitionResponse::Array(_) | GotoDefinitionResponse::Scalar(_) => unreachable!(),
+ });
+
+ client.shutdown();
+ assert_eq_unordered!(actual_links, expected_links);
+}
+
+#[test]
+fn command_definition() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \DeclareMathOperator{\foo}{foo}
+%1.3 ^^^^
+%1.2 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+%SRC \foo
+%CUR ^
+%1.1 ^^^^
+"#,
+ )
+}
+
+#[test]
+fn document() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \addbibresource{baz.bib}
+%CUR ^
+%1.1 ^^^^^^^
+
+%TEX bar.bib
+%SRC @article{foo, bar = {baz}}
+
+%TEX baz.bib
+%SRC @article{foo, bar = {baz}}
+%1.3
+%1.2
+"#,
+ )
+}
+
+#[test]
+fn entry() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \addbibresource{baz.bib}
+%SRC \cite{foo}
+%CUR ^
+%1.1 ^^^
+
+%BIB bar.bib
+%SRC @article{foo, bar = {baz}}
+
+%BIB baz.bib
+%SRC @article{foo, bar = {baz}}
+%1.3 ^^^
+%1.2 ^^^^^^^^^^^^^^^^^^^^^^^^^^
+"#,
+ )
+}
+
+#[test]
+fn string_simple() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {bar}}
+%1.3 ^^^
+%1.2 ^^^^^^^^^^^^^^^^^^^^
+%SRC @article{bar, author = foo}
+%CUR ^
+%1.1 ^^^
+"#,
+ )
+}
+
+#[test]
+fn string_join() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {bar}}
+%1.3 ^^^
+%1.2 ^^^^^^^^^^^^^^^^^^^^
+%SRC @article{bar, author = foo # "bar"}
+%CUR ^
+%1.1 ^^^
+"#,
+ )
+}
+
+#[test]
+fn string_field() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {bar}}
+%SRC @article{bar, author = foo # "bar"}
+%CUR ^
+"#,
+ )
+}
diff --git a/support/texlab/src/tests/text_document/did_change_configuration.rs b/support/texlab/src/tests/text_document/did_change_configuration.rs
new file mode 100644
index 0000000000..15aae0ee4b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/did_change_configuration.rs
@@ -0,0 +1,37 @@
+use insta::assert_snapshot;
+use lsp_types::{
+ notification::{DidChangeConfiguration, Notification, ShowMessage},
+ ClientCapabilities, DidChangeConfigurationParams, ShowMessageParams,
+};
+
+use crate::tests::client::Client;
+
+#[test]
+fn invalid_configuration() {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ client.notify::<DidChangeConfiguration>(DidChangeConfigurationParams {
+ settings: serde_json::json!({
+ "diagnostics": {
+ "allowedPatterns": ["\\"]
+ }
+ }),
+ });
+
+ let result = client.shutdown();
+ let message = result
+ .incoming
+ .notifications
+ .into_iter()
+ .filter_map(|notification| {
+ notification
+ .extract::<ShowMessageParams>(ShowMessage::METHOD)
+ .ok()
+ })
+ .find(|params| params.message.contains("configuration"))
+ .unwrap()
+ .message;
+
+ assert_snapshot!(message);
+}
diff --git a/support/texlab/src/tests/text_document/document_highlight.rs b/support/texlab/src/tests/text_document/document_highlight.rs
new file mode 100644
index 0000000000..01945ce342
--- /dev/null
+++ b/support/texlab/src/tests/text_document/document_highlight.rs
@@ -0,0 +1,60 @@
+use assert_unordered::assert_eq_unordered;
+use lsp_types::{
+ request::DocumentHighlightRequest, ClientCapabilities, DocumentHighlight,
+ DocumentHighlightKind, DocumentHighlightParams,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let mut expected_highlights = Vec::new();
+ for ranges in fixture.ranges.values() {
+ let (i, file_range) = ranges.iter().next().unwrap();
+ let kind = match i {
+ 1 => DocumentHighlightKind::TEXT,
+ 2 => DocumentHighlightKind::READ,
+ 3 => DocumentHighlightKind::WRITE,
+ _ => unreachable!(),
+ };
+
+ expected_highlights.push(DocumentHighlight {
+ range: file_range.range,
+ kind: Some(kind),
+ });
+ }
+
+ let actual_highlights = client
+ .request::<DocumentHighlightRequest>(DocumentHighlightParams {
+ text_document_position_params: fixture.cursor.unwrap().into_params(&client),
+ partial_result_params: Default::default(),
+ work_done_progress_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+ assert_eq_unordered!(actual_highlights, expected_highlights);
+}
+
+#[test]
+fn test_label() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \label{foo}
+%CUR ^
+%1.3 ^^^
+%SRC \ref{foo}
+%2.2 ^^^
+%SRC \label{bar}
+"#,
+ )
+}
diff --git a/support/texlab/src/tests/text_document/document_link.rs b/support/texlab/src/tests/text_document/document_link.rs
new file mode 100644
index 0000000000..8b4a30ceeb
--- /dev/null
+++ b/support/texlab/src/tests/text_document/document_link.rs
@@ -0,0 +1,71 @@
+use assert_unordered::assert_eq_unordered;
+use lsp_types::{
+ request::DocumentLinkRequest, ClientCapabilities, DocumentLink, DocumentLinkParams,
+ TextDocumentIdentifier,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let mut expected_links = Vec::new();
+ for ranges in fixture.ranges.values() {
+ expected_links.push(DocumentLink {
+ range: ranges[&1].range,
+ target: Some(client.uri(ranges[&2].name)),
+ tooltip: None,
+ data: None,
+ });
+ }
+
+ let actual_links = client
+ .request::<DocumentLinkRequest>(DocumentLinkParams {
+ text_document: TextDocumentIdentifier::new(client.uri(fixture.cursor.unwrap().name)),
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+ assert_eq_unordered!(actual_links, expected_links);
+}
+
+#[test]
+fn document_include() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \input{bar.tex}
+%1.1 ^^^^^^^
+%CUR ^
+
+%TEX bar.tex
+%SRC
+%1.2
+"#,
+ )
+}
+
+#[test]
+fn document_import() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \import{.}{bar/baz}
+%1.1 ^^^^^^^
+%CUR ^
+
+%TEX bar/baz.tex
+%SRC
+%1.2
+"#,
+ )
+}
diff --git a/support/texlab/src/tests/text_document/document_symbol.rs b/support/texlab/src/tests/text_document/document_symbol.rs
new file mode 100644
index 0000000000..2eab81631e
--- /dev/null
+++ b/support/texlab/src/tests/text_document/document_symbol.rs
@@ -0,0 +1,408 @@
+use insta::{assert_json_snapshot, internals::Redaction};
+use lsp_types::{
+ request::DocumentSymbolRequest, DocumentSymbolParams, DocumentSymbolResponse,
+ TextDocumentIdentifier, Url,
+};
+
+use crate::tests::{client::Client, fixture};
+
+struct SymbolResult {
+ response: Option<DocumentSymbolResponse>,
+ uri_redaction: Redaction,
+}
+
+fn find_symbols(fixture: &str, client_capabilities: serde_json::Value) -> SymbolResult {
+ let mut client = Client::spawn();
+ client.initialize(serde_json::from_value(client_capabilities).unwrap(), None);
+
+ let fixture = fixture::parse(fixture);
+ let file = fixture.files.into_iter().next().unwrap();
+ client.open(file.name, file.lang, file.text);
+
+ let response = client
+ .request::<DocumentSymbolRequest>(DocumentSymbolParams {
+ text_document: TextDocumentIdentifier::new(client.uri(file.name)),
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ })
+ .unwrap();
+
+ let result = client.shutdown();
+
+ let uri = Url::from_directory_path(result.directory.path()).unwrap();
+ let uri_redaction = insta::dynamic_redaction(move |content, _path| {
+ content.as_str().unwrap().replace(uri.as_str(), "[tmp]/")
+ });
+
+ SymbolResult {
+ response,
+ uri_redaction,
+ }
+}
+
+macro_rules! assert_symbols {
+ ($result:expr) => {
+ let result = $result;
+ assert_json_snapshot!(result.response, {
+ "[].location.uri" => result.uri_redaction
+ });
+ };
+}
+
+#[test]
+fn enumerate_nested() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{enumerate}
+%SRC \item\label{it:foo} Foo
+%SRC \item\label{it:bar} Bar
+%SRC \item[Baz] Baz
+%SRC \item[Qux]\label{it:qux} Qux
+%SRC \end{enumerate}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \newlabel{it:foo}{{1}{1}}
+%SRC \newlabel{it:qux}{{2}{1}}
+"#,
+ serde_json::json!({
+ "textDocument": {
+ "documentSymbol": {
+ "hierarchicalDocumentSymbolSupport": true,
+ },
+ },
+ }),
+ ));
+}
+
+#[test]
+fn enumerate_flat() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{enumerate}
+%SRC \item\label{it:foo} Foo
+%SRC \item\label{it:bar} Bar
+%SRC \item[Baz] Baz
+%SRC \item[Qux]\label{it:qux} Qux
+%SRC \end{enumerate}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \newlabel{it:foo}{{1}{1}}
+%SRC \newlabel{it:qux}{{2}{1}}
+"#,
+ serde_json::json!({}),
+ ));
+}
+
+#[test]
+fn equation_nested() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{equation}\label{eq:foo}
+%SRC Foo
+%SRC \end{equation}
+%SRC
+%SRC \begin{equation}\label{eq:bar}
+%SRC Bar
+%SRC \end{equation}
+%SRC
+%SRC \begin{equation}
+%SRC Baz
+%SRC \end{equation}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \newlabel{eq:foo}{{1}{1}}
+"#,
+ serde_json::json!({
+ "textDocument": {
+ "documentSymbol": {
+ "hierarchicalDocumentSymbolSupport": true,
+ },
+ },
+ }),
+ ));
+}
+
+#[test]
+fn equation_flat() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{equation}\label{eq:foo}
+%SRC Foo
+%SRC \end{equation}
+%SRC
+%SRC \begin{equation}\label{eq:bar}
+%SRC Bar
+%SRC \end{equation}
+%SRC
+%SRC \begin{equation}
+%SRC Baz
+%SRC \end{equation}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \newlabel{eq:foo}{{1}{1}}
+"#,
+ serde_json::json!({}),
+ ));
+}
+
+#[test]
+fn float_nested() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{figure}
+%SRC Foo
+%SRC \caption{Foo}\label{fig:foo}
+%SRC \end{figure}
+%SRC
+%SRC \begin{figure}
+%SRC Bar
+%SRC \caption{Bar}\label{fig:bar}
+%SRC \end{figure}
+%SRC
+%SRC \begin{figure}
+%SRC Baz
+%SRC \caption{Baz}
+%SRC \end{figure}
+%SRC
+%SRC \begin{figure}
+%SRC Qux
+%SRC \end{figure}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Foo}}{1}\protected@file@percent }
+%SRC \newlabel{fig:foo}{{1}{1}}
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Bar}}{1}\protected@file@percent }
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces Baz}}{1}\protected@file@percent }
+"#,
+ serde_json::json!({
+ "textDocument": {
+ "documentSymbol": {
+ "hierarchicalDocumentSymbolSupport": true,
+ },
+ },
+ }),
+ ));
+}
+
+#[test]
+fn float_flat() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{figure}
+%SRC Foo
+%SRC \caption{Foo}\label{fig:foo}
+%SRC \end{figure}
+%SRC
+%SRC \begin{figure}
+%SRC Bar
+%SRC \caption{Bar}\label{fig:bar}
+%SRC \end{figure}
+%SRC
+%SRC \begin{figure}
+%SRC Baz
+%SRC \caption{Baz}
+%SRC \end{figure}
+%SRC
+%SRC \begin{figure}
+%SRC Qux
+%SRC \end{figure}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Foo}}{1}\protected@file@percent }
+%SRC \newlabel{fig:foo}{{1}{1}}
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Bar}}{1}\protected@file@percent }
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces Baz}}{1}\protected@file@percent }
+"#,
+ serde_json::json!({}),
+ ));
+}
+
+#[test]
+fn section_nested() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \section{Foo}
+%SRC
+%SRC \section{Bar}\label{sec:bar}
+%SRC
+%SRC \subsection{Baz}\label{sec:baz}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {1}Foo}{1}\protected@file@percent }
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {2}Bar}{1}\protected@file@percent }
+%SRC \newlabel{sec:bar}{{2}{1}}
+"#,
+ serde_json::json!({
+ "textDocument": {
+ "documentSymbol": {
+ "hierarchicalDocumentSymbolSupport": true,
+ },
+ },
+ }),
+ ));
+}
+
+#[test]
+fn section_flat() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \section{Foo}
+%SRC
+%SRC \section{Bar}\label{sec:bar}
+%SRC
+%SRC \subsection{Baz}\label{sec:baz}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {1}Foo}{1}\protected@file@percent }
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {2}Bar}{1}\protected@file@percent }
+%SRC \newlabel{sec:bar}{{2}{1}}
+"#,
+ serde_json::json!({}),
+ ));
+}
+
+#[test]
+fn theorem_nested() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \usepackage{amsthm}
+%SRC \newtheorem{lemma}{Lemma}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{lemma}[Foo]\label{thm:foo}
+%SRC Foo
+%SRC \end{lemma}
+%SRC
+%SRC \begin{lemma}\label{thm:bar}
+%SRC Bar
+%SRC \end{lemma}
+%SRC
+%SRC \begin{lemma}\label{thm:baz}
+%SRC Baz
+%SRC \end{lemma}
+%SRC
+%SRC \begin{lemma}[Qux]
+%SRC Qux
+%SRC \end{lemma}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \newlabel{thm:foo}{{1}{1}}
+%SRC \newlabel{thm:bar}{{2}{1}}
+"#,
+ serde_json::json!({
+ "textDocument": {
+ "documentSymbol": {
+ "hierarchicalDocumentSymbolSupport": true,
+ },
+ },
+ }),
+ ));
+}
+
+#[test]
+fn theorem_flat() {
+ assert_symbols!(find_symbols(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \usepackage{amsthm}
+%SRC \newtheorem{lemma}{Lemma}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \begin{lemma}[Foo]\label{thm:foo}
+%SRC Foo
+%SRC \end{lemma}
+%SRC
+%SRC \begin{lemma}\label{thm:bar}
+%SRC Bar
+%SRC \end{lemma}
+%SRC
+%SRC \begin{lemma}\label{thm:baz}
+%SRC Baz
+%SRC \end{lemma}
+%SRC
+%SRC \begin{lemma}[Qux]
+%SRC Qux
+%SRC \end{lemma}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \newlabel{thm:foo}{{1}{1}}
+%SRC \newlabel{thm:bar}{{2}{1}}
+"#,
+ serde_json::json!({}),
+ ));
+}
diff --git a/support/texlab/src/tests/text_document/folding_range.rs b/support/texlab/src/tests/text_document/folding_range.rs
new file mode 100644
index 0000000000..4b589222b1
--- /dev/null
+++ b/support/texlab/src/tests/text_document/folding_range.rs
@@ -0,0 +1,96 @@
+use assert_unordered::assert_eq_unordered;
+use lsp_types::{
+ request::FoldingRangeRequest, ClientCapabilities, FoldingRange, FoldingRangeKind,
+ FoldingRangeParams, TextDocumentIdentifier,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str, expected_ranges: Vec<(u32, u32, u32, u32)>) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let actual_foldings = client
+ .request::<FoldingRangeRequest>(FoldingRangeParams {
+ text_document: TextDocumentIdentifier::new(client.uri(fixture.cursor.unwrap().name)),
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+
+ let expected_foldings = expected_ranges
+ .into_iter()
+ .map(
+ |(start_line, start_character, end_line, end_character)| FoldingRange {
+ start_line,
+ start_character: Some(start_character),
+ end_line,
+ end_character: Some(end_character),
+ kind: Some(FoldingRangeKind::Region),
+ },
+ )
+ .collect();
+
+ assert_eq_unordered!(actual_foldings, expected_foldings);
+}
+
+#[test]
+fn latex() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \begin{document}
+%SRC \section{Foo}
+%SRC foo
+%SRC \subsection{Bar}
+%SRC bar
+%SRC \section{Baz}
+%SRC baz
+%SRC \section{Qux}
+%SRC \end{document}
+%CUR ^
+"#,
+ vec![
+ (0, 0, 8, 14),
+ (1, 4, 4, 7),
+ (3, 4, 4, 7),
+ (5, 4, 6, 7),
+ (7, 4, 7, 17),
+ ],
+ )
+}
+
+#[test]
+fn bibtex() {
+ check(
+ r#"
+%BIB main.bib
+%SRC some junk
+%SRC here
+%SRC
+%SRC @article{foo,
+%SRC author = {bar},
+%SRC title = {baz}
+%SRC }
+%SRC
+%SRC @string{foo = "bar"}
+%SRC
+%SRC @comment{foo,
+%SRC author = {bar},
+%SRC title = {baz}
+%SRC }
+%SRC
+%SRC @preamble{"foo"}
+%CUR ^
+"#,
+ vec![(3, 0, 6, 1), (8, 0, 8, 20), (15, 0, 15, 16)],
+ )
+}
diff --git a/support/texlab/src/tests/text_document/formatting.rs b/support/texlab/src/tests/text_document/formatting.rs
new file mode 100644
index 0000000000..eb6804ca66
--- /dev/null
+++ b/support/texlab/src/tests/text_document/formatting.rs
@@ -0,0 +1,132 @@
+use insta::assert_snapshot;
+use lsp_types::{
+ request::Formatting, ClientCapabilities, DocumentFormattingParams, FormattingOptions,
+ TextDocumentIdentifier,
+};
+
+use crate::{
+ tests::{client::Client, fixture},
+ util::{line_index::LineIndex, line_index_ext::LineIndexExt},
+};
+
+fn format(fixture: &str) -> String {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ let file = fixture.files.into_iter().next().unwrap();
+ let old_text = file.text.clone();
+ client.open(file.name, file.lang, file.text);
+
+ let actual_edits = client
+ .request::<Formatting>(DocumentFormattingParams {
+ text_document: TextDocumentIdentifier::new(client.uri(file.name)),
+ work_done_progress_params: Default::default(),
+ options: FormattingOptions {
+ insert_spaces: true,
+ tab_size: 4,
+ ..Default::default()
+ },
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+
+ let line_index = LineIndex::new(&old_text);
+ let mut actual_text = old_text;
+ for edit in actual_edits.into_iter().rev() {
+ let range = line_index.offset_lsp_range(edit.range);
+ actual_text.replace_range::<std::ops::Range<usize>>(range.into(), &edit.new_text);
+ }
+
+ actual_text
+}
+
+#[test]
+fn bibtex_internal_wrap_long_lines() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+%SRC Lorem ipsum dolor sit amet,
+%SRC consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_multiple_entries() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet,
+%SRC consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}
+%SRC
+%SRC @article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet,
+%SRC consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}""#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_trailing_comma() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = baz}"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_insert_braces() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = baz,"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_command() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = "\baz",}"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_join_strings() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = "baz" # "qux"}"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_parens() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @article(foo,)"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_string() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @string{foo="bar"}"#,
+ ));
+}
+
+#[test]
+fn bibtex_internal_preamble() {
+ assert_snapshot!(format(
+ r#"
+%BIB main.bib
+%SRC @preamble{
+%SRC "foo bar baz" }"#,
+ ));
+}
diff --git a/support/texlab/src/tests/text_document/hover.rs b/support/texlab/src/tests/text_document/hover.rs
new file mode 100644
index 0000000000..302be82ca3
--- /dev/null
+++ b/support/texlab/src/tests/text_document/hover.rs
@@ -0,0 +1,303 @@
+use lsp_types::{
+ request::HoverRequest, ClientCapabilities, Hover, HoverContents, HoverParams, MarkupContent,
+ MarkupKind,
+};
+
+use crate::{
+ tests::{client::Client, fixture},
+ util::{components::COMPONENT_DATABASE, lang_data::LANGUAGE_DATA},
+};
+
+fn check(fixture: &str, contents: Option<HoverContents>) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let range = fixture
+ .ranges
+ .values()
+ .next()
+ .and_then(|map| map.values().next())
+ .map(|file_range| file_range.range);
+
+ let actual_hover = client
+ .request::<HoverRequest>(HoverParams {
+ text_document_position_params: fixture.cursor.unwrap().into_params(&client),
+ work_done_progress_params: Default::default(),
+ })
+ .unwrap();
+
+ client.shutdown();
+
+ let expected_hover = contents.map(|contents| Hover { range, contents });
+ assert_eq!(actual_hover, expected_hover);
+}
+
+#[test]
+fn empty_latex_document() {
+ check(
+ r#"
+%TEX main.tex
+%SRC
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn empty_bibtex_document() {
+ check(
+ r#"
+%BIB main.bib
+%SRC
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn citation_inside_cite() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo, author = {Foo Bar}, title = {Baz Qux}, year = 1337}
+
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \cite{foo}
+%CUR ^
+%1.1 ^^^
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value: "F. Bar: \"Baz Qux\". (1337).".to_string(),
+ })),
+ )
+}
+
+#[test]
+fn citation_inside_entry() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo, author = {Foo Bar}, title = {Baz Qux}, year = 1337}
+%CUR ^
+%1.1 ^^^
+
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \cite{foo}
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value: "F. Bar: \"Baz Qux\". (1337).".to_string(),
+ })),
+ )
+}
+
+#[test]
+fn component_known_package() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \usepackage{amsmath}
+%CUR ^
+%1.1 ^^^^^^^
+"#,
+ COMPONENT_DATABASE
+ .documentation("amsmath")
+ .map(HoverContents::Markup),
+ )
+}
+
+#[test]
+fn component_unknown_class() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \documentclass{abcdefghijklmnop}
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn entry_type_known_type() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo,}
+%CUR ^
+%1.1 ^^^^^^^^
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value: LANGUAGE_DATA
+ .entry_type_documentation("article")
+ .unwrap()
+ .to_string(),
+ })),
+ )
+}
+
+#[test]
+fn entry_type_unknown_field() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @foo{bar,}
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn entry_type_key() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @foo{bar,}
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn field_known() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo, author = bar}
+%CUR ^
+%1.1 ^^^^^^
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value: LANGUAGE_DATA
+ .field_documentation("author")
+ .unwrap()
+ .to_string(),
+ })),
+ )
+}
+
+#[test]
+fn field_unknown() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = baz}
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn section() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \section{Foo}
+%SRC \label{sec:foo}
+%CUR ^
+%1.1 ^^^^^^^
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::PlainText,
+ value: "Section (Foo)".to_string(),
+ })),
+ )
+}
+
+#[test]
+fn string_inside_reference() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = "Foo"}
+%SRC @string{bar = "Bar"}
+%SRC @article{baz, author = bar}
+%CUR ^
+%1.1 ^^^
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::PlainText,
+ value: "Bar".to_string(),
+ })),
+ )
+}
+
+#[test]
+fn string_inside_field() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = "Foo"}
+%SRC @string{bar = "Bar"}
+%SRC @article{baz, author = bar}
+%CUR ^
+"#,
+ None,
+ )
+}
+
+#[test]
+fn label_theorem_child_file() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \newtheorem{lemma}{Lemma}
+%SRC \include{child}
+%SRC \ref{thm:foo}
+%CUR ^
+%1.1 ^^^^^^^
+
+%TEX child.tex
+%SRC \begin{lemma}\label{thm:foo}
+%SRC 1 + 1 = 2
+%SRC \end{lemma}
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::PlainText,
+ value: "Lemma".to_string(),
+ })),
+ )
+}
+
+#[test]
+fn label_theorem_child_file_mumber() {
+ check(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \newtheorem{lemma}{Lemma}
+%SRC \include{child}
+%SRC \ref{thm:foo}
+%CUR ^
+%1.1 ^^^^^^^
+
+%TEX child.tex
+%SRC \begin{lemma}[Foo]\label{thm:foo}
+%SRC 1 + 1 = 2
+%SRC \end{lemma}
+
+%TEX child.aux
+%SRC \newlabel{thm:foo}{{1}{1}{Foo}{lemma.1}{}}
+"#,
+ Some(HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::PlainText,
+ value: "Lemma 1 (Foo)".to_string(),
+ })),
+ )
+}
diff --git a/support/texlab/src/tests/text_document/inlay_hint.rs b/support/texlab/src/tests/text_document/inlay_hint.rs
new file mode 100644
index 0000000000..acf5c3e132
--- /dev/null
+++ b/support/texlab/src/tests/text_document/inlay_hint.rs
@@ -0,0 +1,65 @@
+use insta::assert_json_snapshot;
+use lsp_types::{
+ request::InlayHintRequest, ClientCapabilities, InlayHint, InlayHintParams, Position, Range,
+ TextDocumentIdentifier,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str) -> Vec<InlayHint> {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ let uri = client.uri(fixture.files[0].name);
+
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let actual_hints = client
+ .request::<InlayHintRequest>(InlayHintParams {
+ text_document: TextDocumentIdentifier::new(uri),
+ range: Range::new(Position::new(0, 0), Position::new(12, 0)),
+ work_done_progress_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+ actual_hints
+}
+
+#[test]
+fn label_definition() {
+ assert_json_snapshot!(check(
+ r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \usepackage{caption}
+%SRC \begin{document}
+%SRC \section{Foo}\label{sec:foo}
+%SRC \section{Bar}\label{sec:bar}
+%SRC \subsection{Baz}\label{sec:baz}
+%SRC \begin{figure}
+%SRC Test
+%SRC \label{fig:qux}
+%SRC \caption{Qux}
+%SRC \end{figure}
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
+%SRC \newlabel{fig:qux}{{\caption@xref {fig:qux}{ on input line 15}}{1}}
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Qux\relax }}{1}{}\protected@file@percent }
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {1}Foo}{1}{}\protected@file@percent }
+%SRC \newlabel{sec:foo}{{1}{1}}
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {2}Bar}{1}{}\protected@file@percent }
+%SRC \newlabel{sec:bar}{{2}{1}}
+%SRC \@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Baz}{1}{}\protected@file@percent }
+%SRC \newlabel{sec:baz}{{2.1}{1}}
+%SRC \gdef \@abspage@last{1}
+"#,
+ ));
+}
diff --git a/support/texlab/src/tests/text_document/references.rs b/support/texlab/src/tests/text_document/references.rs
new file mode 100644
index 0000000000..a0ebed1fb4
--- /dev/null
+++ b/support/texlab/src/tests/text_document/references.rs
@@ -0,0 +1,271 @@
+use assert_unordered::assert_eq_unordered;
+use lsp_types::{
+ request::References, ClientCapabilities, Location, ReferenceContext, ReferenceParams,
+};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str, context: ReferenceContext) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let mut expected_locations = Vec::new();
+ for ranges in fixture.ranges.values() {
+ expected_locations.push(Location::new(client.uri(ranges[&1].name), ranges[&1].range));
+ }
+
+ let actual_locations = client
+ .request::<References>(ReferenceParams {
+ text_document_position: fixture.cursor.unwrap().into_params(&client),
+ context,
+ partial_result_params: Default::default(),
+ work_done_progress_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+
+ assert_eq_unordered!(actual_locations, expected_locations);
+}
+
+#[test]
+fn entry_definition() {
+ check(
+ r#"
+%BIB foo.bib
+%SRC @article{foo,}
+%CUR ^
+
+%TEX bar.tex
+%SRC \cite{foo}
+%1.1 ^^^
+%SRC \addbibresource{foo.bib}
+"#,
+ ReferenceContext {
+ include_declaration: false,
+ },
+ )
+}
+
+#[test]
+fn entry_definition_include_decl() {
+ check(
+ r#"
+%BIB foo.bib
+%SRC @article{foo,}
+%CUR ^
+%2.1 ^^^
+
+%TEX bar.tex
+%SRC \cite{foo}
+%1.1 ^^^
+%SRC \addbibresource{foo.bib}
+"#,
+ ReferenceContext {
+ include_declaration: true,
+ },
+ )
+}
+
+#[test]
+fn entry_reference() {
+ check(
+ r#"
+%BIB foo.bib
+%SRC @article{foo,}
+
+%TEX bar.tex
+%SRC \cite{foo}
+%CUR ^
+%1.1 ^^^
+%SRC \addbibresource{foo.bib}
+"#,
+ ReferenceContext {
+ include_declaration: false,
+ },
+ )
+}
+
+#[test]
+fn entry_reference_include_decl() {
+ check(
+ r#"
+%BIB foo.bib
+%SRC @article{foo,}
+%2.1 ^^^
+
+%TEX bar.tex
+%SRC \cite{foo}
+%CUR ^
+%1.1 ^^^
+%SRC \addbibresource{foo.bib}
+"#,
+ ReferenceContext {
+ include_declaration: true,
+ },
+ )
+}
+
+#[test]
+fn label_definition() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \label{foo}
+%CUR ^
+
+%TEX bar.tex
+%SRC \ref{foo}
+%1.1 ^^^
+%SRC \input{foo.tex}
+"#,
+ ReferenceContext {
+ include_declaration: false,
+ },
+ )
+}
+
+#[test]
+fn label_definition_include_decl() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \label{foo}
+%CUR ^
+%2.1 ^^^
+
+%TEX bar.tex
+%SRC \ref{foo}
+%1.1 ^^^
+%SRC \input{foo.tex}
+"#,
+ ReferenceContext {
+ include_declaration: true,
+ },
+ )
+}
+
+#[test]
+fn label_reference() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \label{foo}
+%SRC \input{bar.tex}
+
+%TEX bar.tex
+%SRC \ref{foo}
+%CUR ^
+%1.1 ^^^
+
+%TEX baz.tex
+%SRC \ref{foo}
+%2.1 ^^^
+%SRC \input{bar.tex}
+"#,
+ ReferenceContext {
+ include_declaration: false,
+ },
+ )
+}
+
+#[test]
+fn label_reference_include_decl() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \label{foo}
+%3.1 ^^^
+%SRC \input{bar.tex}
+
+%TEX bar.tex
+%SRC \ref{foo}
+%CUR ^
+%1.1 ^^^
+
+%TEX baz.tex
+%SRC \ref{foo}
+%2.1 ^^^
+%SRC \input{bar.tex}
+"#,
+ ReferenceContext {
+ include_declaration: true,
+ },
+ )
+}
+
+#[test]
+fn string_reference() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {Foo}}
+%SRC @string{bar = {Bar}}
+%SRC @article{baz, author = foo}
+%CUR ^
+%1.1 ^^^
+"#,
+ ReferenceContext {
+ include_declaration: false,
+ },
+ )
+}
+
+#[test]
+fn string_reference_include_decl() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {Foo}}
+%2.1 ^^^
+%SRC @string{bar = {Bar}}
+%SRC @article{baz, author = foo}
+%CUR ^
+%1.1 ^^^
+"#,
+ ReferenceContext {
+ include_declaration: true,
+ },
+ )
+}
+
+#[test]
+fn string_definition() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {Foo}}
+%CUR ^
+%SRC @string{bar = {Bar}}
+%SRC @article{baz, author = foo}
+%1.1 ^^^
+"#,
+ ReferenceContext {
+ include_declaration: false,
+ },
+ )
+}
+
+#[test]
+fn string_definition_include_decl() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @string{foo = {Foo}}
+%CUR ^
+%2.1 ^^^
+%SRC @string{bar = {Bar}}
+%SRC @article{baz, author = foo}
+%1.1 ^^^
+"#,
+ ReferenceContext {
+ include_declaration: true,
+ },
+ )
+}
diff --git a/support/texlab/src/tests/text_document/rename.rs b/support/texlab/src/tests/text_document/rename.rs
new file mode 100644
index 0000000000..2bbdfb1513
--- /dev/null
+++ b/support/texlab/src/tests/text_document/rename.rs
@@ -0,0 +1,110 @@
+use std::collections::HashMap;
+
+use lsp_types::{request::Rename, ClientCapabilities, RenameParams, TextEdit, Url, WorkspaceEdit};
+
+use crate::tests::{client::Client, fixture};
+
+fn check(fixture: &str, new_name: &str) {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let mut expected_changes: HashMap<Url, Vec<TextEdit>> = HashMap::new();
+ for ranges in fixture.ranges.values() {
+ expected_changes
+ .entry(client.uri(ranges[&1].name))
+ .or_default()
+ .push(TextEdit::new(ranges[&1].range, new_name.to_string()));
+ }
+
+ let actual_edit = client
+ .request::<Rename>(RenameParams {
+ text_document_position: fixture.cursor.unwrap().into_params(&client),
+ new_name: new_name.to_string(),
+ work_done_progress_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ client.shutdown();
+
+ assert_eq!(actual_edit, WorkspaceEdit::new(expected_changes));
+}
+
+#[test]
+fn command() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \baz
+%CUR ^
+%1.1 ^^^
+%SRC \include{bar.tex}
+
+%TEX bar.tex
+%SRC \baz
+%2.1 ^^^
+"#,
+ "qux",
+ )
+}
+
+#[test]
+fn entry() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = baz}
+%CUR ^
+%1.1 ^^^
+
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \cite{foo}
+%2.1 ^^^
+"#,
+ "qux",
+ )
+}
+
+#[test]
+fn citation() {
+ check(
+ r#"
+%BIB main.bib
+%SRC @article{foo, bar = baz}
+%1.1 ^^^
+
+%TEX main.tex
+%SRC \addbibresource{main.bib}
+%SRC \cite{foo}
+%CUR ^
+%2.1 ^^^
+"#,
+ "qux",
+ )
+}
+
+#[test]
+fn label() {
+ check(
+ r#"
+%TEX foo.tex
+%SRC \label{foo}\include{bar}
+%CUR ^
+%1.1 ^^^
+
+%TEX bar.tex
+%SRC \ref{foo}
+%2.1 ^^^
+
+%TEX baz.tex
+%SRC \ref{foo}
+"#,
+ "bar",
+ )
+}
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_package_ref.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_package_ref.snap
new file mode 100644
index 0000000000..88ba06e91c
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_package_ref.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\acrodef{fpsLabel}[FPS]{Frames per Second}\n%SRC \\ac{f\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "fpsLabel",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fpsLabel"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_empty.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_empty.snap
new file mode 100644
index 0000000000..65259d44a7
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_empty.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\n%SRC \\acrshort{}\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "fpsLabel",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fpsLabel"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_open_brace.snap
new file mode 100644
index 0000000000..f326640993
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_open_brace.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\n%SRC \\acrshort{f\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "fpsLabel",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fpsLabel"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_simple.snap
new file mode 100644
index 0000000000..db7adf0259
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__acronym_ref_simple.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\n%SRC \\acrshort{f}\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "fpsLabel",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fpsLabel"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_empty.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_empty.snap
new file mode 100644
index 0000000000..75a73c5e32
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_empty.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepackage{amsfonts}\n%SRC \\mathbb{}\n%CUR ^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "A",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "A"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "B",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "B"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "C",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "C"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "D",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "D"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "E",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "E"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_open_brace.snap
new file mode 100644
index 0000000000..dd33c0eac3
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_open_brace.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepackage{amsfonts}\n%SRC \\mathbb{\n%CUR ^\n%SRC Test\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "A",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "A"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "B",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "B"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "C",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "C"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "D",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "D"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "E",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "E"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_word.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_word.snap
new file mode 100644
index 0000000000..cf88ba4b2b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__argument_word.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepackage{amsfonts}\n%SRC \\mathbb{A}\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "A",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "A"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__begin_environment_without_snippet_support.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__begin_environment_without_snippet_support.snap
new file mode 100644
index 0000000000..167e764aef
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__begin_environment_without_snippet_support.snap
@@ -0,0 +1,62 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\beg\n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "begin",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "begin"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "begingroup",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "begingroup"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "AtBeginDocument",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "AtBeginDocument"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "AtBeginDvi",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "AtBeginDvi"
+ }
+ },
+ {
+ "detail": "â‹€, built-in",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "bigwedge",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "bigwedge"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation.snap
new file mode 100644
index 0000000000..70453476d5
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation.snap
@@ -0,0 +1,31 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\documentclass{article}\n%SRC \\bibliography{main}\n%SRC \\begin{document}\n%SRC \\cite{\n%CUR ^\n%SRC \\end{document}\n\n%BIB main.bib\n%SRC @article{foo:2019,\n%SRC author = {Foo Bar},\n%SRC title = {Baz Qux},\n%SRC year = {2019},\n%SRC }\n%SRC \n%SRC @article{bar:2005,}\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "filterText": "bar:2005 @article bar:2005",
+ "kind": 1,
+ "label": "bar:2005",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "bar:2005"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "filterText": "foo:2019 @article foo:2019 author Foo Bar title Baz Qux year 2019",
+ "kind": 1,
+ "label": "foo:2019",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "foo:2019"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_acronym.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_acronym.snap
new file mode 100644
index 0000000000..5cf8260317
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_acronym.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\addbibresource{main.bib}\n%SRC \\DeclareAcronym{foo}{cite={}}\n%CUR ^\n\n%BIB main.bib\n%SRC @article{foo,}\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "filterText": "foo @article foo",
+ "kind": 1,
+ "label": "foo",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "foo"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace.snap
new file mode 100644
index 0000000000..3143481442
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\addbibresource{main.bib}\n%SRC \\cite{\n%CUR ^\n\n%BIB main.bib\n%SRC @article{foo,}\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "filterText": "foo @article foo",
+ "kind": 1,
+ "label": "foo",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "foo"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace_multiple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace_multiple.snap
new file mode 100644
index 0000000000..21faa28ecc
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__citation_open_brace_multiple.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\addbibresource{main.bib}\n%SRC \\cite{foo,a\n%CUR ^\n%1.1 ^\n\n%BIB main.bib\n%SRC @article{foo,}\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "filterText": "foo @article foo",
+ "kind": 1,
+ "label": "foo",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "foo"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_open_brace.snap
new file mode 100644
index 0000000000..825d607885
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_open_brace.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\definecolor{foo}{\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "HTML",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "HTML"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "RGB",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "RGB"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "cmyk",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "cmyk"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "gray",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "gray"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "rgb",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "rgb"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_open_brace.snap
new file mode 100644
index 0000000000..27dac7d130
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_open_brace.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\definecolorset{\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "HTML",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "HTML"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "RGB",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "RGB"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "cmyk",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "cmyk"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "gray",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "gray"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "rgb",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "rgb"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_simple.snap
new file mode 100644
index 0000000000..c2a2640786
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_set_simple.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\definecolorset{}\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "HTML",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "HTML"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "RGB",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "RGB"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "cmyk",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "cmyk"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "gray",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "gray"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "rgb",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "rgb"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_simple.snap
new file mode 100644
index 0000000000..da3431986d
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_model_definition_simple.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\definecolor{foo}{}\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "HTML",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "HTML"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "RGB",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "RGB"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "cmyk",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "cmyk"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "gray",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "gray"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "rgb",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "rgb"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_open_brace.snap
new file mode 100644
index 0000000000..f2ae7d8db0
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_open_brace.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\color{\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "Apricot",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Apricot"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Aquamarine",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Aquamarine"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Bittersweet",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Bittersweet"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Black",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Black"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Blue",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Blue"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_simple.snap
new file mode 100644
index 0000000000..fc3db2911b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_simple.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\color{}\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "Apricot",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Apricot"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Aquamarine",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Aquamarine"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Bittersweet",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Bittersweet"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Black",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Black"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Blue",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Blue"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_word.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_word.snap
new file mode 100644
index 0000000000..cd671de9b9
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__color_word.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\color{re}\n%CUR ^\n%1.1 ^^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "red",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "red"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "Red",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "Red"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "RedOrange",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "RedOrange"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "RedViolet",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "RedViolet"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "BrickRed",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "BrickRed"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_bibtex.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_bibtex.snap
new file mode 100644
index 0000000000..76dd1d07f8
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_bibtex.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{b, \n%SRC c = {\\LaT }\n%CUR ^\n%1.1 ^^^\n%SRC }\"#)?"
+---
+[
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "LaTeX",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LaTeX"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "LaTeXe",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LaTeXe"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "latexreleaseversion",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "latexreleaseversion"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "LastDeclaredEncoding",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LastDeclaredEncoding"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "last",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "last"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple.snap
new file mode 100644
index 0000000000..c974de42b7
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple.snap
@@ -0,0 +1,63 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\\n%CUR ^\"#)?"
+---
+[
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "!",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "!"
+ }
+ },
+ {
+ "detail": "built-in",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "\"",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "\""
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "#",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "#"
+ }
+ },
+ {
+ "detail": "$, built-in",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "$",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "$"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "%",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "%"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple_package.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple_package.snap
new file mode 100644
index 0000000000..ada70e293a
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_command_simple_package.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepackage{lipsum}\n%SRC \\lips\n%CUR ^\n%1.1 ^^^^\"#)?"
+---
+[
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "lipsum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lipsum"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "lipsumexp",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lipsumexp"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "LipsumPar",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LipsumPar"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "LipsumProtect",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LipsumProtect"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "LipsumRestoreAll",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LipsumRestoreAll"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_class.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_class.snap
new file mode 100644
index 0000000000..bb409a442b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_class.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\documentclass{article}\n%SRC \\begin{thein}\n%CUR ^\n%1.1 ^^^^^\"#)?"
+---
+[
+ {
+ "detail": "article.cls",
+ "kind": 1,
+ "label": "theindex",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "theindex"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_command_definition.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_command_definition.snap
new file mode 100644
index 0000000000..d319eeb734
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_command_definition.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newcommand{\\foo}{\\begin{doc}\n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "document",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "document"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple.snap
new file mode 100644
index 0000000000..f6b8710db3
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\begin{doc\n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "document",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "document"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple_end.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple_end.snap
new file mode 100644
index 0000000000..e3ffa551d3
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__component_environment_simple_end.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\begin{document}\n%SRC \\end{\n%CUR ^\"#)?"
+---
+[
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "document",
+ "preselect": true,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "document"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "abstract",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "abstract"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "array",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "array"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "center",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "center"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "csname",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "csname"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_preamble.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_preamble.snap
new file mode 100644
index 0000000000..5f29c307a7
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_preamble.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @preamble{\n%CUR ^\n%1.1 ^^^^^^^^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "preamble",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "preamble"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_string.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_string.snap
new file mode 100644
index 0000000000..5b18f17cfe
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_after_string.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @string{\n%CUR ^\n%1.1 ^^^^^^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "string",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "string"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_at_empty.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_at_empty.snap
new file mode 100644
index 0000000000..7928676b92
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_at_empty.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @\n%CUR ^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "article",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "article"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "artwork",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "artwork"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "audio",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "audio"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "bibnote",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "bibnote"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "book",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "book"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_article.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_article.snap
new file mode 100644
index 0000000000..1522a7594a
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_article.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article\n%CUR ^\n%1.1 ^^^^^^^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "article",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "article"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_preamble.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_preamble.snap
new file mode 100644
index 0000000000..50e7a2162d
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_preamble.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @preamble\n%CUR ^\n%1.1 ^^^^^^^^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "preamble",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "preamble"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_string.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_string.snap
new file mode 100644
index 0000000000..0c00ddd5fc
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_before_string.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @string\n%CUR ^\n%1.1 ^^^^^^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "string",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "string"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_complete_entry.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_complete_entry.snap
new file mode 100644
index 0000000000..6afb028f95
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__entry_type_complete_entry.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{foo, author = {foo}}\n%CUR ^\n%1.1 ^^^^^^^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "article",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "article"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_closed.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_closed.snap
new file mode 100644
index 0000000000..86a6e06c89
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_closed.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{foo,}\n%CUR ^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "abstract",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "abstract"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "addendum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "addendum"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "address",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "address"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "afterword",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "afterword"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "annotation",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "annotation"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_open.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_open.snap
new file mode 100644
index 0000000000..75a242d25d
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_empty_entry_open.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{foo,\n%CUR ^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "abstract",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "abstract"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "addendum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "addendum"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "address",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "address"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "afterword",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "afterword"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "annotation",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "annotation"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_field_name.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_field_name.snap
new file mode 100644
index 0000000000..3178da8d07
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_field_name.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{foo, a\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "abstract",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "abstract"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "addendum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "addendum"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "address",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "address"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "afterword",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "afterword"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "annotation",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "annotation"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_closed.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_closed.snap
new file mode 100644
index 0000000000..80595edf98
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_closed.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{foo, author = bar, edit}\n%CUR ^\n%1.1 ^^^^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "edition",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "edition"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editor",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editor"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editora",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editora"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editoratype",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editoratype"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editorb",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editorb"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_open.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_open.snap
new file mode 100644
index 0000000000..8de75313e8
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__field_entry_two_fields_name_open.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%BIB main.bib\n%SRC @article{foo, author = bar, edit\n%CUR ^\n%1.1 ^^^^\"#)?"
+---
+[
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "edition",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "edition"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editor",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editor"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editora",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editora"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editoratype",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editoratype"
+ }
+ },
+ {
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "editorb",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "editorb"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_open_brace.snap
new file mode 100644
index 0000000000..36a77e92e5
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_open_brace.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\n%SRC \\gls{f\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "fpsLabel",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fpsLabel"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_simple.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_simple.snap
new file mode 100644
index 0000000000..7a4d9a584b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__glossary_ref_simple.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\n%SRC \\gls{f}\n%CUR ^\n%1.1 ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "fpsLabel",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fpsLabel"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_closed_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_closed_brace.snap
new file mode 100644
index 0000000000..f346f67f41
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_closed_brace.snap
@@ -0,0 +1,66 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\documentclass{art}\n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "article",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "article"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "articleingud",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "articleingud"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "articoletteracdp",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "articoletteracdp"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "artikel1",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "artikel1"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "artikel2",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "artikel2"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_open_brace.snap
new file mode 100644
index 0000000000..0d236ae496
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_class_open_brace.snap
@@ -0,0 +1,66 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\documentclass{art \\foo\n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "article",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "article"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "articleingud",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "articleingud"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "articoletteracdp",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "articoletteracdp"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "artikel1",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "artikel1"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "artikel2",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "artikel2"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_closed_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_closed_brace.snap
new file mode 100644
index 0000000000..2952c5d401
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_closed_brace.snap
@@ -0,0 +1,66 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepackage{lips}\n%CUR ^\n%1.1 ^^^^\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lips",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lips"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lipsum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lipsum"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lisp-simple-alloc",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lisp-simple-alloc"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lisp-string",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lisp-string"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lwarp-lips",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lwarp-lips"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_open_brace.snap
new file mode 100644
index 0000000000..349830d5f2
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__import_package_open_brace.snap
@@ -0,0 +1,66 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepackage{lips\n%CUR ^\n%1.1 ^^^^\"#)?"
+---
+[
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lips",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lips"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lipsum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lipsum"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lisp-simple-alloc",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lisp-simple-alloc"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lisp-string",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lisp-string"
+ }
+ },
+ {
+ "data": "[data]",
+ "documentation": "[documentation]",
+ "kind": 1,
+ "label": "lwarp-lips",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lwarp-lips"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__label.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__label.snap
new file mode 100644
index 0000000000..a4b26aa41d
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__label.snap
@@ -0,0 +1,67 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX foo.tex\n%SRC \\documentclass{article}\n%SRC \n%SRC \\usepackage{amsmath}\n%SRC \\usepackage{caption}\n%SRC \\usepackage{amsthm}\n%SRC \\newtheorem{lemma}{Lemma}\n%SRC \n%SRC \\begin{document}\n%SRC \n%SRC \\section{Foo}%\n%SRC \\label{sec:foo}\n%SRC \n%SRC \\begin{equation}%\n%SRC \\label{eq:foo}\n%SRC 1 + 1 = 2\n%SRC \\end{equation}\n%SRC \n%SRC \\begin{equation}%\n%SRC \\label{eq:bar}\n%SRC 1 + 1 = 2\n%SRC \\end{equation}\n%SRC \n%SRC \\begin{figure}%\n%SRC \\LaTeX{}\n%SRC \\caption{Baz}%\n%SRC \\label{fig:baz}\n%SRC \\end{figure}\n%SRC \n%SRC \\begin{lemma}%\n%SRC \\label{thm:foo}\n%SRC 1 + 1 = 2\n%SRC \\end{lemma}\n%SRC \n%SRC \\include{bar}\n%SRC \n%SRC \\end{document}\n\n%TEX bar.tex\n%SRC \\section{Bar}%\n%SRC \\label{sec:bar}\n%SRC \n%SRC Lorem ipsum dolor sit amet.\n%SRC \\ref{}\n%CUR ^\n\n%TEX foo.aux\n%SRC \\relax\n%SRC \\@writefile{lof}{\\contentsline {figure}{\\numberline {1}{\\ignorespaces Baz\\relax }}{1}\\protected@file@percent }\n%SRC \\providecommand*\\caption@xref[2]{\\@setref\\relax\\@undefined{#1}}\n%SRC \\newlabel{fig:baz}{{1}{1}}\n%SRC \\@writefile{toc}{\\contentsline {section}{\\numberline {1}Foo}{1}\\protected@file@percent }\n%SRC \\newlabel{sec:foo}{{1}{1}}\n%SRC \\newlabel{eq:foo}{{1}{1}}\n%SRC \\newlabel{eq:bar}{{2}{1}}\n%SRC \\newlabel{thm:foo}{{1}{1}}\n%SRC \\@input{bar.aux}\"#)?"
+---
+[
+ {
+ "detail": "Equation (2)",
+ "filterText": "eq:bar Equation (2)",
+ "kind": 1,
+ "label": "eq:bar",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "eq:bar"
+ }
+ },
+ {
+ "detail": "Equation (1)",
+ "filterText": "eq:foo Equation (1)",
+ "kind": 1,
+ "label": "eq:foo",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "eq:foo"
+ }
+ },
+ {
+ "detail": "Figure 1",
+ "documentation": "[documentation]",
+ "filterText": "fig:baz Figure 1: Baz",
+ "kind": 1,
+ "label": "fig:baz",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "fig:baz"
+ }
+ },
+ {
+ "detail": "Section (Bar)",
+ "filterText": "sec:bar Section (Bar)",
+ "kind": 1,
+ "label": "sec:bar",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "sec:bar"
+ }
+ },
+ {
+ "detail": "Section 1 (Foo)",
+ "filterText": "sec:foo Section 1 (Foo)",
+ "kind": 1,
+ "label": "sec:foo",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "sec:foo"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__project_resolution.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__project_resolution.snap
new file mode 100644
index 0000000000..5ee9c3c207
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__project_resolution.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\documentclass{article}\n%SRC \\import{sub}{sub/sub.tex}\n%SRC \\lipsu\n%CUR ^\n%1.1 ^^^^^\n\n%TEX sub/sub.tex\n%SRC \\input{child.tex}\n\n%TEX sub/child.tex\n%SRC \\usepackage{lipsum}\n\n\"#)"
+---
+[
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "lipsum",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lipsum"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "lipsumexp",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lipsumexp"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "LipsumPar",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LipsumPar"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "LipsumProtect",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LipsumProtect"
+ }
+ },
+ {
+ "detail": "lipsum.sty",
+ "kind": 1,
+ "label": "LipsumRestoreAll",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "LipsumRestoreAll"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_begin.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_begin.snap
new file mode 100644
index 0000000000..a4de708b1b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_begin.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newtheorem{lemma}{Lemma}\n%SRC \\begin{lem \n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "detail": "user-defined",
+ "kind": 1,
+ "label": "lemma",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lemma"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_end.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_end.snap
new file mode 100644
index 0000000000..0c2c20083e
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__theorem_end.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\newtheorem{lemma}{Lemma}\n%SRC \\begin{}\n%SRC \\end{lem\n%CUR ^\n%1.1 ^^^\"#)?"
+---
+[
+ {
+ "detail": "user-defined",
+ "kind": 1,
+ "label": "lemma",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "lemma"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_closed_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_closed_brace.snap
new file mode 100644
index 0000000000..f2a00d22bb
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_closed_brace.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepgflibrary{}\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "arrows",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "arrows"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "arrows.meta",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "arrows.meta"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "arrows.spaced",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "arrows.spaced"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "curvilinear",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "curvilinear"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "datavisualization.barcharts",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "datavisualization.barcharts"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_open_brace.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_open_brace.snap
new file mode 100644
index 0000000000..62bb3729b7
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__tikz_library_open_brace.snap
@@ -0,0 +1,56 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\usepgflibrary{\n%CUR ^\"#)?"
+---
+[
+ {
+ "kind": 1,
+ "label": "arrows",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "arrows"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "arrows.meta",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "arrows.meta"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "arrows.spaced",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "arrows.spaced"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "curvilinear",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "curvilinear"
+ }
+ },
+ {
+ "kind": 1,
+ "label": "datavisualization.barcharts",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "datavisualization.barcharts"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_command.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_command.snap
new file mode 100644
index 0000000000..fe0a397706
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_command.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\foobar\n%SRC \\fooba\n%CUR ^\n%1.1 ^^^^^\n%SRC \\begin{foo}\n%SRC \\end{foo}\n%SRC \\begin{fo}\n\"#)?"
+---
+[
+ {
+ "detail": "user-defined",
+ "kind": 1,
+ "label": "foobar",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "foobar"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_environment.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_environment.snap
new file mode 100644
index 0000000000..7230e9a951
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__completion__user_environment.snap
@@ -0,0 +1,28 @@
+---
+source: src/tests/text_document/completion.rs
+expression: "complete(r#\"\n%TEX main.tex\n%SRC \\foobar\n%SRC \\fooba\n%SRC \\begin{foo}\n%SRC \\end{foo}\n%SRC \\begin{fo}\n%CUR ^\n%1.1 ^^\n\"#)?"
+---
+[
+ {
+ "detail": "user-defined",
+ "kind": 1,
+ "label": "foo",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "foo"
+ }
+ },
+ {
+ "detail": "built-in",
+ "kind": 1,
+ "label": "filecontents",
+ "preselect": false,
+ "sortText": "[sortText]",
+ "textEdit": {
+ "range": "[range]",
+ "newText": "filecontents"
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__did_change_configuration__invalid_configuration.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__did_change_configuration__invalid_configuration.snap
new file mode 100644
index 0000000000..1400d80167
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__did_change_configuration__invalid_configuration.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/text_document/did_change_configuration.rs
+expression: message
+---
+The texlab configuration is invalid; using the default settings instead.
+Details: regex parse error:
+ \
+ ^
+error: incomplete escape sequence, reached end of pattern prematurely
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_flat.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_flat.snap
new file mode 100644
index 0000000000..4c5a17fb98
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_flat.snap
@@ -0,0 +1,96 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Enumerate",
+ "kind": 10,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 9,
+ "character": 15
+ }
+ }
+ }
+ },
+ {
+ "name": "Item",
+ "kind": 22,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 5,
+ "character": 4
+ },
+ "end": {
+ "line": 5,
+ "character": 27
+ }
+ }
+ }
+ },
+ {
+ "name": "Item",
+ "kind": 22,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 6,
+ "character": 4
+ },
+ "end": {
+ "line": 6,
+ "character": 27
+ }
+ }
+ }
+ },
+ {
+ "name": "Baz",
+ "kind": 22,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 7,
+ "character": 4
+ },
+ "end": {
+ "line": 7,
+ "character": 18
+ }
+ }
+ }
+ },
+ {
+ "name": "Qux",
+ "kind": 22,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 8,
+ "character": 4
+ },
+ "end": {
+ "line": 8,
+ "character": 32
+ }
+ }
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_nested.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_nested.snap
new file mode 100644
index 0000000000..630311f32e
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__enumerate_nested.snap
@@ -0,0 +1,140 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Enumerate",
+ "kind": 10,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 9,
+ "character": 15
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 9,
+ "character": 15
+ }
+ },
+ "children": [
+ {
+ "name": "Item",
+ "detail": "it:foo",
+ "kind": 22,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 5,
+ "character": 4
+ },
+ "end": {
+ "line": 5,
+ "character": 27
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 5,
+ "character": 9
+ },
+ "end": {
+ "line": 5,
+ "character": 23
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Item",
+ "detail": "it:bar",
+ "kind": 22,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 6,
+ "character": 4
+ },
+ "end": {
+ "line": 6,
+ "character": 27
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 6,
+ "character": 9
+ },
+ "end": {
+ "line": 6,
+ "character": 23
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Baz",
+ "kind": 22,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 7,
+ "character": 4
+ },
+ "end": {
+ "line": 7,
+ "character": 18
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 7,
+ "character": 4
+ },
+ "end": {
+ "line": 7,
+ "character": 18
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Qux",
+ "detail": "it:qux",
+ "kind": 22,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 8,
+ "character": 4
+ },
+ "end": {
+ "line": 8,
+ "character": 32
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 8,
+ "character": 14
+ },
+ "end": {
+ "line": 8,
+ "character": 28
+ }
+ },
+ "children": []
+ }
+ ]
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_flat.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_flat.snap
new file mode 100644
index 0000000000..24ab44c772
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_flat.snap
@@ -0,0 +1,60 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Equation",
+ "kind": 14,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 6,
+ "character": 14
+ }
+ }
+ }
+ },
+ {
+ "name": "Equation",
+ "kind": 14,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 8,
+ "character": 0
+ },
+ "end": {
+ "line": 10,
+ "character": 14
+ }
+ }
+ }
+ },
+ {
+ "name": "Equation",
+ "kind": 14,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 12,
+ "character": 0
+ },
+ "end": {
+ "line": 14,
+ "character": 14
+ }
+ }
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_nested.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_nested.snap
new file mode 100644
index 0000000000..2efe7a4796
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__equation_nested.snap
@@ -0,0 +1,86 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Equation",
+ "detail": "eq:foo",
+ "kind": 14,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 6,
+ "character": 14
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 4,
+ "character": 16
+ },
+ "end": {
+ "line": 4,
+ "character": 30
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Equation",
+ "detail": "eq:bar",
+ "kind": 14,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 8,
+ "character": 0
+ },
+ "end": {
+ "line": 10,
+ "character": 14
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 8,
+ "character": 16
+ },
+ "end": {
+ "line": 8,
+ "character": 30
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Equation",
+ "kind": 14,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 12,
+ "character": 0
+ },
+ "end": {
+ "line": 14,
+ "character": 14
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 12,
+ "character": 0
+ },
+ "end": {
+ "line": 14,
+ "character": 14
+ }
+ },
+ "children": []
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_flat.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_flat.snap
new file mode 100644
index 0000000000..3d2537d85b
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_flat.snap
@@ -0,0 +1,60 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Figure: Foo",
+ "kind": 6,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 7,
+ "character": 12
+ }
+ }
+ }
+ },
+ {
+ "name": "Figure: Bar",
+ "kind": 6,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 9,
+ "character": 0
+ },
+ "end": {
+ "line": 12,
+ "character": 12
+ }
+ }
+ }
+ },
+ {
+ "name": "Figure: Baz",
+ "kind": 6,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 14,
+ "character": 0
+ },
+ "end": {
+ "line": 17,
+ "character": 12
+ }
+ }
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_nested.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_nested.snap
new file mode 100644
index 0000000000..18c5f3ca93
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__float_nested.snap
@@ -0,0 +1,86 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Figure: Foo",
+ "detail": "fig:foo",
+ "kind": 6,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 7,
+ "character": 12
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 6,
+ "character": 17
+ },
+ "end": {
+ "line": 6,
+ "character": 32
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Figure: Bar",
+ "detail": "fig:bar",
+ "kind": 6,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 9,
+ "character": 0
+ },
+ "end": {
+ "line": 12,
+ "character": 12
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 11,
+ "character": 17
+ },
+ "end": {
+ "line": 11,
+ "character": 32
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Figure: Baz",
+ "kind": 6,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 14,
+ "character": 0
+ },
+ "end": {
+ "line": 17,
+ "character": 12
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 14,
+ "character": 0
+ },
+ "end": {
+ "line": 17,
+ "character": 12
+ }
+ },
+ "children": []
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_flat.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_flat.snap
new file mode 100644
index 0000000000..87ff442db1
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_flat.snap
@@ -0,0 +1,60 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Foo",
+ "kind": 2,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 4,
+ "character": 13
+ }
+ }
+ }
+ },
+ {
+ "name": "Bar",
+ "kind": 2,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 6,
+ "character": 0
+ },
+ "end": {
+ "line": 8,
+ "character": 31
+ }
+ }
+ }
+ },
+ {
+ "name": "Baz",
+ "kind": 2,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 8,
+ "character": 0
+ },
+ "end": {
+ "line": 8,
+ "character": 31
+ }
+ }
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_nested.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_nested.snap
new file mode 100644
index 0000000000..bf4fea547e
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__section_nested.snap
@@ -0,0 +1,87 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Foo",
+ "kind": 2,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 4,
+ "character": 13
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 4,
+ "character": 0
+ },
+ "end": {
+ "line": 4,
+ "character": 13
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Bar",
+ "detail": "sec:bar",
+ "kind": 2,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 6,
+ "character": 0
+ },
+ "end": {
+ "line": 8,
+ "character": 31
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 6,
+ "character": 13
+ },
+ "end": {
+ "line": 6,
+ "character": 28
+ }
+ },
+ "children": [
+ {
+ "name": "Baz",
+ "detail": "sec:baz",
+ "kind": 2,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 8,
+ "character": 0
+ },
+ "end": {
+ "line": 8,
+ "character": 31
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 8,
+ "character": 16
+ },
+ "end": {
+ "line": 8,
+ "character": 31
+ }
+ },
+ "children": []
+ }
+ ]
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_flat.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_flat.snap
new file mode 100644
index 0000000000..d033c3ca62
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_flat.snap
@@ -0,0 +1,78 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Lemma (Foo)",
+ "kind": 13,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 6,
+ "character": 0
+ },
+ "end": {
+ "line": 8,
+ "character": 11
+ }
+ }
+ }
+ },
+ {
+ "name": "Lemma",
+ "kind": 13,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 10,
+ "character": 0
+ },
+ "end": {
+ "line": 12,
+ "character": 11
+ }
+ }
+ }
+ },
+ {
+ "name": "Lemma",
+ "kind": 13,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 14,
+ "character": 0
+ },
+ "end": {
+ "line": 16,
+ "character": 11
+ }
+ }
+ }
+ },
+ {
+ "name": "Lemma (Qux)",
+ "kind": 13,
+ "deprecated": false,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 18,
+ "character": 0
+ },
+ "end": {
+ "line": 20,
+ "character": 11
+ }
+ }
+ }
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_nested.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_nested.snap
new file mode 100644
index 0000000000..22844dd700
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__document_symbol__theorem_nested.snap
@@ -0,0 +1,113 @@
+---
+source: src/tests/text_document/document_symbol.rs
+expression: result.response
+---
+[
+ {
+ "name": "Lemma (Foo)",
+ "detail": "thm:foo",
+ "kind": 13,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 6,
+ "character": 0
+ },
+ "end": {
+ "line": 8,
+ "character": 11
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 6,
+ "character": 18
+ },
+ "end": {
+ "line": 6,
+ "character": 33
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Lemma",
+ "detail": "thm:bar",
+ "kind": 13,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 10,
+ "character": 0
+ },
+ "end": {
+ "line": 12,
+ "character": 11
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 10,
+ "character": 13
+ },
+ "end": {
+ "line": 10,
+ "character": 28
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Lemma",
+ "detail": "thm:baz",
+ "kind": 13,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 14,
+ "character": 0
+ },
+ "end": {
+ "line": 16,
+ "character": 11
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 14,
+ "character": 13
+ },
+ "end": {
+ "line": 14,
+ "character": 28
+ }
+ },
+ "children": []
+ },
+ {
+ "name": "Lemma (Qux)",
+ "kind": 13,
+ "deprecated": false,
+ "range": {
+ "start": {
+ "line": 18,
+ "character": 0
+ },
+ "end": {
+ "line": 20,
+ "character": 11
+ }
+ },
+ "selectionRange": {
+ "start": {
+ "line": 18,
+ "character": 0
+ },
+ "end": {
+ "line": 20,
+ "character": 11
+ }
+ },
+ "children": []
+ }
+]
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_command.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_command.snap
new file mode 100644
index 0000000000..c7d1491245
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_command.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article{foo, bar = \"\\baz\",}\"#)?"
+---
+@article{foo,
+ bar = "\baz",
+}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_insert_braces.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_insert_braces.snap
new file mode 100644
index 0000000000..d05b077188
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_insert_braces.snap
@@ -0,0 +1,7 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article{foo, bar = baz,\"#)?"
+---
+@article{foo,
+ bar = baz,
+}
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_join_strings.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_join_strings.snap
new file mode 100644
index 0000000000..8fb3765129
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_join_strings.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article{foo, bar = \"baz\" # \"qux\"}\"#)?"
+---
+@article{foo,
+ bar = "baz" # "qux",
+}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_multiple_entries.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_multiple_entries.snap
new file mode 100644
index 0000000000..2496f9b5e5
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_multiple_entries.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, \n%SRC consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}\n%SRC \n%SRC @article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, \n%SRC consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}\"\"#)?"
+---
+@article{foo,
+ bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum
+ dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit
+ amet, consectetur adipiscing elit.},
+}
+
+@article{foo,
+ bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum
+ dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit
+ amet, consectetur adipiscing elit.},
+}"
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_parens.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_parens.snap
new file mode 100644
index 0000000000..883b97b375
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_parens.snap
@@ -0,0 +1,7 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article(foo,)\"#)?"
+---
+@article{foo,
+}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_preamble.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_preamble.snap
new file mode 100644
index 0000000000..fdac219c35
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_preamble.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @preamble{\n%SRC \"foo bar baz\" }\"#)?"
+---
+@preamble{"foo bar baz"}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_string.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_string.snap
new file mode 100644
index 0000000000..8f9589773e
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_string.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @string{foo=\"bar\"}\"#)?"
+---
+@string{foo = "bar"}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_trailing_comma.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_trailing_comma.snap
new file mode 100644
index 0000000000..1051e00cf3
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_trailing_comma.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article{foo, bar = baz}\"#)?"
+---
+@article{foo,
+ bar = baz,
+}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_wrap_long_lines.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_wrap_long_lines.snap
new file mode 100644
index 0000000000..e7e1f39386
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__formatting__bibtex_internal_wrap_long_lines.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/text_document/formatting.rs
+expression: "format(r#\"\n%BIB main.bib\n%SRC @article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n%SRC Lorem ipsum dolor sit amet,\n%SRC consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}\"#)?"
+---
+@article{foo,
+ bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum
+ dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit
+ amet, consectetur adipiscing elit.},
+}
+
diff --git a/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__inlay_hint__label_definition.snap b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__inlay_hint__label_definition.snap
new file mode 100644
index 0000000000..ff04d65efb
--- /dev/null
+++ b/support/texlab/src/tests/text_document/snapshots/texlab__tests__text_document__inlay_hint__label_definition.snap
@@ -0,0 +1,38 @@
+---
+source: src/tests/text_document/inlay_hint.rs
+expression: "check(r#\"\n%TEX main.tex\n%SRC \\documentclass{article}\n%SRC \\usepackage{caption}\n%SRC \\begin{document}\n%SRC \\section{Foo}\\label{sec:foo}\n%SRC \\section{Bar}\\label{sec:bar}\n%SRC \\subsection{Baz}\\label{sec:baz}\n%SRC \\begin{figure}\n%SRC Test\n%SRC \\label{fig:qux}\n%SRC \\caption{Qux}\n%SRC \\end{figure}\n%SRC \\end{document}\n\n%TEX main.aux\n%SRC \\relax \n%SRC \\providecommand*\\caption@xref[2]{\\@setref\\relax\\@undefined{#1}}\n%SRC \\newlabel{fig:qux}{{\\caption@xref {fig:qux}{ on input line 15}}{1}}\n%SRC \\@writefile{lof}{\\contentsline {figure}{\\numberline {1}{\\ignorespaces Qux\\relax }}{1}{}\\protected@file@percent }\n%SRC \\@writefile{toc}{\\contentsline {section}{\\numberline {1}Foo}{1}{}\\protected@file@percent }\n%SRC \\newlabel{sec:foo}{{1}{1}}\n%SRC \\@writefile{toc}{\\contentsline {section}{\\numberline {2}Bar}{1}{}\\protected@file@percent }\n%SRC \\newlabel{sec:bar}{{2}{1}}\n%SRC \\@writefile{toc}{\\contentsline {subsection}{\\numberline {2.1}Baz}{1}{}\\protected@file@percent }\n%SRC \\newlabel{sec:baz}{{2.1}{1}}\n%SRC \\gdef \\@abspage@last{1}\n\"#)?"
+---
+[
+ {
+ "position": {
+ "line": 3,
+ "character": 27
+ },
+ "label": "Section 1 (Foo)",
+ "paddingLeft": true
+ },
+ {
+ "position": {
+ "line": 4,
+ "character": 27
+ },
+ "label": "Section 2 (Bar)",
+ "paddingLeft": true
+ },
+ {
+ "position": {
+ "line": 5,
+ "character": 30
+ },
+ "label": "Subsection 2.1 (Baz)",
+ "paddingLeft": true
+ },
+ {
+ "position": {
+ "line": 8,
+ "character": 18
+ },
+ "label": "Figure fig:qux: Qux",
+ "paddingLeft": true
+ }
+]
diff --git a/support/texlab/src/tests/workspace.rs b/support/texlab/src/tests/workspace.rs
new file mode 100644
index 0000000000..15e0679dea
--- /dev/null
+++ b/support/texlab/src/tests/workspace.rs
@@ -0,0 +1 @@
+mod symbol;
diff --git a/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_bibtex.snap b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_bibtex.snap
new file mode 100644
index 0000000000..16963641bf
--- /dev/null
+++ b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_bibtex.snap
@@ -0,0 +1,42 @@
+---
+source: src/tests/workspace/symbol.rs
+expression: result.actual_symbols
+---
+[
+ {
+ "deprecated": false,
+ "kind": 24,
+ "location": {
+ "uri": "[tmp]/main.bib",
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 0
+ },
+ "end": {
+ "line": 0,
+ "character": 14
+ }
+ }
+ },
+ "name": "foo"
+ },
+ {
+ "deprecated": false,
+ "kind": 15,
+ "location": {
+ "uri": "[tmp]/main.bib",
+ "range": {
+ "start": {
+ "line": 2,
+ "character": 0
+ },
+ "end": {
+ "line": 2,
+ "character": 20
+ }
+ }
+ },
+ "name": "bar"
+ }
+]
diff --git a/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_figure.snap b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_figure.snap
new file mode 100644
index 0000000000..1b1d2371a4
--- /dev/null
+++ b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_figure.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/workspace/symbol.rs
+expression: result.actual_symbols
+---
+[
+ {
+ "deprecated": false,
+ "kind": 6,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 15,
+ "character": 0
+ },
+ "end": {
+ "line": 19,
+ "character": 12
+ }
+ }
+ },
+ "name": "Figure 1: Bar"
+ }
+]
diff --git a/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_item.snap b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_item.snap
new file mode 100644
index 0000000000..ed45c13cf1
--- /dev/null
+++ b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_item.snap
@@ -0,0 +1,60 @@
+---
+source: src/tests/workspace/symbol.rs
+expression: result.actual_symbols
+---
+[
+ {
+ "deprecated": false,
+ "kind": 22,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 24,
+ "character": 4
+ },
+ "end": {
+ "line": 24,
+ "character": 28
+ }
+ }
+ },
+ "name": "1"
+ },
+ {
+ "deprecated": false,
+ "kind": 22,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 25,
+ "character": 4
+ },
+ "end": {
+ "line": 25,
+ "character": 28
+ }
+ }
+ },
+ "name": "2"
+ },
+ {
+ "deprecated": false,
+ "kind": 22,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 26,
+ "character": 4
+ },
+ "end": {
+ "line": 26,
+ "character": 28
+ }
+ }
+ },
+ "name": "3"
+ }
+]
diff --git a/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_math.snap b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_math.snap
new file mode 100644
index 0000000000..d4cea99cc5
--- /dev/null
+++ b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_math.snap
@@ -0,0 +1,42 @@
+---
+source: src/tests/workspace/symbol.rs
+expression: result.actual_symbols
+---
+[
+ {
+ "deprecated": false,
+ "kind": 14,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 9,
+ "character": 0
+ },
+ "end": {
+ "line": 11,
+ "character": 14
+ }
+ }
+ },
+ "name": "Equation (1)"
+ },
+ {
+ "deprecated": false,
+ "kind": 13,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 33,
+ "character": 0
+ },
+ "end": {
+ "line": 35,
+ "character": 11
+ }
+ }
+ },
+ "name": "Lemma 1 (Qux)"
+ }
+]
diff --git a/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_section.snap b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_section.snap
new file mode 100644
index 0000000000..3a3ef78ab2
--- /dev/null
+++ b/support/texlab/src/tests/workspace/snapshots/texlab__tests__workspace__symbol__filter_type_section.snap
@@ -0,0 +1,78 @@
+---
+source: src/tests/workspace/symbol.rs
+expression: result.actual_symbols
+---
+[
+ {
+ "deprecated": false,
+ "kind": 2,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 7,
+ "character": 0
+ },
+ "end": {
+ "line": 11,
+ "character": 14
+ }
+ }
+ },
+ "name": "1 Foo"
+ },
+ {
+ "deprecated": false,
+ "kind": 2,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 13,
+ "character": 0
+ },
+ "end": {
+ "line": 19,
+ "character": 12
+ }
+ }
+ },
+ "name": "2 Bar"
+ },
+ {
+ "deprecated": false,
+ "kind": 2,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 21,
+ "character": 0
+ },
+ "end": {
+ "line": 27,
+ "character": 15
+ }
+ }
+ },
+ "name": "3 Baz"
+ },
+ {
+ "deprecated": false,
+ "kind": 2,
+ "location": {
+ "uri": "[tmp]/main.tex",
+ "range": {
+ "start": {
+ "line": 29,
+ "character": 0
+ },
+ "end": {
+ "line": 35,
+ "character": 11
+ }
+ }
+ },
+ "name": "4 Qux"
+ }
+]
diff --git a/support/texlab/src/tests/workspace/symbol.rs b/support/texlab/src/tests/workspace/symbol.rs
new file mode 100644
index 0000000000..626cce3c0b
--- /dev/null
+++ b/support/texlab/src/tests/workspace/symbol.rs
@@ -0,0 +1,142 @@
+use insta::{assert_json_snapshot, internals::Redaction};
+use lsp_types::{
+ request::WorkspaceSymbol, ClientCapabilities, SymbolInformation, Url, WorkspaceSymbolParams,
+};
+
+use crate::tests::{client::Client, fixture};
+
+struct SymbolResult {
+ actual_symbols: Vec<SymbolInformation>,
+ uri_redaction: Redaction,
+}
+
+fn find_symbols(fixture: &str, query: &str) -> SymbolResult {
+ let mut client = Client::spawn();
+ client.initialize(ClientCapabilities::default(), None);
+
+ let fixture = fixture::parse(fixture);
+ for file in fixture.files {
+ client.open(file.name, file.lang, file.text);
+ }
+
+ let actual_symbols = client
+ .request::<WorkspaceSymbol>(WorkspaceSymbolParams {
+ query: query.to_string(),
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ })
+ .unwrap()
+ .unwrap_or_default();
+
+ let result = client.shutdown();
+
+ let uri = Url::from_directory_path(result.directory.path()).unwrap();
+ let uri_redaction = insta::dynamic_redaction(move |content, _path| {
+ content.as_str().unwrap().replace(uri.as_str(), "[tmp]/")
+ });
+
+ SymbolResult {
+ actual_symbols,
+ uri_redaction,
+ }
+}
+
+macro_rules! assert_symbols {
+ ($result:expr) => {
+ let result = $result;
+ assert_json_snapshot!(result.actual_symbols, {
+ "[].location.uri" => result.uri_redaction,
+ "[]" => insta::sorted_redaction()
+ });
+ };
+}
+
+const FIXTURE: &str = r#"
+%TEX main.tex
+%SRC \documentclass{article}
+%SRC \usepackage{caption}
+%SRC \usepackage{amsmath}
+%SRC \usepackage{amsthm}
+%SRC
+%SRC \begin{document}
+%SRC
+%SRC \section{Foo}\label{sec:foo}
+%SRC
+%SRC \begin{equation}\label{eq:foo}
+%SRC Foo
+%SRC \end{equation}
+%SRC
+%SRC \section{Bar}\label{sec:bar}
+%SRC
+%SRC \begin{figure}
+%SRC Bar
+%SRC \caption{Bar}
+%SRC \label{fig:bar}
+%SRC \end{figure}
+%SRC
+%SRC \section{Baz}\label{sec:baz}
+%SRC
+%SRC \begin{enumerate}
+%SRC \item\label{itm:foo} Foo
+%SRC \item\label{itm:bar} Bar
+%SRC \item\label{itm:baz} Baz
+%SRC \end{enumerate}
+%SRC
+%SRC \section{Qux}\label{sec:qux}
+%SRC
+%SRC \newtheorem{lemma}{Lemma}
+%SRC
+%SRC \begin{lemma}[Qux]\label{thm:qux}
+%SRC Qux
+%SRC \end{lemma}
+%SRC
+%SRC \end{document}
+
+%TEX main.aux
+%SRC \relax
+%SRC \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Bar\relax }}{1}\protected@file@percent }
+%SRC \providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
+%SRC \newlabel{fig:bar}{{1}{1}}
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {1}Foo}{1}\protected@file@percent }
+%SRC \newlabel{sec:foo}{{1}{1}}
+%SRC \newlabel{eq:foo}{{1}{1}}
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {2}Bar}{1}\protected@file@percent }
+%SRC \newlabel{sec:bar}{{2}{1}}
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {3}Baz}{1}\protected@file@percent }
+%SRC \newlabel{sec:baz}{{3}{1}}
+%SRC \newlabel{itm:foo}{{1}{1}}
+%SRC \newlabel{itm:bar}{{2}{1}}
+%SRC \newlabel{itm:baz}{{3}{1}}
+%SRC \@writefile{toc}{\contentsline {section}{\numberline {4}Qux}{1}\protected@file@percent }
+%SRC \newlabel{sec:qux}{{4}{1}}
+%SRC \newlabel{thm:qux}{{1}{1}}
+
+%BIB main.bib
+%SRC @article{foo,}
+%SRC
+%SRC @string{bar = "bar"}"#;
+
+#[test]
+fn filter_type_section() {
+ assert_symbols!(find_symbols(FIXTURE, "section"));
+}
+
+#[test]
+fn filter_type_figure() {
+ assert_symbols!(find_symbols(FIXTURE, "figure"));
+}
+
+#[test]
+fn filter_type_item() {
+ assert_symbols!(find_symbols(FIXTURE, "item"));
+}
+
+#[test]
+fn filter_type_math() {
+ assert_symbols!(find_symbols(FIXTURE, "math"));
+}
+
+#[test]
+fn filter_bibtex() {
+ assert_symbols!(find_symbols(FIXTURE, "bibtex"));
+}
diff --git a/support/texlab/src/util.rs b/support/texlab/src/util.rs
new file mode 100644
index 0000000000..1c4d55a19d
--- /dev/null
+++ b/support/texlab/src/util.rs
@@ -0,0 +1,15 @@
+pub mod capabilities;
+pub mod chktex;
+pub mod components;
+pub mod cursor;
+pub mod label;
+pub mod lang_data;
+pub mod line_index;
+pub mod line_index_ext;
+pub mod lsp_enums;
+
+use std::path::PathBuf;
+
+use once_cell::sync::Lazy;
+
+pub static HOME_DIR: Lazy<Option<PathBuf>> = Lazy::new(|| dirs::home_dir());
diff --git a/support/texlab/src/capabilities.rs b/support/texlab/src/util/capabilities.rs
index bcb5bbba8b..987c0b48dd 100644
--- a/support/texlab/src/capabilities.rs
+++ b/support/texlab/src/util/capabilities.rs
@@ -7,6 +7,8 @@ pub trait ClientCapabilitiesExt {
fn has_work_done_progress_support(&self) -> bool;
+ fn has_completion_markdown_support(&self) -> bool;
+
fn has_hover_markdown_support(&self) -> bool;
fn has_pull_configuration_support(&self) -> bool;
@@ -14,6 +16,8 @@ pub trait ClientCapabilitiesExt {
fn has_push_configuration_support(&self) -> bool;
fn has_file_watching_support(&self) -> bool;
+
+ fn has_snippet_support(&self) -> bool;
}
impl ClientCapabilitiesExt for ClientCapabilities {
@@ -37,13 +41,21 @@ impl ClientCapabilitiesExt for ClientCapabilities {
self.window.as_ref().and_then(|cap| cap.work_done_progress) == Some(true)
}
+ fn has_completion_markdown_support(&self) -> bool {
+ self.text_document
+ .as_ref()
+ .and_then(|cap| cap.completion.as_ref())
+ .and_then(|cap| cap.completion_item.as_ref())
+ .and_then(|cap| cap.documentation_format.as_ref())
+ .map_or(false, |formats| formats.contains(&MarkupKind::Markdown))
+ }
+
fn has_hover_markdown_support(&self) -> bool {
self.text_document
.as_ref()
.and_then(|cap| cap.hover.as_ref())
.and_then(|cap| cap.content_format.as_ref())
- .filter(|formats| formats.contains(&MarkupKind::Markdown))
- .is_some()
+ .map_or(false, |formats| formats.contains(&MarkupKind::Markdown))
}
fn has_pull_configuration_support(&self) -> bool {
@@ -65,6 +77,15 @@ impl ClientCapabilitiesExt for ClientCapabilities {
.and_then(|cap| cap.dynamic_registration)
== Some(true)
}
+
+ fn has_snippet_support(&self) -> bool {
+ self.text_document
+ .as_ref()
+ .and_then(|cap| cap.completion.as_ref())
+ .and_then(|cap| cap.completion_item.as_ref())
+ .and_then(|cap| cap.snippet_support)
+ == Some(true)
+ }
}
#[cfg(test)]
diff --git a/support/texlab/src/util/chktex.rs b/support/texlab/src/util/chktex.rs
new file mode 100644
index 0000000000..29a7ac5630
--- /dev/null
+++ b/support/texlab/src/util/chktex.rs
@@ -0,0 +1,112 @@
+use std::{
+ io::{BufRead, BufReader, Write},
+ path::PathBuf,
+ process::Stdio,
+};
+
+use encoding_rs_io::DecodeReaderBytesBuilder;
+use lsp_types::{DiagnosticSeverity, Position, Range};
+use once_cell::sync::Lazy;
+use regex::Regex;
+
+use crate::{
+ db::{
+ diagnostics::{Diagnostic, DiagnosticCode},
+ Document, Workspace,
+ },
+ Db,
+};
+
+#[derive(Debug)]
+pub struct Command {
+ text: String,
+ working_dir: PathBuf,
+}
+
+impl Command {
+ pub fn new(db: &dyn Db, document: Document) -> Option<Self> {
+ document.parse(db).as_tex()?;
+
+ let workspace = Workspace::get(db);
+ let parent = workspace
+ .parents(db, document)
+ .iter()
+ .next()
+ .map_or(document, Clone::clone);
+
+ let working_dir = workspace
+ .working_dir(db, parent.directory(db))
+ .path(db)
+ .as_deref()?
+ .to_owned();
+
+ log::debug!("Calling ChkTeX from directory: {}", working_dir.display());
+
+ let text = document.contents(db).text(db).clone();
+
+ Some(Self { text, working_dir })
+ }
+
+ pub fn run(self) -> std::io::Result<Vec<Diagnostic>> {
+ let mut child = std::process::Command::new("chktex")
+ .args(&["-I0", "-f%l:%c:%d:%k:%n:%m\n"])
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::null())
+ .current_dir(self.working_dir)
+ .spawn()?;
+
+ let stdout = child.stdout.take().unwrap();
+ let reader = std::thread::spawn(move || {
+ let mut diagnostics = Vec::new();
+ let reader = BufReader::new(
+ DecodeReaderBytesBuilder::new()
+ .encoding(Some(encoding_rs::UTF_8))
+ .utf8_passthru(true)
+ .strip_bom(true)
+ .build(stdout),
+ );
+
+ for line in reader.lines().flatten() {
+ let captures = LINE_REGEX.captures(&line).unwrap();
+ let line = captures[1].parse::<u32>().unwrap() - 1;
+ let character = captures[2].parse::<u32>().unwrap() - 1;
+ let digit = captures[3].parse::<u32>().unwrap();
+ let kind = &captures[4];
+ let code = &captures[5];
+ let message = captures[6].into();
+ let range = Range::new(
+ Position::new(line, character),
+ Position::new(line, character + digit),
+ );
+
+ let severity = match kind {
+ "Message" => DiagnosticSeverity::INFORMATION,
+ "Warning" => DiagnosticSeverity::WARNING,
+ _ => DiagnosticSeverity::ERROR,
+ };
+
+ diagnostics.push(Diagnostic {
+ range,
+ severity,
+ code: DiagnosticCode::Chktex(code.into()),
+ message,
+ });
+ }
+
+ diagnostics
+ });
+
+ let mut stdin = child.stdin.take().unwrap();
+ let bytes = self.text.into_bytes();
+ let writer = std::thread::spawn(move || stdin.write_all(&bytes));
+
+ child.wait()?;
+ writer.join().unwrap()?;
+ let diagnostics = reader.join().unwrap();
+ Ok(diagnostics)
+ }
+}
+
+static LINE_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap());
diff --git a/support/texlab/src/util/components.rs b/support/texlab/src/util/components.rs
new file mode 100644
index 0000000000..022e7efbc8
--- /dev/null
+++ b/support/texlab/src/util/components.rs
@@ -0,0 +1,120 @@
+use std::io::Read;
+
+use flate2::read::GzDecoder;
+use itertools::Itertools;
+use lsp_types::{MarkupContent, MarkupKind};
+use once_cell::sync::Lazy;
+use serde::Deserialize;
+use smol_str::SmolStr;
+
+use crate::{
+ db::{analysis::TexLinkKind, Document, Workspace},
+ Db,
+};
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ComponentDatabase {
+ pub components: Vec<Component>,
+ pub metadata: Vec<ComponentMetadata>,
+}
+
+impl ComponentDatabase {
+ pub fn find(&self, name: &str) -> Option<&Component> {
+ self.components.iter().find(|component| {
+ component
+ .file_names
+ .iter()
+ .any(|file_name| file_name == name)
+ })
+ }
+
+ pub fn linked_components(&self, db: &dyn Db, child: Document) -> Vec<&Component> {
+ Workspace::get(db)
+ .related(db, child)
+ .iter()
+ .filter_map(|document| document.parse(db).as_tex())
+ .flat_map(|data| data.analyze(db).links(db))
+ .filter_map(|link| match link.kind(db) {
+ TexLinkKind::Sty => Some(format!("{}.sty", link.path(db).text(db))),
+ TexLinkKind::Cls => Some(format!("{}.cls", link.path(db).text(db))),
+ _ => None,
+ })
+ .filter_map(|name| self.find(&name))
+ .chain(std::iter::once(self.kernel()))
+ .flat_map(|comp| {
+ comp.references
+ .iter()
+ .filter_map(|name| self.find(name))
+ .chain(std::iter::once(comp))
+ })
+ .unique_by(|comp| &comp.file_names)
+ .collect()
+ }
+
+ pub fn kernel(&self) -> &Component {
+ self.components
+ .iter()
+ .find(|component| component.file_names.is_empty())
+ .unwrap()
+ }
+
+ pub fn documentation(&self, name: &str) -> Option<MarkupContent> {
+ let metadata = self
+ .metadata
+ .iter()
+ .find(|metadata| metadata.name == name)?;
+
+ let desc = metadata.description.clone()?;
+ Some(MarkupContent {
+ kind: MarkupKind::PlainText,
+ value: desc,
+ })
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Component {
+ pub file_names: Vec<SmolStr>,
+ pub references: Vec<SmolStr>,
+ pub commands: Vec<ComponentCommand>,
+ pub environments: Vec<SmolStr>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ComponentCommand {
+ pub name: SmolStr,
+ pub image: Option<String>,
+ pub glyph: Option<SmolStr>,
+ pub parameters: Vec<ComponentParameter>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ComponentParameter(pub Vec<ComponentArgument>);
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ComponentArgument {
+ pub name: SmolStr,
+ pub image: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ComponentMetadata {
+ pub name: String,
+ pub caption: Option<String>,
+ pub description: Option<String>,
+}
+
+const JSON_GZ: &[u8] = include_bytes!("../../data/components.json.gz");
+
+pub static COMPONENT_DATABASE: Lazy<ComponentDatabase> = Lazy::new(|| {
+ let mut decoder = GzDecoder::new(JSON_GZ);
+ let mut buf = String::new();
+ decoder.read_to_string(&mut buf).unwrap();
+ serde_json::from_str(&buf).unwrap()
+});
diff --git a/support/texlab/src/features/cursor.rs b/support/texlab/src/util/cursor.rs
index 84ee6e98c4..5c3758ef46 100644
--- a/support/texlab/src/features/cursor.rs
+++ b/support/texlab/src/util/cursor.rs
@@ -1,25 +1,23 @@
-use lsp_types::{
- CompletionParams, DocumentHighlightParams, GotoDefinitionParams, HoverParams, Position,
- ReferenceParams, RenameParams, TextDocumentPositionParams,
-};
+use lsp_types::{Position, Url};
use rowan::{ast::AstNode, TextRange, TextSize};
use crate::{
+ db::{parse::DocumentData, Document, Workspace},
syntax::{bibtex, latex},
- DocumentData, LineIndexExt,
+ Db,
};
-use super::FeatureRequest;
+use super::{line_index::LineIndex, line_index_ext::LineIndexExt};
#[derive(Debug)]
pub enum Cursor {
- Latex(latex::SyntaxToken),
- Bibtex(bibtex::SyntaxToken),
+ Tex(latex::SyntaxToken),
+ Bib(bibtex::SyntaxToken),
Nothing,
}
impl Cursor {
- pub fn new_latex(
+ pub fn new_tex(
left: Option<latex::SyntaxToken>,
right: Option<latex::SyntaxToken>,
) -> Option<Self> {
@@ -27,35 +25,35 @@ impl Cursor {
let right = right?;
if left.kind().is_command_name() {
- return Some(Self::Latex(left));
+ return Some(Self::Tex(left));
}
if right.kind() == latex::WORD {
- return Some(Self::Latex(right));
+ return Some(Self::Tex(right));
}
if left.kind() == latex::WORD {
- return Some(Self::Latex(left));
+ return Some(Self::Tex(left));
}
if right.kind().is_command_name() {
- return Some(Self::Latex(right));
+ return Some(Self::Tex(right));
}
if left.kind() == latex::WHITESPACE && left.parent()?.kind() == latex::KEY {
- return Some(Self::Latex(left));
+ return Some(Self::Tex(left));
}
if matches!(right.kind(), latex::WHITESPACE | latex::LINE_BREAK)
&& right.parent()?.kind() == latex::KEY
{
- return Some(Self::Latex(right));
+ return Some(Self::Tex(right));
}
- Some(Self::Latex(right))
+ Some(Self::Tex(right))
}
- pub fn new_bibtex(
+ pub fn new_bib(
left: Option<bibtex::SyntaxToken>,
right: Option<bibtex::SyntaxToken>,
) -> Option<Self> {
@@ -63,42 +61,42 @@ impl Cursor {
let right = right?;
if right.kind() == bibtex::TYPE {
- return Some(Self::Bibtex(right));
+ return Some(Self::Bib(right));
}
if left.kind() == bibtex::TYPE {
- return Some(Self::Bibtex(left));
+ return Some(Self::Bib(left));
}
if matches!(left.kind(), bibtex::COMMAND_NAME | bibtex::ACCENT_NAME) {
- return Some(Self::Bibtex(left));
+ return Some(Self::Bib(left));
}
if matches!(right.kind(), bibtex::WORD | bibtex::NAME) {
- return Some(Self::Bibtex(right));
+ return Some(Self::Bib(right));
}
if matches!(left.kind(), bibtex::WORD | bibtex::NAME) {
- return Some(Self::Bibtex(left));
+ return Some(Self::Bib(left));
}
if matches!(right.kind(), bibtex::COMMAND_NAME | bibtex::ACCENT_NAME) {
- return Some(Self::Bibtex(right));
+ return Some(Self::Bib(right));
}
- Some(Self::Bibtex(right))
+ Some(Self::Bib(right))
}
- pub fn as_latex(&self) -> Option<&latex::SyntaxToken> {
- if let Self::Latex(v) = self {
+ pub fn as_tex(&self) -> Option<&latex::SyntaxToken> {
+ if let Self::Tex(v) = self {
Some(v)
} else {
None
}
}
- pub fn as_bibtex(&self) -> Option<&bibtex::SyntaxToken> {
- if let Self::Bibtex(v) = self {
+ pub fn as_bib(&self) -> Option<&bibtex::SyntaxToken> {
+ if let Self::Bib(v) = self {
Some(v)
} else {
None
@@ -106,13 +104,13 @@ impl Cursor {
}
pub fn command_range(&self, offset: TextSize) -> Option<TextRange> {
- self.as_latex()
+ self.as_tex()
.filter(|token| token.kind().is_command_name())
.filter(|token| token.text_range().start() != offset)
.map(|token| token.text_range())
.map(|range| TextRange::new(range.start() + TextSize::from(1), range.end()))
.or_else(|| {
- self.as_bibtex()
+ self.as_bib()
.filter(|token| {
matches!(token.kind(), bibtex::COMMAND_NAME | bibtex::ACCENT_NAME)
})
@@ -123,40 +121,55 @@ impl Cursor {
}
}
-pub struct CursorContext<P> {
- pub request: FeatureRequest<P>,
+pub struct CursorContext<'db, T = ()> {
+ pub db: &'db dyn Db,
+ pub document: Document,
+ pub line_index: &'db LineIndex,
+ pub workspace: Workspace,
pub cursor: Cursor,
pub offset: TextSize,
+ pub params: T,
}
-impl<P: HasPosition> CursorContext<P> {
- pub fn new(request: FeatureRequest<P>) -> Self {
- let main_document = request.main_document();
- let offset = main_document
- .line_index
- .offset_lsp(request.params.position());
+impl<'db, T> CursorContext<'db, T> {
+ pub fn new(db: &'db dyn Db, uri: &Url, position: Position, params: T) -> Option<Self> {
+ let workspace = Workspace::get(db);
+ let document = workspace.lookup_uri(db, uri)?;
+ let line_index = document.contents(db).line_index(db);
+ let offset = line_index.offset_lsp(position);
- let cursor = match &main_document.data {
- DocumentData::Latex(data) => {
- let root = latex::SyntaxNode::new_root(data.green.clone());
+ let cursor = match document.parse(db) {
+ DocumentData::Tex(data) => {
+ let root = data.root(db);
let left = root.token_at_offset(offset).left_biased();
let right = root.token_at_offset(offset).right_biased();
- Cursor::new_latex(left, right)
+ Cursor::new_tex(left, right)
}
- DocumentData::Bibtex(data) => {
- let root = bibtex::SyntaxNode::new_root(data.green.clone());
+ DocumentData::Bib(data) => {
+ let root = data.root(db);
let left = root.token_at_offset(offset).left_biased();
let right = root.token_at_offset(offset).right_biased();
- Cursor::new_bibtex(left, right)
+ Cursor::new_bib(left, right)
}
- DocumentData::BuildLog(_) => None,
+ DocumentData::Log(_) => None,
};
- Self {
- request,
+ Some(Self {
+ db,
+ document,
+ line_index,
+ workspace,
cursor: cursor.unwrap_or(Cursor::Nothing),
offset,
- }
+ params,
+ })
+ }
+
+ pub fn related(&self) -> impl Iterator<Item = Document> + '_ {
+ self.workspace
+ .related(self.db, self.document)
+ .iter()
+ .copied()
}
pub fn is_inside_latex_curly(&self, group: &impl latex::HasCurly) -> bool {
@@ -166,7 +179,7 @@ impl<P: HasPosition> CursorContext<P> {
pub fn find_citation_key_word(&self) -> Option<(String, TextRange)> {
let word = self
.cursor
- .as_latex()
+ .as_tex()
.filter(|token| token.kind() == latex::WORD)?;
let key = latex::Key::cast(word.parent()?)?;
@@ -177,7 +190,7 @@ impl<P: HasPosition> CursorContext<P> {
}
pub fn find_citation_key_command(&self) -> Option<(String, TextRange)> {
- let command = self.cursor.as_latex()?;
+ let command = self.cursor.as_tex()?;
let citation = latex::Citation::cast(command.parent()?)?;
let key = citation.key_list()?.keys().next()?;
@@ -187,7 +200,7 @@ impl<P: HasPosition> CursorContext<P> {
pub fn find_entry_key(&self) -> Option<(String, TextRange)> {
let key = self
.cursor
- .as_bibtex()
+ .as_bib()
.filter(|token| token.kind() == bibtex::NAME)?;
bibtex::Entry::cast(key.parent()?)?;
@@ -197,7 +210,7 @@ impl<P: HasPosition> CursorContext<P> {
pub fn find_label_name_key(&self) -> Option<(String, TextRange)> {
let name = self
.cursor
- .as_latex()
+ .as_tex()
.filter(|token| token.kind() == latex::WORD)?;
let key = latex::Key::cast(name.parent()?)?;
@@ -213,7 +226,7 @@ impl<P: HasPosition> CursorContext<P> {
}
pub fn find_label_name_command(&self) -> Option<(String, TextRange)> {
- let node = self.cursor.as_latex()?.parent()?;
+ let node = self.cursor.as_tex()?.parent()?;
if let Some(label) = latex::LabelDefinition::cast(node.clone()) {
let name = label.name()?.key()?;
Some((name.to_string(), latex::small_range(&name)))
@@ -239,7 +252,7 @@ impl<P: HasPosition> CursorContext<P> {
}
pub fn find_curly_group_word(&self) -> Option<(String, TextRange, latex::CurlyGroupWord)> {
- let token = self.cursor.as_latex()?;
+ let token = self.cursor.as_tex()?;
let key = latex::Key::cast(token.parent()?);
let group = key
@@ -257,7 +270,7 @@ impl<P: HasPosition> CursorContext<P> {
pub fn find_curly_group_word_list(
&self,
) -> Option<(String, TextRange, latex::CurlyGroupWordList)> {
- let token = self.cursor.as_latex()?;
+ let token = self.cursor.as_tex()?;
let key = latex::Key::cast(token.parent()?);
let group = key
@@ -285,49 +298,3 @@ impl<P: HasPosition> CursorContext<P> {
.or_else(|| Some((String::new(), TextRange::empty(self.offset), group)))
}
}
-
-pub trait HasPosition {
- fn position(&self) -> Position;
-}
-
-impl HasPosition for CompletionParams {
- fn position(&self) -> Position {
- self.text_document_position.position
- }
-}
-
-impl HasPosition for TextDocumentPositionParams {
- fn position(&self) -> Position {
- self.position
- }
-}
-
-impl HasPosition for RenameParams {
- fn position(&self) -> Position {
- self.text_document_position.position
- }
-}
-
-impl HasPosition for ReferenceParams {
- fn position(&self) -> Position {
- self.text_document_position.position
- }
-}
-
-impl HasPosition for HoverParams {
- fn position(&self) -> Position {
- self.text_document_position_params.position
- }
-}
-
-impl HasPosition for GotoDefinitionParams {
- fn position(&self) -> Position {
- self.text_document_position_params.position
- }
-}
-
-impl HasPosition for DocumentHighlightParams {
- fn position(&self) -> Position {
- self.text_document_position_params.position
- }
-}
diff --git a/support/texlab/src/label.rs b/support/texlab/src/util/label.rs
index b66db836f2..f0fae34337 100644
--- a/support/texlab/src/label.rs
+++ b/support/texlab/src/util/label.rs
@@ -1,24 +1,26 @@
use std::str::FromStr;
-use lsp_types::{MarkupContent, MarkupKind};
use rowan::{ast::AstNode, TextRange};
use crate::{
+ db::{analysis::label, Document, Word, Workspace},
syntax::latex::{self, HasBrack, HasCurly},
- Workspace, LANGUAGE_DATA,
+ Db,
};
-use self::LabelledObject::*;
+use self::LabeledObject::*;
+
+use super::lang_data::LANGUAGE_DATA;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum LabelledFloatKind {
+pub enum LabeledFloatKind {
Figure,
Table,
Listing,
Algorithm,
}
-impl LabelledFloatKind {
+impl LabeledFloatKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Figure => "Figure",
@@ -29,7 +31,7 @@ impl LabelledFloatKind {
}
}
-impl FromStr for LabelledFloatKind {
+impl FromStr for LabeledFloatKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -44,17 +46,17 @@ impl FromStr for LabelledFloatKind {
}
#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum LabelledObject {
+pub enum LabeledObject {
Section {
prefix: &'static str,
text: String,
},
Float {
- kind: LabelledFloatKind,
+ kind: LabeledFloatKind,
caption: String,
},
Theorem {
- kind: String,
+ kind: Word,
description: Option<String>,
},
Equation,
@@ -64,27 +66,28 @@ pub enum LabelledObject {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct RenderedLabel {
pub range: TextRange,
- pub number: Option<String>,
- pub object: LabelledObject,
+ pub number: Option<Word>,
+ pub object: LabeledObject,
}
impl RenderedLabel {
- #[must_use]
- pub fn reference(&self) -> String {
+ pub fn reference(&self, db: &dyn Db) -> String {
match &self.number {
Some(number) => match &self.object {
- Section { prefix, text } => format!("{} {} ({})", prefix, number, text),
- Float { kind, caption } => format!("{} {}: {}", kind.as_str(), number, caption),
+ Section { prefix, text } => format!("{} {} ({})", prefix, number.text(db), text),
+ Float { kind, caption } => {
+ format!("{} {}: {}", kind.as_str(), number.text(db), caption)
+ }
Theorem {
kind,
description: None,
- } => format!("{} {}", kind, number),
+ } => format!("{} {}", kind.text(db), number.text(db)),
Theorem {
kind,
description: Some(description),
- } => format!("{} {} ({})", kind, number, description),
- Equation => format!("Equation ({})", number),
- EnumItem => format!("Item {}", number),
+ } => format!("{} {} ({})", kind.text(db), number.text(db), description),
+ Equation => format!("Equation ({})", number.text(db)),
+ EnumItem => format!("Item {}", number.text(db)),
},
None => match &self.object {
Section { prefix, text } => format!("{} ({})", prefix, text),
@@ -92,117 +95,93 @@ impl RenderedLabel {
Theorem {
kind,
description: None,
- } => kind.into(),
+ } => kind.text(db).into(),
Theorem {
kind,
description: Some(description),
- } => format!("{} ({})", kind, description),
+ } => format!("{} ({})", kind.text(db), description),
Equation => "Equation".into(),
EnumItem => "Item".into(),
},
}
}
- #[must_use]
- pub fn detail(&self) -> Option<String> {
+ pub fn detail(&self, db: &dyn Db) -> Option<String> {
match &self.object {
- Section { .. } | Theorem { .. } | Equation | EnumItem => Some(self.reference()),
+ Section { .. } | Theorem { .. } | Equation | EnumItem => Some(self.reference(db)),
Float { kind, .. } => {
let result = match &self.number {
- Some(number) => format!("{} {}", kind.as_str(), number),
+ Some(number) => format!("{} {}", kind.as_str(), number.text(db)),
None => kind.as_str().to_owned(),
};
Some(result)
}
}
}
-
- #[must_use]
- pub fn documentation(&self) -> MarkupContent {
- MarkupContent {
- kind: MarkupKind::PlainText,
- value: self.reference(),
- }
- }
}
-pub fn render_label(
- workspace: &Workspace,
- label_name: &str,
- mut label: Option<latex::LabelDefinition>,
-) -> Option<RenderedLabel> {
- let mut number = find_label_number(workspace, label_name).map(ToString::to_string);
+pub fn render(db: &dyn Db, document: Document, label_def: label::Name) -> Option<RenderedLabel> {
+ let workspace = Workspace::get(db);
+ let label_num = workspace.number_of_label(db, document, label_def.name(db));
+ let root = document.parse(db).as_tex()?.root(db);
- for document in workspace.documents_by_uri.values() {
- if let Some(data) = document.data.as_latex() {
- label = label.or_else(|| {
- find_label_definition(&latex::SyntaxNode::new_root(data.green.clone()), label_name)
- });
- }
- }
-
- label?.syntax().ancestors().find_map(|parent| {
- render_label_float(parent.clone(), &mut number)
- .or_else(|| render_label_section(parent.clone(), &mut number))
- .or_else(|| render_label_enum_item(parent.clone(), &mut number))
- .or_else(|| render_label_equation(parent.clone(), &mut number))
- .or_else(|| render_label_theorem(workspace, parent, &mut number))
- })
+ label_def
+ .origin(db)
+ .as_definition()?
+ .to_node(&root)
+ .syntax()
+ .ancestors()
+ .find_map(|parent| {
+ render_label_float(parent.clone(), label_num)
+ .or_else(|| render_label_section(parent.clone(), label_num))
+ .or_else(|| render_label_enum_item(db, parent.clone(), label_num))
+ .or_else(|| render_label_equation(parent.clone(), label_num))
+ .or_else(|| render_label_theorem(db, document, parent, label_num))
+ })
}
pub fn find_label_definition(
- root: &latex::SyntaxNode,
- label_name: &str,
-) -> Option<latex::LabelDefinition> {
- root.descendants()
- .filter_map(latex::LabelDefinition::cast)
- .find(|label| {
- label
- .name()
- .and_then(|name| name.key())
- .map(|name| name.to_string())
- .as_deref()
- == Some(label_name)
- })
-}
+ db: &dyn Db,
+ child: Document,
+ name: Word,
+) -> Option<(Document, label::Name)> {
+ Workspace::get(db)
+ .related(db, child)
+ .iter()
+ .find_map(|document| {
+ let data = document.parse(db).as_tex()?;
+ let label = data
+ .analyze(db)
+ .labels(db)
+ .iter()
+ .filter(|label| label.origin(db).as_definition().is_some())
+ .find(|label| label.name(db) == name)?;
-pub fn find_label_number<'a>(workspace: &'a Workspace, label_name: &str) -> Option<&'a str> {
- workspace.documents_by_uri.values().find_map(|document| {
- document
- .data
- .as_latex()
- .and_then(|data| data.extras.label_numbers_by_name.get(label_name))
- .map(|number| number.as_str())
- })
+ Some((*document, *label))
+ })
}
-fn render_label_float(
- parent: latex::SyntaxNode,
- number: &mut Option<String>,
-) -> Option<RenderedLabel> {
+fn render_label_float(parent: latex::SyntaxNode, number: Option<Word>) -> Option<RenderedLabel> {
let environment = latex::Environment::cast(parent.clone())?;
let environment_name = environment.begin()?.name()?.key()?.to_string();
- let kind = LabelledFloatKind::from_str(&environment_name).ok()?;
+ let kind = LabeledFloatKind::from_str(&environment_name).ok()?;
let caption = find_caption_by_parent(&parent)?;
Some(RenderedLabel {
range: latex::small_range(&environment),
- number: number.take(),
- object: LabelledObject::Float { caption, kind },
+ number,
+ object: LabeledObject::Float { caption, kind },
})
}
-fn render_label_section(
- parent: latex::SyntaxNode,
- number: &mut Option<String>,
-) -> Option<RenderedLabel> {
+fn render_label_section(parent: latex::SyntaxNode, number: Option<Word>) -> Option<RenderedLabel> {
let section = latex::Section::cast(parent)?;
let text_group = section.name()?;
let text = text_group.content_text()?;
Some(RenderedLabel {
range: latex::small_range(&section),
- number: number.take(),
- object: LabelledObject::Section {
+ number,
+ object: LabeledObject::Section {
prefix: match section.syntax().kind() {
latex::PART => "Part",
latex::CHAPTER => "Chapter",
@@ -219,24 +198,23 @@ fn render_label_section(
}
fn render_label_enum_item(
+ db: &dyn Db,
parent: latex::SyntaxNode,
- number: &mut Option<String>,
+ number: Option<Word>,
) -> Option<RenderedLabel> {
let enum_item = latex::EnumItem::cast(parent)?;
Some(RenderedLabel {
range: latex::small_range(&enum_item),
number: enum_item
.label()
- .and_then(|number| number.content_text())
- .or_else(|| number.take()),
- object: LabelledObject::EnumItem,
+ .and_then(|label| label.content_text())
+ .map(|text| Word::new(db, text))
+ .or_else(|| number),
+ object: LabeledObject::EnumItem,
})
}
-fn render_label_equation(
- parent: latex::SyntaxNode,
- number: &mut Option<String>,
-) -> Option<RenderedLabel> {
+fn render_label_equation(parent: latex::SyntaxNode, number: Option<Word>) -> Option<RenderedLabel> {
let environment = latex::Environment::cast(parent)?;
let environment_name = environment.begin()?.name()?.key()?.to_string();
@@ -250,15 +228,16 @@ fn render_label_equation(
Some(RenderedLabel {
range: latex::small_range(&environment),
- number: number.take(),
- object: LabelledObject::Equation,
+ number,
+ object: LabeledObject::Equation,
})
}
fn render_label_theorem(
- workspace: &Workspace,
+ db: &dyn Db,
+ document: Document,
parent: latex::SyntaxNode,
- number: &mut Option<String>,
+ number: Option<Word>,
) -> Option<RenderedLabel> {
let environment = latex::Environment::cast(parent)?;
let begin = environment.begin()?;
@@ -266,22 +245,18 @@ fn render_label_theorem(
let environment_name = begin.name()?.key()?.to_string();
- let theorem = workspace.documents_by_uri.values().find_map(|document| {
- document.data.as_latex().and_then(|data| {
- data.extras
- .theorem_environments
- .iter()
- .find(|theorem| theorem.name.as_str() == environment_name)
- })
- })?;
+ let kind = Workspace::get(db)
+ .related(db, document)
+ .iter()
+ .filter_map(|document| document.parse(db).as_tex())
+ .flat_map(|data| data.analyze(db).theorem_environments(db))
+ .find(|env| env.name(db).text(db) == &environment_name)
+ .map(|env| env.description(db))?;
Some(RenderedLabel {
range: latex::small_range(&environment),
- number: number.take(),
- object: LabelledObject::Theorem {
- kind: theorem.description.clone(),
- description,
- },
+ number,
+ object: LabeledObject::Theorem { kind, description },
})
}
diff --git a/support/texlab/src/lang_data.rs b/support/texlab/src/util/lang_data.rs
index 28e6a540e0..f270c3f65e 100644
--- a/support/texlab/src/lang_data.rs
+++ b/support/texlab/src/util/lang_data.rs
@@ -65,6 +65,6 @@ impl LanguageData {
}
pub static LANGUAGE_DATA: Lazy<LanguageData> = Lazy::new(|| {
- const JSON: &str = include_str!("../data/lang_data.json");
+ const JSON: &str = include_str!("../../data/lang_data.json");
serde_json::from_str(JSON).expect("Failed to deserialize language.json")
});
diff --git a/support/texlab/src/line_index.rs b/support/texlab/src/util/line_index.rs
index 70e8f8128b..70e8f8128b 100644
--- a/support/texlab/src/line_index.rs
+++ b/support/texlab/src/util/line_index.rs
diff --git a/support/texlab/src/line_index_ext.rs b/support/texlab/src/util/line_index_ext.rs
index 9faccf1c86..becbb8bde7 100644
--- a/support/texlab/src/line_index_ext.rs
+++ b/support/texlab/src/util/line_index_ext.rs
@@ -1,7 +1,7 @@
use lsp_types::{Position, Range};
use rowan::{TextRange, TextSize};
-use crate::{LineColUtf16, LineIndex};
+use super::line_index::{LineColUtf16, LineIndex};
pub trait LineIndexExt {
fn offset_lsp(&self, line_col: Position) -> TextSize;
diff --git a/support/texlab/src/features/lsp_kinds.rs b/support/texlab/src/util/lsp_enums.rs
index 82706b5aee..75b97097c5 100644
--- a/support/texlab/src/features/lsp_kinds.rs
+++ b/support/texlab/src/util/lsp_enums.rs
@@ -1,6 +1,6 @@
use lsp_types::{CompletionItemKind, SymbolKind};
-use crate::BibtexEntryTypeCategory;
+use super::lang_data::BibtexEntryTypeCategory;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Structure {
diff --git a/support/texlab/src/workspace.rs b/support/texlab/src/workspace.rs
deleted file mode 100644
index e01a2fede4..0000000000
--- a/support/texlab/src/workspace.rs
+++ /dev/null
@@ -1,225 +0,0 @@
-use std::{
- fs::{self, FileType},
- path::PathBuf,
- sync::Arc,
-};
-
-use anyhow::Result;
-use crossbeam_channel::Sender;
-use lsp_types::Url;
-use petgraph::{graphmap::UnGraphMap, visit::Dfs};
-use rustc_hash::{FxHashMap, FxHashSet};
-
-use crate::{
- component_db::COMPONENT_DATABASE, syntax::latex::ExplicitLink, Document, DocumentLanguage,
- Environment,
-};
-
-#[derive(Debug, Clone)]
-pub enum WorkspaceEvent {
- Changed(Workspace, Document),
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Workspace {
- pub documents_by_uri: FxHashMap<Arc<Url>, Document>,
- pub viewport: FxHashSet<Arc<Url>>,
- pub listeners: Vec<Sender<WorkspaceEvent>>,
- pub environment: Environment,
-}
-
-impl Workspace {
- #[must_use]
- pub fn new(environment: Environment) -> Self {
- Self {
- environment,
- ..Self::default()
- }
- }
-
- pub fn open(
- &mut self,
- uri: Arc<Url>,
- text: Arc<String>,
- language: DocumentLanguage,
- ) -> Result<Document> {
- log::debug!("(Re)Loading document: {}", uri);
- let document = Document::parse(&self.environment, Arc::clone(&uri), text, language);
-
- self.documents_by_uri
- .insert(Arc::clone(&uri), document.clone());
-
- for listener in &self.listeners {
- listener.send(WorkspaceEvent::Changed(self.clone(), document.clone()))?;
- }
-
- self.expand_parent(&document);
- self.expand_children(&document);
- Ok(document)
- }
-
- pub fn reload(&mut self, path: PathBuf) -> Result<Option<Document>> {
- let uri = Arc::new(Url::from_file_path(path.clone()).unwrap());
-
- if self.is_open(&uri) && !uri.as_str().ends_with(".log") {
- return Ok(self.documents_by_uri.get(&uri).cloned());
- }
-
- if let Some(language) = DocumentLanguage::by_path(&path) {
- let data = fs::read(&path)?;
- let text = Arc::new(String::from_utf8_lossy(&data).into_owned());
- Ok(Some(self.open(uri, text, language)?))
- } else {
- Ok(None)
- }
- }
-
- pub fn load(&mut self, path: PathBuf) -> Result<Option<Document>> {
- let uri = Arc::new(Url::from_file_path(path.clone()).unwrap());
-
- if let Some(document) = self.documents_by_uri.get(&uri).cloned() {
- return Ok(Some(document));
- }
-
- let data = fs::read(&path)?;
- let text = Arc::new(String::from_utf8_lossy(&data).into_owned());
- if let Some(language) = DocumentLanguage::by_path(&path) {
- Ok(Some(self.open(uri, text, language)?))
- } else {
- Ok(None)
- }
- }
-
- pub fn close(&mut self, uri: &Url) {
- self.viewport.remove(uri);
- }
-
- pub fn is_open(&self, uri: &Url) -> bool {
- self.viewport.contains(uri)
- }
-
- pub fn slice(&self, uri: &Url) -> Self {
- let all_uris: Vec<_> = self.documents_by_uri.keys().cloned().collect();
-
- all_uris
- .iter()
- .position(|u| u.as_ref() == uri)
- .map(|start| {
- let mut edges = Vec::new();
- for (i, uri) in all_uris.iter().enumerate() {
- let document = self.documents_by_uri.get(uri);
- if let Some(data) = document
- .as_ref()
- .and_then(|document| document.data.as_latex())
- {
- let extras = &data.extras;
- let mut all_targets =
- vec![&extras.implicit_links.aux, &extras.implicit_links.log];
- for link in &extras.explicit_links {
- all_targets.push(&link.targets);
- }
-
- for targets in all_targets {
- for target in targets {
- if let Some(j) = all_uris.iter().position(|uri| uri == target) {
- edges.push((i, j, ()));
- break;
- }
- }
- }
- }
- }
-
- let mut slice = self.clone();
- slice.documents_by_uri = FxHashMap::default();
- let graph = UnGraphMap::from_edges(edges);
- let mut dfs = Dfs::new(&graph, start);
- while let Some(i) = dfs.next(&graph) {
- let uri = &all_uris[i];
- let doc = self.documents_by_uri[uri].clone();
- slice.documents_by_uri.insert(Arc::clone(uri), doc);
- }
-
- slice
- })
- .unwrap_or_default()
- }
-
- #[must_use]
- pub fn find_parent(&self, uri: &Url) -> Option<Document> {
- self.slice(uri)
- .documents_by_uri
- .values()
- .find(|document| {
- document.data.as_latex().map_or(false, |data| {
- data.extras.has_document_environment
- && !data
- .extras
- .explicit_links
- .iter()
- .filter_map(ExplicitLink::as_component_name)
- .any(|name| name == "subfiles.cls")
- })
- })
- .cloned()
- }
-
- fn expand_parent(&mut self, document: &Document) {
- let all_current_paths = self
- .documents_by_uri
- .values()
- .filter_map(|doc| doc.uri.to_file_path().ok())
- .collect::<FxHashSet<_>>();
-
- if document.uri.scheme() == "file" {
- if let Ok(mut path) = document.uri.to_file_path() {
- while path.pop() && self.find_parent(&document.uri).is_none() {
- std::fs::read_dir(&path)
- .into_iter()
- .flatten()
- .filter_map(Result::ok)
- .filter(|entry| entry.file_type().ok().filter(FileType::is_file).is_some())
- .map(|entry| entry.path())
- .filter(|path| {
- matches!(
- DocumentLanguage::by_path(path),
- Some(DocumentLanguage::Latex)
- )
- })
- .filter(|path| !all_current_paths.contains(path))
- .for_each(|path| {
- let _ = self.load(path);
- });
- }
- }
- }
- }
-
- fn expand_children(&mut self, document: &Document) {
- if let Some(data) = document.data.as_latex() {
- let extras = &data.extras;
- let mut all_targets = vec![&extras.implicit_links.aux, &extras.implicit_links.log];
- for link in &extras.explicit_links {
- if link
- .as_component_name()
- .and_then(|name| COMPONENT_DATABASE.find(&name))
- .is_none()
- {
- all_targets.push(&link.targets);
- }
- }
-
- for targets in all_targets {
- for path in targets
- .iter()
- .filter(|uri| uri.scheme() == "file" && uri.fragment().is_none())
- .filter_map(|uri| uri.to_file_path().ok())
- {
- if self.load(path).is_ok() {
- break;
- }
- }
- }
- }
- }
-}