summaryrefslogtreecommitdiff
path: root/support/texlab/src/build.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/build.rs')
-rw-r--r--support/texlab/src/build.rs348
1 files changed, 160 insertions, 188 deletions
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<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,
+ client: Arc<C>,
+ handles_by_token: Mutex<HashMap<ProgressToken, AbortHandle>>,
+ current_docs: CHashMap<Uri, ()>,
}
-impl<C> BuildProvider<C>
-where
- C: LspClient + Send + Sync + 'static,
-{
- pub fn new(client: Arc<C>, options: BuildOptions) -> Self {
+impl<C> BuildProvider<C> {
+ pub fn new(client: Arc<C>) -> 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<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,
- };
+ 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<C> FeatureProvider for BuildProvider<C>
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<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;
+ async fn execute<'a>(&'a self, req: &'a FeatureRequest<BuildParams>) -> 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<C> {
- handles_by_token: Mutex<HashMap<ProgressToken, AbortHandle>>,
- client: Arc<C>,
-}
+ let doc = req
+ .snapshot()
+ .parent(&req.current().uri, &req.options, &req.current_dir)
+ .unwrap_or_else(|| Arc::clone(&req.view.current));
-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,
+ 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<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);
+ 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<C>(path: &Path, options: &LatexOptions, client: Arc<C>) -> io::Result<bool>
+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())
}
}