1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
use std::{path::PathBuf, process::Stdio};
use anyhow::Result;
use base_db::{Document, Workspace};
use thiserror::Error;
use url::Url;
use crate::placeholders::replace_placeholders;
#[derive(Debug, Error)]
pub enum ForwardSearchError {
#[error("Forward search is not configured")]
Unconfigured,
#[error("Document \"{0}\" does not exist on the local file system")]
NotLocal(Url),
#[error("Document \"{0}\" has an invalid file path")]
InvalidPath(Url),
#[error("TeX document \"{0}\" not found")]
TexNotFound(Url),
#[error("PDF document \"{0}\" not found")]
PdfNotFound(PathBuf),
#[error("Unable to launch PDF viewer: {0}")]
LaunchViewer(#[from] std::io::Error),
}
#[derive(Debug)]
pub struct ForwardSearch {
program: String,
args: Vec<String>,
}
impl ForwardSearch {
pub fn new(
workspace: &Workspace,
uri: &Url,
line: Option<u32>,
) -> Result<Self, ForwardSearchError> {
log::debug!("[FwdSearch] Preparing forward search: document={uri}, line={line:#?}");
let synctex_config = workspace
.config()
.synctex
.as_ref()
.ok_or(ForwardSearchError::Unconfigured)?;
log::debug!("[FwdSearch] synctex_config={:?}", synctex_config);
let child = workspace
.lookup(uri)
.ok_or_else(|| ForwardSearchError::TexNotFound(uri.clone()))?;
let parent = workspace.parents(child).into_iter().next().unwrap_or(child);
log::debug!("[FwdSearch] root_document={}", parent.uri,);
let pdf_path = Self::find_pdf(workspace, parent)?;
let pdf_path = pdf_path.to_string_lossy().into_owned();
let tex_path = child
.path
.as_deref()
.ok_or_else(|| ForwardSearchError::InvalidPath(child.uri.clone()))?;
let tex_path = tex_path.to_string_lossy().into_owned();
let line = line.unwrap_or(child.cursor.line);
let line = (line + 1).to_string();
let program = synctex_config.program.clone();
let args = replace_placeholders(
&synctex_config.args,
&[('f', &tex_path), ('p', &pdf_path), ('l', &line)],
);
Ok(Self { program, args })
}
fn find_pdf(workspace: &Workspace, document: &Document) -> Result<PathBuf, ForwardSearchError> {
let base_dir = workspace.current_dir(&document.dir);
let pdf_dir = workspace.pdf_dir(&base_dir);
log::debug!("[FwdSearch] base_dir={base_dir}, pdf_dir={pdf_dir}");
let pdf_dir = pdf_dir
.to_file_path()
.map_err(|()| ForwardSearchError::InvalidPath(document.uri.clone()))?;
let pdf_name_override = workspace.config().build.output_filename.clone();
log::debug!("[FwdSearch] pdf_name_override={pdf_name_override:?}");
let pdf_name = pdf_name_override
.or_else(|| {
let stem = document.path.as_ref()?.file_stem()?;
Some(format!("{}.pdf", stem.to_string_lossy()))
})
.ok_or_else(|| ForwardSearchError::InvalidPath(document.uri.clone()))?;
let pdf_path = pdf_dir.join(&pdf_name);
let pdf_exists = pdf_path.exists();
log::debug!("[FwdSearch] pdf_path={pdf_path:?}, pdf_exists={pdf_exists}");
if !pdf_exists {
return Err(ForwardSearchError::PdfNotFound(pdf_path));
}
Ok(pdf_path)
}
}
impl ForwardSearch {
pub fn run(self) -> Result<(), ForwardSearchError> {
log::debug!(
"[FwdSearch] Executing command: {} {:?}",
self.program,
self.args
);
std::process::Command::new(self.program)
.args(self.args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()?;
Ok(())
}
}
|