diff options
Diffstat (limited to 'support/texlab/src/hover/latex_preview.rs')
-rw-r--r-- | support/texlab/src/hover/latex_preview.rs | 357 |
1 files changed, 0 insertions, 357 deletions
diff --git a/support/texlab/src/hover/latex_preview.rs b/support/texlab/src/hover/latex_preview.rs deleted file mode 100644 index 7af66f6a52..0000000000 --- a/support/texlab/src/hover/latex_preview.rs +++ /dev/null @@ -1,357 +0,0 @@ -use crate::capabilities::ClientCapabilitiesExt; -use crate::completion::DATABASE; -use crate::range::RangeExt; -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use image::png::PNGEncoder; -use image::{DynamicImage, GenericImage, GenericImageView}; -use log::*; -use lsp_types::*; -use std::io; -use std::io::Cursor; -use std::process::Stdio; -use std::time::Duration; -use tempfile::TempDir; -use tokio_net::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, PartialEq, Eq, Clone)] -enum MathElement<'a> { - Environment(&'a LatexEnvironment), - Equation(&'a LatexEquation), - Inline(&'a LatexInline), -} - -impl<'a> SyntaxNode for MathElement<'a> { - fn range(&self) -> Range { - match self { - MathElement::Environment(environment) => environment.range(), - MathElement::Equation(equation) => equation.range(), - MathElement::Inline(inline) => inline.range(), - } - } -} - -#[derive(Debug)] -enum RenderError { - IO(io::Error), - Compile(tex::CompileError), - DviNotFound, - DviPngNotInstalled, - DviPngFaulty, - DecodeImage, -} - -impl From<io::Error> for RenderError { - fn from(error: io::Error) -> Self { - RenderError::IO(error) - } -} - -impl From<tex::CompileError> for RenderError { - fn from(error: tex::CompileError) -> Self { - RenderError::Compile(error) - } -} - -pub struct LatexPreviewHoverProvider; - -impl LatexPreviewHoverProvider { - fn is_math_environment( - request: &FeatureRequest<TextDocumentPositionParams>, - environment: &LatexEnvironment, - ) -> bool { - let canonical_name = environment - .left - .name() - .map(LatexToken::text) - .unwrap_or_default() - .replace('*', ""); - - PREVIEW_ENVIRONMENTS.contains(&canonical_name.as_ref()) - || Self::theorem_environments(request).contains(&canonical_name.as_ref()) - } - - fn theorem_environments(request: &FeatureRequest<TextDocumentPositionParams>) -> Vec<&str> { - let mut names = Vec::new(); - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - tree.math - .theorem_definitions - .iter() - .map(|thm| thm.name().text()) - .for_each(|thm| names.push(thm)); - } - } - names - } - - async fn render( - request: &FeatureRequest<TextDocumentPositionParams>, - range: Range, - ) -> Result<Hover, RenderError> { - let code = Self::generate_code(request, range); - let params = tex::CompileParams { - file_name: "preview.tex", - code: &code, - format: tex::Format::Latex, - timeout: Duration::from_secs(10), - }; - let directory = request.distribution.compile(params).await?.directory; - - if !directory.path().join("preview.dvi").exists() { - return Err(RenderError::DviNotFound); - } - - let image = Self::add_margin(Self::dvipng(&directory).await?); - let base64 = Self::encode_image(image); - let markdown = format!("![preview](data:image/png;base64,{})", base64); - directory.close()?; - Ok(Hover { - range: Some(range), - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: markdown, - }), - }) - } - - fn generate_code(request: &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(request, &mut code); - Self::generate_command_definitions(request, &mut code); - Self::generate_math_operators(request, &mut code); - Self::generate_theorem_definitions(request, &mut code); - code.push_str("\\begin{document}\n"); - code.push_str(&CharStream::extract(&request.document().text, range)); - code.push('\n'); - code.push_str("\\end{document}\n"); - code - } - - fn generate_includes(request: &FeatureRequest<TextDocumentPositionParams>, code: &mut String) { - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - let text = &request.document().text; - for include in &tree.includes { - if include.kind == LatexIncludeKind::Package { - if include - .paths() - .iter() - .all(|path| IGNORED_PACKAGES.contains(&path.text())) - { - continue; - } - - if include - .paths() - .iter() - .map(|path| format!("{}.sty", path.text())) - .any(|name| !DATABASE.exists(&name)) - { - continue; - } - - code.push_str(&CharStream::extract(&text, include.command.range)); - code.push('\n'); - } - } - } - } - } - - fn generate_command_definitions( - request: &FeatureRequest<TextDocumentPositionParams>, - code: &mut String, - ) { - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - tree.command_definitions - .iter() - .map(|def| CharStream::extract(&document.text, def.range())) - .for_each(|def| { - code.push_str(&def); - code.push('\n'); - }); - } - } - } - - fn generate_math_operators( - request: &FeatureRequest<TextDocumentPositionParams>, - code: &mut String, - ) { - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - tree.math - .operators - .iter() - .map(|op| CharStream::extract(&document.text, op.range())) - .for_each(|op| { - code.push_str(&op); - code.push('\n'); - }); - } - } - } - - fn generate_theorem_definitions( - request: &FeatureRequest<TextDocumentPositionParams>, - code: &mut String, - ) { - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - tree.math - .theorem_definitions - .iter() - .map(|thm| CharStream::extract(&document.text, thm.range())) - .for_each(|thm| { - code.push_str(&thm); - code.push('\n'); - }) - } - } - } - - async fn dvipng(directory: &TempDir) -> Result<DynamicImage, RenderError> { - let process = Command::new("dvipng") - .args(&["-D", "175", "-T", "tight", "preview.dvi"]) - .current_dir(directory.path()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .map_err(|_| RenderError::DviPngNotInstalled)?; - - process.await.map_err(|_| RenderError::DviPngFaulty)?; - - let png_file = directory.path().join("preview1.png"); - let png = image::open(png_file).map_err(|_| RenderError::DecodeImage)?; - Ok(png) - } - - fn add_margin(image: DynamicImage) -> DynamicImage { - let margin = 5; - let width = image.width() + 2 * margin; - let height = image.height() + 2 * margin; - let mut result = DynamicImage::new_rgb8(width, height); - for x in 0..result.width() { - for y in 0..result.height() { - result.put_pixel(x, y, 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: DynamicImage) -> String { - let mut image_buf = Cursor::new(Vec::new()); - let png_encoder = PNGEncoder::new(&mut image_buf); - png_encoder - .encode( - &image.raw_pixels(), - image.width(), - image.height(), - image.color(), - ) - .unwrap(); - base64::encode(&image_buf.into_inner()) - } -} - -impl FeatureProvider for LatexPreviewHoverProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Hover>; - - #[boxed] - async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { - if !request.client_capabilities.has_hover_markdown_support() - || !request.distribution.supports_format(tex::Format::Latex) - || request.distribution.output_kind(tex::Format::Latex) != tex::OutputKind::Dvi - { - return None; - } - - if let SyntaxTree::Latex(tree) = &request.document().tree { - let mut elements = Vec::new(); - tree.math - .inlines - .iter() - .map(MathElement::Inline) - .for_each(|inline| elements.push(inline)); - - tree.math - .equations - .iter() - .map(MathElement::Equation) - .for_each(|eq| elements.push(eq)); - - tree.env - .environments - .iter() - .filter(|env| Self::is_math_environment(request, env)) - .map(MathElement::Environment) - .for_each(|env| elements.push(env)); - - let range = elements - .iter() - .find(|elem| elem.range().contains(request.params.position)) - .map(MathElement::range)?; - - return match Self::render(request, range).await { - Ok(hover) => Some(hover), - Err(why) => { - let message = match why { - RenderError::IO(why) => format!("I/O error: {}", why), - RenderError::Compile(why) => match why { - tex::CompileError::NotInstalled => "latex not installed".into(), - tex::CompileError::Timeout => "compilation timed out".into(), - tex::CompileError::IO(_) => "an I/O error occurred".into(), - }, - RenderError::DviNotFound => "compilation failed".to_owned(), - RenderError::DviPngNotInstalled => "dvipng is not installed".to_owned(), - RenderError::DviPngFaulty => "dvipng failed".to_owned(), - RenderError::DecodeImage => "failed to decode image".to_owned(), - }; - warn!("Preview failed: {}", message); - None - } - }; - } - None - } -} |