summaryrefslogtreecommitdiff
path: root/support/texlab/src/diagnostics/latex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/diagnostics/latex.rs')
-rw-r--r--support/texlab/src/diagnostics/latex.rs204
1 files changed, 110 insertions, 94 deletions
diff --git a/support/texlab/src/diagnostics/latex.rs b/support/texlab/src/diagnostics/latex.rs
index 95fcaeeacf..b835f88398 100644
--- a/support/texlab/src/diagnostics/latex.rs
+++ b/support/texlab/src/diagnostics/latex.rs
@@ -1,113 +1,129 @@
+use std::sync::Arc;
+
+use cstree::TextRange;
+use lsp_types::{Diagnostic, DiagnosticSeverity};
+use multimap::MultiMap;
+
use crate::{
- protocol::{Diagnostic, DiagnosticSeverity, NumberOrString, Range, RangeExt, Uri},
- workspace::Document,
-};
-use chashmap::CHashMap;
-use futures::{
- future::{AbortHandle, Abortable, Aborted},
- lock::Mutex,
+ syntax::{latex, CstNode},
+ Document, LineIndexExt, Uri, Workspace,
};
-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 fn analyze_latex_static(
+ workspace: &dyn Workspace,
+ diagnostics_by_uri: &mut MultiMap<Arc<Uri>, Diagnostic>,
+ uri: &Uri,
+) -> Option<()> {
+ let document = workspace.get(uri)?;
+ if !document.uri.as_str().ends_with(".tex") {
+ return None;
}
- pub async fn update(&self, uri: &Uri, text: &str) {
- if uri.scheme() != "file" {
- return;
- }
+ let data = document.data.as_latex()?;
- let mut handle_guard = self.handle.lock().await;
- if let Some(handle) = &*handle_guard {
- handle.abort();
- }
+ for node in data.root.descendants() {
+ analyze_environment(&document, diagnostics_by_uri, node)
+ .or_else(|| analyze_curly_group(&document, diagnostics_by_uri, node))
+ .or_else(|| {
+ if node.kind() == latex::ERROR && node.first_token()?.text() == "}" {
+ diagnostics_by_uri.insert(
+ Arc::clone(&document.uri),
+ Diagnostic {
+ range: document.line_index.line_col_lsp_range(node.text_range()),
+ severity: Some(DiagnosticSeverity::Error),
+ code: None,
+ code_description: None,
+ source: Some("texlab".to_string()),
+ message: "Unexpected \"}\"".to_string(),
+ related_information: None,
+ tags: None,
+ data: None,
+ },
+ );
+ Some(())
+ } else {
+ None
+ }
+ });
+ }
- let (handle, registration) = AbortHandle::new_pair();
- *handle_guard = Some(handle);
- drop(handle_guard);
+ Some(())
+}
- let future = Abortable::new(
- async move {
- self.diagnostics_by_uri
- .insert(uri.clone(), lint(text.into()).await.unwrap_or_default());
+fn analyze_environment(
+ document: &Document,
+ diagnostics_by_uri: &mut MultiMap<Arc<Uri>, Diagnostic>,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ let environment = latex::Environment::cast(node)?;
+ let name1 = environment.begin()?.name()?.word()?;
+ let name2 = environment.end()?.name()?.word()?;
+ if name1.text() != name2.text() {
+ diagnostics_by_uri.insert(
+ Arc::clone(&document.uri),
+ Diagnostic {
+ range: document.line_index.line_col_lsp_range(name1.text_range()),
+ severity: Some(DiagnosticSeverity::Error),
+ code: None,
+ code_description: None,
+ source: Some("texlab".to_string()),
+ message: "Mismatched environment".to_string(),
+ related_information: None,
+ tags: None,
+ data: None,
},
- registration,
);
-
- if let Err(Aborted) = future.await {
- trace!("Killed ChkTeX because it took too long to execute")
- }
}
+ Some(())
}
-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?;
+fn analyze_curly_group(
+ document: &Document,
+ diagnostics_by_uri: &mut MultiMap<Arc<Uri>, Diagnostic>,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ if !matches!(
+ node.kind(),
+ latex::CURLY_GROUP
+ | latex::CURLY_GROUP_COMMAND
+ | latex::CURLY_GROUP_KEY_VALUE
+ | latex::CURLY_GROUP_WORD
+ | latex::CURLY_GROUP_WORD_LIST
+ ) {
+ return None;
+ }
- 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,
- };
+ let is_inside_verbatim_environment = node
+ .ancestors()
+ .filter_map(latex::Environment::cast)
+ .filter_map(|env| env.begin())
+ .filter_map(|begin| begin.name())
+ .filter_map(|name| name.word())
+ .any(|name| ["asy", "lstlisting", "minted", "verbatim"].contains(&name.text()));
- diagnostics.push(Diagnostic {
- source: Some("chktex".into()),
- code: Some(NumberOrString::String(code.into())),
- message,
- severity: Some(severity),
- range,
+ if !is_inside_verbatim_environment
+ && !node
+ .children_with_tokens()
+ .filter_map(|element| element.into_token())
+ .any(|token| token.kind() == latex::R_CURLY)
+ {
+ diagnostics_by_uri.insert(
+ Arc::clone(&document.uri),
+ Diagnostic {
+ range: document
+ .line_index
+ .line_col_lsp_range(TextRange::empty(node.text_range().end())),
+ severity: Some(DiagnosticSeverity::Error),
+ code: None,
+ code_description: None,
+ source: Some("texlab".to_string()),
+ message: "Missing \"}\" inserted".to_string(),
related_information: None,
tags: None,
- })
- }
+ data: None,
+ },
+ );
}
- Ok(diagnostics)
+
+ Some(())
}