summaryrefslogtreecommitdiff
path: root/support/texlab/crates/diagnostics/src/chktex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/crates/diagnostics/src/chktex.rs')
-rw-r--r--support/texlab/crates/diagnostics/src/chktex.rs121
1 files changed, 121 insertions, 0 deletions
diff --git a/support/texlab/crates/diagnostics/src/chktex.rs b/support/texlab/crates/diagnostics/src/chktex.rs
new file mode 100644
index 0000000000..a342f78349
--- /dev/null
+++ b/support/texlab/crates/diagnostics/src/chktex.rs
@@ -0,0 +1,121 @@
+use std::{
+ io::{BufRead, BufReader, Write},
+ path::PathBuf,
+ process::Stdio,
+};
+
+use base_db::{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 = workspace
+ .parents(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 working_dir = workspace.current_dir(&parent.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().flatten() {
+ 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());