use std::str::FromStr;
use lsp_types::{MarkupContent, MarkupKind};
use rowan::{ast::AstNode, TextRange};
use crate::{
syntax::latex::{self, HasBrack, HasCurly},
Workspace, LANGUAGE_DATA,
};
use self::LabelledObject::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LabelledFloatKind {
Figure,
Table,
Listing,
Algorithm,
}
impl LabelledFloatKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Figure => "Figure",
Self::Table => "Table",
Self::Listing => "Listing",
Self::Algorithm => "Algorithm",
}
}
}
impl FromStr for LabelledFloatKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"figure" | "subfigure" => Ok(Self::Figure),
"table" | "subtable" => Ok(Self::Table),
"listing" | "lstlisting" => Ok(Self::Listing),
"algorithm" => Ok(Self::Algorithm),
_ => Err(()),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum LabelledObject {
Section {
prefix: &'static str,
text: String,
},
Float {
kind: LabelledFloatKind,
caption: String,
},
Theorem {
kind: String,
description: Option<String>,
},
Equation,
EnumItem,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct RenderedLabel {
pub range: TextRange,
pub number: Option<String>,
pub object: LabelledObject,
}
impl RenderedLabel {
#[must_use]
pub fn reference(&self) -> String {
match &self.number {
Some(number) => match &self.object {
Section { prefix, text } => format!("{} {} ({})", prefix, number, text),
Float { kind, caption } => format!("{} {}: {}", kind.as_str(), number, caption),
Theorem {
kind,
description: None,
} => format!("{} {}", kind, number),
Theorem {
kind,
description: Some(description),
} => format!("{} {} ({})", kind, number, description),
Equation => format!("Equation ({})", number),
EnumItem => format!("Item {}", number),
},
None => match &self.object {
Section { prefix, text } => format!("{} ({})", prefix, text),
Float { kind, caption } => format!("{}: {}", kind.as_str(), caption),
Theorem {
kind,
description: None,
} => kind.into(),
Theorem {
kind,
description: Some(description),
} => format!("{} ({})", kind, description),
Equation => "Equation".into(),
EnumItem => "Item".into(),
},
}
}
#[must_use]
pub fn detail(&self) -> Option<String> {
match &self.object {
Section { .. } | Theorem { .. } | Equation | EnumItem => Some(self.reference()),
Float { kind, .. } => {
let result = match &self.number {
Some(number) => format!("{} {}", kind.as_str(), number),
None => kind.as_str().to_owned(),
};
Some(result)
}
}
}
#[must_use]
pub fn documentation(&self) -> MarkupContent {
MarkupContent {
kind: MarkupKind::PlainText,
value: self.reference(),
}
}
}
pub fn render_label(
workspace: &Workspace,
label_name: &str,
mut label: Option<latex::LabelDefinition>,
) -> Option<RenderedLabel> {
let mut number = find_label_number(workspace, label_name).map(ToString::to_string);
for document in workspace.documents_by_uri.values() {
if let Some(data) = document.data.as_latex() {
label = label.or_else(|| {
find_label_definition(&latex::SyntaxNode::new_root(data.green.clone()), label_name)
});
}
}
label?.syntax().ancestors().find_map(|parent| {
render_label_float(parent.clone(), &mut number)
.or_else(|| render_label_section(parent.clone(), &mut number))
.or_else(|| render_label_enum_item(parent.clone(), &mut number))
.or_else(|| render_label_equation(parent.clone(), &mut number))
.or_else(|| render_label_theorem(workspace, parent, &mut number))
})
}
pub fn find_label_definition(
root: &latex::SyntaxNode,
label_name: &str,
) -> Option<latex::LabelDefinition> {
root.descendants()
.filter_map(latex::LabelDefinition::cast)
.find(|label| {
label
.name()
.and_then(|name| name.key())
.map(|name| name.to_string())
.as_deref()
== Some(label_name)
})
}
pub fn find_label_number<'a>(workspace: &'a Workspace, label_name: &str) -> Option<&'a str> {
workspace.documents_by_uri.values().find_map(|document| {
document
.data
.as_latex()
.and_then(|data| data.extras.label_numbers_by_name.get(label_name))
.map(|number| number.as_str())
})
}
fn render_label_float(
parent: latex::SyntaxNode,
number: &mut Option<String>,
) -> Option<RenderedLabel> {
let environment = latex::Environment::cast(parent.clone())?;
let environment_name = environment.begin()?.name()?.key()?.to_string();
let kind = LabelledFloatKind::from_str(&environment_name).ok()?;
let caption = find_caption_by_parent(&parent)?;
Some(RenderedLabel {
range: latex::small_range(&environment),
number: number.take(),
object: LabelledObject::Float { caption, kind },
})
}
fn render_label_section(
parent: latex::SyntaxNode,
number: &mut Option<String>,
) -> Option<RenderedLabel> {
let section = latex::Section::cast(parent)?;
let text_group = section.name()?;
let text = text_group.content_text()?;
Some(RenderedLabel {
range: latex::small_range(§ion),
number: number.take(),
object: LabelledObject::Section {
prefix: match section.syntax().kind() {
latex::PART => "Part",
latex::CHAPTER => "Chapter",
latex::SECTION => "Section",
latex::SUBSECTION => "Subsection",
latex::SUBSUBSECTION => "Subsubsection",
latex::PARAGRAPH => "Paragraph",
latex::SUBPARAGRAPH => "Subparagraph",
_ => unreachable!(),
},
text,
},
})
}
fn render_label_enum_item(
parent: latex::SyntaxNode,
number: &mut Option<String>,
) -> Option<RenderedLabel> {
let enum_item = latex::EnumItem::cast(parent)?;
Some(RenderedLabel {
range: latex::small_range(&enum_item),
number: enum_item
.label()
.and_then(|number| number.content_text())
.or_else(|| number.take()),
object: LabelledObject::EnumItem,
})
}
fn render_label_equation(
parent: latex::SyntaxNode,
number: &mut Option<String>,
) -> Option<RenderedLabel> {
let environment = latex::Environment::cast(parent)?;
let environment_name = environment.begin()?.name()?.key()?.to_string();
if !LANGUAGE_DATA
.math_environments
.iter()
.any(|name| name == &environment_name)
{
return None;
}
Some(RenderedLabel {
range: latex::small_range(&environment),
number: number.take(),
object: LabelledObject::Equation,
})
}
fn render_label_theorem(
workspace: &Workspace,
parent: latex::SyntaxNode,
number: &mut Option<String>,
) -> Option<RenderedLabel> {
let environment = latex::Environment::cast(parent)?;
let begin = environment.begin()?;
let description = begin.options().and_then(|options| options.content_text());
let environment_name = begin.name()?.key()?.to_string();
let theorem = workspace.documents_by_uri.values().find_map(|document| {
document.data.as_latex().and_then(|data| {
data.extras
.theorem_environments
.iter()
.find(|theorem| theorem.name.as_str() == environment_name)
})
})?;
Some(RenderedLabel {
range: latex::small_range(&environment),
number: number.take(),
object: LabelledObject::Theorem {
kind: theorem.description.clone(),
description,
},
})
}
pub fn find_caption_by_parent(parent: &latex::SyntaxNode) -> Option<String> {
parent
.children()
.filter_map(latex::Caption::cast)
.find_map(|node| node.long())
.and_then(|node| node.content_text())
}