From 2ba4b1ac7860085c0b259aacb0d99bd2c730edbc Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Sun, 13 Jun 2021 03:03:20 +0000 Subject: CTAN sync 202106130303 --- support/texlab/src/features/build.rs | 277 ++++++++++++++++++++++++++--------- 1 file changed, 206 insertions(+), 71 deletions(-) (limited to 'support/texlab/src/features/build.rs') diff --git a/support/texlab/src/features/build.rs b/support/texlab/src/features/build.rs index c6a670528f..6953698a29 100644 --- a/support/texlab/src/features/build.rs +++ b/support/texlab/src/features/build.rs @@ -1,20 +1,29 @@ use std::{ - io::{self, BufRead, BufReader, Read}, + io::{BufRead, BufReader, Read}, path::Path, process::{Command, Stdio}, - thread, + sync::{Arc, Mutex}, + thread::{self, JoinHandle}, }; +use anyhow::Result; use cancellation::CancellationToken; +use chashmap::CHashMap; use crossbeam_channel::Sender; use encoding_rs_io::DecodeReaderBytesBuilder; -use lsp_types::TextDocumentIdentifier; +use lsp_types::{ + notification::{LogMessage, Progress}, + LogMessageParams, NumberOrString, Position, ProgressParams, ProgressParamsValue, + TextDocumentIdentifier, TextDocumentPositionParams, WorkDoneProgress, WorkDoneProgressBegin, + WorkDoneProgressCreateParams, WorkDoneProgressEnd, +}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use uuid::Uuid; -use crate::Options; +use crate::{client, req_queue::ReqQueue, ClientCapabilitiesExt, DocumentLanguage, Uri}; -use super::FeatureRequest; +use super::{forward_search, FeatureRequest}; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -25,10 +34,10 @@ pub struct BuildParams { #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)] #[repr(i32)] pub enum BuildStatus { - Success = 0, - Error = 1, - Failure = 2, - Cancelled = 3, + SUCCESS = 0, + ERROR = 1, + FAILURE = 2, + CANCELLED = 3, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] @@ -37,78 +46,203 @@ pub struct BuildResult { pub status: BuildStatus, } -pub fn build_document( - request: FeatureRequest, - _cancellation_token: &CancellationToken, - log_sender: Sender, -) -> BuildResult { - let document = request - .subset - .documents - .iter() - .find(|document| { - if let Some(data) = document.data.as_latex() { - data.extras.has_document_environment - } else { - false - } - }) - .map(|document| document.as_ref()) - .unwrap_or_else(|| request.main_document()); +struct ProgressReporter<'a> { + supports_progress: bool, + req_queue: &'a Mutex, + lsp_sender: Sender, + token: &'a str, +} - if document.uri.scheme() != "file" { - return BuildResult { - status: BuildStatus::Failure, +impl<'a> ProgressReporter<'a> { + pub fn start(&self, uri: &Uri) -> Result<()> { + if self.supports_progress { + client::send_request::( + self.req_queue, + &self.lsp_sender, + WorkDoneProgressCreateParams { + token: NumberOrString::String(self.token.to_string()), + }, + )?; + client::send_notification::( + &self.lsp_sender, + ProgressParams { + token: NumberOrString::String(self.token.to_string()), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin( + WorkDoneProgressBegin { + title: "Building".to_string(), + message: Some(uri.as_str().to_string()), + cancellable: Some(false), + percentage: None, + }, + )), + }, + )?; }; + Ok(()) } - log::info!("Building document {}", document.uri.as_str()); - - let options = { request.context.options.read().unwrap().clone() }; +} - let status = match build_internal(&document.uri.to_file_path().unwrap(), &options, log_sender) { - Ok(true) => BuildStatus::Success, - Ok(false) => BuildStatus::Error, - Err(why) => { - log::error!("Failed to execute textDocument/build: {}", why); - BuildStatus::Failure +impl<'a> Drop for ProgressReporter<'a> { + fn drop(&mut self) { + if self.supports_progress { + let _ = client::send_notification::( + &self.lsp_sender, + ProgressParams { + token: NumberOrString::String(self.token.to_string()), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::End( + WorkDoneProgressEnd { message: None }, + )), + }, + ); } - }; - BuildResult { status } + } +} + +#[derive(Default)] +pub struct BuildEngine { + lock: Mutex<()>, + pub positions_by_uri: CHashMap, Position>, } -fn build_internal(path: &Path, options: &Options, log_sender: Sender) -> io::Result { - let build_dir = options - .root_directory - .as_ref() - .map(AsRef::as_ref) - .or_else(|| path.parent()) - .unwrap(); - - let args: Vec<_> = options - .build - .args() - .into_iter() - .map(|arg| replace_placeholder(arg, path)) - .collect(); - - let mut process = Command::new(options.build.executable()) - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .current_dir(build_dir) - .spawn()?; +impl BuildEngine { + pub fn build( + &self, + request: FeatureRequest, + cancellation_token: &CancellationToken, + req_queue: &Mutex, + lsp_sender: &Sender, + ) -> Result { + let lock = self.lock.lock().unwrap(); - track_output(process.stdout.take().unwrap(), log_sender.clone()); - track_output(process.stderr.take().unwrap(), log_sender.clone()); + let document = request + .subset + .documents + .iter() + .find(|document| { + if let Some(data) = document.data.as_latex() { + data.extras.has_document_environment + } else { + false + } + }) + .map(|document| document.as_ref()) + .unwrap_or_else(|| request.main_document()); - if !options.build.is_continuous { - process.wait().map(|status| status.success()) - } else { - Ok(true) + if document.language() != DocumentLanguage::Latex { + return Ok(BuildResult { + status: BuildStatus::SUCCESS, + }); + } + + if document.uri.scheme() != "file" { + return Ok(BuildResult { + status: BuildStatus::FAILURE, + }); + } + let path = document.uri.to_file_path().unwrap(); + + let supports_progress = { + request + .context + .client_capabilities + .lock() + .unwrap() + .has_work_done_progress_support() + }; + + let token = format!("texlab-build-{}", Uuid::new_v4()); + let progress_reporter = ProgressReporter { + supports_progress, + req_queue, + lsp_sender: lsp_sender.clone(), + token: &token, + }; + progress_reporter.start(&document.uri)?; + + let options = { request.context.options.read().unwrap().clone() }; + + let build_dir = options + .root_directory + .as_ref() + .map(AsRef::as_ref) + .or_else(|| path.parent()) + .unwrap(); + + let args: Vec<_> = options + .build + .args() + .into_iter() + .map(|arg| replace_placeholder(arg, &path)) + .collect(); + + let mut process = Command::new(options.build.executable()) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .current_dir(build_dir) + .spawn()?; + + let log_handle = capture_output(&mut process, lsp_sender); + let success = process.wait().map(|status| status.success())?; + log_handle.join().unwrap(); + let status = if success { + BuildStatus::SUCCESS + } else { + BuildStatus::ERROR + }; + + drop(progress_reporter); + drop(lock); + + if options.build.forward_search_after { + let request = FeatureRequest { + params: TextDocumentPositionParams { + position: self + .positions_by_uri + .get(&request.main_document().uri) + .map(|guard| guard.clone()) + .unwrap_or_default(), + text_document: TextDocumentIdentifier::new( + request.main_document().uri.as_ref().clone().into(), + ), + }, + context: request.context, + workspace: request.workspace, + subset: request.subset, + }; + forward_search::execute_forward_search(request, cancellation_token); + } + + Ok(BuildResult { status }) } } +fn capture_output( + process: &mut std::process::Child, + lsp_sender: &Sender, +) -> JoinHandle<()> { + let (log_sender, log_receiver) = crossbeam_channel::unbounded(); + track_output(process.stdout.take().unwrap(), log_sender.clone()); + track_output(process.stderr.take().unwrap(), log_sender); + let log_handle = { + let lsp_sender = lsp_sender.clone(); + thread::spawn(move || { + for message in &log_receiver { + client::send_notification::( + &lsp_sender, + LogMessageParams { + message, + typ: lsp_types::MessageType::Log, + }, + ) + .unwrap(); + } + }) + }; + log_handle +} + fn replace_placeholder(arg: String, file: &Path) -> String { if arg.starts_with('"') || arg.ends_with('"') { arg @@ -117,7 +251,7 @@ fn replace_placeholder(arg: String, file: &Path) -> String { } } -fn track_output(output: impl Read + Send + 'static, sender: Sender) { +fn track_output(output: impl Read + Send + 'static, sender: Sender) -> JoinHandle<()> { let reader = BufReader::new( DecodeReaderBytesBuilder::new() .encoding(Some(encoding_rs::UTF_8)) @@ -125,9 +259,10 @@ fn track_output(output: impl Read + Send + 'static, sender: Sender) { .strip_bom(true) .build(output), ); + thread::spawn(move || { for line in reader.lines() { sender.send(line.unwrap()).unwrap(); } - }); + }) } -- cgit v1.2.3