summaryrefslogtreecommitdiff
path: root/support/texlab/crates/symbols/src/document/tex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/crates/symbols/src/document/tex.rs')
-rw-r--r--support/texlab/crates/symbols/src/document/tex.rs257
1 files changed, 257 insertions, 0 deletions
diff --git a/support/texlab/crates/symbols/src/document/tex.rs b/support/texlab/crates/symbols/src/document/tex.rs
new file mode 100644
index 0000000000..55612ee11d
--- /dev/null
+++ b/support/texlab/crates/symbols/src/document/tex.rs
@@ -0,0 +1,257 @@
+use std::str::FromStr;
+
+use base_db::{semantics::Span, util::FloatKind, Config, Project};
+use rowan::ast::AstNode;
+use syntax::latex::{self, HasBrack, HasCurly, LatexLanguage};
+use titlecase::titlecase;
+
+use crate::{Symbol, SymbolKind};
+
+#[derive(Debug)]
+pub struct SymbolBuilder<'a> {
+ project: &'a Project<'a>,
+ config: &'a Config,
+}
+
+impl<'a> SymbolBuilder<'a> {
+ pub fn new(project: &'a Project<'a>, config: &'a Config) -> Self {
+ Self { project, config }
+ }
+
+ pub fn visit(&self, node: &latex::SyntaxNode) -> Vec<Symbol> {
+ let symbol = if let Some(section) = latex::Section::cast(node.clone()) {
+ self.visit_section(&section)
+ } else if let Some(enum_item) = latex::EnumItem::cast(node.clone()) {
+ self.visit_enum_item(&enum_item)
+ } else if let Some(equation) = latex::Equation::cast(node.clone()) {
+ self.visit_equation(&equation)
+ } else if let Some(environment) = latex::Environment::cast(node.clone()) {
+ environment.begin().and_then(|begin| {
+ let name = begin.name()?.key()?.to_string();
+ if self.config.syntax.math_environments.contains(&name) {
+ self.visit_equation(&environment)
+ } else if self.config.syntax.enum_environments.contains(&name) {
+ self.visit_enumeration(&environment, &name)
+ } else if let Ok(float_kind) = FloatKind::from_str(&name) {
+ self.visit_float(&environment, float_kind)
+ } else {
+ self.visit_theorem(&environment, &name)
+ }
+ })
+ } else {
+ None
+ };
+
+ match symbol {
+ Some(mut parent) => {
+ for child in node.children() {
+ parent.children.append(&mut self.visit(&child));
+ }
+
+ vec![parent]
+ }
+ None => {
+ let mut symbols = Vec::new();
+ for child in node.children() {
+ symbols.append(&mut self.visit(&child));
+ }
+
+ symbols
+ }
+ }
+ }
+
+ fn visit_section(&self, section: &latex::Section) -> Option<Symbol> {
+ let range = latex::small_range(section);
+ let group = section.name()?;
+ let group_text = group.content_text()?;
+ let kind = SymbolKind::Section;
+
+ let symbol = match self.find_label(section.syntax()) {
+ Some(label) => {
+ let name = match self.find_label_number(&label.text) {
+ Some(number) => format!("{number} {group_text}"),
+ None => group_text,
+ };
+
+ Symbol::new_label(name, kind, range, label)
+ }
+ None => Symbol::new_simple(group_text, kind, range, range),
+ };
+
+ Some(symbol)
+ }
+
+ fn visit_enum_item(&self, enum_item: &latex::EnumItem) -> Option<Symbol> {
+ let enum_envs = &self.config.syntax.enum_environments;
+ if !enum_item
+ .syntax()
+ .ancestors()
+ .filter_map(latex::Environment::cast)
+ .filter_map(|environment| environment.begin())
+ .filter_map(|begin| begin.name())
+ .filter_map(|name| name.key())
+ .any(|name| enum_envs.contains(&name.to_string()))
+ {
+ return None;
+ }
+
+ let range = latex::small_range(enum_item);
+ let kind = SymbolKind::EnumerationItem;
+ let name = enum_item
+ .label()
+ .and_then(|label| label.content_text())
+ .unwrap_or_else(|| "Item".into());
+
+ let symbol = match self.find_label(enum_item.syntax()) {
+ Some(label) => {
+ let name = self
+ .find_label_number(&label.text)
+ .map_or_else(|| name.clone(), String::from);
+
+ Symbol::new_label(name, kind, range, label)
+ }
+ None => Symbol::new_simple(name, kind, range, range),
+ };
+
+ Some(symbol)
+ }
+
+ fn visit_theorem(&self, environment: &latex::Environment, name: &str) -> Option<Symbol> {
+ let heading = self
+ .project
+ .documents
+ .iter()
+ .filter_map(|document| document.data.as_tex())
+ .flat_map(|data| data.semantics.theorem_definitions.iter())
+ .find(|theorem| theorem.name.text == name)
+ .map(|theorem| theorem.heading.as_str())?;
+
+ let options = environment.begin().and_then(|begin| begin.options());
+ let caption = options.and_then(|options| options.content_text());
+ let range = latex::small_range(environment);
+ let kind = SymbolKind::Theorem;
+
+ let symbol = match self.find_label(environment.syntax()) {
+ Some(label) => {
+ let name = match (self.find_label_number(&label.text), caption) {
+ (Some(number), Some(caption)) => {
+ format!("{heading} {number} ({caption})")
+ }
+ (Some(number), None) => format!("{heading} {number}"),
+ (None, Some(caption)) => format!("{heading} ({caption})"),
+ (None, None) => heading.into(),
+ };
+
+ Symbol::new_label(name, kind, range, label)
+ }
+ None => {
+ let name = caption.map_or_else(
+ || heading.into(),
+ |caption| format!("{heading} ({caption})"),
+ );
+
+ Symbol::new_simple(name, kind, range, range)
+ }
+ };
+
+ Some(symbol)
+ }
+
+ fn visit_float(
+ &self,
+ environment: &latex::Environment,
+ float_kind: FloatKind,
+ ) -> Option<Symbol> {
+ let range = latex::small_range(environment);
+
+ let (float_kind, symbol_kind) = match float_kind {
+ FloatKind::Algorithm => ("Algorithm", SymbolKind::Algorithm),
+ FloatKind::Figure => ("Figure", SymbolKind::Figure),
+ FloatKind::Listing => ("Listing", SymbolKind::Listing),
+ FloatKind::Table => ("Table", SymbolKind::Table),
+ };
+
+ let caption = environment
+ .syntax()
+ .children()
+ .filter_map(latex::Caption::cast)
+ .find_map(|node| node.long())
+ .and_then(|node| node.content_text())?;
+
+ let symbol = match self.find_label(environment.syntax()) {
+ Some(label) => {
+ let name = match self.find_label_number(&label.text) {
+ Some(number) => format!("{float_kind} {number}: {caption}"),
+ None => format!("{float_kind}: {caption}"),
+ };
+
+ Symbol::new_label(name, symbol_kind, range, label)
+ }
+ None => {
+ let name = format!("{float_kind}: {caption}");
+ Symbol::new_simple(name, symbol_kind, range, range)
+ }
+ };
+
+ Some(symbol)
+ }
+
+ fn visit_enumeration(
+ &self,
+ environment: &latex::Environment,
+ environment_name: &str,
+ ) -> Option<Symbol> {
+ let range = latex::small_range(environment);
+
+ let kind = SymbolKind::Enumeration;
+ let name = titlecase(environment_name);
+ let symbol = match self.find_label(environment.syntax()) {
+ Some(label) => {
+ let name = match self.find_label_number(&label.text) {
+ Some(number) => format!("{name} {number}"),
+ None => name,
+ };
+
+ Symbol::new_label(name, kind, range, label)
+ }
+ None => Symbol::new_simple(name, kind, range, range),
+ };
+
+ Some(symbol)
+ }
+
+ fn visit_equation(&self, node: &dyn AstNode<Language = LatexLanguage>) -> Option<Symbol> {
+ let range = latex::small_range(node);
+ let kind = SymbolKind::Equation;
+ let symbol = match self.find_label(node.syntax()) {
+ Some(label) => {
+ let name = match self.find_label_number(&label.text) {
+ Some(number) => format!("Equation ({number})"),
+ None => "Equation".into(),
+ };
+
+ Symbol::new_label(name, kind, range, label)
+ }
+ None => Symbol::new_simple("Equation".into(), kind, range, range),
+ };
+
+ Some(symbol)
+ }
+
+ fn find_label(&self, parent: &latex::SyntaxNode) -> Option<Span> {
+ let label = parent.children().find_map(latex::LabelDefinition::cast)?;
+ let range = latex::small_range(&label);
+ let text = label.name()?.key()?.to_string();
+ Some(Span { text, range })
+ }
+
+ fn find_label_number(&self, name: &str) -> Option<&str> {
+ self.project
+ .documents
+ .iter()
+ .filter_map(|document| document.data.as_aux())
+ .find_map(|data| data.semantics.label_numbers.get(name))
+ .map(String::as_str)
+ }
+}