diff options
Diffstat (limited to 'support/texlab/crates/tex/src')
-rw-r--r-- | support/texlab/crates/tex/src/compile.rs | 87 | ||||
-rw-r--r-- | support/texlab/crates/tex/src/language.rs | 23 | ||||
-rw-r--r-- | support/texlab/crates/tex/src/lib.rs | 106 | ||||
-rw-r--r-- | support/texlab/crates/tex/src/miktex.rs | 18 | ||||
-rw-r--r-- | support/texlab/crates/tex/src/tectonic.rs | 32 | ||||
-rw-r--r-- | support/texlab/crates/tex/src/texlive.rs | 18 |
6 files changed, 284 insertions, 0 deletions
diff --git a/support/texlab/crates/tex/src/compile.rs b/support/texlab/crates/tex/src/compile.rs new file mode 100644 index 0000000000..eeb7db9e4e --- /dev/null +++ b/support/texlab/crates/tex/src/compile.rs @@ -0,0 +1,87 @@ +use futures::future::TryFutureExt; +use std::io; +use std::process::Stdio; +use std::time::Duration; +use tempfile::{tempdir, TempDir}; +use tokio::fs; +use tokio::future::FutureExt; +use tokio_net::process::Command; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Format { + Latex, + Pdflatex, + Xelatex, + Lualatex, +} + +impl Format { + pub fn executable(self) -> &'static str { + match self { + Self::Latex => "latex", + Self::Pdflatex => "pdflatex", + Self::Xelatex => "xelatex", + Self::Lualatex => "lualatex", + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum OutputKind { + Dvi, + Pdf, +} + +#[derive(Debug)] +pub struct CompileResult { + pub log: String, + pub directory: TempDir, +} + +#[derive(Debug)] +pub enum CompileError { + IO(io::Error), + NotInstalled, + Timeout, +} + +impl From<io::Error> for CompileError { + fn from(error: io::Error) -> Self { + Self::IO(error) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct CompileParams<'a> { + pub file_name: &'a str, + pub code: &'a str, + pub format: Format, + pub timeout: Duration, +} + +pub async fn compile<'a>( + executable: &'a str, + args: &'a [&'a str], + params: CompileParams<'a>, +) -> Result<CompileResult, CompileError> { + let directory = tempdir()?; + let code_file = directory.path().join(params.file_name); + fs::write(code_file.clone(), params.code).await?; + + Command::new(executable) + .args(args) + .current_dir(&directory) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map_err(|_| CompileError::NotInstalled) + .timeout(params.timeout) + .map_err(|_| CompileError::Timeout) + .await? + .map_err(|_| CompileError::NotInstalled)?; + + let log_file = code_file.with_extension("log"); + let log_bytes = fs::read(log_file).await?; + let log = String::from_utf8_lossy(&log_bytes).into_owned(); + Ok(CompileResult { log, directory }) +} diff --git a/support/texlab/crates/tex/src/language.rs b/support/texlab/crates/tex/src/language.rs new file mode 100644 index 0000000000..ea2b6eec90 --- /dev/null +++ b/support/texlab/crates/tex/src/language.rs @@ -0,0 +1,23 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Language { + Latex, + Bibtex, +} + +impl Language { + pub fn by_extension(extension: &str) -> Option<Self> { + match extension.to_lowercase().as_ref() { + "tex" | "sty" | "cls" | "lco" | "aux" => Some(Language::Latex), + "bib" => Some(Language::Bibtex), + _ => None, + } + } + + pub fn by_language_id(language_id: &str) -> Option<Self> { + match language_id { + "latex" | "tex" => Some(Language::Latex), + "bibtex" | "bib" => Some(Language::Bibtex), + _ => None, + } + } +} diff --git a/support/texlab/crates/tex/src/lib.rs b/support/texlab/crates/tex/src/lib.rs new file mode 100644 index 0000000000..5075ec0efb --- /dev/null +++ b/support/texlab/crates/tex/src/lib.rs @@ -0,0 +1,106 @@ +mod compile; +mod language; +mod miktex; +mod tectonic; +mod texlive; + +pub use self::compile::*; +pub use self::language::Language; + +use self::miktex::Miktex; +use self::tectonic::Tectonic; +use self::texlive::Texlive; +use futures_boxed::boxed; +use tokio_net::process::Command; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DistributionKind { + Texlive, + Miktex, + Tectonic, + Unknown, +} + +impl DistributionKind { + pub async fn detect() -> Self { + if Command::new("tectonic") + .arg("--version") + .status() + .await + .is_ok() + { + return Self::Tectonic; + } + + match Command::new("latex").arg("--version").output().await { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout); + if stdout.contains("TeX Live") { + Self::Texlive + } else if stdout.contains("MiKTeX") { + Self::Miktex + } else { + Self::Unknown + } + } + Err(_) => Self::Unknown, + } + } +} + +pub trait Distribution: Send + Sync { + fn kind(&self) -> DistributionKind; + + fn supports_format(&self, format: Format) -> bool; + + fn output_kind(&self, format: Format) -> OutputKind { + match format { + Format::Latex => OutputKind::Dvi, + Format::Pdflatex | Format::Xelatex | Format::Lualatex => OutputKind::Pdf, + } + } + + #[boxed] + async fn compile<'a>( + &'a self, + params: CompileParams<'a>, + ) -> Result<CompileResult, CompileError> { + let executable = params.format.executable(); + let args = &["--interaction=batchmode", "-shell-escape", params.file_name]; + compile(executable, args, params).await + } +} + +impl dyn Distribution { + pub async fn detect() -> Box<Self> { + let kind = DistributionKind::detect().await; + let distro: Box<Self> = match kind { + DistributionKind::Texlive => Box::new(Texlive), + DistributionKind::Miktex => Box::new(Miktex), + DistributionKind::Tectonic => Box::new(Tectonic), + DistributionKind::Unknown => Box::new(Unknown), + }; + distro + } +} + +#[derive(Debug, Default)] +pub struct Unknown; + +impl Distribution for Unknown { + fn kind(&self) -> DistributionKind { + DistributionKind::Unknown + } + + fn supports_format(&self, _format: Format) -> bool { + false + } + + #[boxed] + async fn compile<'a>( + &'a self, + _params: CompileParams<'a>, + ) -> Result<CompileResult, CompileError> { + Err(CompileError::NotInstalled) + } +} diff --git a/support/texlab/crates/tex/src/miktex.rs b/support/texlab/crates/tex/src/miktex.rs new file mode 100644 index 0000000000..8ab399dc2b --- /dev/null +++ b/support/texlab/crates/tex/src/miktex.rs @@ -0,0 +1,18 @@ +use super::compile::*; +use super::{Distribution, DistributionKind}; + +#[derive(Debug, Default)] +pub struct Miktex; + +impl Distribution for Miktex { + fn kind(&self) -> DistributionKind { + DistributionKind::Miktex + } + + fn supports_format(&self, format: Format) -> bool { + match format { + Format::Latex | Format::Pdflatex => true, + Format::Xelatex | Format::Lualatex => true, + } + } +} diff --git a/support/texlab/crates/tex/src/tectonic.rs b/support/texlab/crates/tex/src/tectonic.rs new file mode 100644 index 0000000000..605e79dc92 --- /dev/null +++ b/support/texlab/crates/tex/src/tectonic.rs @@ -0,0 +1,32 @@ +use super::compile::*; +use super::{Distribution, DistributionKind}; +use futures_boxed::boxed; + +#[derive(Debug, Default)] +pub struct Tectonic; + +impl Distribution for Tectonic { + fn kind(&self) -> DistributionKind { + DistributionKind::Tectonic + } + + fn supports_format(&self, format: Format) -> bool { + match format { + Format::Latex | Format::Pdflatex | Format::Xelatex => true, + Format::Lualatex => false, + } + } + + fn output_kind(&self, _format: Format) -> OutputKind { + OutputKind::Pdf + } + + #[boxed] + async fn compile<'a>( + &'a self, + params: CompileParams<'a>, + ) -> Result<CompileResult, CompileError> { + let args = [params.file_name]; + compile("tectonic", &args, params).await + } +} diff --git a/support/texlab/crates/tex/src/texlive.rs b/support/texlab/crates/tex/src/texlive.rs new file mode 100644 index 0000000000..4f90d07926 --- /dev/null +++ b/support/texlab/crates/tex/src/texlive.rs @@ -0,0 +1,18 @@ +use super::compile::*; +use super::{Distribution, DistributionKind}; + +#[derive(Debug, Default)] +pub struct Texlive; + +impl Distribution for Texlive { + fn kind(&self) -> DistributionKind { + DistributionKind::Texlive + } + + fn supports_format(&self, format: Format) -> bool { + match format { + Format::Latex | Format::Pdflatex => true, + Format::Xelatex | Format::Lualatex => true, + } + } +} |