summaryrefslogtreecommitdiff
path: root/support/texlab/src/build.rs
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-12-03 03:01:24 +0000
committerNorbert Preining <norbert@preining.info>2019-12-03 03:01:24 +0000
commitb8d4bb76703bcb15578e2b23c5d256532180b894 (patch)
treebedd1df7a00521a2bd986b3c0289d6556a59e39b /support/texlab/src/build.rs
parent02e4625a78a5029e8b5dc2a4ec70193b232f497e (diff)
CTAN sync 201912030301
Diffstat (limited to 'support/texlab/src/build.rs')
-rw-r--r--support/texlab/src/build.rs231
1 files changed, 231 insertions, 0 deletions
diff --git a/support/texlab/src/build.rs b/support/texlab/src/build.rs
new file mode 100644
index 0000000000..c4f284c21e
--- /dev/null
+++ b/support/texlab/src/build.rs
@@ -0,0 +1,231 @@
+use crate::capabilities::ClientCapabilitiesExt;
+use crate::client::LspClient;
+use crate::workspace::*;
+use futures::future::{AbortHandle, Abortable, Aborted};
+use futures::lock::Mutex;
+use futures::prelude::*;
+use futures::stream;
+use futures_boxed::boxed;
+use lsp_types::*;
+use serde::{Deserialize, Serialize};
+use serde_repr::*;
+use std::collections::HashMap;
+use std::io;
+use std::path::Path;
+use std::process::Stdio;
+use std::sync::Arc;
+use tokio::io::{AsyncBufReadExt, BufReader};
+use tokio_net::process::Command;
+use uuid::Uuid;
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildParams {
+ pub text_document: TextDocumentIdentifier,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildOptions {
+ pub executable: Option<String>,
+ pub args: Option<Vec<String>>,
+ pub on_save: Option<bool>,
+}
+
+impl BuildOptions {
+ pub fn executable(&self) -> String {
+ self.executable
+ .as_ref()
+ .map(Clone::clone)
+ .unwrap_or_else(|| "latexmk".to_owned())
+ }
+
+ pub fn args(&self) -> Vec<String> {
+ self.args.as_ref().map(Clone::clone).unwrap_or_else(|| {
+ vec![
+ "-pdf".to_owned(),
+ "-interaction=nonstopmode".to_owned(),
+ "-synctex=1".to_owned(),
+ ]
+ })
+ }
+
+ pub fn on_save(&self) -> bool {
+ self.on_save.unwrap_or(false)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum BuildStatus {
+ Success = 0,
+ Error = 1,
+ Failure = 2,
+ Cancelled = 3,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildResult {
+ pub status: BuildStatus,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BuildProvider<C> {
+ pub client: Arc<C>,
+ pub options: BuildOptions,
+ pub token: ProgressToken,
+}
+
+impl<C> BuildProvider<C>
+where
+ C: LspClient + Send + Sync + 'static,
+{
+ pub fn new(client: Arc<C>, options: BuildOptions) -> Self {
+ Self {
+ client,
+ options,
+ token: ProgressToken::String(format!("texlab-build-{}", Uuid::new_v4())),
+ }
+ }
+
+ async fn build<'a>(&'a self, path: &'a Path) -> io::Result<bool> {
+ let mut args = Vec::new();
+ args.append(&mut self.options.args());
+ args.push(path.file_name().unwrap().to_string_lossy().into_owned());
+
+ let mut process = Command::new(self.options.executable())
+ .args(args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .current_dir(path.parent().unwrap())
+ .spawn()?;
+
+ let stdout = BufReader::new(process.stdout().take().unwrap()).lines();
+ let stderr = BufReader::new(process.stderr().take().unwrap()).lines();
+ let mut output = stream::select(stdout, stderr);
+
+ while let Some(Ok(line)) = output.next().await {
+ let params = LogMessageParams {
+ typ: MessageType::Log,
+ message: line,
+ };
+
+ self.client.log_message(params).await;
+ }
+
+ Ok(process.await?.success())
+ }
+}
+
+impl<C> FeatureProvider for BuildProvider<C>
+where
+ C: LspClient + Send + Sync + 'static,
+{
+ type Params = BuildParams;
+ type Output = BuildResult;
+
+ #[boxed]
+ async fn execute<'a>(&'a self, request: &'a FeatureRequest<BuildParams>) -> BuildResult {
+ let document = request
+ .workspace()
+ .find_parent(&request.document().uri)
+ .unwrap();
+ let path = document.uri.to_file_path().unwrap();
+
+ if request.client_capabilities.has_work_done_progress() {
+ let params = WorkDoneProgressCreateParams {
+ token: self.token.clone(),
+ };
+ self.client.work_done_progress_create(params).await.unwrap();
+
+ let title = path.file_name().unwrap().to_string_lossy().into_owned();
+ let params = ProgressParams {
+ token: self.token.clone(),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(
+ WorkDoneProgressBegin {
+ title,
+ cancellable: Some(true),
+ message: Some("Building".into()),
+ percentage: None,
+ },
+ )),
+ };
+ self.client.progress(params).await;
+ }
+
+ let status = match self.build(&path).await {
+ Ok(true) => BuildStatus::Success,
+ Ok(false) => BuildStatus::Error,
+ Err(_) => BuildStatus::Failure,
+ };
+
+ BuildResult { status }
+ }
+}
+
+pub struct BuildManager<C> {
+ handles_by_token: Mutex<HashMap<ProgressToken, AbortHandle>>,
+ client: Arc<C>,
+}
+
+impl<C> BuildManager<C>
+where
+ C: LspClient + Send + Sync + 'static,
+{
+ pub fn new(client: Arc<C>) -> Self {
+ Self {
+ handles_by_token: Mutex::new(HashMap::new()),
+ client,
+ }
+ }
+
+ pub async fn build(
+ &self,
+ request: FeatureRequest<BuildParams>,
+ options: BuildOptions,
+ ) -> BuildResult {
+ let provider = BuildProvider::new(Arc::clone(&self.client), options);
+ let (handle, reg) = AbortHandle::new_pair();
+ {
+ let mut handles_by_token = self.handles_by_token.lock().await;
+ handles_by_token.insert(provider.token.clone(), handle);
+ }
+
+ let result = match Abortable::new(provider.execute(&request), reg).await {
+ Ok(result) => result,
+ Err(Aborted) => BuildResult {
+ status: BuildStatus::Cancelled,
+ },
+ };
+
+ if request.client_capabilities.has_work_done_progress() {
+ let params = ProgressParams {
+ token: provider.token.clone(),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::Done(
+ WorkDoneProgressDone { message: None },
+ )),
+ };
+ self.client.progress(params).await;
+ }
+
+ {
+ let mut handles_by_token = self.handles_by_token.lock().await;
+ handles_by_token.remove(&provider.token);
+ }
+
+ result
+ }
+
+ pub async fn cancel(&self, token: ProgressToken) {
+ let handles_by_token = self.handles_by_token.lock().await;
+ if let Some(handle) = handles_by_token.get(&token) {
+ handle.abort();
+ } else if let ProgressToken::String(id) = token {
+ if id == "texlab-build-*" {
+ handles_by_token.values().for_each(|handle| handle.abort());
+ }
+ }
+ }
+}