summaryrefslogtreecommitdiff
path: root/support/texlab/src/features/symbol/latex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/features/symbol/latex.rs')
-rw-r--r--support/texlab/src/features/symbol/latex.rs488
1 files changed, 488 insertions, 0 deletions
diff --git a/support/texlab/src/features/symbol/latex.rs b/support/texlab/src/features/symbol/latex.rs
new file mode 100644
index 0000000000..ce584c0c76
--- /dev/null
+++ b/support/texlab/src/features/symbol/latex.rs
@@ -0,0 +1,488 @@
+use std::str::FromStr;
+
+use cancellation::CancellationToken;
+use lsp_types::Range;
+use smol_str::SmolStr;
+use titlecase::titlecase;
+
+use crate::{
+ find_caption_by_parent, find_label_number,
+ syntax::{
+ latex::{self, HasBrack, HasCurly},
+ CstNode,
+ },
+ LabelledFloatKind, LatexDocumentData, LineIndexExt, WorkspaceSubset, LANGUAGE_DATA,
+};
+
+use super::types::{InternalSymbol, InternalSymbolKind};
+
+pub fn find_latex_symbols(
+ subset: &WorkspaceSubset,
+ buf: &mut Vec<InternalSymbol>,
+ token: &CancellationToken,
+) -> Option<()> {
+ let main_document = subset
+ .documents
+ .first()
+ .filter(|document| document.uri.as_str().ends_with(".tex"))?;
+
+ let data = main_document.data.as_latex()?;
+ let mut context = Context {
+ subset,
+ data,
+ token,
+ };
+
+ let root = &context.data.root;
+ let mut symbols = visit(&mut context, root);
+ buf.append(&mut symbols);
+ Some(())
+}
+
+struct Context<'a> {
+ subset: &'a WorkspaceSubset,
+ data: &'a LatexDocumentData,
+ token: &'a CancellationToken,
+}
+
+fn visit(context: &mut Context, node: &latex::SyntaxNode) -> Vec<InternalSymbol> {
+ let symbol = match node.kind() {
+ latex::PART
+ | latex::CHAPTER
+ | latex::SECTION
+ | latex::SUBSECTION
+ | latex::SUBSUBSECTION
+ | latex::PARAGRAPH
+ | latex::SUBPARAGRAPH => visit_section(context, node),
+ latex::ENUM_ITEM => visit_enum_item(context, node),
+ latex::EQUATION => visit_equation(context, node),
+ latex::ENVIRONMENT => latex::Environment::cast(node)
+ .and_then(|env| env.begin())
+ .and_then(|begin| begin.name())
+ .and_then(|name| name.word())
+ .map(|name| name.text())
+ .and_then(|name| {
+ if LANGUAGE_DATA
+ .math_environments
+ .iter()
+ .any(|env| env == name)
+ {
+ visit_equation_environment(context, node)
+ } else if LANGUAGE_DATA
+ .enum_environments
+ .iter()
+ .any(|env| env == name)
+ {
+ visit_enumeration(context, node, name)
+ } else if let Ok(float_kind) = LabelledFloatKind::from_str(name) {
+ visit_float(context, node, float_kind)
+ } else {
+ visit_theorem(context, node, name)
+ }
+ }),
+ _ => None,
+ };
+
+ match symbol {
+ Some(mut parent) => {
+ for child in node.children() {
+ if context.token.is_canceled() {
+ break;
+ }
+
+ parent.children.append(&mut visit(context, child));
+ }
+ vec![parent]
+ }
+ None => {
+ let mut symbols = Vec::new();
+ for child in node.children() {
+ if context.token.is_canceled() {
+ break;
+ }
+ symbols.append(&mut visit(context, child));
+ }
+ symbols
+ }
+ }
+}
+
+fn visit_section(context: &mut Context, node: &latex::SyntaxNode) -> Option<InternalSymbol> {
+ let section = latex::Section::cast(node)?;
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(section.small_range());
+
+ let group = section.name()?;
+ let group_text = group.content_text()?;
+
+ let symbol = match find_label_by_parent(context, section.syntax()) {
+ Some(NumberedLabel {
+ name: label,
+ range: selection_range,
+ number,
+ }) => {
+ let name = match number {
+ Some(number) => format!("{} {}", number, group_text),
+ None => group_text.to_string(),
+ };
+
+ InternalSymbol {
+ name,
+ label: Some(label),
+ kind: InternalSymbolKind::Section,
+ deprecated: false,
+ full_range,
+ selection_range,
+ children: Vec::new(),
+ }
+ }
+ None => InternalSymbol {
+ name: group_text.to_string(),
+ label: None,
+ kind: InternalSymbolKind::Section,
+ deprecated: false,
+ full_range,
+ selection_range: full_range,
+ children: Vec::new(),
+ },
+ };
+ Some(symbol)
+}
+
+fn visit_enum_item(context: &mut Context, node: &latex::SyntaxNode) -> Option<InternalSymbol> {
+ let enum_item = latex::EnumItem::cast(node)?;
+ if !enum_item
+ .syntax()
+ .ancestors()
+ .filter_map(latex::Environment::cast)
+ .filter_map(|environment| environment.begin())
+ .filter_map(|begin| begin.name())
+ .filter_map(|name| name.word())
+ .any(|name| {
+ LANGUAGE_DATA
+ .enum_environments
+ .iter()
+ .any(|e| e == name.text())
+ })
+ {
+ return None;
+ }
+
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(enum_item.small_range());
+
+ let name = enum_item
+ .label()
+ .and_then(|label| label.content_text())
+ .unwrap_or_else(|| "Item".to_string());
+
+ let symbol = match find_label_by_parent(context, node) {
+ Some(NumberedLabel {
+ name: label,
+ range: selection_range,
+ number,
+ }) => InternalSymbol {
+ name: number.map(Into::into).unwrap_or_else(|| name.to_string()),
+ label: Some(label),
+ kind: InternalSymbolKind::EnumerationItem,
+ deprecated: false,
+ full_range,
+ selection_range,
+ children: Vec::new(),
+ },
+ None => InternalSymbol {
+ name: name.to_string(),
+ label: None,
+ kind: InternalSymbolKind::EnumerationItem,
+ deprecated: false,
+ full_range,
+ selection_range: full_range,
+ children: Vec::new(),
+ },
+ };
+ Some(symbol)
+}
+
+fn visit_equation(context: &mut Context, node: &latex::SyntaxNode) -> Option<InternalSymbol> {
+ let equation = latex::Equation::cast(node)?;
+
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(equation.small_range());
+
+ make_equation_symbol(context, node, full_range)
+}
+
+fn visit_equation_environment(
+ context: &mut Context,
+ node: &latex::SyntaxNode,
+) -> Option<InternalSymbol> {
+ let environment = latex::Environment::cast(node)?;
+
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(environment.small_range());
+
+ make_equation_symbol(context, node, full_range)
+}
+
+fn make_equation_symbol(
+ context: &mut Context,
+ node: &cstree::ResolvedNode<latex::Language>,
+ full_range: Range,
+) -> Option<InternalSymbol> {
+ let symbol = match find_label_by_parent(context, node) {
+ Some(NumberedLabel {
+ name: label,
+ range: selection_range,
+ number,
+ }) => {
+ let name = match number {
+ Some(number) => format!("Equation ({})", number),
+ None => "Equation".to_string(),
+ };
+
+ InternalSymbol {
+ name,
+ label: Some(label),
+ kind: InternalSymbolKind::Equation,
+ deprecated: false,
+ full_range,
+ selection_range,
+ children: Vec::new(),
+ }
+ }
+ None => InternalSymbol {
+ name: "Equation".to_string(),
+ label: None,
+ kind: InternalSymbolKind::Equation,
+ deprecated: false,
+ full_range,
+ selection_range: full_range,
+ children: Vec::new(),
+ },
+ };
+ Some(symbol)
+}
+
+fn visit_enumeration(
+ context: &mut Context,
+ node: &latex::SyntaxNode,
+ env_name: &str,
+) -> Option<InternalSymbol> {
+ let environment = latex::Environment::cast(node)?;
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(environment.small_range());
+
+ let name = titlecase(env_name);
+ let symbol = match find_label_by_parent(context, environment.syntax()) {
+ Some(NumberedLabel {
+ name: label,
+ range: selection_range,
+ number,
+ }) => {
+ let name = match number {
+ Some(number) => format!("{} {}", name, number),
+ None => name,
+ };
+
+ InternalSymbol {
+ name,
+ label: Some(label),
+ kind: InternalSymbolKind::Enumeration,
+ deprecated: false,
+ full_range,
+ selection_range,
+ children: Vec::new(),
+ }
+ }
+ None => InternalSymbol {
+ name,
+ label: None,
+ kind: InternalSymbolKind::Enumeration,
+ deprecated: false,
+ full_range,
+ selection_range: full_range,
+ children: Vec::new(),
+ },
+ };
+ Some(symbol)
+}
+
+fn visit_float(
+ context: &mut Context,
+ node: &latex::SyntaxNode,
+ float_kind: LabelledFloatKind,
+) -> Option<InternalSymbol> {
+ let environment = latex::Environment::cast(node)?;
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(environment.small_range());
+
+ let (float_kind, symbol_kind) = match float_kind {
+ LabelledFloatKind::Algorithm => ("Algorithm", InternalSymbolKind::Algorithm),
+ LabelledFloatKind::Figure => ("Figure", InternalSymbolKind::Figure),
+ LabelledFloatKind::Listing => ("Listing", InternalSymbolKind::Listing),
+ LabelledFloatKind::Table => ("Table", InternalSymbolKind::Table),
+ };
+
+ let caption = find_caption_by_parent(environment.syntax())?;
+ let symbol = match find_label_by_parent(context, environment.syntax()) {
+ Some(NumberedLabel {
+ name: label,
+ range: selection_range,
+ number,
+ }) => {
+ let name = match number {
+ Some(number) => format!("{} {}: {}", float_kind, number, caption),
+ None => format!("{}: {}", float_kind, caption),
+ };
+
+ InternalSymbol {
+ name,
+ label: Some(label),
+ kind: symbol_kind,
+ deprecated: false,
+ full_range,
+ selection_range,
+ children: Vec::new(),
+ }
+ }
+ None => InternalSymbol {
+ name: format!("{}: {}", float_kind, caption),
+ label: None,
+ kind: symbol_kind,
+ deprecated: false,
+ full_range,
+ selection_range: full_range,
+ children: Vec::new(),
+ },
+ };
+
+ Some(symbol)
+}
+
+fn visit_theorem(
+ context: &mut Context,
+ node: &latex::SyntaxNode,
+ environment_name: &str,
+) -> Option<InternalSymbol> {
+ let definition = context
+ .subset
+ .documents
+ .iter()
+ .filter_map(|document| document.data.as_latex())
+ .find_map(|data| {
+ data.extras
+ .theorem_environments
+ .iter()
+ .find(|environment| environment.name == environment_name)
+ .cloned()
+ })?;
+
+ let node = latex::Environment::cast(node)?;
+ let theorem_description = node
+ .begin()?
+ .options()
+ .and_then(|option| option.content_text());
+
+ let full_range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(node.small_range());
+
+ let symbol = match find_label_by_parent(context, node.syntax()) {
+ Some(NumberedLabel {
+ name: label,
+ range: selection_range,
+ number,
+ }) => {
+ let name = match (number, theorem_description) {
+ (Some(number), Some(desc)) => {
+ format!("{} {} ({})", definition.description, number, desc)
+ }
+ (Some(number), None) => format!("{} {}", definition.description, number),
+ (None, Some(desc)) => format!("{} ({})", definition.description, desc),
+ (None, None) => definition.description.clone(),
+ };
+
+ InternalSymbol {
+ name,
+ label: Some(label),
+ kind: InternalSymbolKind::Theorem,
+ deprecated: false,
+ full_range,
+ selection_range,
+ children: Vec::new(),
+ }
+ }
+ None => {
+ let name = match theorem_description {
+ Some(desc) => format!("{} ({})", definition.description, desc),
+ None => definition.description.to_string(),
+ };
+ InternalSymbol {
+ name,
+ label: None,
+ kind: InternalSymbolKind::Theorem,
+ deprecated: false,
+ full_range,
+ selection_range: full_range,
+ children: Vec::new(),
+ }
+ }
+ };
+ Some(symbol)
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+struct NumberedLabel {
+ name: String,
+ range: Range,
+ number: Option<SmolStr>,
+}
+
+fn find_label_by_parent(
+ context: &mut Context,
+ parent: &latex::SyntaxNode,
+) -> Option<NumberedLabel> {
+ let node = parent.children().find_map(latex::LabelDefinition::cast)?;
+
+ let name = node.name()?.word()?.text();
+ let range = context
+ .subset
+ .documents
+ .first()?
+ .line_index
+ .line_col_lsp_range(node.small_range());
+
+ let number = find_label_number(&context.subset, name);
+ Some(NumberedLabel {
+ name: name.to_string(),
+ range,
+ number: number.map(Into::into),
+ })
+}