diff --git a/src/hover/latex_component.rs b/src/hover/latex_component.rs index 4b9076928..c41e7eb54 100644 --- a/src/hover/latex_component.rs +++ b/src/hover/latex_component.rs @@ -14,8 +14,8 @@ impl LatexComponentHoverProvider { .includes .iter() .filter(|include| { - include.kind == LatexIncludeKind::Package - || include.kind == LatexIncludeKind::Class + include.kind() == LatexIncludeKind::Package + || include.kind() == LatexIncludeKind::Class }) .find(|include| include.path().range().contains(request.params.position)) .map(|include| include.path().text()) diff --git a/src/link/latex_include.rs b/src/link/latex_include.rs index 57736210c..317b2a94e 100644 --- a/src/link/latex_include.rs +++ b/src/link/latex_include.rs @@ -24,7 +24,7 @@ impl LatexIncludeLinkProvider { ) -> Option { request .workspace - .resolve_document(&request.document.uri, include) + .find(include.target()) .map(|target| DocumentLink { range: include.path().range(), target: target.uri.clone(), diff --git a/src/syntax/latex/analysis/citation.rs b/src/syntax/latex/analysis/citation.rs index a99634241..93cb19891 100644 --- a/src/syntax/latex/analysis/citation.rs +++ b/src/syntax/latex/analysis/citation.rs @@ -15,7 +15,7 @@ impl LatexCitation { self.command.extract_word(0).unwrap() } - pub fn parse(commands: &[Arc]) -> Vec { + pub fn parse_all(commands: &[Arc]) -> Vec { let mut citations = Vec::new(); for command in commands { if CITATION_COMMANDS.contains(&command.name.text()) && command.has_word(0) { @@ -84,36 +84,3 @@ pub static CITATION_COMMANDS: &[&str] = &[ "\\Pnotecite", "\\fnotecite", ]; - -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; - - fn verify(text: &str, expected: Vec<&str>) { - let tree = LatexSyntaxTree::from(text); - let actual: Vec<&str> = tree - .citations - .iter() - .map(|citation| citation.key().text()) - .collect(); - assert_eq!(expected, actual); - } - - #[test] - fn test_valid() { - verify("\\cite{foo}", vec!["foo"]); - verify("\\Cite{bar}", vec!["bar"]); - } - - #[test] - fn test_invalid() { - verify("\\cite", vec![]); - verify("\\cite{}", vec![]); - } - - #[test] - fn test_unrelated() { - verify("\\foo", vec![]); - verify("\\foo{bar}", vec![]); - } -} diff --git a/src/syntax/latex/analysis/command.rs b/src/syntax/latex/analysis/command.rs index 5ce714044..f4de96d65 100644 --- a/src/syntax/latex/analysis/command.rs +++ b/src/syntax/latex/analysis/command.rs @@ -1,15 +1,16 @@ use crate::syntax::latex::ast::*; use std::sync::Arc; +#[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct LatexCommandAnalyzer { pub commands: Vec>, } impl LatexCommandAnalyzer { - pub fn new() -> Self { - LatexCommandAnalyzer { - commands: Vec::new(), - } + pub fn find(root: Arc) -> Vec> { + let mut analyzer = LatexCommandAnalyzer::default(); + analyzer.visit_root(root); + analyzer.commands } } @@ -29,19 +30,3 @@ impl LatexVisitor for LatexCommandAnalyzer { fn visit_text(&mut self, _text: Arc) {} } - -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; - - #[test] - fn test() { - let tree = LatexSyntaxTree::from("\\a[\\b]{\\c}{d}"); - let commands: Vec<&str> = tree - .commands - .iter() - .map(|command| command.name.text()) - .collect(); - assert_eq!(vec!["\\a", "\\b", "\\c"], commands); - } -} diff --git a/src/syntax/latex/analysis/environment.rs b/src/syntax/latex/analysis/environment.rs index 0f548ece2..391a363c8 100644 --- a/src/syntax/latex/analysis/environment.rs +++ b/src/syntax/latex/analysis/environment.rs @@ -45,7 +45,7 @@ impl LatexEnvironment { } } - pub fn parse(commands: &[Arc]) -> Vec { + pub fn parse_all(commands: &[Arc]) -> Vec { let mut stack = Vec::new(); let mut environments = Vec::new(); for command in commands { @@ -62,34 +62,3 @@ impl LatexEnvironment { } pub static ENVIRONMENT_COMMANDS: &[&str] = &["\\begin", "\\end"]; - -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; - - #[test] - fn test_nested() { - let tree = LatexSyntaxTree::from("\\begin{foo}\\begin{bar}\\end{baz}\\end{qux}"); - let environments = tree.environments; - assert_eq!(2, environments.len()); - assert_eq!("bar", environments[0].left.name().unwrap().text()); - assert_eq!("baz", environments[0].right.name().unwrap().text()); - assert_eq!("foo", environments[1].left.name().unwrap().text()); - assert_eq!("qux", environments[1].right.name().unwrap().text()); - } - - #[test] - fn test_empty_name() { - let tree = LatexSyntaxTree::from("\\begin{}\\end{}"); - let environments = tree.environments; - assert_eq!(1, environments.len()); - assert_eq!(None, environments[0].left.name()); - assert_eq!(None, environments[0].right.name()); - } - - #[test] - fn test_ummatched() { - let tree = LatexSyntaxTree::from("\\end{foo} \\begin{bar}"); - assert_eq!(tree.environments, Vec::new()); - } -} diff --git a/src/syntax/latex/analysis/equation.rs b/src/syntax/latex/analysis/equation.rs index d9b3c259f..d83a06e3c 100644 --- a/src/syntax/latex/analysis/equation.rs +++ b/src/syntax/latex/analysis/equation.rs @@ -12,7 +12,7 @@ impl LatexEquation { LatexEquation { left, right } } - pub fn parse(commands: &[Arc]) -> Vec { + pub fn parse_all(commands: &[Arc]) -> Vec { let mut equations = Vec::new(); let mut left = None; for command in commands { @@ -28,25 +28,3 @@ impl LatexEquation { } pub static EQUATION_COMMANDS: &[&str] = &["\\[", "\\]"]; - -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; - use crate::syntax::text::SyntaxNode; - use lsp_types::Range; - - #[test] - fn test_matched() { - let tree = LatexSyntaxTree::from("\\[ foo \\]"); - let equations = tree.equations; - assert_eq!(1, equations.len()); - assert_eq!(Range::new_simple(0, 0, 0, 2), equations[0].left.range()); - assert_eq!(Range::new_simple(0, 7, 0, 9), equations[0].right.range()); - } - - #[test] - fn test_unmatched() { - let tree = LatexSyntaxTree::from("\\] \\["); - assert_eq!(tree.equations, Vec::new()); - } -} diff --git a/src/syntax/latex/analysis/include.rs b/src/syntax/latex/analysis/include.rs index 6f7666c16..ec6a5de7b 100644 --- a/src/syntax/latex/analysis/include.rs +++ b/src/syntax/latex/analysis/include.rs @@ -1,102 +1,95 @@ use crate::syntax::latex::ast::*; +use lsp_types::Uri; +use path_clean::PathClean; use std::sync::Arc; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum LatexIncludeKind { - Package, - Class, TexFile, BibFile, + Package, + Class, } #[derive(Debug, PartialEq, Eq, Clone)] pub struct LatexInclude { pub command: Arc, - pub kind: LatexIncludeKind, + target: Option, } impl LatexInclude { - pub fn path(&self) -> &LatexToken { - self.command.extract_word(0).unwrap() - } + fn parse(uri: &Uri, command: Arc) -> Option { + let mut include = LatexInclude { + command, + target: None, + }; - pub fn new(command: Arc, kind: LatexIncludeKind) -> Self { - LatexInclude { command, kind } + let mut path = uri.to_file_path().ok()?; + path.pop(); + path.push(include.path().text()); + path = path.clean(); + let has_extension = path.extension().is_some(); + let mut path = path.to_str()?.to_owned(); + if !has_extension { + path = format!("{}{}", path, include.extension()); + } + include.target = Some(Uri::from_file_path(path).ok()?); + Some(include) } - pub fn parse(commands: &[Arc]) -> (Vec, Vec) { + pub fn parse_all(uri: &Uri, commands: &[Arc]) -> Vec { let mut includes = Vec::new(); - let mut components = Vec::new(); for command in commands { - let kind = match command.name.text() { - "\\include" | "\\input" => Some(LatexIncludeKind::TexFile), - "\\bibliography" | "\\addbibresource" => Some(LatexIncludeKind::BibFile), - "\\usepackage" => Some(LatexIncludeKind::Package), - "\\documentclass" => Some(LatexIncludeKind::Class), - _ => None, - }; - - if let Some(kind) = kind { - if command.has_word(0) { - let include = LatexInclude::new(Arc::clone(&command), kind); - match include.kind { - LatexIncludeKind::Package => { - components.push(format!("{}.sty", include.path().text())); - } - LatexIncludeKind::Class => { - components.push(format!("{}.cls", include.path().text())); - } - LatexIncludeKind::TexFile | LatexIncludeKind::BibFile => {} - } + if INCLUDE_COMMANDS.contains(&command.name.text()) && command.has_word(0) { + if let Some(include) = LatexInclude::parse(uri, Arc::clone(&command)) { includes.push(include); } } } - (includes, components) + includes } -} -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; + pub fn target(&self) -> &Uri { + self.target.as_ref().unwrap() + } - fn verify(text: &str, includes: Vec<&str>, components: Vec<&str>) { - let tree = LatexSyntaxTree::from(text); - let actual_includes: Vec<&str> = tree - .includes - .iter() - .map(|include| include.path().text()) - .collect(); - assert_eq!(includes, actual_includes); - assert_eq!(components, tree.components); + pub fn path(&self) -> &LatexToken { + self.command.extract_word(0).unwrap() } - #[test] - fn test_valid() { - verify("\\include{foo}", vec!["foo"], vec![]); - verify("\\bibliography{foo}", vec!["foo"], vec![]); - verify( - "\\usepackage{amsmath}", - vec!["amsmath"], - vec!["amsmath.sty"], - ); - verify( - "\\documentclass{article}", - vec!["article"], - vec!["article.cls"], - ); + pub fn kind(&self) -> LatexIncludeKind { + match self.command.name.text() { + "\\include" | "\\input" => LatexIncludeKind::TexFile, + "\\bibliography" | "\\addbibresource" => LatexIncludeKind::BibFile, + "\\usepackage" => LatexIncludeKind::Package, + "\\documentclass" => LatexIncludeKind::Class, + _ => unreachable!(), + } } - #[test] - fn test_invalid() { - verify("\\include", vec![], vec![]); - verify("\\include{}", vec![], vec![]); - verify("\\include{foo bar}", vec![], vec![]); + pub fn name(&self) -> Option { + match self.kind() { + LatexIncludeKind::TexFile | LatexIncludeKind::BibFile => None, + LatexIncludeKind::Package => Some(format!("{}.sty", self.path().text())), + LatexIncludeKind::Class => Some(format!("{}.cls", self.path().text())), + } } - #[test] - fn test_unrelated() { - verify("\\foo", vec![], vec![]); - verify("\\foo{bar}", vec![], vec![]); + pub fn extension(&self) -> &'static str { + match self.kind() { + LatexIncludeKind::TexFile => ".tex", + LatexIncludeKind::BibFile => ".bib", + LatexIncludeKind::Package => ".sty", + LatexIncludeKind::Class => ".cls", + } } } + +pub static INCLUDE_COMMANDS: &[&str] = &[ + "\\include", + "\\input", + "\\bibliography", + "\\addbibresource", + "\\usepackage", + "\\documentclass", +]; diff --git a/src/syntax/latex/analysis/label.rs b/src/syntax/latex/analysis/label.rs index 7ce527c54..2fa8f00d9 100644 --- a/src/syntax/latex/analysis/label.rs +++ b/src/syntax/latex/analysis/label.rs @@ -29,7 +29,7 @@ impl LatexLabel { } } - pub fn parse(commands: &[Arc]) -> Vec { + pub fn parse_all(commands: &[Arc]) -> Vec { let mut labels = Vec::new(); for command in commands { if command.has_word(0) @@ -46,36 +46,3 @@ impl LatexLabel { pub static LABEL_DEFINITION_COMMANDS: &[&str] = &["\\label"]; pub static LABEL_REFERENCE_COMMANDS: &[&str] = &["\\ref", "\\autoref", "\\eqref"]; - -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; - - fn verify(text: &str, expected: Vec<&str>) { - let tree = LatexSyntaxTree::from(text); - let actual: Vec<&str> = tree - .labels - .iter() - .map(|label| label.name().text()) - .collect(); - assert_eq!(expected, actual); - } - - #[test] - fn test_valid() { - verify("\\label{foo}", vec!["foo"]); - verify("\\ref{bar}", vec!["bar"]); - } - - #[test] - fn test_invalid() { - verify("\\label", vec![]); - verify("\\label{}", vec![]); - } - - #[test] - fn test_unrelated() { - verify("\\foo", vec![]); - verify("\\foo{bar}", vec![]); - } -} diff --git a/src/syntax/latex/analysis/section.rs b/src/syntax/latex/analysis/section.rs index 37e239cf8..2e07c1502 100644 --- a/src/syntax/latex/analysis/section.rs +++ b/src/syntax/latex/analysis/section.rs @@ -17,7 +17,7 @@ impl LatexSection { } } - pub fn parse(commands: &[Arc]) -> Vec { + pub fn parse_all(commands: &[Arc]) -> Vec { let mut sections = Vec::new(); for command in commands { if SECTION_COMMANDS.contains(&command.name.text()) { @@ -52,36 +52,3 @@ pub static SECTION_COMMANDS: &[&str] = &[ "\\subparagraph", "\\subparagraph*", ]; - -#[cfg(test)] -mod tests { - use crate::syntax::latex::LatexSyntaxTree; - - fn verify(text: &str, expected: Vec<&str>) { - let tree = LatexSyntaxTree::from(text); - let actual: Vec<&str> = tree - .sections - .iter() - .map(|section| section.text.as_ref()) - .collect(); - assert_eq!(expected, actual); - } - - #[test] - fn test_valid() { - verify("\\section{foo bar}", vec!["foo bar"]); - verify("\\chapter{bar}", vec!["bar"]); - } - - #[test] - fn test_invalid() { - verify("\\section", vec![]); - verify("\\section{}", vec![]); - } - - #[test] - fn test_unrelated() { - verify("\\foo", vec![]); - verify("\\foo{bar}", vec![]); - } -} diff --git a/src/syntax/latex/ast.rs b/src/syntax/latex/ast.rs index f1c3bbde0..9dc83a07c 100644 --- a/src/syntax/latex/ast.rs +++ b/src/syntax/latex/ast.rs @@ -35,7 +35,7 @@ impl SyntaxNode for LatexToken { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct LatexRoot { pub children: Vec, } diff --git a/src/syntax/latex/mod.rs b/src/syntax/latex/mod.rs index 7b7a5b049..dd7613c64 100644 --- a/src/syntax/latex/mod.rs +++ b/src/syntax/latex/mod.rs @@ -1,3 +1,5 @@ +use crate::syntax::latex::ast::LatexRoot; + mod analysis; mod ast; mod lexer; @@ -15,24 +17,55 @@ pub use crate::syntax::latex::ast::*; use crate::syntax::latex::lexer::LatexLexer; use crate::syntax::latex::parser::LatexParser; use crate::syntax::text::SyntaxNode; -use lsp_types::Position; +use lsp_types::{Position, Uri}; use std::sync::Arc; -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct LatexSyntaxTree { pub root: Arc, pub commands: Vec>, pub includes: Vec, pub components: Vec, pub environments: Vec, + pub is_standalone: bool, pub labels: Vec, pub sections: Vec, pub citations: Vec, pub equations: Vec, - pub is_standalone: bool, } impl LatexSyntaxTree { + pub fn new(uri: &Uri, text: &str) -> Self { + let lexer = LatexLexer::new(text); + let mut parser = LatexParser::new(lexer); + let root = Arc::new(parser.root()); + let commands = LatexCommandAnalyzer::find(Arc::clone(&root)); + let includes = LatexInclude::parse_all(uri, &commands); + let components = includes.iter().flat_map(|include| include.name()).collect(); + let environments = LatexEnvironment::parse_all(&commands); + let is_standalone = environments + .iter() + .any(|env| env.left.name().map(LatexToken::text) == Some("document")); + + let labels = LatexLabel::parse_all(&commands); + let sections = LatexSection::parse_all(&commands); + let citations = LatexCitation::parse_all(&commands); + let equations = LatexEquation::parse_all(&commands); + + LatexSyntaxTree { + root, + commands, + includes, + components, + environments, + is_standalone, + labels, + sections, + citations, + equations, + } + } + pub fn find(&self, position: Position) -> Vec { let mut finder = LatexFinder::new(position); finder.visit_root(Arc::clone(&self.root)); @@ -52,42 +85,3 @@ impl LatexSyntaxTree { None } } - -impl From for LatexSyntaxTree { - fn from(root: LatexRoot) -> Self { - let root = Arc::new(root); - let mut analyzer = LatexCommandAnalyzer::new(); - analyzer.visit_root(Arc::clone(&root)); - let commands = analyzer.commands; - let (includes, components) = LatexInclude::parse(&commands); - let environments = LatexEnvironment::parse(&commands); - let labels = LatexLabel::parse(&commands); - let sections = LatexSection::parse(&commands); - let citations = LatexCitation::parse(&commands); - let equations = LatexEquation::parse(&commands); - let is_standalone = environments - .iter() - .any(|env| env.left.name().map(LatexToken::text) == Some("document")); - LatexSyntaxTree { - root, - commands, - includes, - components, - environments, - labels, - sections, - citations, - equations, - is_standalone, - } - } -} - -impl From<&str> for LatexSyntaxTree { - fn from(text: &str) -> Self { - let lexer = LatexLexer::new(text); - let mut parser = LatexParser::new(lexer); - let root = parser.root(); - LatexSyntaxTree::from(root) - } -} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 1a12de2ac..e94b3ddea 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -4,6 +4,7 @@ pub mod text; use crate::syntax::bibtex::BibtexSyntaxTree; use crate::syntax::latex::LatexSyntaxTree; +use lsp_types::Uri; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Language { @@ -36,9 +37,9 @@ pub enum SyntaxTree { } impl SyntaxTree { - pub fn parse(text: &str, language: Language) -> Self { + pub fn parse(uri: &Uri, text: &str, language: Language) -> Self { match language { - Language::Latex => SyntaxTree::Latex(LatexSyntaxTree::from(text)), + Language::Latex => SyntaxTree::Latex(LatexSyntaxTree::new(uri, text)), Language::Bibtex => SyntaxTree::Bibtex(BibtexSyntaxTree::from(text)), } } diff --git a/src/workspace.rs b/src/workspace.rs index 7bcb1acaf..78de1552d 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -2,7 +2,6 @@ use crate::syntax::latex::*; use crate::syntax::{Language, SyntaxTree}; use log::*; use lsp_types::{TextDocumentItem, Uri}; -use path_clean::PathClean; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; @@ -21,7 +20,7 @@ impl Document { } pub fn parse(uri: Uri, text: String, language: Language) -> Self { - let tree = SyntaxTree::parse(&text, language); + let tree = SyntaxTree::parse(&uri, &text, language); Document::new(uri, text, tree) } @@ -49,51 +48,12 @@ impl Workspace { .map(|document| Arc::clone(&document)) } - pub fn find_path(&self, path: &str) -> Option> { - self.documents - .iter() - .find(|document| document.is_file() && document.uri.path() == path) - .map(|document| Arc::clone(&document)) - } - - pub fn resolve_document(&self, uri: &Uri, include: &LatexInclude) -> Option> { - let targets = Self::resolve_link_targets(uri, include)?; - for target in &targets { - if let Some(document) = self.find_path(target) { - return Some(document); - } - } - None - } - - fn resolve_link_targets(uri: &Uri, include: &LatexInclude) -> Option<[String; 2]> { - if uri.scheme() != "file" { - return None; - } - - let mut path = uri.to_file_path().ok()?; - path.pop(); - path.push(include.path().text()); - path = path.clean(); - let path1 = path.to_string_lossy().into_owned(); - - let extension = match include.kind { - LatexIncludeKind::Package => ".sty", - LatexIncludeKind::Class => ".cls", - LatexIncludeKind::TexFile => ".tex", - LatexIncludeKind::BibFile => ".bib", - }; - let path2 = format!("{}{}", path1, extension); - - Some([path1, path2]) - } - pub fn related_documents(&self, uri: &Uri) -> Vec> { let mut edges: Vec<(Arc, Arc)> = Vec::new(); for parent in self.documents.iter().filter(|document| document.is_file()) { if let SyntaxTree::Latex(tree) = &parent.tree { for include in &tree.includes { - if let Some(ref child) = self.resolve_document(&parent.uri, include) { + if let Some(ref child) = self.find(include.target()) { edges.push((Arc::clone(&parent), Arc::clone(&child))); edges.push((Arc::clone(&child), Arc::clone(&parent))); } @@ -139,17 +99,16 @@ impl Workspace { for document in &self.documents { if let SyntaxTree::Latex(tree) = &document.tree { for include in &tree.includes { - if self.resolve_document(&document.uri, include).is_some() { + if self.find(include.target()).is_some() + || include.kind() == LatexIncludeKind::Package + || include.kind() == LatexIncludeKind::Class + { continue; } - if let Some(targets) = Self::resolve_link_targets(&document.uri, &include) { - for target in &targets { - let path = PathBuf::from(target); - if path.exists() { - includes.push(path); - } - } + let path = PathBuf::from(include.target().to_file_path().unwrap()); + if path.exists() { + includes.push(path); } } }