use base_db::{Config, FeatureParams};
use expect_test::{Expect, expect};
use parser::SyntaxConfig;
use rowan::TextRange;

use crate::CompletionParams;

fn check_with_syntax_config(config: SyntaxConfig, input: &str, expect: Expect) {
    let mut fixture = test_utils::fixture::Fixture::parse(input);
    fixture.workspace.set_config(Config {
        syntax: config,
        ..Config::default()
    });
    let fixture = fixture;

    let (offset, spec) = fixture
        .documents
        .iter()
        .find_map(|document| Some((document.cursor?, document)))
        .unwrap();

    let document = fixture.workspace.lookup(&spec.uri).unwrap();
    let feature = FeatureParams::new(&fixture.workspace, document);
    let params = CompletionParams { feature, offset };
    let result = crate::complete(&params);

    let range = spec
        .ranges
        .first()
        .map_or_else(|| TextRange::empty(offset), |range| *range);

    for item in &result.items {
        assert_eq!(item.range, range);
    }

    let items = result
        .items
        .into_iter()
        .take(5)
        .map(|item| item.data)
        .collect::<Vec<_>>();

    expect.assert_debug_eq(&items);
}

fn check(input: &str, expect: Expect) {
    check_with_syntax_config(SyntaxConfig::default(), input, expect)
}

#[test]
fn acronym_ref_simple() {
    check(
        r#"
%! main.tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
\acrshort{f}
          |
          ^"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "fpsLabel",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn acronym_ref_empty() {
    check(
        r#"
%! main.tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
\acrshort{}
          |"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "fpsLabel",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn acronym_ref_after_group() {
    check(
        r#"
%! main.tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
\acrshort{}
           |"#,
        expect![[r#"
            []
        "#]],
    );
}

#[test]
fn acronym_ref_open_brace() {
    check(
        r#"
%! main.tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
\acrshort{f
          |
          ^"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "fpsLabel",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn acronym_package_ref() {
    check(
        r#"
%! main.tex
\acrodef{fpsLabel}[FPS]{Frames per Second}
\ac{f
    |
    ^"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "fpsLabel",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn glossary_ref_simple() {
    check(
        r#"
%! main.tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
\gls{f}
     |
     ^"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "fpsLabel",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn glossary_ref_open_brace() {
    check(
        r#"
%! main.tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
\gls{f
     |
     ^"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "fpsLabel",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn argument_empty() {
    check(
        r#"
%! main.tex
\usepackage{amsfonts}
\mathbb{}
        |"#,
        expect![[r#"
            [
                Argument(
                    ArgumentData(
                        "A",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "B",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "C",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "D",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "E",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn argument_word() {
    check(
        r#"
%! main.tex
\usepackage{amsfonts}
\mathbb{A}
        |
        ^"#,
        expect![[r#"
            [
                Argument(
                    ArgumentData(
                        "A",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn argument_open_brace() {
    check(
        r#"
%! main.tex
\usepackage{amsfonts}
\mathbb{
        |
Test"#,
        expect![[r#"
            [
                Argument(
                    ArgumentData(
                        "A",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "B",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "C",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "D",
                    ),
                ),
                Argument(
                    ArgumentData(
                        "E",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn argument_open_brace_unrelated() {
    check(
        r#"
%! main.tex
\usepackage{amsfonts}
\mathbb{}{
          |
Test"#,
        expect![[r#"
            []
        "#]],
    );
}

#[test]
fn begin_environment_without_snippet_support() {
    check(
        r#"
%! main.tex
\beg
    |
 ^^^"#,
        expect![[r#"
            [
                BeginEnvironment,
                Command(
                    CommandData {
                        name: "begingroup",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "AtBeginDocument",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "AtBeginDvi",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "bigwedge",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn citation() {
    check(
        r#"
%! main.tex
\documentclass{article}
\bibliography{main}
\begin{document}
\cite{
      |
\end{document}

%! main.bib
@article{foo:2019,
    author = {Foo Bar},
    title = {Baz Qux},
    year = {2019},
}

@article{bar:2005,}"#,
        expect![[r#"
            [
                Citation(
                    CitationData {
                        document: Document(
                            "file:///texlab/main.bib",
                        ),
                        entry: Entry {
                            name: Span(
                                "bar:2005",
                                97..105,
                            ),
                            full_range: 88..107,
                            keywords: "bar:2005 @article",
                            category: Article,
                        },
                    },
                ),
                Citation(
                    CitationData {
                        document: Document(
                            "file:///texlab/main.bib",
                        ),
                        entry: Entry {
                            name: Span(
                                "foo:2019",
                                9..17,
                            ),
                            full_range: 0..86,
                            keywords: "foo:2019 @article Foo Bar Baz Qux 2019",
                            category: Article,
                        },
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn citation_open_brace() {
    check(
        r#"
%! main.tex
\addbibresource{main.bib}
\cite{
      |

%! main.bib
@article{foo,}"#,
        expect![[r#"
            [
                Citation(
                    CitationData {
                        document: Document(
                            "file:///texlab/main.bib",
                        ),
                        entry: Entry {
                            name: Span(
                                "foo",
                                9..12,
                            ),
                            full_range: 0..14,
                            keywords: "foo @article",
                            category: Article,
                        },
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn citation_open_brace_multiple() {
    check(
        r#"
%! main.tex
\addbibresource{main.bib}
\cite{foo,f
          |
          ^

%! main.bib
@article{foo,}"#,
        expect![[r#"
            [
                Citation(
                    CitationData {
                        document: Document(
                            "file:///texlab/main.bib",
                        ),
                        entry: Entry {
                            name: Span(
                                "foo",
                                9..12,
                            ),
                            full_range: 0..14,
                            keywords: "foo @article",
                            category: Article,
                        },
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn citation_acronym() {
    check(
        r#"
%! main.tex
\addbibresource{main.bib}
\DeclareAcronym{foo}{cite={}}
                           |

%! main.bib
@article{foo,}"#,
        expect![[r#"
            [
                Citation(
                    CitationData {
                        document: Document(
                            "file:///texlab/main.bib",
                        ),
                        entry: Entry {
                            name: Span(
                                "foo",
                                9..12,
                            ),
                            full_range: 0..14,
                            keywords: "foo @article",
                            category: Article,
                        },
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn citation_after_brace() {
    check(
        r#"
%! main.tex
\documentclass{article}
\bibliography{main}
\begin{document}
\cite{}
       |
\end{document}

%! main.bib
@article{foo,}"#,
        expect![[r#"
            []
        "#]],
    );
}

#[test]
fn color_model_definition_simple() {
    check(
        r#"
%! main.tex
\definecolor{foo}{}
                  |"#,
        expect![[r#"
            [
                ColorModel(
                    "HTML",
                ),
                ColorModel(
                    "RGB",
                ),
                ColorModel(
                    "cmyk",
                ),
                ColorModel(
                    "gray",
                ),
                ColorModel(
                    "rgb",
                ),
            ]
        "#]],
    );
}

#[test]
fn color_model_definition_open_brace() {
    check(
        r#"
%! main.tex
\definecolor{foo}{
                  |"#,
        expect![[r#"
            [
                ColorModel(
                    "HTML",
                ),
                ColorModel(
                    "RGB",
                ),
                ColorModel(
                    "cmyk",
                ),
                ColorModel(
                    "gray",
                ),
                ColorModel(
                    "rgb",
                ),
            ]
        "#]],
    );
}

#[test]
fn color_model_definition_set_simple() {
    check(
        r#"
%! main.tex
\definecolorset{}
                |"#,
        expect![[r#"
            [
                ColorModel(
                    "HTML",
                ),
                ColorModel(
                    "RGB",
                ),
                ColorModel(
                    "cmyk",
                ),
                ColorModel(
                    "gray",
                ),
                ColorModel(
                    "rgb",
                ),
            ]
        "#]],
    );
}

#[test]
fn color_model_definition_set_open_brace() {
    check(
        r#"
%! main.tex
\definecolorset{
                |"#,
        expect![[r#"
            [
                ColorModel(
                    "HTML",
                ),
                ColorModel(
                    "RGB",
                ),
                ColorModel(
                    "cmyk",
                ),
                ColorModel(
                    "gray",
                ),
                ColorModel(
                    "rgb",
                ),
            ]
        "#]],
    );
}

#[test]
fn color_simple() {
    check(
        r#"
%! main.tex
\color{}
       |"#,
        expect![[r#"
            [
                Color(
                    "Apricot",
                ),
                Color(
                    "Aquamarine",
                ),
                Color(
                    "Bittersweet",
                ),
                Color(
                    "Black",
                ),
                Color(
                    "Blue",
                ),
            ]
        "#]],
    );
}

#[test]
fn color_word() {
    check(
        r#"
%! main.tex
\color{re}
        |
       ^^"#,
        expect![[r#"
            [
                Color(
                    "red",
                ),
                Color(
                    "Red",
                ),
                Color(
                    "RedOrange",
                ),
                Color(
                    "RedViolet",
                ),
                Color(
                    "BrickRed",
                ),
            ]
        "#]],
    );
}

#[test]
fn color_open_brace() {
    check(
        r#"
%! main.tex
\color{
       |"#,
        expect![[r#"
            [
                Color(
                    "Apricot",
                ),
                Color(
                    "Aquamarine",
                ),
                Color(
                    "Bittersweet",
                ),
                Color(
                    "Black",
                ),
                Color(
                    "Blue",
                ),
            ]
        "#]],
    );
}

#[test]
fn component_command_simple() {
    check(
        r#"
%! main.tex
\
 |"#,
        expect![[r#"
            [
                CommandLikeDelimiter(
                    "(",
                    ")",
                ),
                CommandLikeDelimiter(
                    "[",
                    "]",
                ),
                CommandLikeDelimiter(
                    "{",
                    "\\}",
                ),
                Command(
                    CommandData {
                        name: "!",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "\"",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn component_command_simple_before() {
    check(
        r#"
%! main.tex
\
|"#,
        expect![[r#"
            []
        "#]],
    );
}

#[test]
fn component_command_simple_package() {
    check(
        r#"
%! main.tex
\usepackage{lipsum}
\lips
   |
 ^^^^"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "lipsum",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "lipsumexp",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumPar",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumProtect",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumRestoreAll",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn component_command_bibtex() {
    check(
        r#"
%! main.bib
@article{b,
    c = {\LaT }
           |
          ^^^
}"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "LaTeX",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "LaTeXe",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "latexreleaseversion",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "LastDeclaredEncoding",
                        package: [],
                    },
                ),
                Command(
                    CommandData {
                        name: "last",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn component_environment_simple() {
    check(
        r#"
%! main.tex
\begin{doc
          |
       ^^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "document",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn component_environment_simple_end() {
    check(
        r#"
%! main.tex
\begin{document}
\end{
     |"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "document",
                        package: [],
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "abstract",
                        package: [],
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "array",
                        package: [],
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "center",
                        package: [],
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "csname",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn component_environment_class() {
    check(
        r#"
%! main.tex
\documentclass{article}
\begin{thein}
          |
       ^^^^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "theindex",
                        package: [
                            "article.cls",
                        ],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn component_environment_command_definition() {
    check(
        r#"
%! main.tex
\newcommand{\foo}{\begin{doc}
                           |
                         ^^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "document",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_at_empty() {
    check(
        r#"
%! main.bib
@
 |
^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@article",
                    ),
                ),
                EntryType(
                    EntryTypeData(
                        "@artwork",
                    ),
                ),
                EntryType(
                    EntryTypeData(
                        "@audio",
                    ),
                ),
                EntryType(
                    EntryTypeData(
                        "@bibnote",
                    ),
                ),
                EntryType(
                    EntryTypeData(
                        "@book",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_before_preamble() {
    check(
        r#"
%! main.bib
@preamble
 |
^^^^^^^^^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@preamble",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_before_string() {
    check(
        r#"
%! main.bib
@string
 |
^^^^^^^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@string",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_before_article() {
    check(
        r#"
%! main.bib
@article
 |
^^^^^^^^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@article",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_after_preamble() {
    check(
        r#"
%! main.bib
@preamble{
         |
^^^^^^^^^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@preamble",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_after_string() {
    check(
        r#"
%! main.bib
@string{
       |
^^^^^^^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@string",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn entry_type_complete_entry() {
    check(
        r#"
%! main.bib
@article{foo, author = {foo}}
   |
^^^^^^^^"#,
        expect![[r#"
            [
                EntryType(
                    EntryTypeData(
                        "@article",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn field_empty_entry_open() {
    check(
        r#"
%! main.bib
@article{foo,
             |"#,
        expect![[r#"
            [
                Field(
                    FieldTypeData(
                        "abstract",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "addendum",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "address",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "afterword",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "annotation",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn field_empty_entry_closed() {
    check(
        r#"
%! main.bib
@article{foo,}
             |"#,
        expect![[r#"
            [
                Field(
                    FieldTypeData(
                        "abstract",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "addendum",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "address",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "afterword",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "annotation",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn field_entry_field_name() {
    check(
        r#"
%! main.bib
@article{foo, a
               |
              ^"#,
        expect![[r#"
            [
                Field(
                    FieldTypeData(
                        "abstract",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "addendum",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "address",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "afterword",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "annotation",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn field_entry_two_fields_name_open() {
    check(
        r#"
%! main.bib
@article{foo, author = bar, edit
                             |
                            ^^^^"#,
        expect![[r#"
            [
                Field(
                    FieldTypeData(
                        "edition",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editor",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editora",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editoratype",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editorb",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn field_entry_two_fields_name_closed() {
    check(
        r#"
%! main.bib
@article{foo, author = bar, edit}
                             |
                            ^^^^"#,
        expect![[r#"
            [
                Field(
                    FieldTypeData(
                        "edition",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editor",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editora",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editoratype",
                    ),
                ),
                Field(
                    FieldTypeData(
                        "editorb",
                    ),
                ),
            ]
        "#]],
    );
}

#[test]
fn import_package_open_brace() {
    check(
        r#"
%! main.tex
\usepackage{lips
             |
            ^^^^"#,
        expect![[r#"
            [
                Package(
                    "lips",
                ),
                Package(
                    "lipsum",
                ),
                Package(
                    "lisp-simple-alloc",
                ),
                Package(
                    "lisp-string",
                ),
                Package(
                    "lwarp-lips",
                ),
            ]
        "#]],
    );
}

#[test]
fn import_package_closed_brace() {
    check(
        r#"
%! main.tex
\usepackage{lips}
             |
            ^^^^"#,
        expect![[r#"
            [
                Package(
                    "lips",
                ),
                Package(
                    "lipsum",
                ),
                Package(
                    "lisp-simple-alloc",
                ),
                Package(
                    "lisp-string",
                ),
                Package(
                    "lwarp-lips",
                ),
            ]
        "#]],
    );
}

#[test]
fn import_class_open_brace() {
    check(
        r#"
%! main.tex
\documentclass{art \foo
                |
               ^^^"#,
        expect![[r#"
            [
                DocumentClass(
                    "article",
                ),
                DocumentClass(
                    "articleingud",
                ),
                DocumentClass(
                    "articoletteracdp",
                ),
                DocumentClass(
                    "artikel1",
                ),
                DocumentClass(
                    "artikel2",
                ),
            ]
        "#]],
    );
}

#[test]
fn import_class_closed_brace() {
    check(
        r#"
%! main.tex
\documentclass{art}
                |
               ^^^"#,
        expect![[r#"
            [
                DocumentClass(
                    "article",
                ),
                DocumentClass(
                    "articleingud",
                ),
                DocumentClass(
                    "articoletteracdp",
                ),
                DocumentClass(
                    "artikel1",
                ),
                DocumentClass(
                    "artikel2",
                ),
            ]
        "#]],
    );
}

#[test]
fn label() {
    check(
        r#"
%! foo.tex
\documentclass{article}

\usepackage{amsmath}
\usepackage{caption}
\usepackage{amsthm}
\newtheorem{lemma}{Lemma}

\begin{document}

\section{Foo}%
\label{sec:foo}

\begin{equation}%
\label{eq:foo}
    1 + 1 = 2
\end{equation}

\begin{equation}%
\label{eq:bar}
    1 + 1 = 2
\end{equation}

\begin{figure}%
\LaTeX{}
\caption{Baz}%
\label{fig:baz}
\end{figure}

\begin{lemma}%
\label{thm:foo}
    1 + 1 = 2
\end{lemma}

\include{bar}

\end{document}

%! bar.tex
\section{Bar}%
\label{sec:bar}

Lorem ipsum dolor sit amet.
\ref{}
     |

%! foo.aux
\relax
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Baz\relax }}{1}\protected@file@percent }
\providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
\newlabel{fig:baz}{{1}{1}}
\@writefile{toc}{\contentsline {section}{\numberline {1}Foo}{1}\protected@file@percent }
\newlabel{sec:foo}{{1}{1}}
\newlabel{eq:foo}{{1}{1}}
\newlabel{eq:bar}{{2}{1}}
\newlabel{thm:foo}{{1}{1}}
\@input{bar.aux}"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "eq:bar",
                        header: Some(
                            "Equation (2)",
                        ),
                        footer: None,
                        object: Some(
                            Equation,
                        ),
                        keywords: "eq:bar Equation (2)",
                    },
                ),
                Label(
                    LabelData {
                        name: "eq:foo",
                        header: Some(
                            "Equation (1)",
                        ),
                        footer: None,
                        object: Some(
                            Equation,
                        ),
                        keywords: "eq:foo Equation (1)",
                    },
                ),
                Label(
                    LabelData {
                        name: "fig:baz",
                        header: Some(
                            "Figure 1",
                        ),
                        footer: Some(
                            "Baz",
                        ),
                        object: Some(
                            Float {
                                kind: Figure,
                                caption: "Baz",
                            },
                        ),
                        keywords: "fig:baz Figure 1: Baz",
                    },
                ),
                Label(
                    LabelData {
                        name: "sec:bar",
                        header: Some(
                            "Section (Bar)",
                        ),
                        footer: None,
                        object: Some(
                            Section {
                                prefix: "Section",
                                text: "Bar",
                            },
                        ),
                        keywords: "sec:bar Section (Bar)",
                    },
                ),
                Label(
                    LabelData {
                        name: "sec:foo",
                        header: Some(
                            "Section 1 (Foo)",
                        ),
                        footer: None,
                        object: Some(
                            Section {
                                prefix: "Section",
                                text: "Foo",
                            },
                        ),
                        keywords: "sec:foo Section 1 (Foo)",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn label_undefined() {
    check(
        r#"
%! foo.tex
\label{f}
        |
       ^
\ref{foo}"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "foo",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "foo",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn theorem_begin() {
    check(
        r#"
%! main.tex
\newtheorem{lemma}{Lemma}
\begin{lem
        |
       ^^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "lemma",
                        package: "<user>",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn theorem_end() {
    check(
        r#"
%! main.tex
\newtheorem{lemma}{Lemma}
\begin{}
\end{lem
      |
     ^^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "lemma",
                        package: "<user>",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn theorem_begin_multiple() {
    check(
        r#"
%! main.tex
\declaretheorem[sibling=table, style=thmbox]{def1, def2, def3, def4, def5}
\begin{def
        |
       ^^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "def1",
                        package: "<user>",
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "def2",
                        package: "<user>",
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "def3",
                        package: "<user>",
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "def4",
                        package: "<user>",
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "def5",
                        package: "<user>",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn tikz_library_open_brace() {
    check(
        r#"
%! main.tex
\usepgflibrary{
               |"#,
        expect![[r#"
            [
                TikzLibrary(
                    "arrows",
                ),
                TikzLibrary(
                    "arrows.meta",
                ),
                TikzLibrary(
                    "arrows.spaced",
                ),
                TikzLibrary(
                    "curvilinear",
                ),
                TikzLibrary(
                    "datavisualization.barcharts",
                ),
            ]
        "#]],
    );
}

#[test]
fn tikz_library_closed_brace() {
    check(
        r#"
%! main.tex
\usepgflibrary{}
               |"#,
        expect![[r#"
            [
                TikzLibrary(
                    "arrows",
                ),
                TikzLibrary(
                    "arrows.meta",
                ),
                TikzLibrary(
                    "arrows.spaced",
                ),
                TikzLibrary(
                    "curvilinear",
                ),
                TikzLibrary(
                    "datavisualization.barcharts",
                ),
            ]
        "#]],
    );
}

#[test]
fn test_user_command() {
    check(
        r#"
%! main.tex
\foobar
\fooba
   |
 ^^^^^
\begin{foo}
\end{foo}
\begin{fo}"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "foobar",
                        package: "<user>",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_user_environment() {
    check(
        r#"
%! main.tex
\foobar
\fooba
\begin{foo}
\end{foo}
\begin{fo}
        |
       ^^"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "foo",
                        package: "<user>",
                    },
                ),
                Environment(
                    EnvironmentData {
                        name: "filecontents",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_project_resolution_import() {
    check(
        r#"
%! main.tex
\documentclass{article}
\import{sub}{sub.tex}
\lipsu
     |
 ^^^^^

%! sub/sub.tex
\input{child.tex}

%! sub/child.tex
\usepackage{lipsum}"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "lipsum",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "lipsumexp",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumPar",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumProtect",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumRestoreAll",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_project_resolution_texlabroot() {
    check(
        r#"
%! src/main.tex
\documentclass{article}
\include{src/foo}
\lipsu
     |
 ^^^^^

%! src/foo.tex
\include{src/bar}

%! src/bar.tex
\usepackage{lipsum}

%! .texlabroot"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "lipsum",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "lipsumexp",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumPar",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumProtect",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
                Command(
                    CommandData {
                        name: "LipsumRestoreAll",
                        package: [
                            "lipsum.sty",
                        ],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn issue_857_1() {
    check(
        r#"
%! bug.tex
\documentclass{article}
\newcommand{\ö}{foo}
\newcommand{\öö}{bar}
\newcommand{\ööabc}{baz}
\begin{document}
\ö
  |
 ^
\end{document}
"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "ö",
                        package: "<user>",
                    },
                ),
                Command(
                    CommandData {
                        name: "öö",
                        package: "<user>",
                    },
                ),
                Command(
                    CommandData {
                        name: "ööabc",
                        package: "<user>",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn issue_864() {
    check(
        r#"
%! bug.tex
\documentclass{article}
\def\あいうえお{}
\begin{document}
\あ
  |
 ^
\end{document}"#,
        expect![[r#"
            [
                Command(
                    CommandData {
                        name: "あいうえお",
                        package: "<user>",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn issue_883() {
    check(
        r#"
%! bug.tex
\begin{doc
          |
       ^^^
% Comment"#,
        expect![[r#"
            [
                Environment(
                    EnvironmentData {
                        name: "document",
                        package: [],
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn issue_885() {
    check(
        r#"
%! main.tex
\documentclass{book}
\usepackage{import}
\begin{document}
\subincludefrom{part 1}{main}
\include{part 2/main}

\ref{sec}
      |
     ^^^
\end{document}

%! part 1/main.tex
\part{1}
\label{part 1}
\subimport{chapter 1}{main}

%! part 1/chapter 1/main.tex
\chapter{1}
\label{chapter 1}
\subimport{./}{section 1}
%\subimport{}{section 1}

%! part 1/chapter 1/section 1.tex
\section{1}
\label{section 1}

%! part 2/main.tex
\part{2}
\label{part 2}
\input{part 2/chapter 2/main}

%! part 2/chapter 2/main.tex
\chapter{2}
\label{chapter 2}
\input{part 2/chapter 2/section 2}

%! part 2/chapter 2/section 2.tex
\section{2}
\label{section 2}
"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "section 1",
                        header: Some(
                            "Section (1)",
                        ),
                        footer: None,
                        object: Some(
                            Section {
                                prefix: "Section",
                                text: "1",
                            },
                        ),
                        keywords: "section 1 Section (1)",
                    },
                ),
                Label(
                    LabelData {
                        name: "section 2",
                        header: Some(
                            "Section (2)",
                        ),
                        footer: None,
                        object: Some(
                            Section {
                                prefix: "Section",
                                text: "2",
                            },
                        ),
                        keywords: "section 2 Section (2)",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_custom_label_prefix_ref() {
    let mut config = SyntaxConfig::default();
    config.label_definition_commands.insert("asm".to_string());
    config
        .label_definition_prefixes
        .push(("asm".to_string(), "asm:".to_string()));

    check_with_syntax_config(
        config,
        r#"
%! main.tex
    \begin{enumerate}\label{baz}
        \asm{foo}{what}
    \end{enumerate}

    \ref{}
         |
% Comment"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "asm:foo",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "asm:foo",
                    },
                ),
                Label(
                    LabelData {
                        name: "baz",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "baz",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_custom_label_prefix_custom_ref() {
    let mut config = SyntaxConfig::default();
    config.label_definition_commands.insert("asm".to_string());
    config
        .label_definition_prefixes
        .push(("asm".to_string(), "asm:".to_string()));
    config.label_reference_commands.insert("asmref".to_string());
    config
        .label_reference_prefixes
        .push(("asmref".to_string(), "asm:".to_string()));

    check_with_syntax_config(
        config,
        r#"
%! main.tex
    \begin{enumerate}\label{baz}
        \asm{foo}{what}
    \end{enumerate}

    \asmref{}
            |
% Comment"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "foo",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "foo",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_custom_label_multiple_prefix_custom_ref() {
    let mut config = SyntaxConfig::default();
    config
        .label_definition_commands
        .extend(vec!["asm", "goal"].into_iter().map(String::from));
    config.label_definition_prefixes.extend(
        vec![("asm", "asm:"), ("goal", "goal:")]
            .into_iter()
            .map(|(x, y)| (String::from(x), String::from(y))),
    );
    config
        .label_reference_commands
        .extend(vec!["asmref", "goalref"].into_iter().map(String::from));
    config.label_reference_prefixes.extend(
        vec![("asmref", "asm:"), ("goalref", "goal:")]
            .into_iter()
            .map(|(x, y)| (String::from(x), String::from(y))),
    );

    check_with_syntax_config(
        config,
        r#"
%! main.tex
    \begin{enumerate}\label{baz}
        \asm{foo}{what}
        \goal{foo}{what}
    \end{enumerate}

    \goalref{}
             |
% Comment"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "foo",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "foo",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_custom_label_multiple_prefix_ref() {
    let mut config = SyntaxConfig::default();
    config
        .label_definition_commands
        .extend(vec!["asm", "goal"].into_iter().map(String::from));
    config.label_definition_prefixes.extend(
        vec![("asm", "asm:"), ("goal", "goal:")]
            .into_iter()
            .map(|(x, y)| (String::from(x), String::from(y))),
    );
    config
        .label_reference_commands
        .extend(vec!["asmref", "goalref"].into_iter().map(String::from));
    config.label_reference_prefixes.extend(
        vec![("asmref", "asm:"), ("goalref", "goal:")]
            .into_iter()
            .map(|(x, y)| (String::from(x), String::from(y))),
    );

    check_with_syntax_config(
        config,
        r#"
%! main.tex
    \begin{enumerate}\label{baz}
        \asm{foo}{what}
        \goal{foo}{what}
    \end{enumerate}

    \ref{}
         |
% Comment"#,
        expect![[r#"
            [
                Label(
                    LabelData {
                        name: "asm:foo",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "asm:foo",
                    },
                ),
                Label(
                    LabelData {
                        name: "baz",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "baz",
                    },
                ),
                Label(
                    LabelData {
                        name: "goal:foo",
                        header: None,
                        footer: None,
                        object: None,
                        keywords: "goal:foo",
                    },
                ),
            ]
        "#]],
    );
}

#[test]
fn test_custom_glossary_reference_command() {
    let mut config = SyntaxConfig::default();
    config
        .glossary_reference_commands
        .insert("glsed".to_string());

    check_with_syntax_config(
        config,
        r#"
%! main.tex
\newglossaryentry{foo}{
  name={Foo Bar Baz},
  description={The trio of default variable names.}
}

\glsed{}
       |"#,
        expect![[r#"
            [
                GlossaryEntry(
                    GlossaryEntryData {
                        name: "foo",
                    },
                ),
            ]
        "#]],
    );
}
