diff options
Diffstat (limited to 'support/texlab/src/hover')
-rw-r--r-- | support/texlab/src/hover/bibtex/entry_type.rs | 116 | ||||
-rw-r--r-- | support/texlab/src/hover/bibtex/field.rs | 111 | ||||
-rw-r--r-- | support/texlab/src/hover/bibtex/mod.rs | 3 | ||||
-rw-r--r-- | support/texlab/src/hover/bibtex/string_reference.rs | 165 | ||||
-rw-r--r-- | support/texlab/src/hover/latex/citation.rs | 179 | ||||
-rw-r--r-- | support/texlab/src/hover/latex/component.rs | 95 | ||||
-rw-r--r-- | support/texlab/src/hover/latex/label.rs | 118 | ||||
-rw-r--r-- | support/texlab/src/hover/latex/mod.rs | 6 | ||||
-rw-r--r-- | support/texlab/src/hover/latex/preview.rs | 338 | ||||
-rw-r--r-- | support/texlab/src/hover/mod.rs | 58 |
10 files changed, 0 insertions, 1189 deletions
diff --git a/support/texlab/src/hover/bibtex/entry_type.rs b/support/texlab/src/hover/bibtex/entry_type.rs deleted file mode 100644 index 1e009565d7..0000000000 --- a/support/texlab/src/hover/bibtex/entry_type.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::{ - feature::{FeatureProvider, FeatureRequest}, - protocol::{ - Hover, HoverContents, MarkupContent, MarkupKind, RangeExt, TextDocumentPositionParams, - }, - syntax::{SyntaxNode, LANGUAGE_DATA}, -}; -use async_trait::async_trait; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct BibtexEntryTypeHoverProvider; - -#[async_trait] -impl FeatureProvider for BibtexEntryTypeHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - let tree = req.current().content.as_bibtex()?; - for entry in tree - .children(tree.root) - .filter_map(|node| tree.as_entry(node)) - { - if entry.ty.range().contains(req.params.position) { - let ty = &entry.ty.text()[1..]; - let docs = LANGUAGE_DATA.entry_type_documentation(ty)?; - return Some(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: docs.into(), - }), - range: Some(entry.ty.range()), - }); - } - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{feature::FeatureTester, protocol::Range}; - - #[tokio::test] - async fn empty_latex_document() { - let actual_hover = FeatureTester::new() - .file("main.tex", "") - .main("main.tex") - .position(0, 0) - .test_position(BibtexEntryTypeHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn empty_bibtex_document() { - let actual_hover = FeatureTester::new() - .file("main.bib", "") - .main("main.bib") - .position(0, 0) - .test_position(BibtexEntryTypeHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn known_entry_type() { - let actual_hover = FeatureTester::new() - .file("main.bib", "@article{foo,}") - .main("main.bib") - .position(0, 3) - .test_position(BibtexEntryTypeHoverProvider) - .await - .unwrap(); - - let expected_hover = Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: LANGUAGE_DATA - .entry_type_documentation("article") - .unwrap() - .into(), - }), - range: Some(Range::new_simple(0, 0, 0, 8)), - }; - - assert_eq!(actual_hover, expected_hover); - } - - #[tokio::test] - async fn unknown_entry_type() { - let actual_hover = FeatureTester::new() - .file("main.bib", "@foo{bar,}") - .main("main.bib") - .position(0, 3) - .test_position(BibtexEntryTypeHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn entry_key() { - let actual_hover = FeatureTester::new() - .file("main.bib", "@article{foo,}") - .main("main.bib") - .position(0, 11) - .test_position(BibtexEntryTypeHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } -} diff --git a/support/texlab/src/hover/bibtex/field.rs b/support/texlab/src/hover/bibtex/field.rs deleted file mode 100644 index 4b00f3364c..0000000000 --- a/support/texlab/src/hover/bibtex/field.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::{ - feature::{FeatureProvider, FeatureRequest}, - protocol::{ - Hover, HoverContents, MarkupContent, MarkupKind, RangeExt, TextDocumentPositionParams, - }, - syntax::{SyntaxNode, LANGUAGE_DATA}, -}; -use async_trait::async_trait; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct BibtexFieldHoverProvider; - -#[async_trait] -impl FeatureProvider for BibtexFieldHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - let tree = req.current().content.as_bibtex()?; - let name = tree - .find(req.params.position) - .into_iter() - .filter_map(|node| tree.as_field(node)) - .map(|field| &field.name) - .find(|name| name.range().contains(req.params.position))?; - - let docs = LANGUAGE_DATA.field_documentation(name.text())?; - Some(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: docs.into(), - }), - range: Some(name.range()), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{feature::FeatureTester, protocol::Range}; - - #[tokio::test] - async fn empty_latex_document() { - let actual_hover = FeatureTester::new() - .file("main.tex", "") - .main("main.tex") - .position(0, 0) - .test_position(BibtexFieldHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn empty_bibtex_document() { - let actual_hover = FeatureTester::new() - .file("main.bib", "") - .main("main.bib") - .position(0, 0) - .test_position(BibtexFieldHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn known_field() { - let actual_hover = FeatureTester::new() - .file("main.bib", "@article{foo, author = bar}") - .main("main.bib") - .position(0, 15) - .test_position(BibtexFieldHoverProvider) - .await - .unwrap(); - - let expected_hover = Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: LANGUAGE_DATA.field_documentation("author").unwrap().into(), - }), - range: Some(Range::new_simple(0, 14, 0, 20)), - }; - - assert_eq!(actual_hover, expected_hover); - } - - #[tokio::test] - async fn unknown_field() { - let actual_hover = FeatureTester::new() - .file("main.bib", "@article{foo, bar = baz}") - .main("main.bib") - .position(0, 15) - .test_position(BibtexFieldHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn entry_key() { - let actual_hover = FeatureTester::new() - .file("main.bib", "@article{foo, bar = baz}") - .main("main.bib") - .position(0, 11) - .test_position(BibtexFieldHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } -} diff --git a/support/texlab/src/hover/bibtex/mod.rs b/support/texlab/src/hover/bibtex/mod.rs deleted file mode 100644 index 59ff356fa0..0000000000 --- a/support/texlab/src/hover/bibtex/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod entry_type; -pub mod field; -pub mod string_reference; diff --git a/support/texlab/src/hover/bibtex/string_reference.rs b/support/texlab/src/hover/bibtex/string_reference.rs deleted file mode 100644 index 46724a4f60..0000000000 --- a/support/texlab/src/hover/bibtex/string_reference.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::{ - feature::{FeatureProvider, FeatureRequest}, - protocol::{ - BibtexFormattingOptions, Hover, HoverContents, MarkupContent, MarkupKind, Position, - TextDocumentPositionParams, - }, - syntax::{bibtex, SyntaxNode}, -}; -use async_trait::async_trait; -use petgraph::graph::NodeIndex; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct BibtexStringReferenceHoverProvider; - -#[async_trait] -impl FeatureProvider for BibtexStringReferenceHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - let tree = req.current().content.as_bibtex()?; - let reference = Self::find_reference(tree, req.params.position)?; - for string_node in tree.children(tree.root) { - let hover = Self::find_definition(tree, string_node, reference); - if hover.is_some() { - return hover; - } - } - None - } -} - -impl BibtexStringReferenceHoverProvider { - fn find_reference(tree: &bibtex::Tree, pos: Position) -> Option<&bibtex::Token> { - let mut results = tree.find(pos); - results.reverse(); - match ( - &tree.graph[results[0]], - results.get(1).map(|node| &tree.graph[*node]), - ) { - (bibtex::Node::Word(reference), Some(bibtex::Node::Concat(_))) => { - Some(&reference.token) - } - (bibtex::Node::Word(reference), Some(bibtex::Node::Field(_))) => Some(&reference.token), - _ => None, - } - } - - fn find_definition( - tree: &bibtex::Tree, - string_node: NodeIndex, - reference: &bibtex::Token, - ) -> Option<Hover> { - let string = tree.as_string(string_node)?; - if string.name.as_ref()?.text() != reference.text() { - return None; - } - - let options = BibtexFormattingOptions { - line_length: None, - formatter: None, - }; - let text = bibtex::format( - tree, - tree.children(string_node).next()?, - bibtex::FormattingParams { - tab_size: 4, - insert_spaces: true, - options: &options, - }, - ); - Some(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::PlainText, - value: text, - }), - range: Some(reference.range()), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - feature::FeatureTester, - protocol::{Range, RangeExt}, - }; - use indoc::indoc; - - #[tokio::test] - async fn empty_latex_document() { - let actual_hover = FeatureTester::new() - .file("main.tex", "") - .main("main.tex") - .position(0, 0) - .test_position(BibtexStringReferenceHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn empty_bibtex_document() { - let actual_hover = FeatureTester::new() - .file("main.bib", "") - .main("main.bib") - .position(0, 0) - .test_position(BibtexStringReferenceHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn inside_reference() { - let actual_hover = FeatureTester::new() - .file( - "main.bib", - indoc!( - r#" - @string{foo = "Foo"} - @string{bar = "Bar"} - @article{baz, author = bar} - "# - ), - ) - .main("main.bib") - .position(2, 24) - .test_position(BibtexStringReferenceHoverProvider) - .await - .unwrap(); - - let expected_hover = Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::PlainText, - value: "\"Bar\"".into(), - }), - range: Some(Range::new_simple(2, 23, 2, 26)), - }; - - assert_eq!(actual_hover, expected_hover); - } - - #[tokio::test] - async fn outside_reference() { - let actual_hover = FeatureTester::new() - .file( - "main.bib", - indoc!( - r#" - @string{foo = "Foo"} - @string{bar = "Bar"} - @article{baz, author = bar} - "# - ), - ) - .main("main.bib") - .position(2, 20) - .test_position(BibtexStringReferenceHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } -} diff --git a/support/texlab/src/hover/latex/citation.rs b/support/texlab/src/hover/latex/citation.rs deleted file mode 100644 index 8b5468e52e..0000000000 --- a/support/texlab/src/hover/latex/citation.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::{ - citeproc::render_citation, - feature::{FeatureProvider, FeatureRequest}, - protocol::{Hover, HoverContents, RangeExt, TextDocumentPositionParams}, - syntax::{bibtex, Span, SyntaxNode}, - workspace::DocumentContent, -}; -use async_trait::async_trait; -use log::warn; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct LatexCitationHoverProvider; - -#[async_trait] -impl FeatureProvider for LatexCitationHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - let (tree, src_key, entry) = Self::get_entry(req)?; - if entry.is_comment() { - None - } else { - let key = entry.key.as_ref()?; - match render_citation(&tree, key.text()) { - Some(markdown) => Some(Hover { - contents: HoverContents::Markup(markdown), - range: Some(src_key.range()), - }), - None => { - warn!("Failed to render entry: {}", key.text()); - None - } - } - } - } -} - -impl LatexCitationHoverProvider { - fn get_entry( - req: &FeatureRequest<TextDocumentPositionParams>, - ) -> Option<(&bibtex::Tree, &Span, &bibtex::Entry)> { - let key = Self::get_key(req)?; - for tree in req - .related() - .iter() - .filter_map(|doc| doc.content.as_bibtex()) - { - for entry in tree - .children(tree.root) - .filter_map(|node| tree.as_entry(node)) - { - if let Some(current_key) = &entry.key { - if current_key.text() == key.text { - return Some((tree, key, entry)); - } - } - } - } - None - } - - fn get_key(req: &FeatureRequest<TextDocumentPositionParams>) -> Option<&Span> { - match &req.current().content { - DocumentContent::Latex(table) => table - .citations - .iter() - .flat_map(|citation| citation.keys(&table)) - .find(|key| key.range().contains(req.params.position)) - .map(|token| &token.span), - DocumentContent::Bibtex(tree) => tree - .children(tree.root) - .filter_map(|node| tree.as_entry(node)) - .filter_map(|entry| entry.key.as_ref()) - .find(|key| key.range().contains(req.params.position)) - .map(|token| &token.span), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - feature::FeatureTester, - protocol::{MarkupContent, MarkupKind, Range, RangeExt}, - }; - use indoc::indoc; - - #[tokio::test] - async fn empty_latex_document() { - let actual_hover = FeatureTester::new() - .file("main.tex", "") - .main("main.tex") - .position(0, 0) - .test_position(LatexCitationHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn empty_bibtex_document() { - let actual_hover = FeatureTester::new() - .file("main.bib", "") - .main("main.bib") - .position(0, 0) - .test_position(LatexCitationHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn inside_label() { - let actual_hover = FeatureTester::new() - .file( - "main.bib", - "@article{foo, author = {Foo Bar}, title = {Baz Qux}, year = 1337}", - ) - .file( - "main.tex", - indoc!( - r#" - \addbibresource{main.bib} - \cite{foo} - "# - ), - ) - .main("main.tex") - .position(1, 7) - .test_position(LatexCitationHoverProvider) - .await - .unwrap(); - - let expected_hover = Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: "Bar, F. (1337). *Baz Qux*.".into(), - }), - range: Some(Range::new_simple(1, 6, 1, 9)), - }; - - assert_eq!(actual_hover, expected_hover); - } - - #[tokio::test] - async fn inside_entry() { - let actual_hover = FeatureTester::new() - .file( - "main.bib", - "@article{foo, author = {Foo Bar}, title = {Baz Qux}, year = 1337}", - ) - .file( - "main.tex", - indoc!( - r#" - \addbibresource{main.bib} - \cite{foo} - "# - ), - ) - .main("main.bib") - .position(0, 11) - .test_position(LatexCitationHoverProvider) - .await - .unwrap(); - - let expected_hover = Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: "Bar, F. (1337). *Baz Qux*.".into(), - }), - range: Some(Range::new_simple(0, 9, 0, 12)), - }; - - assert_eq!(actual_hover, expected_hover); - } -} diff --git a/support/texlab/src/hover/latex/component.rs b/support/texlab/src/hover/latex/component.rs deleted file mode 100644 index 5285ec795a..0000000000 --- a/support/texlab/src/hover/latex/component.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::{ - components::COMPONENT_DATABASE, - feature::{FeatureProvider, FeatureRequest}, - protocol::{Hover, HoverContents, RangeExt, TextDocumentPositionParams}, - syntax::{LatexIncludeKind, SyntaxNode}, -}; -use async_trait::async_trait; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct LatexComponentHoverProvider; - -#[async_trait] -impl FeatureProvider for LatexComponentHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - let table = req.current().content.as_latex()?; - for include in &table.includes { - match include.kind { - LatexIncludeKind::Package | LatexIncludeKind::Class => { - for path in include.paths(&table) { - if path.range().contains(req.params.position) { - let docs = COMPONENT_DATABASE.documentation(path.text())?; - return Some(Hover { - contents: HoverContents::Markup(docs), - range: Some(path.range()), - }); - } - } - } - _ => {} - } - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - feature::FeatureTester, - protocol::{Range, RangeExt}, - }; - - #[tokio::test] - async fn empty_latex_document() { - let actual_hover = FeatureTester::new() - .file("main.tex", "") - .main("main.tex") - .position(0, 0) - .test_position(LatexComponentHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn empty_bibtex_document() { - let actual_hover = FeatureTester::new() - .file("main.bib", "") - .main("main.bib") - .position(0, 0) - .test_position(LatexComponentHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn known_package() { - let actual_hover = FeatureTester::new() - .file("main.tex", r#"\usepackage{amsmath}"#) - .main("main.tex") - .position(0, 15) - .test_position(LatexComponentHoverProvider) - .await - .unwrap(); - - assert_eq!(actual_hover.range.unwrap(), Range::new_simple(0, 12, 0, 19)); - } - - #[tokio::test] - async fn unknown_class() { - let actual_hover = FeatureTester::new() - .file("main.tex", r#"\documentclass{abcdefghijklmnop}"#) - .main("main.tex") - .position(0, 20) - .test_position(LatexComponentHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } -} diff --git a/support/texlab/src/hover/latex/label.rs b/support/texlab/src/hover/latex/label.rs deleted file mode 100644 index 66724d442b..0000000000 --- a/support/texlab/src/hover/latex/label.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::{ - feature::{DocumentView, FeatureProvider, FeatureRequest}, - outline::{Outline, OutlineContext}, - protocol::{Hover, HoverContents, Position, RangeExt, TextDocumentPositionParams}, - syntax::{latex, LatexLabelKind, SyntaxNode}, - workspace::{Document, DocumentContent}, -}; -use async_trait::async_trait; -use std::sync::Arc; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct LatexLabelHoverProvider; - -#[async_trait] -impl FeatureProvider for LatexLabelHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - let table = req.current().content.as_latex()?; - let reference = Self::find_reference(table, req.params.position)?; - let (doc, def) = Self::find_definition(&req.view, reference)?; - - let snapshot = Arc::clone(&req.view.snapshot); - let view = DocumentView::analyze(snapshot, doc, &req.options, &req.current_dir); - let outline = Outline::analyze(&view, &req.options, &req.current_dir); - let outline_ctx = OutlineContext::parse(&view, &outline, def)?; - let markup = outline_ctx.documentation(); - Some(Hover { - contents: HoverContents::Markup(markup), - range: Some(reference.range()), - }) - } -} - -impl LatexLabelHoverProvider { - fn find_reference(table: &latex::SymbolTable, pos: Position) -> Option<&latex::Token> { - for label in &table.labels { - let names = label.names(&table); - if names.len() == 1 && table[label.parent].range().contains(pos) { - return Some(&label.names(&table)[0]); - } - - for name in &names { - if name.range().contains(pos) { - return Some(name); - } - } - } - None - } - - fn find_definition( - view: &DocumentView, - reference: &latex::Token, - ) -> Option<(Arc<Document>, latex::Label)> { - for doc in &view.related { - if let DocumentContent::Latex(table) = &doc.content { - for label in &table.labels { - if label.kind == LatexLabelKind::Definition { - for name in label.names(&table) { - if name.text() == reference.text() { - return Some((Arc::clone(&doc), *label)); - } - } - } - } - } - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - feature::FeatureTester, - protocol::{Range, RangeExt}, - }; - - #[tokio::test] - async fn empty_latex_document() { - let actual_hover = FeatureTester::new() - .file("main.tex", "") - .main("main.tex") - .position(0, 0) - .test_position(LatexLabelHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn empty_bibtex_document() { - let actual_hover = FeatureTester::new() - .file("main.bib", "") - .main("main.bib") - .position(0, 0) - .test_position(LatexLabelHoverProvider) - .await; - - assert_eq!(actual_hover, None); - } - - #[tokio::test] - async fn section() { - let actual_hover = FeatureTester::new() - .file("main.tex", r#"\section{Foo}\label{sec:foo}"#) - .main("main.tex") - .position(0, 23) - .test_position(LatexLabelHoverProvider) - .await - .unwrap(); - - assert_eq!(actual_hover.range.unwrap(), Range::new_simple(0, 20, 0, 27)); - } -} diff --git a/support/texlab/src/hover/latex/mod.rs b/support/texlab/src/hover/latex/mod.rs deleted file mode 100644 index c119bfc832..0000000000 --- a/support/texlab/src/hover/latex/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(feature = "citation")] -pub mod citation; - -pub mod component; -pub mod label; -pub mod preview; diff --git a/support/texlab/src/hover/latex/preview.rs b/support/texlab/src/hover/latex/preview.rs deleted file mode 100644 index 8d2ca00cb5..0000000000 --- a/support/texlab/src/hover/latex/preview.rs +++ /dev/null @@ -1,338 +0,0 @@ -use crate::{ - components::COMPONENT_DATABASE, - feature::{FeatureProvider, FeatureRequest}, - protocol::{ - ClientCapabilitiesExt, Hover, HoverContents, MarkupContent, MarkupKind, Range, RangeExt, - TextDocumentPositionParams, - }, - syntax::{latex, CharStream, LatexIncludeKind, SyntaxNode}, - tex::{CompileError, CompileParams, DistributionKind, Format}, - workspace::DocumentContent, -}; -use async_trait::async_trait; -use image::{png::PNGEncoder, ColorType, DynamicImage, GenericImageView, ImageBuffer, RgbaImage}; -use io::Cursor; -use log::warn; -use std::{io, process::Stdio, time::Duration}; -use tempfile::TempDir; -use thiserror::Error; -use tokio::process::Command; - -const PREVIEW_ENVIRONMENTS: &[&str] = &[ - "align", - "alignat", - "aligned", - "alignedat", - "algorithmic", - "array", - "Bmatrix", - "bmatrix", - "cases", - "CD", - "eqnarray", - "equation", - "gather", - "gathered", - "matrix", - "multline", - "pmatrix", - "smallmatrix", - "split", - "subarray", - "Vmatrix", - "vmatrix", -]; - -const IGNORED_PACKAGES: &[&str] = &["biblatex", "pgf", "tikz"]; - -#[derive(Debug, Clone, Copy)] -enum MathElement { - Environment(latex::Environment), - Equation(latex::Equation), - Inline(latex::Inline), -} - -impl MathElement { - fn range(&self, tree: &latex::Tree) -> Range { - match self { - Self::Environment(env) => env.range(tree), - Self::Equation(eq) => eq.range(tree), - Self::Inline(inline) => inline.range(tree), - } - } -} - -#[derive(Debug, Error)] -enum RenderError { - #[error("an I/O error occurred: `{0}`")] - IO(#[from] io::Error), - #[error("a compile error occurred: `{0}`")] - Compile(#[from] CompileError), - #[error("compilation failed")] - DviNotFound, - #[error("dvipng is not installed")] - DviPngNotInstalled, - #[error("calling dvipng failed")] - DviPngFaulty, - #[error("failed to decode image")] - DecodeImage, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct LatexPreviewHoverProvider; - -impl LatexPreviewHoverProvider { - fn is_preview_environment( - req: &FeatureRequest<TextDocumentPositionParams>, - table: &latex::SymbolTable, - environment: latex::Environment, - ) -> bool { - let canonical_name = environment - .left - .name(&table) - .map(latex::Token::text) - .unwrap_or_default() - .replace('*', ""); - - PREVIEW_ENVIRONMENTS.contains(&canonical_name.as_ref()) - || Self::theorem_environments(req).contains(&canonical_name.as_ref()) - } - - fn theorem_environments(req: &FeatureRequest<TextDocumentPositionParams>) -> Vec<&str> { - let mut names = Vec::new(); - for doc in req.related() { - if let DocumentContent::Latex(table) = &doc.content { - table - .theorem_definitions - .iter() - .map(|thm| thm.name(&table).text()) - .for_each(|thm| names.push(thm)); - } - } - names - } - - async fn render( - req: &FeatureRequest<TextDocumentPositionParams>, - range: Range, - ) -> Result<Hover, RenderError> { - let code = Self::generate_code(req, range); - let params = CompileParams { - file_name: "preview.tex", - code: &code, - format: Format::Latex, - timeout: Duration::from_secs(10), - }; - let dir = req.distro.compile(params).await?.dir; - if !dir.path().join("preview.dvi").exists() { - return Err(RenderError::DviNotFound); - } - - let img = Self::add_margin(Self::dvipng(&dir).await?); - let base64 = Self::encode_image(img); - let markdown = format!("![preview](data:image/png;base64,{})", base64); - dir.close()?; - Ok(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: markdown, - }), - range: Some(range), - }) - } - - fn generate_code(req: &FeatureRequest<TextDocumentPositionParams>, range: Range) -> String { - let mut code = String::new(); - code.push_str("\\documentclass{article}\n"); - code.push_str("\\thispagestyle{empty}\n"); - Self::generate_includes(req, &mut code); - Self::generate_command_definitions(req, &mut code); - Self::generate_math_operators(req, &mut code); - Self::generate_theorem_definitions(req, &mut code); - code.push_str("\\begin{document}\n"); - code.push_str(&CharStream::extract(&req.current().text, range)); - code.push('\n'); - code.push_str("\\end{document}\n"); - code - } - - fn generate_includes(req: &FeatureRequest<TextDocumentPositionParams>, code: &mut String) { - for doc in req.related() { - if let DocumentContent::Latex(table) = &doc.content { - let text = &doc.text; - for include in &table.includes { - if include.kind == LatexIncludeKind::Package { - if include - .paths(&table) - .iter() - .all(|path| IGNORED_PACKAGES.contains(&path.text())) - { - continue; - } - - if include - .paths(&table) - .iter() - .map(|path| format!("{}.sty", path.text())) - .any(|name| !COMPONENT_DATABASE.exists(&name)) - { - continue; - } - - code.push_str(&CharStream::extract(&text, table[include.parent].range())); - code.push('\n'); - } - } - } - } - } - - fn generate_command_definitions( - req: &FeatureRequest<TextDocumentPositionParams>, - code: &mut String, - ) { - for doc in req.related() { - if let DocumentContent::Latex(table) = &doc.content { - table - .command_definitions - .iter() - .filter(|def| !def.definition_name(&table).contains("@")) - .map(|def| CharStream::extract(&doc.text, table[def.parent].range())) - .for_each(|def| { - code.push_str(&def); - code.push('\n'); - }); - } - } - } - - fn generate_math_operators( - req: &FeatureRequest<TextDocumentPositionParams>, - code: &mut String, - ) { - for doc in req.related() { - if let DocumentContent::Latex(table) = &doc.content { - table - .math_operators - .iter() - .filter(|op| !op.definition_name(&table).contains("@")) - .map(|op| CharStream::extract(&doc.text, table[op.parent].range())) - .for_each(|op| { - code.push_str(&op); - code.push('\n'); - }); - } - } - } - - fn generate_theorem_definitions( - req: &FeatureRequest<TextDocumentPositionParams>, - code: &mut String, - ) { - for doc in req.related() { - if let DocumentContent::Latex(table) = &doc.content { - table - .theorem_definitions - .iter() - .map(|thm| CharStream::extract(&doc.text, table[thm.parent].range())) - .for_each(|thm| { - code.push_str(&thm); - code.push('\n'); - }) - } - } - } - - async fn dvipng(dir: &TempDir) -> Result<DynamicImage, RenderError> { - let process = Command::new("dvipng") - .args(&["-D", "175", "-T", "tight", "preview.dvi"]) - .current_dir(dir.path()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .map_err(|_| RenderError::DviPngNotInstalled)?; - - process.await.map_err(|_| RenderError::DviPngFaulty)?; - - let png_file = dir.path().join("preview1.png"); - let png = image::open(png_file).map_err(|_| RenderError::DecodeImage)?; - Ok(png) - } - - fn add_margin(image: DynamicImage) -> RgbaImage { - let margin = 5; - let width = image.width() + 2 * margin; - let height = image.height() + 2 * margin; - let mut result = - ImageBuffer::from_pixel(width, height, image::Rgba([0xFF, 0xFF, 0xFF, 0xFF])); - - for x in 0..image.width() { - for y in 0..image.height() { - let pixel = image.get_pixel(x, y); - result.put_pixel(x + margin, y + margin, pixel); - } - } - result - } - - fn encode_image(image: RgbaImage) -> String { - let mut image_buf = Cursor::new(Vec::new()); - let png_encoder = PNGEncoder::new(&mut image_buf); - let width = image.width(); - let height = image.height(); - png_encoder - .encode(&image.into_raw(), width, height, ColorType::Rgba8) - .unwrap(); - base64::encode(&image_buf.into_inner()) - } -} - -#[async_trait] -impl FeatureProvider for LatexPreviewHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - if !req.client_capabilities.has_hover_markdown_support() - || req.distro.kind() == DistributionKind::Tectonic - { - return None; - } - - if let DocumentContent::Latex(table) = &req.current().content { - let mut elements = Vec::new(); - table - .inlines - .iter() - .map(|inline| MathElement::Inline(*inline)) - .for_each(|inline| elements.push(inline)); - - table - .equations - .iter() - .map(|eq| MathElement::Equation(*eq)) - .for_each(|eq| elements.push(eq)); - - table - .environments - .iter() - .filter(|env| Self::is_preview_environment(req, table, **env)) - .map(|env| MathElement::Environment(*env)) - .for_each(|env| elements.push(env)); - - let range = elements - .iter() - .map(|elem| elem.range(&table)) - .find(|range| range.contains(req.params.position))?; - - return match Self::render(req, range).await { - Ok(hover) => Some(hover), - Err(why) => { - warn!("Preview failed: {}", why); - None - } - }; - } - None - } -} diff --git a/support/texlab/src/hover/mod.rs b/support/texlab/src/hover/mod.rs deleted file mode 100644 index e740657add..0000000000 --- a/support/texlab/src/hover/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -mod bibtex; -mod latex; - -#[cfg(feature = "citation")] -use self::latex::citation::LatexCitationHoverProvider; - -use self::{ - bibtex::{ - entry_type::BibtexEntryTypeHoverProvider, field::BibtexFieldHoverProvider, - string_reference::BibtexStringReferenceHoverProvider, - }, - latex::{ - component::LatexComponentHoverProvider, label::LatexLabelHoverProvider, - preview::LatexPreviewHoverProvider, - }, -}; -use crate::{ - feature::{ChoiceProvider, FeatureProvider, FeatureRequest}, - protocol::{Hover, TextDocumentPositionParams}, -}; -use async_trait::async_trait; - -pub struct HoverProvider { - provider: ChoiceProvider<TextDocumentPositionParams, Hover>, -} - -impl HoverProvider { - pub fn new() -> Self { - Self { - provider: ChoiceProvider::new(vec![ - Box::new(BibtexEntryTypeHoverProvider), - Box::new(BibtexStringReferenceHoverProvider), - Box::new(BibtexFieldHoverProvider), - #[cfg(feature = "citation")] - Box::new(LatexCitationHoverProvider), - Box::new(LatexComponentHoverProvider), - Box::new(LatexLabelHoverProvider), - Box::new(LatexPreviewHoverProvider), - ]), - } - } -} - -impl Default for HoverProvider { - fn default() -> Self { - Self::new() - } -} - -#[async_trait] -impl FeatureProvider for HoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { - self.provider.execute(req).await - } -} |