summaryrefslogtreecommitdiff
path: root/support/texlab/src/outline.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/outline.rs')
-rw-r--r--support/texlab/src/outline.rs437
1 files changed, 437 insertions, 0 deletions
diff --git a/support/texlab/src/outline.rs b/support/texlab/src/outline.rs
new file mode 100644
index 0000000000..07ffaee09f
--- /dev/null
+++ b/support/texlab/src/outline.rs
@@ -0,0 +1,437 @@
+use crate::{
+ feature::DocumentView,
+ protocol::{MarkupContent, MarkupKind, Options, Position, Range, RangeExt, Uri},
+ syntax::{latex, SyntaxNode},
+ workspace::{Document, DocumentContent},
+};
+use std::{borrow::Cow, collections::HashSet, path::Path};
+use titlecase::titlecase;
+use OutlineContextItem::*;
+
+#[derive(Debug, Clone, Copy)]
+pub struct OutlineSection<'a> {
+ pub document: &'a Document,
+ pub item: &'a latex::Section,
+}
+
+pub struct Outline<'a> {
+ sections: Vec<OutlineSection<'a>>,
+}
+
+impl<'a> Outline<'a> {
+ pub fn find(&'a self, uri: &Uri, pos: Position) -> Option<&'a latex::Section> {
+ self.sections
+ .iter()
+ .filter(|section| section.document.uri == *uri)
+ .rev()
+ .find(|section| {
+ let table = section.document.content.as_latex().unwrap();
+ table[section.item.parent].end() <= pos
+ })
+ .map(|sec| sec.item)
+ }
+
+ pub fn analyze(view: &'a DocumentView, options: &Options, current_dir: &Path) -> Self {
+ let mut finder = OutlineSectionFinder::default();
+ let doc = view
+ .snapshot
+ .parent(&view.current.uri, options, current_dir)
+ .map(|parent| {
+ view.related
+ .iter()
+ .find(|doc| doc.uri == parent.uri)
+ .unwrap()
+ })
+ .unwrap_or(&view.current);
+
+ finder.analyze(view, doc);
+ Self {
+ sections: finder.sections,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum OutlineItem<'a> {
+ Section(&'a latex::Section),
+ Include(&'a latex::Include),
+}
+
+#[derive(Debug, Default)]
+struct OutlineSectionFinder<'a> {
+ visited: HashSet<&'a Uri>,
+ sections: Vec<OutlineSection<'a>>,
+}
+
+impl<'a> OutlineSectionFinder<'a> {
+ fn analyze(&mut self, view: &'a DocumentView, doc: &'a Document) {
+ if !self.visited.insert(&doc.uri) {
+ return;
+ }
+
+ if let DocumentContent::Latex(table) = &doc.content {
+ let mut items = Vec::new();
+ for section in &table.sections {
+ items.push(OutlineItem::Section(section));
+ }
+ for include in &table.includes {
+ items.push(OutlineItem::Include(include));
+ }
+ items.sort_by_key(|item| match item {
+ OutlineItem::Include(include) => table[include.parent].start(),
+ OutlineItem::Section(section) => table[section.parent].start(),
+ });
+
+ for item in items {
+ match item {
+ OutlineItem::Section(item) => {
+ let section = OutlineSection {
+ document: doc,
+ item,
+ };
+ self.sections.push(section);
+ }
+ OutlineItem::Include(item) => {
+ for doc in &view.related {
+ for targets in &item.all_targets {
+ if targets.contains(&doc.uri) {
+ self.analyze(view, doc);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum OutlineCaptionKind {
+ Figure,
+ Table,
+ Listing,
+ Algorithm,
+}
+
+impl OutlineCaptionKind {
+ pub fn as_str(self) -> &'static str {
+ match self {
+ Self::Figure => "Figure",
+ Self::Table => "Table",
+ Self::Listing => "Listing",
+ Self::Algorithm => "Algorithm",
+ }
+ }
+
+ pub fn parse(env_name: &str) -> Option<Self> {
+ match env_name {
+ "figure" | "subfigure" => Some(Self::Figure),
+ "table" | "subtable" => Some(Self::Table),
+ "listing" | "lstlisting" => Some(Self::Listing),
+ "algorithm" => Some(Self::Algorithm),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum OutlineContextItem {
+ Section {
+ prefix: Cow<'static, str>,
+ text: String,
+ },
+ Caption {
+ kind: Option<OutlineCaptionKind>,
+ text: String,
+ },
+ Theorem {
+ kind: String,
+ description: Option<String>,
+ },
+ Equation,
+ Item,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct OutlineContext {
+ pub range: Range,
+ pub number: Option<String>,
+ pub item: OutlineContextItem,
+}
+
+impl OutlineContext {
+ pub fn reference(&self) -> String {
+ match &self.number {
+ Some(number) => match &self.item {
+ Section { prefix, text } => format!("{} {} ({})", prefix, number, text),
+ Caption { kind: None, text } => format!("{} {}", number, text),
+ Caption {
+ kind: Some(kind),
+ text,
+ } => format!("{} {}: {}", kind.as_str(), number, text),
+ Theorem {
+ kind,
+ description: None,
+ } => format!("{} {}", kind, number),
+ Theorem {
+ kind,
+ description: Some(description),
+ } => format!("{} {} ({})", kind, number, description),
+ Equation => format!("Equation ({})", number),
+ Item => format!("Item {}", number),
+ },
+ None => match &self.item {
+ Section { prefix, text } => format!("{} ({})", prefix, text),
+ Caption { kind: None, text } => text.clone(),
+ Caption {
+ kind: Some(kind),
+ text,
+ } => format!("{}: {}", kind.as_str(), text),
+ Theorem {
+ kind,
+ description: None,
+ } => kind.into(),
+ Theorem {
+ kind,
+ description: Some(description),
+ } => format!("{} ({})", kind, description),
+ Equation => "Equation".into(),
+ Item => "Item".into(),
+ },
+ }
+ }
+
+ pub fn detail(&self) -> Option<String> {
+ match &self.item {
+ Section { .. } | Theorem { .. } | Equation | Item => Some(self.reference()),
+ Caption {
+ kind: Some(kind), ..
+ } => {
+ let result = match &self.number {
+ Some(number) => format!("{} {}", kind.as_str(), number),
+ None => kind.as_str().to_owned(),
+ };
+ Some(result)
+ }
+ Caption { .. } => None,
+ }
+ }
+
+ pub fn documentation(&self) -> MarkupContent {
+ MarkupContent {
+ kind: MarkupKind::PlainText,
+ value: self.reference(),
+ }
+ }
+
+ pub fn parse(view: &DocumentView, outline: &Outline, label: latex::Label) -> Option<Self> {
+ if let DocumentContent::Latex(table) = &view.current.content {
+ Self::find_caption(view, table, label)
+ .or_else(|| Self::find_theorem(view, table, label))
+ .or_else(|| Self::find_equation(view, table, label))
+ .or_else(|| Self::find_item(view, table, label))
+ .or_else(|| Self::find_section(view, outline, table, label))
+ } else {
+ None
+ }
+ }
+
+ fn find_caption(
+ view: &DocumentView,
+ table: &latex::SymbolTable,
+ label: latex::Label,
+ ) -> Option<Self> {
+ let label_range = table[label.parent].range();
+ let caption_env = table
+ .environments
+ .iter()
+ .filter(|env| !env.is_root(&table))
+ .find(|env| env.range(&table).contains(label_range.start))?;
+
+ let caption = table
+ .captions
+ .iter()
+ .find(|cap| table.is_direct_child(*caption_env, table[cap.parent].start()))?;
+
+ let caption_text = table.print_group_content(
+ caption.parent,
+ latex::GroupKind::Group,
+ caption.arg_index,
+ )?;
+
+ let caption_kind = caption_env
+ .left
+ .name(&table)
+ .map(latex::Token::text)
+ .and_then(OutlineCaptionKind::parse);
+
+ Some(Self {
+ range: caption_env.range(&table),
+ number: Self::find_number(view, table, label),
+ item: Caption {
+ kind: caption_kind,
+ text: caption_text,
+ },
+ })
+ }
+
+ fn find_theorem(
+ view: &DocumentView,
+ main_table: &latex::SymbolTable,
+ label: latex::Label,
+ ) -> Option<Self> {
+ let label_range = main_table[label.parent].range();
+ let env = main_table
+ .environments
+ .iter()
+ .find(|env| env.range(&main_table).contains(label_range.start))?;
+
+ let env_name = env.left.name(&main_table).map(latex::Token::text)?;
+
+ for doc in &view.related {
+ if let DocumentContent::Latex(table) = &doc.content {
+ for def in &table.theorem_definitions {
+ if env_name == def.name(&table).text() {
+ let kind = table
+ .print_group_content(
+ def.parent,
+ latex::GroupKind::Group,
+ def.arg_index + 1,
+ )
+ .unwrap_or_else(|| titlecase(&env_name));
+
+ let description = main_table.print_group_content(
+ env.left.parent,
+ latex::GroupKind::Options,
+ 0,
+ );
+
+ return Some(Self {
+ range: env.range(&main_table),
+ number: Self::find_number(view, main_table, label),
+ item: Theorem { kind, description },
+ });
+ }
+ }
+ }
+ }
+ None
+ }
+
+ fn find_equation(
+ view: &DocumentView,
+ table: &latex::SymbolTable,
+ label: latex::Label,
+ ) -> Option<Self> {
+ let label_range = table[label.parent].range();
+ table
+ .environments
+ .iter()
+ .filter(|env| env.left.is_math(&table))
+ .map(|env| env.range(&table))
+ .find(|range| range.contains(label_range.start))
+ .map(|range| Self {
+ range,
+ number: Self::find_number(view, table, label),
+ item: Equation,
+ })
+ }
+
+ fn find_item(
+ view: &DocumentView,
+ table: &latex::SymbolTable,
+ label: latex::Label,
+ ) -> Option<Self> {
+ let label_range = table[label.parent].range();
+ struct LatexItemNode {
+ item: latex::Item,
+ range: Range,
+ }
+
+ let enumeration = table
+ .environments
+ .iter()
+ .filter(|env| env.left.is_enum(&table))
+ .find(|env| env.range(&table).contains(label_range.start))?;
+
+ let mut item_nodes: Vec<_> = table
+ .items
+ .iter()
+ .filter(|item| table.is_enum_item(*enumeration, **item))
+ .map(|item| LatexItemNode {
+ item: *item,
+ range: Range::default(),
+ })
+ .collect();
+
+ for i in 0..item_nodes.len() {
+ let start = table[item_nodes[i].item.parent].start();
+ let end = item_nodes
+ .get(i + 1)
+ .map(|node| table[node.item.parent].start())
+ .unwrap_or_else(|| table[enumeration.right.parent].start());
+ item_nodes[i].range = Range::new(start, end);
+ }
+
+ let node = item_nodes
+ .iter()
+ .find(|node| node.range.contains(label_range.start))?;
+
+ let number = node
+ .item
+ .name(&table)
+ .or_else(|| Self::find_number(view, table, label));
+
+ Some(Self {
+ range: enumeration.range(&table),
+ number,
+ item: Item,
+ })
+ }
+
+ fn find_section(
+ view: &DocumentView,
+ outline: &Outline,
+ table: &latex::SymbolTable,
+ label: latex::Label,
+ ) -> Option<Self> {
+ let label_range = table[label.parent].range();
+ let section = outline.find(&view.current.uri, label_range.start)?;
+ Some(Self {
+ range: table[section.parent].range(),
+ number: Self::find_number(view, table, label),
+ item: Section {
+ prefix: section.prefix.clone(),
+ text: table.print_group_content(
+ section.parent,
+ latex::GroupKind::Group,
+ section.arg_index,
+ )?,
+ },
+ })
+ }
+
+ pub fn find_number(
+ view: &DocumentView,
+ table: &latex::SymbolTable,
+ label: latex::Label,
+ ) -> Option<String> {
+ let label_names = label.names(&table);
+ if label_names.len() != 1 {
+ return None;
+ }
+
+ for doc in &view.related {
+ if let DocumentContent::Latex(table) = &doc.content {
+ for numbering in &table.label_numberings {
+ if numbering.name(&table).text() == label_names[0].text() {
+ return Some(numbering.number.clone());
+ }
+ }
+ }
+ }
+ None
+ }
+}