summaryrefslogtreecommitdiff
path: root/support/texlab/src/symbol/latex_section/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/symbol/latex_section/mod.rs')
-rw-r--r--support/texlab/src/symbol/latex_section/mod.rs634
1 files changed, 634 insertions, 0 deletions
diff --git a/support/texlab/src/symbol/latex_section/mod.rs b/support/texlab/src/symbol/latex_section/mod.rs
new file mode 100644
index 0000000000..f6fe2aede9
--- /dev/null
+++ b/support/texlab/src/symbol/latex_section/mod.rs
@@ -0,0 +1,634 @@
+mod enumeration;
+mod equation;
+mod float;
+mod theorem;
+
+use super::{LatexSymbol, LatexSymbolKind};
+use crate::range::RangeExt;
+use crate::syntax::*;
+use crate::workspace::*;
+use futures_boxed::boxed;
+use lsp_types::*;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct LatexSectionSymbolProvider;
+
+impl FeatureProvider for LatexSectionSymbolProvider {
+ type Params = DocumentSymbolParams;
+ type Output = Vec<LatexSymbol>;
+
+ #[boxed]
+ async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
+ let mut symbols = Vec::new();
+ if let SyntaxTree::Latex(tree) = &request.document().tree {
+ let mut section_tree = build_section_tree(&request.view, tree);
+ for symbol in enumeration::symbols(&request.view, tree) {
+ section_tree.insert_symbol(&symbol);
+ }
+
+ for symbol in equation::symbols(&request.view, tree) {
+ section_tree.insert_symbol(&symbol);
+ }
+
+ for symbol in float::symbols(&request.view, tree) {
+ section_tree.insert_symbol(&symbol);
+ }
+
+ for symbol in theorem::symbols(&request.view, tree) {
+ section_tree.insert_symbol(&symbol);
+ }
+
+ for symbol in section_tree.symbols {
+ symbols.push(symbol);
+ }
+
+ for child in section_tree.children {
+ symbols.push(child.into());
+ }
+ }
+ symbols
+ }
+}
+
+pub fn build_section_tree<'a>(
+ view: &'a DocumentView,
+ tree: &'a LatexSyntaxTree,
+) -> LatexSectionTree<'a> {
+ let mut section_tree = LatexSectionTree::from(tree);
+ section_tree.set_full_text(&view.document.text);
+ let end_position = compute_end_position(tree, &view.document.text);
+ LatexSectionNode::set_full_range(&mut section_tree.children, end_position);
+ let outline = Outline::from(view);
+ for child in &mut section_tree.children {
+ child.set_label(tree, view, &outline);
+ }
+ section_tree
+}
+
+fn compute_end_position(tree: &LatexSyntaxTree, text: &str) -> Position {
+ let mut stream = CharStream::new(text);
+ while stream.next().is_some() {}
+ tree.env
+ .environments
+ .iter()
+ .find(|env| env.left.name().map(LatexToken::text) == Some("document"))
+ .map(|env| env.right.start())
+ .unwrap_or(stream.current_position)
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexSectionNode<'a> {
+ pub section: &'a LatexSection,
+ pub full_range: Range,
+ full_text: &'a str,
+ label: Option<String>,
+ number: Option<String>,
+ symbols: Vec<LatexSymbol>,
+ children: Vec<Self>,
+}
+
+impl<'a> LatexSectionNode<'a> {
+ fn new(section: &'a LatexSection) -> Self {
+ Self {
+ section,
+ full_range: Range::default(),
+ full_text: "",
+ label: None,
+ number: None,
+ symbols: Vec::new(),
+ children: Vec::new(),
+ }
+ }
+
+ fn set_full_text(&mut self, full_text: &'a str) {
+ self.full_text = full_text;
+ for child in &mut self.children {
+ child.set_full_text(full_text);
+ }
+ }
+
+ fn name(&self) -> String {
+ self.section
+ .extract_text(self.full_text)
+ .unwrap_or_else(|| "Unknown".to_owned())
+ }
+
+ fn set_full_range(children: &mut Vec<Self>, end_position: Position) {
+ for i in 0..children.len() {
+ let current_end = children
+ .get(i + 1)
+ .map(|next| next.section.start())
+ .unwrap_or(end_position);
+
+ let mut current = &mut children[i];
+ current.full_range = Range::new(current.section.start(), current_end);
+ Self::set_full_range(&mut current.children, current_end);
+ }
+ }
+
+ fn set_label(&mut self, tree: &LatexSyntaxTree, view: &DocumentView, outline: &Outline) {
+ if let Some(label) = tree
+ .structure
+ .labels
+ .iter()
+ .filter(|label| label.kind == LatexLabelKind::Definition)
+ .find(|label| self.full_range.contains(label.start()))
+ {
+ if let Some(ctx) = OutlineContext::parse(view, label, outline) {
+ let mut is_section = false;
+ if let OutlineContextItem::Section { text, .. } = &ctx.item {
+ if self.name() == *text {
+ for name in label.names() {
+ self.label = Some(name.text().to_owned());
+ }
+
+ is_section = true;
+ }
+ }
+
+ if is_section {
+ self.number = ctx.number;
+ }
+ }
+ }
+
+ for child in &mut self.children {
+ child.set_label(tree, view, outline);
+ }
+ }
+
+ fn insert_section(nodes: &mut Vec<Self>, section: &'a LatexSection) {
+ match nodes.last_mut() {
+ Some(parent) => {
+ if parent.section.level < section.level {
+ Self::insert_section(&mut parent.children, section);
+ } else {
+ nodes.push(LatexSectionNode::new(section));
+ }
+ }
+ None => {
+ nodes.push(LatexSectionNode::new(section));
+ }
+ }
+ }
+
+ fn insert_symbol(&mut self, symbol: &LatexSymbol) -> bool {
+ if !self.full_range.contains(symbol.selection_range.start) {
+ return false;
+ }
+
+ for child in &mut self.children {
+ if child.insert_symbol(symbol) {
+ return true;
+ }
+ }
+
+ self.symbols.push(symbol.clone());
+ true
+ }
+
+ fn find(&self, label: &str) -> Option<&Self> {
+ if self.label.as_ref().map(AsRef::as_ref) == Some(label) {
+ Some(self)
+ } else {
+ for child in &self.children {
+ let result = child.find(label);
+ if result.is_some() {
+ return result;
+ }
+ }
+ None
+ }
+ }
+}
+
+impl<'a> Into<LatexSymbol> for LatexSectionNode<'a> {
+ fn into(self) -> LatexSymbol {
+ let name = self.name();
+
+ let mut children: Vec<LatexSymbol> = self.children.into_iter().map(Into::into).collect();
+
+ for symbol in self.symbols {
+ children.push(symbol);
+ }
+
+ let full_name = match &self.number {
+ Some(number) => format!("{} {}", number, name),
+ None => name,
+ };
+
+ LatexSymbol {
+ name: full_name,
+ label: self.label,
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: self.full_range,
+ selection_range: self.section.range(),
+ children,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexSectionTree<'a> {
+ symbols: Vec<LatexSymbol>,
+ children: Vec<LatexSectionNode<'a>>,
+}
+
+impl<'a> LatexSectionTree<'a> {
+ fn new() -> Self {
+ Self {
+ symbols: Vec::new(),
+ children: Vec::new(),
+ }
+ }
+
+ fn set_full_text(&mut self, full_text: &'a str) {
+ for child in &mut self.children {
+ child.set_full_text(full_text);
+ }
+ }
+
+ fn insert_symbol(&mut self, symbol: &LatexSymbol) {
+ for child in &mut self.children {
+ if child.insert_symbol(symbol) {
+ return;
+ }
+ }
+ self.symbols.push(symbol.clone());
+ }
+
+ pub fn find(&self, label: &str) -> Option<&LatexSectionNode<'a>> {
+ for child in &self.children {
+ let result = child.find(label);
+ if result.is_some() {
+ return result;
+ }
+ }
+ None
+ }
+}
+
+impl<'a> From<&'a LatexSyntaxTree> for LatexSectionTree<'a> {
+ fn from(tree: &'a LatexSyntaxTree) -> Self {
+ let mut root = Self::new();
+ for section in &tree.structure.sections {
+ LatexSectionNode::insert_section(&mut root.children, section);
+ }
+ root
+ }
+}
+
+pub fn label_name(label: Option<&LatexLabel>) -> Option<String> {
+ label.map(|label| label.names()[0].text().to_owned())
+}
+
+pub fn selection_range(full_range: Range, label: Option<&LatexLabel>) -> Range {
+ label.map(|label| label.range()).unwrap_or(full_range)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::range::RangeExt;
+
+ #[test]
+ fn test_subsection() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![
+ FeatureSpec::file(
+ "foo.tex",
+ "\\section{Foo}\n\\subsection{Bar}\\label{sec:bar}\n\\subsection{Baz}\n\\section{Qux}",
+ ),
+ FeatureSpec::file(
+ "foo.aux",
+ "\\newlabel{sec:bar}{{\\relax 2.1}{4}{Bar\\relax }{figure.caption.4}{}}"
+ ),
+ ],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![
+ LatexSymbol {
+ name: "Foo".into(),
+ label: None,
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: Range::new_simple(0, 0, 3, 0),
+ selection_range: Range::new_simple(0, 0, 0, 13),
+ children: vec![
+ LatexSymbol {
+ name: "2.1 Bar".into(),
+ label: Some("sec:bar".into()),
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: Range::new_simple(1, 0, 2, 0),
+ selection_range: Range::new_simple(1, 0, 1, 16),
+ children: Vec::new(),
+ },
+ LatexSymbol {
+ name: "Baz".into(),
+ label: None,
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: Range::new_simple(2, 0, 3, 0),
+ selection_range: Range::new_simple(2, 0, 2, 16),
+ children: Vec::new(),
+ },
+ ],
+ },
+ LatexSymbol {
+ name: "Qux".into(),
+ label: None,
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: Range::new_simple(3, 0, 3, 13),
+ selection_range: Range::new_simple(3, 0, 3, 13),
+ children: Vec::new(),
+ }
+ ]
+ );
+ }
+
+ #[test]
+ fn test_section_inside_document_environment() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![FeatureSpec::file(
+ "foo.tex",
+ "\\begin{document}\\section{Foo}\\relax\n\\end{document}",
+ )],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Foo".into(),
+ label: None,
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: Range::new_simple(0, 16, 1, 0),
+ selection_range: Range::new_simple(0, 16, 0, 29),
+ children: Vec::new()
+ }]
+ );
+ }
+
+ #[test]
+ fn test_enumeration() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![FeatureSpec::file(
+ "foo.tex",
+ "\\section{Foo}\n\\begin{enumerate}\n\\end{enumerate}",
+ )],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Foo".into(),
+ label: None,
+ kind: LatexSymbolKind::Section,
+ deprecated: false,
+ full_range: Range::new_simple(0, 0, 2, 15),
+ selection_range: Range::new_simple(0, 0, 0, 13),
+ children: vec![LatexSymbol {
+ name: "Enumerate".into(),
+ label: None,
+ kind: LatexSymbolKind::Enumeration,
+ deprecated: false,
+ full_range: Range::new_simple(1, 0, 2, 15),
+ selection_range: Range::new_simple(1, 0, 2, 15),
+ children: Vec::new(),
+ },],
+ },]
+ );
+ }
+
+ #[test]
+ fn test_equation() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![FeatureSpec::file(
+ "foo.tex",
+ "\\[Foo\\]\n\\begin{equation}\\label{eq:foo}\\end{equation}",
+ )],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![
+ LatexSymbol {
+ name: "Equation".into(),
+ label: None,
+ kind: LatexSymbolKind::Equation,
+ deprecated: false,
+ full_range: Range::new_simple(0, 0, 0, 7),
+ selection_range: Range::new_simple(0, 0, 0, 7),
+ children: Vec::new(),
+ },
+ LatexSymbol {
+ name: "Equation".into(),
+ label: Some("eq:foo".into()),
+ kind: LatexSymbolKind::Equation,
+ deprecated: false,
+ full_range: Range::new_simple(1, 0, 1, 44),
+ selection_range: Range::new_simple(1, 16, 1, 30),
+ children: Vec::new(),
+ },
+ ]
+ );
+ }
+
+ #[test]
+ fn test_equation_number() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![
+ FeatureSpec::file("foo.tex", "\\[\\label{eq:foo}\\]"),
+ FeatureSpec::file(
+ "foo.aux",
+ "\\newlabel{eq:foo}{{\\relax 2.1}{4}{Bar\\relax }{figure.caption.4}{}}",
+ ),
+ ],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Equation (2.1)".into(),
+ label: Some("eq:foo".into()),
+ kind: LatexSymbolKind::Equation,
+ deprecated: false,
+ full_range: Range::new_simple(0, 0, 0, 18),
+ selection_range: Range::new_simple(0, 2, 0, 16),
+ children: Vec::new(),
+ },]
+ );
+ }
+
+ #[test]
+ fn test_table() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![FeatureSpec::file(
+ "foo.tex",
+ "\\begin{table}\\caption{Foo}\\end{table}",
+ )],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Table: Foo".into(),
+ label: None,
+ kind: LatexSymbolKind::Table,
+ deprecated: false,
+ full_range: Range::new_simple(0, 0, 0, 37),
+ selection_range: Range::new_simple(0, 0, 0, 37),
+ children: Vec::new(),
+ },]
+ );
+ }
+
+ #[test]
+ fn test_figure_number() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![
+ FeatureSpec::file(
+ "foo.tex",
+ "\\begin{figure}\\caption{Foo}\\label{fig:foo}\\end{figure}",
+ ),
+ FeatureSpec::file(
+ "foo.aux",
+ "\\newlabel{fig:foo}{{\\relax 2.1}{4}{Bar\\relax }{figure.caption.4}{}}",
+ ),
+ ],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Figure 2.1: Foo".into(),
+ label: Some("fig:foo".into()),
+ kind: LatexSymbolKind::Figure,
+ deprecated: false,
+ full_range: Range::new_simple(0, 0, 0, 54),
+ selection_range: Range::new_simple(0, 27, 0, 42),
+ children: Vec::new(),
+ },]
+ );
+ }
+
+ #[test]
+ fn test_lemma() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![FeatureSpec::file(
+ "foo.tex",
+ "\\newtheorem{lemma}{Lemma}\\begin{lemma}\\end{lemma}",
+ )],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Lemma".into(),
+ label: None,
+ kind: LatexSymbolKind::Theorem,
+ deprecated: false,
+ full_range: Range::new_simple(0, 25, 0, 49),
+ selection_range: Range::new_simple(0, 25, 0, 49),
+ children: Vec::new(),
+ },]
+ );
+ }
+
+ #[test]
+ fn test_lemma_number() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![
+ FeatureSpec::file(
+ "foo.tex",
+ "\\newtheorem{lemma}{Lemma}\n\\begin{lemma}\\label{thm:foo}\\end{lemma}",
+ ),
+ FeatureSpec::file(
+ "foo.aux",
+ "\\newlabel{thm:foo}{{\\relax 2.1}{4}{Bar\\relax }{figure.caption.4}{}}",
+ ),
+ ],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Lemma 2.1".into(),
+ label: Some("thm:foo".into()),
+ kind: LatexSymbolKind::Theorem,
+ deprecated: false,
+ full_range: Range::new_simple(1, 0, 1, 39),
+ selection_range: Range::new_simple(1, 13, 1, 28),
+ children: Vec::new(),
+ },]
+ );
+ }
+
+ #[test]
+ fn test_lemma_description() {
+ let symbols = test_feature(
+ LatexSectionSymbolProvider,
+ FeatureSpec {
+ files: vec![FeatureSpec::file(
+ "foo.tex",
+ "\\newtheorem{lemma}{Lemma}\\begin{lemma}[Foo]\\end{lemma}",
+ )],
+ main_file: "foo.tex",
+ ..FeatureSpec::default()
+ },
+ );
+ assert_eq!(
+ symbols,
+ vec![LatexSymbol {
+ name: "Lemma (Foo)".into(),
+ label: None,
+ kind: LatexSymbolKind::Theorem,
+ deprecated: false,
+ full_range: Range::new_simple(0, 25, 0, 54),
+ selection_range: Range::new_simple(0, 25, 0, 54),
+ children: Vec::new(),
+ },]
+ );
+ }
+}