summaryrefslogtreecommitdiff
path: root/support/texlab/crates/hover
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-07-31 03:02:46 +0000
committerNorbert Preining <norbert@preining.info>2023-07-31 03:02:46 +0000
commit17d547a1effe2cafcdfbf704bdf8cb0484790ef2 (patch)
tree30556f1ebbb411da2d3e7297c0c4a2ffbd5af4ee /support/texlab/crates/hover
parent595d37aac232836c0519c45f2078c5272122eb32 (diff)
CTAN sync 202307310302
Diffstat (limited to 'support/texlab/crates/hover')
-rw-r--r--support/texlab/crates/hover/Cargo.toml21
-rw-r--r--support/texlab/crates/hover/src/citation.rs39
-rw-r--r--support/texlab/crates/hover/src/entry_type.rs18
-rw-r--r--support/texlab/crates/hover/src/field_type.rs21
-rw-r--r--support/texlab/crates/hover/src/label.rs28
-rw-r--r--support/texlab/crates/hover/src/lib.rs61
-rw-r--r--support/texlab/crates/hover/src/package.rs20
-rw-r--r--support/texlab/crates/hover/src/string_ref.rs35
-rw-r--r--support/texlab/crates/hover/src/tests.rs323
9 files changed, 566 insertions, 0 deletions
diff --git a/support/texlab/crates/hover/Cargo.toml b/support/texlab/crates/hover/Cargo.toml
new file mode 100644
index 0000000000..f92939dd24
--- /dev/null
+++ b/support/texlab/crates/hover/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "hover"
+version = "0.0.0"
+license.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+base-db = { path = "../base-db" }
+citeproc = { path = "../citeproc" }
+completion-data = { path = "../completion-data" }
+rowan = "0.15.11"
+syntax = { path = "../syntax" }
+
+[dev-dependencies]
+expect-test = "1.4.1"
+test-utils = { path = "../test-utils" }
+
+[lib]
+doctest = false
diff --git a/support/texlab/crates/hover/src/citation.rs b/support/texlab/crates/hover/src/citation.rs
new file mode 100644
index 0000000000..d36370e709
--- /dev/null
+++ b/support/texlab/crates/hover/src/citation.rs
@@ -0,0 +1,39 @@
+use base_db::{util::queries, DocumentData};
+use rowan::ast::AstNode;
+use syntax::bibtex;
+
+use crate::{Hover, HoverData, HoverParams};
+
+pub(super) fn find_hover<'db>(params: &HoverParams<'db>) -> Option<Hover<'db>> {
+ let offset = params.offset;
+
+ let (name, range) = match &params.document.data {
+ DocumentData::Tex(data) => {
+ let result = queries::object_at_cursor(
+ &data.semantics.citations,
+ offset,
+ queries::SearchMode::Full,
+ )?;
+ (&result.object.name.text, result.range)
+ }
+ DocumentData::Bib(data) => {
+ let result = queries::object_at_cursor(
+ &data.semantics.entries,
+ offset,
+ queries::SearchMode::Name,
+ )?;
+ (&result.object.name.text, result.range)
+ }
+ _ => return None,
+ };
+
+ let text = params.project.documents.iter().find_map(|document| {
+ let data = document.data.as_bib()?;
+ let root = bibtex::Root::cast(data.root_node())?;
+ let entry = root.find_entry(&name)?;
+ citeproc::render(&entry)
+ })?;
+
+ let data = HoverData::Citation(text);
+ Some(Hover { range, data })
+}
diff --git a/support/texlab/crates/hover/src/entry_type.rs b/support/texlab/crates/hover/src/entry_type.rs
new file mode 100644
index 0000000000..c28c88b611
--- /dev/null
+++ b/support/texlab/crates/hover/src/entry_type.rs
@@ -0,0 +1,18 @@
+use base_db::data::BibtexEntryType;
+use syntax::bibtex;
+
+use crate::{Hover, HoverData, HoverParams};
+
+pub(super) fn find_hover<'db>(params: &'db HoverParams) -> Option<Hover<'db>> {
+ let data = params.document.data.as_bib()?;
+ let root = data.root_node();
+ let name = root
+ .token_at_offset(params.offset)
+ .find(|x| x.kind() == bibtex::TYPE)?;
+
+ let entry_type = BibtexEntryType::find(&name.text()[1..])?;
+ Some(Hover {
+ range: name.text_range(),
+ data: HoverData::EntryType(entry_type),
+ })
+}
diff --git a/support/texlab/crates/hover/src/field_type.rs b/support/texlab/crates/hover/src/field_type.rs
new file mode 100644
index 0000000000..18ebe6b78f
--- /dev/null
+++ b/support/texlab/crates/hover/src/field_type.rs
@@ -0,0 +1,21 @@
+use base_db::data::BibtexFieldType;
+use rowan::ast::AstNode;
+use syntax::bibtex;
+
+use crate::{Hover, HoverData, HoverParams};
+
+pub(super) fn find_hover<'db>(params: &HoverParams<'db>) -> Option<Hover<'db>> {
+ let data = params.document.data.as_bib()?;
+ let root = data.root_node();
+ let name = root
+ .token_at_offset(params.offset)
+ .find(|token| token.kind() == bibtex::NAME)?;
+
+ bibtex::Field::cast(name.parent()?)?;
+
+ let field_type = BibtexFieldType::find(name.text())?;
+ Some(Hover {
+ range: name.text_range(),
+ data: HoverData::FieldType(field_type),
+ })
+}
diff --git a/support/texlab/crates/hover/src/label.rs b/support/texlab/crates/hover/src/label.rs
new file mode 100644
index 0000000000..c5f72a287e
--- /dev/null
+++ b/support/texlab/crates/hover/src/label.rs
@@ -0,0 +1,28 @@
+use base_db::{
+ semantics::tex,
+ util::{
+ queries::{self, Object},
+ render_label,
+ },
+};
+
+use crate::{Hover, HoverData, HoverParams};
+
+pub(super) fn find_hover<'db>(params: &'db HoverParams<'db>) -> Option<Hover<'db>> {
+ let data = params.document.data.as_tex()?;
+ let cursor = queries::object_at_cursor(
+ &data.semantics.labels,
+ params.offset,
+ queries::SearchMode::Full,
+ )?;
+
+ let (_, definition) = tex::Label::find_all(&params.project)
+ .filter(|(_, label)| label.kind == tex::LabelKind::Definition)
+ .find(|(_, label)| label.name_text() == cursor.object.name_text())?;
+
+ let label = render_label(&params.workspace, &params.project, definition)?;
+ Some(Hover {
+ range: cursor.range,
+ data: HoverData::Label(label),
+ })
+}
diff --git a/support/texlab/crates/hover/src/lib.rs b/support/texlab/crates/hover/src/lib.rs
new file mode 100644
index 0000000000..4b06111603
--- /dev/null
+++ b/support/texlab/crates/hover/src/lib.rs
@@ -0,0 +1,61 @@
+mod citation;
+mod entry_type;
+mod field_type;
+mod label;
+mod package;
+mod string_ref;
+
+use base_db::{
+ data::{BibtexEntryType, BibtexFieldType},
+ util::RenderedLabel,
+ Document, Project, Workspace,
+};
+use rowan::{TextRange, TextSize};
+
+#[derive(Debug)]
+pub struct HoverParams<'db> {
+ pub document: &'db Document,
+ pub project: Project<'db>,
+ pub workspace: &'db Workspace,
+ pub offset: TextSize,
+}
+
+impl<'db> HoverParams<'db> {
+ pub fn new(workspace: &'db Workspace, document: &'db Document, offset: TextSize) -> Self {
+ let project = workspace.project(document);
+ Self {
+ document,
+ project,
+ workspace,
+ offset,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Hover<'db> {
+ pub range: TextRange,
+ pub data: HoverData<'db>,
+}
+
+#[derive(Debug, Clone)]
+pub enum HoverData<'db> {
+ Citation(String),
+ Package(&'db str),
+ EntryType(BibtexEntryType<'db>),
+ FieldType(BibtexFieldType<'db>),
+ Label(RenderedLabel<'db>),
+ StringRef(String),
+}
+
+pub fn find<'db>(params: &'db HoverParams<'db>) -> Option<Hover<'db>> {
+ citation::find_hover(&params)
+ .or_else(|| package::find_hover(&params))
+ .or_else(|| entry_type::find_hover(&params))
+ .or_else(|| field_type::find_hover(&params))
+ .or_else(|| label::find_hover(&params))
+ .or_else(|| string_ref::find_hover(&params))
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/support/texlab/crates/hover/src/package.rs b/support/texlab/crates/hover/src/package.rs
new file mode 100644
index 0000000000..1d6b328860
--- /dev/null
+++ b/support/texlab/crates/hover/src/package.rs
@@ -0,0 +1,20 @@
+use base_db::semantics::tex::LinkKind;
+
+use crate::{Hover, HoverData, HoverParams};
+
+pub(super) fn find_hover<'db>(params: &HoverParams<'db>) -> Option<Hover<'db>> {
+ let data = params.document.data.as_tex()?;
+ data.semantics
+ .links
+ .iter()
+ .filter(|link| matches!(link.kind, LinkKind::Sty | LinkKind::Cls))
+ .filter(|link| link.path.range.contains_inclusive(params.offset))
+ .find_map(|link| {
+ let meta = completion_data::DATABASE.meta(&link.path.text)?;
+ let description = meta.description.as_deref()?;
+ Some(Hover {
+ range: link.path.range,
+ data: HoverData::Package(description),
+ })
+ })
+}
diff --git a/support/texlab/crates/hover/src/string_ref.rs b/support/texlab/crates/hover/src/string_ref.rs
new file mode 100644
index 0000000000..64449909d7
--- /dev/null
+++ b/support/texlab/crates/hover/src/string_ref.rs
@@ -0,0 +1,35 @@
+use citeproc::field::text::TextFieldData;
+use rowan::ast::AstNode;
+use syntax::bibtex::{self, HasName, HasValue};
+
+use crate::{Hover, HoverData, HoverParams};
+
+pub(super) fn find_hover<'db>(params: &HoverParams<'db>) -> Option<Hover<'db>> {
+ let data = params.document.data.as_bib()?;
+ let root = bibtex::Root::cast(data.root_node())?;
+ let name = root
+ .syntax()
+ .token_at_offset(params.offset)
+ .find(|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 root.strings() {
+ if !string
+ .name_token()
+ .map_or(false, |token| token.text() == name.text())
+ {
+ continue;
+ }
+
+ let value = TextFieldData::parse(&string.value()?)?.text;
+ return Some(Hover {
+ range: name.text_range(),
+ data: HoverData::StringRef(value),
+ });
+ }
+
+ None
+}
diff --git a/support/texlab/crates/hover/src/tests.rs b/support/texlab/crates/hover/src/tests.rs
new file mode 100644
index 0000000000..2890af938f
--- /dev/null
+++ b/support/texlab/crates/hover/src/tests.rs
@@ -0,0 +1,323 @@
+use expect_test::{expect, Expect};
+
+use crate::HoverParams;
+
+fn check(input: &str, expect: Expect) {
+ let fixture = test_utils::fixture::Fixture::parse(input);
+ let workspace = &fixture.workspace;
+ let document = workspace.lookup(&fixture.documents[0].uri).unwrap();
+ let offset = fixture.documents[0].cursor.unwrap();
+ let params = HoverParams::new(workspace, document, offset);
+
+ let data = crate::find(&params).map(|hover| {
+ assert_eq!(fixture.documents[0].ranges[0], hover.range);
+ hover.data
+ });
+
+ expect.assert_debug_eq(&data);
+}
+
+#[test]
+fn test_smoke() {
+ check(
+ r#"
+%! main.tex
+
+|"#,
+ expect![[r#"
+ None
+ "#]],
+ );
+}
+
+#[test]
+fn test_latex_citation() {
+ check(
+ r#"
+%! main.tex
+\addbibresource{main.bib}
+\cite{foo}
+ |
+ ^^^
+%! main.bib
+@article{foo, author = {Foo Bar}, title = {Baz Qux}, year = 1337}"#,
+ expect![[r#"
+ Some(
+ Citation(
+ "F. Bar: \"Baz Qux\". (1337).",
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_entry_key() {
+ check(
+ r#"
+%! main.bib
+@article{foo, author = {Foo Bar}, title = {Baz Qux}, year = 1337}
+ |
+ ^^^
+
+%! main.tex
+\addbibresource{main.bib}
+\cite{foo}"#,
+ expect![[r#"
+ Some(
+ Citation(
+ "F. Bar: \"Baz Qux\". (1337).",
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_entry_key_empty() {
+ check(
+ r#"
+%! main.bib
+@foo{bar,}
+ |"#,
+ expect![[r#"
+ None
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_entry_type_known() {
+ check(
+ r#"
+%! main.bib
+@article{foo,}
+ |
+^^^^^^^^"#,
+ expect![[r#"
+ Some(
+ EntryType(
+ BibtexEntryType {
+ name: "article",
+ category: Article,
+ documentation: Some(
+ "An article in a journal, magazine, newspaper, or other periodical which forms a \n self-contained unit with its own title. The title of the periodical is given in the \n journaltitle field. If the issue has its own title in addition to the main title of \n the periodical, it goes in the issuetitle field. Note that editor and related \n fields refer to the journal while translator and related fields refer to the article.\n\nRequired fields: `author`, `title`, `journaltitle`, `year/date`",
+ ),
+ },
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_entry_type_unknown() {
+ check(
+ r#"
+%! main.bib
+@foo{bar,}
+ |"#,
+ expect![[r#"
+ None
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_field_known() {
+ check(
+ r#"
+%! main.bib
+@article{foo, author = bar}
+ |
+ ^^^^^^"#,
+ expect![[r#"
+ Some(
+ FieldType(
+ BibtexFieldType {
+ name: "author",
+ documentation: "The author(s) of the `title`.",
+ },
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_field_unknown() {
+ check(
+ r#"
+%! main.bib
+@article{foo, bar = baz}
+ |"#,
+ expect![[r#"
+ None
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_string_ref() {
+ check(
+ r#"
+%! main.bib
+@string{foo = "Foo"}
+@string{bar = "Bar"}
+@article{baz, author = bar}
+ |
+ ^^^"#,
+ expect![[r#"
+ Some(
+ StringRef(
+ "Bar",
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_bibtex_value() {
+ check(
+ r#"
+%! main.bib
+@string{foo = "Foo"}
+@string{bar = "Bar"}
+@article{baz, author = bar}
+ |"#,
+ expect![[r#"
+ None
+ "#]],
+ );
+}
+
+#[test]
+fn test_latex_package_known() {
+ check(
+ r#"
+%! main.tex
+\usepackage{amsmath}
+ |
+ ^^^^^^^"#,
+ expect![[r#"
+ Some(
+ Package(
+ "The package provides the principal packages in the AMS-LaTeX distribution. It adapts for use in LaTeX most of the mathematical features found in AMS-TeX; it is highly recommended as an adjunct to serious mathematical typesetting in LaTeX. When amsmath is loaded, AMS-LaTeX packages amsbsy (for bold symbols), amsopn (for operator names) and amstext (for text embedded in mathematics) are also loaded. amsmath is part of the LaTeX required distribution; however, several contributed packages add still further to its appeal; examples are empheq, which provides functions for decorating and highlighting mathematics, and ntheorem, for specifying theorem (and similar) definitions.",
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_latex_class_unknown() {
+ check(
+ r#"
+%! main.tex
+\documentclass{abcdefghijklmnop}
+ |"#,
+ expect![[r#"
+ None
+ "#]],
+ );
+}
+
+#[test]
+fn test_latex_label_section() {
+ check(
+ r#"
+%! main.tex
+\section{Foo}
+\label{sec:foo}
+ |
+ ^^^^^^^"#,
+ expect![[r#"
+ Some(
+ Label(
+ RenderedLabel {
+ range: 0..29,
+ number: None,
+ object: Section {
+ prefix: "Section",
+ text: "Foo",
+ },
+ },
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_latex_label_theorem_child_file() {
+ check(
+ r#"
+%! main.tex
+\documentclass{article}
+\newtheorem{lemma}{Lemma}
+\include{child}
+\ref{thm:foo}
+ |
+ ^^^^^^^
+
+%! child.tex
+\begin{lemma}\label{thm:foo}
+ 1 + 1 = 2
+\end{lemma}"#,
+ expect![[r#"
+ Some(
+ Label(
+ RenderedLabel {
+ range: 0..54,
+ number: None,
+ object: Theorem {
+ kind: "Lemma",
+ description: None,
+ },
+ },
+ ),
+ )
+ "#]],
+ );
+}
+
+#[test]
+fn test_latex_label_theorem_child_file_mumber() {
+ check(
+ r#"
+%! main.tex
+\documentclass{article}
+\newtheorem{lemma}{Lemma}
+\include{child}
+\ref{thm:foo}
+ |
+ ^^^^^^^
+
+%! child.tex
+\begin{lemma}[Foo]\label{thm:foo}
+ 1 + 1 = 2
+\end{lemma}
+
+%! child.aux
+\newlabel{thm:foo}{{1}{1}{Foo}{lemma.1}{}}"#,
+ expect![[r#"
+ Some(
+ Label(
+ RenderedLabel {
+ range: 0..59,
+ number: Some(
+ "1",
+ ),
+ object: Theorem {
+ kind: "Lemma",
+ description: Some(
+ "Foo",
+ ),
+ },
+ },
+ ),
+ )
+ "#]],
+ );
+}