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.rs133
1 files changed, 133 insertions, 0 deletions
diff --git a/support/texlab/src/features/build.rs b/support/texlab/src/features/build.rs
new file mode 100644
index 0000000000..c6a670528f
--- /dev/null
+++ b/support/texlab/src/features/build.rs
@@ -0,0 +1,133 @@
+use std::{
+ io::{self, BufRead, BufReader, Read},
+ path::Path,
+ process::{Command, Stdio},
+ thread,
+};
+
+use cancellation::CancellationToken;
+use crossbeam_channel::Sender;
+use encoding_rs_io::DecodeReaderBytesBuilder;
+use lsp_types::TextDocumentIdentifier;
+use serde::{Deserialize, Serialize};
+use serde_repr::{Deserialize_repr, Serialize_repr};
+
+use crate::Options;
+
+use super::FeatureRequest;
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildParams {
+ pub text_document: TextDocumentIdentifier,
+}
+
+#[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,
+}
+
+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());
+
+ if document.uri.scheme() != "file" {
+ return BuildResult {
+ status: BuildStatus::Failure,
+ };
+ }
+ 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
+ }
+ };
+ BuildResult { status }
+}
+
+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()?;
+
+ track_output(process.stdout.take().unwrap(), log_sender.clone());
+ track_output(process.stderr.take().unwrap(), log_sender.clone());
+
+ if !options.build.is_continuous {
+ process.wait().map(|status| status.success())
+ } else {
+ Ok(true)
+ }
+}
+
+fn replace_placeholder(arg: String, file: &Path) -> String {
+ if arg.starts_with('"') || arg.ends_with('"') {
+ arg
+ } else {
+ arg.replace("%f", &file.to_string_lossy())
+ }
+}
+
+fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) {
+ let reader = BufReader::new(
+ DecodeReaderBytesBuilder::new()
+ .encoding(Some(encoding_rs::UTF_8))
+ .utf8_passthru(true)
+ .strip_bom(true)
+ .build(output),
+ );
+ thread::spawn(move || {
+ for line in reader.lines() {
+ sender.send(line.unwrap()).unwrap();
+ }
+ });
+}