From 745892fbddea56040139108277e728b53fd8fc11 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Thu, 28 May 2020 03:03:21 +0000 Subject: CTAN sync 202005280303 --- support/texlab/src/build.rs | 348 ++++++++++++++++++++------------------------ 1 file changed, 160 insertions(+), 188 deletions(-) (limited to 'support/texlab/src/build.rs') diff --git a/support/texlab/src/build.rs b/support/texlab/src/build.rs index c4f284c21e..fe4597c0d7 100644 --- a/support/texlab/src/build.rs +++ b/support/texlab/src/build.rs @@ -1,124 +1,60 @@ -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 crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{ + BuildParams, BuildResult, BuildStatus, ClientCapabilitiesExt, LatexOptions, + LogMessageParams, LspClient, MessageType, ProgressParams, ProgressParamsValue, + ProgressToken, Uri, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, + WorkDoneProgressEnd, + }, +}; +use async_trait::async_trait; +use chashmap::CHashMap; +use futures::{ + future::{AbortHandle, Abortable, Aborted}, + lock::Mutex, + prelude::*, + stream, +}; +use log::error; +use std::{collections::HashMap, io, path::Path, process::Stdio, sync::Arc}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + 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, - pub args: Option>, - pub on_save: Option, -} - -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 { - 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 { - pub client: Arc, - pub options: BuildOptions, - pub token: ProgressToken, + client: Arc, + handles_by_token: Mutex>, + current_docs: CHashMap, } -impl BuildProvider -where - C: LspClient + Send + Sync + 'static, -{ - pub fn new(client: Arc, options: BuildOptions) -> Self { +impl BuildProvider { + pub fn new(client: Arc) -> Self { Self { client, - options, - token: ProgressToken::String(format!("texlab-build-{}", Uuid::new_v4())), + handles_by_token: Mutex::new(HashMap::new()), + current_docs: CHashMap::new(), } } - async fn build<'a>(&'a self, path: &'a Path) -> io::Result { - 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, - }; + pub fn is_building(&self) -> bool { + self.current_docs.len() > 0 + } - self.client.log_message(params).await; + 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()); + } } - - Ok(process.await?.success()) } } +#[async_trait] impl FeatureProvider for BuildProvider where C: LspClient + Send + Sync + 'static, @@ -126,106 +62,142 @@ where type Params = BuildParams; type Output = BuildResult; - #[boxed] - async fn execute<'a>(&'a self, request: &'a FeatureRequest) -> 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; + async fn execute<'a>(&'a self, req: &'a FeatureRequest) -> BuildResult { + let token = ProgressToken::String(format!("texlab-build-{}", Uuid::new_v4())); + let (handle, reg) = AbortHandle::new_pair(); + { + let mut handles_by_token = self.handles_by_token.lock().await; + handles_by_token.insert(token.clone(), handle); } - let status = match self.build(&path).await { - Ok(true) => BuildStatus::Success, - Ok(false) => BuildStatus::Error, - Err(_) => BuildStatus::Failure, - }; - - BuildResult { status } - } -} - -pub struct BuildManager { - handles_by_token: Mutex>, - client: Arc, -} + let doc = req + .snapshot() + .parent(&req.current().uri, &req.options, &req.current_dir) + .unwrap_or_else(|| Arc::clone(&req.view.current)); -impl BuildManager -where - C: LspClient + Send + Sync + 'static, -{ - pub fn new(client: Arc) -> Self { - Self { - handles_by_token: Mutex::new(HashMap::new()), - client, + if !doc.is_file() { + error!("Unable to build the document {}: wrong URI scheme", doc.uri); + return BuildResult { + status: BuildStatus::Failure, + }; } - } - pub async fn build( - &self, - request: FeatureRequest, - 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); + if self.current_docs.get(&doc.uri).is_some() { + return BuildResult { + status: BuildStatus::Success, + }; } - - let result = match Abortable::new(provider.execute(&request), reg).await { - Ok(result) => result, - Err(Aborted) => BuildResult { - status: BuildStatus::Cancelled, - }, + self.current_docs.insert(doc.uri.clone(), ()); + + let status = match doc.uri.to_file_path() { + Ok(path) => { + if req.client_capabilities.has_work_done_progress_support() { + let params = WorkDoneProgressCreateParams { + token: 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: token.clone(), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin( + WorkDoneProgressBegin { + title, + cancellable: Some(true), + message: Some("Building".into()), + percentage: None, + }, + )), + }; + self.client.progress(params).await; + } + + let latex_options = req.options.latex.clone().unwrap_or_default(); + let client = Arc::clone(&self.client); + match Abortable::new(build(&path, &latex_options, client), reg).await { + Ok(Ok(true)) => BuildStatus::Success, + Ok(Ok(false)) => BuildStatus::Error, + Ok(Err(why)) => { + error!("Unable to build the document {}: {}", doc.uri, why); + BuildStatus::Failure + } + Err(Aborted) => BuildStatus::Cancelled, + } + } + Err(()) => { + error!("Unable to build the document {}: invalid URI", doc.uri); + BuildStatus::Failure + } }; - if request.client_capabilities.has_work_done_progress() { + if req.client_capabilities.has_work_done_progress_support() { let params = ProgressParams { - token: provider.token.clone(), - value: ProgressParamsValue::WorkDone(WorkDoneProgress::Done( - WorkDoneProgressDone { message: None }, - )), + token: token.clone(), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd { + message: None, + })), }; self.client.progress(params).await; } - { let mut handles_by_token = self.handles_by_token.lock().await; - handles_by_token.remove(&provider.token); + handles_by_token.remove(&token); } - result + self.current_docs.remove(&doc.uri); + BuildResult { status } } +} - 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()); - } +async fn build(path: &Path, options: &LatexOptions, client: Arc) -> io::Result +where + C: LspClient + Send + Sync + 'static, +{ + let build_options = options.build.as_ref().cloned().unwrap_or_default(); + let build_dir = options + .root_directory + .as_ref() + .map(AsRef::as_ref) + .or_else(|| path.parent()) + .unwrap(); + + let args: Vec<_> = build_options + .args() + .into_iter() + .map(|arg| replace_placeholder(arg, path)) + .collect(); + + let mut process = Command::new(build_options.executable()) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .current_dir(build_dir) + .kill_on_drop(true) + .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); + + tokio::spawn(async move { + while let Some(Ok(line)) = output.next().await { + let params = LogMessageParams { + typ: MessageType::Log, + message: line, + }; + + client.log_message(params).await; } + }); + + Ok(process.await?.success()) +} + +fn replace_placeholder(arg: String, file: &Path) -> String { + if arg.starts_with('"') || arg.ends_with('"') { + arg + } else { + arg.replace("%f", &file.to_string_lossy()) } } -- cgit v1.2.3