/src/diagnostics/latex.rs

https://github.com/latex-lsp/texlab · Rust · 113 lines · 100 code · 13 blank · 0 comment · 8 complexity · 398efec1ac4db74572e5e77ec7d8a55a MD5 · raw file

  1. use crate::{
  2. protocol::{Diagnostic, DiagnosticSeverity, NumberOrString, Range, RangeExt, Uri},
  3. workspace::Document,
  4. };
  5. use chashmap::CHashMap;
  6. use futures::{
  7. future::{AbortHandle, Abortable, Aborted},
  8. lock::Mutex,
  9. };
  10. use log::trace;
  11. use once_cell::sync::Lazy;
  12. use regex::Regex;
  13. use std::process::Stdio;
  14. use tokio::{prelude::*, process::Command};
  15. #[derive(Debug, Default)]
  16. pub struct LatexDiagnosticsProvider {
  17. diagnostics_by_uri: CHashMap<Uri, Vec<Diagnostic>>,
  18. handle: Mutex<Option<AbortHandle>>,
  19. }
  20. impl LatexDiagnosticsProvider {
  21. pub fn get(&self, document: &Document) -> Vec<Diagnostic> {
  22. match self.diagnostics_by_uri.get(&document.uri) {
  23. Some(diagnostics) => diagnostics.to_owned(),
  24. None => Vec::new(),
  25. }
  26. }
  27. pub async fn update(&self, uri: &Uri, text: &str) {
  28. if uri.scheme() != "file" {
  29. return;
  30. }
  31. let mut handle_guard = self.handle.lock().await;
  32. if let Some(handle) = &*handle_guard {
  33. handle.abort();
  34. }
  35. let (handle, registration) = AbortHandle::new_pair();
  36. *handle_guard = Some(handle);
  37. drop(handle_guard);
  38. let future = Abortable::new(
  39. async move {
  40. self.diagnostics_by_uri
  41. .insert(uri.clone(), lint(text.into()).await.unwrap_or_default());
  42. },
  43. registration,
  44. );
  45. if let Err(Aborted) = future.await {
  46. trace!("Killed ChkTeX because it took too long to execute")
  47. }
  48. }
  49. }
  50. pub static LINE_REGEX: Lazy<Regex> =
  51. Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap());
  52. async fn lint(text: String) -> io::Result<Vec<Diagnostic>> {
  53. let mut process: tokio::process::Child = Command::new("chktex")
  54. .args(&["-I0", "-f%l:%c:%d:%k:%n:%m\n"])
  55. .stdin(Stdio::piped())
  56. .stdout(Stdio::piped())
  57. .stderr(Stdio::null())
  58. .kill_on_drop(true)
  59. .spawn()?;
  60. process
  61. .stdin
  62. .take()
  63. .unwrap()
  64. .write_all(text.as_bytes())
  65. .await?;
  66. let mut stdout = String::new();
  67. process
  68. .stdout
  69. .take()
  70. .unwrap()
  71. .read_to_string(&mut stdout)
  72. .await?;
  73. let mut diagnostics = Vec::new();
  74. for line in stdout.lines() {
  75. if let Some(captures) = LINE_REGEX.captures(line) {
  76. let line = captures[1].parse::<u64>().unwrap() - 1;
  77. let character = captures[2].parse::<u64>().unwrap() - 1;
  78. let digit = captures[3].parse::<u64>().unwrap();
  79. let kind = &captures[4];
  80. let code = &captures[5];
  81. let message = captures[6].into();
  82. let range = Range::new_simple(line, character, line, character + digit);
  83. let severity = match kind {
  84. "Message" => DiagnosticSeverity::Information,
  85. "Warning" => DiagnosticSeverity::Warning,
  86. _ => DiagnosticSeverity::Error,
  87. };
  88. diagnostics.push(Diagnostic {
  89. source: Some("chktex".into()),
  90. code: Some(NumberOrString::String(code.into())),
  91. message,
  92. severity: Some(severity),
  93. range,
  94. related_information: None,
  95. tags: None,
  96. })
  97. }
  98. }
  99. Ok(diagnostics)
  100. }