summaryrefslogtreecommitdiff
path: root/support/texlab/src/features/forward_search.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/features/forward_search.rs')
-rw-r--r--support/texlab/src/features/forward_search.rs124
1 files changed, 124 insertions, 0 deletions
diff --git a/support/texlab/src/features/forward_search.rs b/support/texlab/src/features/forward_search.rs
new file mode 100644
index 0000000000..39470a64ec
--- /dev/null
+++ b/support/texlab/src/features/forward_search.rs
@@ -0,0 +1,124 @@
+use std::{
+ io,
+ path::Path,
+ process::{Command, Stdio},
+};
+
+use cancellation::CancellationToken;
+use log::error;
+use lsp_types::TextDocumentPositionParams;
+use serde::{Deserialize, Serialize};
+use serde_repr::{Deserialize_repr, Serialize_repr};
+
+use super::FeatureRequest;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum ForwardSearchStatus {
+ Success = 0,
+ Error = 1,
+ Failure = 2,
+ Unconfigured = 3,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct ForwardSearchResult {
+ pub status: ForwardSearchStatus,
+}
+
+pub fn execute_forward_search(
+ request: FeatureRequest<TextDocumentPositionParams>,
+ _cancellation_token: &CancellationToken,
+) -> Option<ForwardSearchResult> {
+ let options = {
+ request
+ .context
+ .options
+ .read()
+ .unwrap()
+ .forward_search
+ .clone()
+ .unwrap_or_default()
+ };
+
+ if options.executable.is_none() || options.args.is_none() {
+ return Some(ForwardSearchResult {
+ status: ForwardSearchStatus::Unconfigured,
+ });
+ }
+
+ let root_document = request
+ .subset
+ .documents
+ .iter()
+ .find(|document| {
+ if let Some(data) = document.data.as_latex() {
+ data.extras.has_document_environment
+ && !data
+ .extras
+ .explicit_links
+ .iter()
+ .filter_map(|link| link.as_component_name())
+ .any(|name| name == "subfiles.cls")
+ } else {
+ false
+ }
+ })
+ .filter(|document| document.uri.scheme() == "file")?;
+
+ let data = root_document.data.as_latex()?;
+ let pdf_path = data
+ .extras
+ .implicit_links
+ .pdf
+ .iter()
+ .filter_map(|uri| uri.to_file_path().ok())
+ .find(|path| path.exists())?;
+
+ let tex_path = request.main_document().uri.to_file_path().ok()?;
+
+ let args: Vec<String> = options
+ .args
+ .unwrap()
+ .into_iter()
+ .flat_map(|arg| {
+ replace_placeholder(&tex_path, &pdf_path, request.params.position.line, arg)
+ })
+ .collect();
+
+ let status = match run_process(options.executable.unwrap(), args) {
+ Ok(()) => ForwardSearchStatus::Success,
+ Err(why) => {
+ error!("Unable to execute forward search: {}", why);
+ ForwardSearchStatus::Failure
+ }
+ };
+ Some(ForwardSearchResult { status })
+}
+
+fn replace_placeholder(
+ tex_file: &Path,
+ pdf_file: &Path,
+ line_number: u32,
+ argument: String,
+) -> Option<String> {
+ let result = if argument.starts_with('"') || argument.ends_with('"') {
+ argument
+ } else {
+ argument
+ .replace("%f", tex_file.to_str()?)
+ .replace("%p", pdf_file.to_str()?)
+ .replace("%l", &(line_number + 1).to_string())
+ };
+ Some(result)
+}
+
+fn run_process(executable: String, args: Vec<String>) -> io::Result<()> {
+ Command::new(executable)
+ .args(args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status()?;
+ Ok(())
+}