summaryrefslogtreecommitdiff
path: root/support/texlab/src/features/build.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/features/build.rs')
-rw-r--r--support/texlab/src/features/build.rs277
1 files changed, 206 insertions, 71 deletions
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<BuildParams>,
- _cancellation_token: &CancellationToken,
- log_sender: Sender<String>,
-) -> 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<ReqQueue>,
+ lsp_sender: Sender<lsp_server::Message>,
+ 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::<lsp_types::request::WorkDoneProgressCreate>(
+ self.req_queue,
+ &self.lsp_sender,
+ WorkDoneProgressCreateParams {
+ token: NumberOrString::String(self.token.to_string()),
+ },
+ )?;
+ client::send_notification::<Progress>(
+ &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::<Progress>(
+ &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<Arc<Uri>, Position>,
}
-fn build_internal(path: &Path, options: &Options, log_sender: Sender<String>) -> io::Result<bool> {
- 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<BuildParams>,
+ cancellation_token: &CancellationToken,
+ req_queue: &Mutex<ReqQueue>,
+ lsp_sender: &Sender<lsp_server::Message>,
+ ) -> Result<BuildResult> {
+ 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<lsp_server::Message>,
+) -> 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::<LogMessage>(
+ &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<String>) {
+fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) -> 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<String>) {
.strip_bom(true)
.build(output),
);
+
thread::spawn(move || {
for line in reader.lines() {
sender.send(line.unwrap()).unwrap();
}
- });
+ })
}