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
|
use crate::range::RangeExt;
use crate::workspace::{Document, Uri};
use lsp_types::*;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::{Read, Write};
use std::process::{Command, Stdio};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct LatexLintOptions {
pub on_change: Option<bool>,
pub on_save: Option<bool>,
}
impl LatexLintOptions {
pub fn on_change(&self) -> bool {
self.on_change.unwrap_or(false)
}
pub fn on_save(&self) -> bool {
self.on_save.unwrap_or(false)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct LatexDiagnosticsProvider {
diagnostics_by_uri: HashMap<Uri, Vec<Diagnostic>>,
}
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 fn update(&mut self, uri: &Uri, text: &str) {
if uri.scheme() != "file" {
return;
}
self.diagnostics_by_uri
.insert(uri.clone(), lint(text).unwrap_or_default());
}
}
pub static LINE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w)+:(.*)").unwrap());
fn lint(text: &str) -> Option<Vec<Diagnostic>> {
let mut process = Command::new("chktex")
.args(&["-I0", "-f%l:%c:%d:%k:%n:%m\n"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
process
.stdin
.take()
.unwrap()
.write_all(text.as_bytes())
.ok()?;
let mut stdout = String::new();
process
.stdout
.take()
.unwrap()
.read_to_string(&mut stdout)
.ok()?;
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].to_owned();
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.to_owned())),
message,
severity: Some(severity),
range,
related_information: None,
})
}
}
Some(diagnostics)
}
|