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
|
use std::{
io::{BufRead, BufReader, Write},
path::PathBuf,
process::Stdio,
};
use base_db::{
deps::{self, ProjectRoot},
Document, Workspace,
};
use encoding_rs_io::DecodeReaderBytesBuilder;
use line_index::LineCol;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::{types::Diagnostic, ChktexError, ChktexSeverity};
#[derive(Debug)]
pub struct Command {
text: String,
working_dir: PathBuf,
additional_args: Vec<String>,
}
impl Command {
pub fn new(workspace: &Workspace, document: &Document) -> Option<Self> {
document.data.as_tex()?;
let parent = deps::parents(workspace, document)
.into_iter()
.next()
.unwrap_or(document);
if parent.path.is_none() {
log::warn!("Calling ChkTeX on non-local files is not supported yet.");
return None;
}
let root = ProjectRoot::walk_and_find(workspace, parent.dir.as_ref()?);
let working_dir = root.src_dir.to_file_path().ok()?;
log::debug!("Calling ChkTeX from directory: {}", working_dir.display());
let text = document.text.clone();
let config = &workspace.config().diagnostics.chktex;
let additional_args = config.additional_args.clone();
Some(Self {
text,
working_dir,
additional_args,
})
}
pub fn run(mut self) -> std::io::Result<Vec<Diagnostic>> {
let mut args = vec!["-I0".into(), "-f%l:%c:%d:%k:%n:%m\n".into()];
args.append(&mut self.additional_args);
let mut child = std::process::Command::new("chktex")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.current_dir(self.working_dir)
.spawn()?;
let stdout = child.stdout.take().unwrap();
let reader = std::thread::spawn(move || {
let mut diagnostics = Vec::new();
let reader = BufReader::new(
DecodeReaderBytesBuilder::new()
.encoding(Some(encoding_rs::UTF_8))
.utf8_passthru(true)
.strip_bom(true)
.build(stdout),
);
for line in reader.lines().map_while(Result::ok) {
let captures = LINE_REGEX.captures(&line).unwrap();
let line = captures[1].parse::<u32>().unwrap() - 1;
let character = captures[2].parse::<u32>().unwrap() - 1;
let digit = captures[3].parse::<u32>().unwrap();
let kind = &captures[4];
let code = String::from(&captures[5]);
let message = captures[6].into();
let start = LineCol {
line,
col: character,
};
let end = LineCol {
line,
col: character + digit,
};
let severity = match kind {
"Message" => ChktexSeverity::Message,
"Warning" => ChktexSeverity::Warning,
_ => ChktexSeverity::Error,
};
diagnostics.push(Diagnostic::Chktex(ChktexError {
start,
end,
message,
severity,
code,
}));
}
diagnostics
});
let mut stdin = child.stdin.take().unwrap();
let bytes = self.text.into_bytes();
let writer = std::thread::spawn(move || stdin.write_all(&bytes));
child.wait()?;
writer.join().unwrap()?;
let diagnostics = reader.join().unwrap();
Ok(diagnostics)
}
}
static LINE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap());
|