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
|
use crate::{
protocol::{Diagnostic, DiagnosticSeverity, NumberOrString, Range, RangeExt, Uri},
workspace::Document,
};
use chashmap::CHashMap;
use futures::{
future::{AbortHandle, Abortable, Aborted},
lock::Mutex,
};
use log::trace;
use once_cell::sync::Lazy;
use regex::Regex;
use std::process::Stdio;
use tokio::{prelude::*, process::Command};
#[derive(Debug, Default)]
pub struct LatexDiagnosticsProvider {
diagnostics_by_uri: CHashMap<Uri, Vec<Diagnostic>>,
handle: Mutex<Option<AbortHandle>>,
}
impl LatexDiagnosticsProvider {
pub fn get(&self, document: &Document) -> Vec<Diagnostic> {
match self.diagnostics_by_uri.get(&document.uri) {
Some(diagnostics) => diagnostics.to_owned(),
None => Vec::new(),
}
}
pub async fn update(&self, uri: &Uri, text: &str) {
if uri.scheme() != "file" {
return;
}
let mut handle_guard = self.handle.lock().await;
if let Some(handle) = &*handle_guard {
handle.abort();
}
let (handle, registration) = AbortHandle::new_pair();
*handle_guard = Some(handle);
drop(handle_guard);
let future = Abortable::new(
async move {
self.diagnostics_by_uri
.insert(uri.clone(), lint(text.into()).await.unwrap_or_default());
},
registration,
);
if let Err(Aborted) = future.await {
trace!("Killed ChkTeX because it took too long to execute")
}
}
}
pub static LINE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap());
async fn lint(text: String) -> io::Result<Vec<Diagnostic>> {
let mut process: tokio::process::Child = Command::new("chktex")
.args(&["-I0", "-f%l:%c:%d:%k:%n:%m\n"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.kill_on_drop(true)
.spawn()?;
process
.stdin
.take()
.unwrap()
.write_all(text.as_bytes())
.await?;
let mut stdout = String::new();
process
.stdout
.take()
.unwrap()
.read_to_string(&mut stdout)
.await?;
let mut diagnostics = Vec::new();
for line in stdout.lines() {
if let Some(captures) = LINE_REGEX.captures(line) {
let line = captures[1].parse::<u64>().unwrap() - 1;
let character = captures[2].parse::<u64>().unwrap() - 1;
let digit = captures[3].parse::<u64>().unwrap();
let kind = &captures[4];
let code = &captures[5];
let message = captures[6].into();
let range = Range::new_simple(line, character, line, character + digit);
let severity = match kind {
"Message" => DiagnosticSeverity::Information,
"Warning" => DiagnosticSeverity::Warning,
_ => DiagnosticSeverity::Error,
};
diagnostics.push(Diagnostic {
source: Some("chktex".into()),
code: Some(NumberOrString::String(code.into())),
message,
severity: Some(severity),
range,
related_information: None,
tags: None,
})
}
}
Ok(diagnostics)
}
|