summaryrefslogtreecommitdiff
path: root/support/texlab/src/diagnostics/chktex.rs
blob: 6e7422d8b0af3aefeee7891abbab94e3d81f3dfd (plain)
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
use std::{
    fs, io,
    path::Path,
    process::{Command, Stdio},
    sync::Arc,
};

use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Range, Url};
use multimap::MultiMap;
use once_cell::sync::Lazy;
use regex::Regex;
use tempfile::tempdir;

use crate::{Options, RangeExt, Workspace};

pub fn analyze_latex_chktex(
    workspace: &Workspace,
    diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>,
    uri: &Url,
    options: &Options,
) -> Option<()> {
    let document = workspace.documents_by_uri.get(uri)?;
    document.data.as_latex()?;

    let current_dir = options
        .root_directory
        .as_ref()
        .cloned()
        .or_else(|| {
            if document.uri.scheme() == "file" {
                document
                    .uri
                    .to_file_path()
                    .unwrap()
                    .parent()
                    .map(ToOwned::to_owned)
            } else {
                None
            }
        })
        .unwrap_or_else(|| ".".into());

    diagnostics_by_uri.remove(uri);
    diagnostics_by_uri.insert_many(
        Arc::clone(&document.uri),
        lint(&document.text, &current_dir).unwrap_or_default(),
    );
    Some(())
}

pub static LINE_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap());

fn lint(text: &str, current_dir: &Path) -> io::Result<Vec<Diagnostic>> {
    let directory = tempdir()?;
    fs::write(directory.path().join("file.tex"), text)?;
    let _ = fs::copy(
        current_dir.join("chktexrc"),
        directory.path().join("chktexrc"),
    );

    let output = Command::new("chktex")
        .args(&["-I0", "-f%l:%c:%d:%k:%n:%m\n", "file.tex"])
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .current_dir(directory.path())
        .output()?;

    let mut diagnostics = Vec::new();
    for line in String::from_utf8_lossy(&output.stdout).lines() {
        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 = &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 {
            range,
            severity: Some(severity),
            code: Some(NumberOrString::String(code.into())),
            code_description: None,
            source: Some("chktex".into()),
            message,
            related_information: None,
            tags: None,
            data: None,
        });
    }

    Ok(diagnostics)
}