Refactor document and workspace symbol requests

This commit is contained in:
Patrick Förster 2023-04-22 13:20:55 +02:00
parent e6fe836ed6
commit edee410a66
34 changed files with 765 additions and 877 deletions

6
Cargo.lock generated
View file

@ -1487,7 +1487,12 @@ name = "symbols"
version = "0.0.0"
dependencies = [
"base-db",
"distro",
"itertools",
"rowan",
"syntax",
"titlecase",
"url",
]
[[package]]
@ -1569,6 +1574,7 @@ dependencies = [
"serde_regex",
"serde_repr",
"smol_str",
"symbols",
"syntax",
"tempfile",
"threadpool",

View file

@ -7,8 +7,9 @@ use url::Url;
use crate::{
diagnostics::{self, Diagnostic},
line_index::LineIndex,
semantics, Config,
semantics,
util::LineIndex,
Config,
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]

View file

@ -3,8 +3,8 @@ pub mod data;
pub mod diagnostics;
mod document;
pub mod graph;
mod line_index;
pub mod semantics;
pub mod util;
mod workspace;
pub use self::{config::*, document::*, line_index::*, workspace::*};
pub use self::{config::*, document::*, workspace::*};

View file

@ -215,12 +215,12 @@ impl Semantics {
let Some(name) = theorem_def.name().and_then(|name| name.key()) else { return };
let Some(description) = theorem_def
.description()
.heading()
.and_then(|group| group.content_text()) else { return };
self.theorem_definitions.push(TheoremDefinition {
name: Span::from(&name),
description,
heading: description,
});
}
}
@ -288,5 +288,5 @@ pub enum LabelObject {
#[derive(Debug, Clone)]
pub struct TheoremDefinition {
pub name: Span,
pub description: String,
pub heading: String,
}

View file

@ -0,0 +1,9 @@
mod label;
mod line_index;
mod regex_filter;
pub use self::{
label::{render_label, FloatKind, RenderedLabel, RenderedObject},
line_index::{LineCol, LineColUtf16, LineIndex},
regex_filter::filter_regex_patterns,
};

View file

@ -1,23 +1,23 @@
use std::str::FromStr;
use base_db::{
use rowan::TextRange;
use self::RenderedObject::*;
use crate::{
semantics::tex::{Label, LabelObject},
Project, Workspace,
};
use rowan::{ast::AstNode, TextRange};
use syntax::latex::{self, HasCurly};
use self::LabeledObject::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LabeledFloatKind {
pub enum FloatKind {
Figure,
Table,
Listing,
Algorithm,
}
impl LabeledFloatKind {
impl FloatKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Figure => "Figure",
@ -28,7 +28,7 @@ impl LabeledFloatKind {
}
}
impl FromStr for LabeledFloatKind {
impl FromStr for FloatKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -43,13 +43,13 @@ impl FromStr for LabeledFloatKind {
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum LabeledObject<'a> {
pub enum RenderedObject<'a> {
Section {
prefix: &'a str,
text: &'a str,
},
Float {
kind: LabeledFloatKind,
kind: FloatKind,
caption: &'a str,
},
Theorem {
@ -64,7 +64,7 @@ pub enum LabeledObject<'a> {
pub struct RenderedLabel<'a> {
pub range: TextRange,
pub number: Option<&'a str>,
pub object: LabeledObject<'a>,
pub object: RenderedObject<'a>,
}
impl<'a> RenderedLabel<'a> {
@ -117,7 +117,7 @@ impl<'a> RenderedLabel<'a> {
}
}
pub fn render<'a>(
pub fn render_label<'a>(
workspace: &'a Workspace,
project: &Project<'a>,
label: &'a Label,
@ -135,14 +135,14 @@ pub fn render<'a>(
return Some(RenderedLabel {
range: target.range,
number,
object: LabeledObject::Section { prefix, text },
object: RenderedObject::Section { prefix, text },
});
}
LabelObject::EnumItem => {
return Some(RenderedLabel {
range: target.range,
number,
object: LabeledObject::EnumItem,
object: RenderedObject::EnumItem,
});
}
LabelObject::Environment {
@ -155,15 +155,15 @@ pub fn render<'a>(
return Some(RenderedLabel {
range: target.range,
number,
object: LabeledObject::Equation,
object: RenderedObject::Equation,
});
}
if let Ok(kind) = LabeledFloatKind::from_str(name) {
if let Ok(kind) = FloatKind::from_str(name) {
return Some(RenderedLabel {
range: target.range,
number,
object: LabeledObject::Float {
object: RenderedObject::Float {
kind,
caption: caption.as_deref()?,
},
@ -180,8 +180,8 @@ pub fn render<'a>(
return Some(RenderedLabel {
range: target.range,
number,
object: LabeledObject::Theorem {
kind: &theorem.description,
object: RenderedObject::Theorem {
kind: &theorem.heading,
description: options.as_deref(),
},
});
@ -192,11 +192,3 @@ pub fn render<'a>(
None
}
pub(crate) 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())
}

View file

@ -1,6 +1,10 @@
use regex::Regex;
pub fn filter(text: &str, allowed_patterns: &[Regex], ignored_patterns: &[Regex]) -> bool {
pub fn filter_regex_patterns(
text: &str,
allowed_patterns: &[Regex],
ignored_patterns: &[Regex],
) -> bool {
if !allowed_patterns.is_empty()
&& !allowed_patterns
.iter()

19
crates/symbols/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "symbols"
version = "0.0.0"
license.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
base-db = { path = "../base-db" }
distro = { path = "../distro" }
itertools = "0.10.5"
rowan = "0.15.11"
syntax = { path = "../syntax" }
url = "2.3.1"
titlecase = "2.2.1"
[lib]
doctest = false

View file

@ -0,0 +1,48 @@
mod bib;
mod tex;
use base_db::{util, Document, DocumentData, SymbolConfig, Workspace};
use crate::Symbol;
pub fn document_symbols(workspace: &Workspace, document: &Document) -> Vec<Symbol> {
let project = workspace.project(document);
let mut symbols = match &document.data {
DocumentData::Tex(data) => {
let builder = tex::SymbolBuilder::new(&project, workspace.config());
builder.visit(&data.root_node())
}
DocumentData::Bib(data) => {
let builder = bib::SymbolBuilder;
data.root_node()
.children()
.filter_map(|node| builder.visit(&node))
.collect()
}
DocumentData::Aux(_)
| DocumentData::Log(_)
| DocumentData::Root
| DocumentData::Tectonic => Vec::new(),
};
filter_symbols(&mut symbols, &workspace.config().symbols);
symbols
}
fn filter_symbols(container: &mut Vec<Symbol>, config: &SymbolConfig) {
let allowed = &config.allowed_patterns;
let ignored = &config.ignored_patterns;
let mut i = 0;
while i < container.len() {
let symbol = &mut container[i];
if symbol.name.is_empty() || !util::filter_regex_patterns(&symbol.name, allowed, ignored) {
drop(symbol);
let mut symbol = container.remove(i);
container.append(&mut symbol.children);
} else {
filter_symbols(&mut symbol.children, config);
i += 1;
}
}
}

View file

@ -0,0 +1,64 @@
use base_db::data::{BibtexEntryType, BibtexEntryTypeCategory};
use rowan::ast::AstNode;
use syntax::bibtex::{self, HasName, HasType};
use crate::{Symbol, SymbolKind};
#[derive(Debug)]
pub struct SymbolBuilder;
impl SymbolBuilder {
pub fn visit(&self, node: &bibtex::SyntaxNode) -> Option<Symbol> {
if let Some(string) = bibtex::StringDef::cast(node.clone()) {
self.visit_string(&string)
} else if let Some(entry) = bibtex::Entry::cast(node.clone()) {
self.visit_entry(&entry)
} else {
None
}
}
fn visit_string(&self, string: &bibtex::StringDef) -> Option<Symbol> {
let name = string.name_token()?;
Some(Symbol {
name: name.text().into(),
kind: SymbolKind::Entry(BibtexEntryTypeCategory::String),
label: None,
full_range: string.syntax().text_range(),
selection_range: name.text_range(),
children: Vec::new(),
})
}
fn visit_entry(&self, entry: &bibtex::Entry) -> Option<Symbol> {
let ty = entry.type_token()?;
let key = entry.name_token()?;
let children = entry
.fields()
.filter_map(|field| self.visit_field(&field))
.collect();
let category = BibtexEntryType::find(&ty.text()[1..])
.map_or(BibtexEntryTypeCategory::Misc, |ty| ty.category);
Some(Symbol {
name: key.text().into(),
kind: SymbolKind::Entry(category),
label: None,
full_range: entry.syntax().text_range(),
selection_range: key.text_range(),
children,
})
}
fn visit_field(&self, field: &bibtex::Field) -> Option<Symbol> {
let name = field.name_token()?;
Some(Symbol::new_simple(
name.text().into(),
SymbolKind::Field,
field.syntax().text_range(),
name.text_range(),
))
}
}

View file

@ -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)
}
}

View file

@ -0,0 +1,9 @@
mod document;
mod types;
mod workspace;
pub use self::{
document::document_symbols,
types::{Symbol, SymbolKind, SymbolLocation},
workspace::workspace_symbols,
};

View file

@ -0,0 +1,89 @@
use base_db::{data::BibtexEntryTypeCategory, semantics::Span, Document};
use rowan::TextRange;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SymbolKind {
Section,
Figure,
Algorithm,
Table,
Listing,
Enumeration,
EnumerationItem,
Theorem,
Equation,
Entry(BibtexEntryTypeCategory),
Field,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Symbol {
pub name: String,
pub kind: SymbolKind,
pub label: Option<Span>,
pub full_range: TextRange,
pub selection_range: TextRange,
pub children: Vec<Symbol>,
}
impl Symbol {
pub fn new_simple(
name: String,
kind: SymbolKind,
full_range: TextRange,
selection_range: TextRange,
) -> Self {
Self {
name,
kind,
label: None,
full_range,
selection_range,
children: Vec::new(),
}
}
pub fn new_label(name: String, kind: SymbolKind, range: TextRange, label: Span) -> Self {
Self {
name,
kind,
full_range: range,
selection_range: label.range,
label: Some(label),
children: Vec::new(),
}
}
pub fn keywords<'a>(&'a self) -> Vec<&'a str> {
match self.kind {
SymbolKind::Section => vec![&self.name, "latex", "section"],
SymbolKind::Figure => vec![&self.name, "latex", "float", "figure"],
SymbolKind::Algorithm => vec![&self.name, "latex", "float", "algorithm"],
SymbolKind::Table => vec![&self.name, "latex", "float", "table"],
SymbolKind::Listing => vec![&self.name, "latex", "float", "listing"],
SymbolKind::Enumeration => vec![&self.name, "latex", "enumeration"],
SymbolKind::EnumerationItem => vec![&self.name, "latex", "enumeration", "item"],
SymbolKind::Theorem => vec![&self.name, "latex", "math"],
SymbolKind::Equation => vec![&self.name, "latex", "math", "equation"],
SymbolKind::Entry(BibtexEntryTypeCategory::String) => {
vec![&self.name, "bibtex", "string"]
}
SymbolKind::Entry(_) => vec![&self.name, "bibtex", "entry"],
SymbolKind::Field => vec![&self.name, "bibtex", "field"],
}
}
pub fn flatten(mut self, buffer: &mut Vec<Self>) {
for symbol in self.children.drain(..) {
symbol.flatten(buffer);
}
buffer.push(self);
}
}
#[derive(Debug)]
pub struct SymbolLocation<'a> {
pub document: &'a Document,
pub symbol: Symbol,
}

View file

@ -0,0 +1,44 @@
mod sort;
use std::cmp::Reverse;
use base_db::Workspace;
use crate::{document_symbols, types::SymbolLocation, SymbolKind};
use self::sort::ProjectOrdering;
pub fn workspace_symbols<'a>(workspace: &'a Workspace, query: &str) -> Vec<SymbolLocation<'a>> {
let query = query.split_whitespace().collect::<Vec<_>>();
let mut results = Vec::new();
for document in workspace.iter() {
let mut buf = Vec::new();
let symbols = document_symbols(workspace, document);
for symbol in symbols {
symbol.flatten(&mut buf);
}
for symbol in buf
.into_iter()
.filter(|symbol| symbol.kind != SymbolKind::Field)
{
let keywords = symbol.keywords();
if itertools::iproduct!(keywords.iter(), query.iter())
.any(|(keyword, query)| keyword.eq_ignore_ascii_case(query))
{
results.push(SymbolLocation { document, symbol });
}
}
}
let ordering = ProjectOrdering::from(workspace);
results.sort_by_key(|item| {
let index = ordering.get(&item.document.uri);
let range = item.symbol.full_range;
(index, range.start(), Reverse(range.end()))
});
results
}

View file

@ -1,50 +1,48 @@
use base_db::{graph, Document, Workspace};
use itertools::Itertools;
use lsp_types::Url;
use url::Url;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ProjectOrdering<'a> {
ordering: Vec<&'a Document>,
inner: Vec<&'a Document>,
}
impl<'a> ProjectOrdering<'a> {
pub fn new(workspace: &'a Workspace) -> Self {
let ordering: Vec<_> = workspace
.iter()
.filter(|document| {
document
.data
.as_tex()
.map_or(false, |data| data.semantics.can_be_root)
})
.chain(workspace.iter())
.flat_map(|document| {
graph::Graph::new(workspace, document)
.preorder()
.rev()
.collect_vec()
})
.unique()
.collect();
Self { ordering }
}
pub fn get(&self, uri: &Url) -> usize {
self.ordering
self.inner
.iter()
.position(|doc| doc.uri == *uri)
.unwrap_or(std::usize::MAX)
}
}
impl<'a> From<&'a Workspace> for ProjectOrdering<'a> {
fn from(workspace: &'a Workspace) -> Self {
let inner = workspace
.iter()
.filter(|document| {
let data = document.data.as_tex();
data.map_or(false, |data| data.semantics.can_be_root)
})
.chain(workspace.iter())
.flat_map(|document| {
let graph = graph::Graph::new(workspace, document);
graph.preorder().rev().collect_vec()
})
.unique()
.collect_vec();
Self { inner }
}
}
#[cfg(test)]
mod tests {
use base_db::Owner;
use distro::Language;
use rowan::TextSize;
use super::*;
use super::{ProjectOrdering, Url, Workspace};
#[test]
fn test_no_cycles() {
@ -78,7 +76,7 @@ mod tests {
TextSize::default(),
);
let ordering = ProjectOrdering::new(&workspace);
let ordering = ProjectOrdering::from(&workspace);
assert_eq!(ordering.get(&a), 0);
assert_eq!(ordering.get(&b), 1);
assert_eq!(ordering.get(&c), 2);
@ -99,6 +97,7 @@ mod tests {
Owner::Client,
TextSize::default(),
);
workspace.open(
b.clone(),
r#"\include{a}"#.to_string(),
@ -106,6 +105,7 @@ mod tests {
Owner::Client,
TextSize::default(),
);
workspace.open(
c.clone(),
r#"\begin{documnent}\include{b}\end{document}"#.to_string(),
@ -114,7 +114,7 @@ mod tests {
TextSize::default(),
);
let ordering = ProjectOrdering::new(&workspace);
let ordering = ProjectOrdering::from(&workspace);
assert_eq!(ordering.get(&a), 0);
assert_eq!(ordering.get(&b), 1);
assert_eq!(ordering.get(&c), 2);
@ -151,7 +151,7 @@ mod tests {
TextSize::default(),
);
let ordering = ProjectOrdering::new(&workspace);
let ordering = ProjectOrdering::from(&workspace);
assert_ne!(ordering.get(&a), 0);
}
@ -196,7 +196,7 @@ mod tests {
TextSize::default(),
);
let ordering = ProjectOrdering::new(&workspace);
let ordering = ProjectOrdering::from(&workspace);
assert!(ordering.get(&b) < ordering.get(&a));
assert!(ordering.get(&c) < ordering.get(&d));
}

View file

@ -539,7 +539,7 @@ impl TheoremDefinition {
self.syntax().children().find_map(CurlyGroupWord::cast)
}
pub fn description(&self) -> Option<CurlyGroup> {
pub fn heading(&self) -> Option<CurlyGroup> {
self.syntax().children().find_map(CurlyGroup::cast)
}
}

View file

@ -64,6 +64,7 @@ syntax = { path = "../syntax" }
tempfile = "3.5.0"
threadpool = "1.8.1"
titlecase = "2.2.1"
symbols = { path = "../symbols" }
[dev-dependencies]
assert_unordered = "0.3.5"

View file

@ -8,4 +8,4 @@ pub mod inlay_hint;
pub mod link;
pub mod reference;
pub mod rename;
pub mod symbol;
pub mod symbols;

View file

@ -1,8 +1,12 @@
use base_db::{semantics::tex::LabelKind, DocumentData};
use base_db::{
semantics::tex::LabelKind,
util::{render_label, RenderedObject},
DocumentData,
};
use rowan::{ast::AstNode, TextRange};
use syntax::latex;
use crate::util::{self, cursor::CursorContext, label::LabeledObject, lsp_enums::Structure};
use crate::util::{cursor::CursorContext, lsp_enums::Structure};
use super::builder::CompletionBuilder;
@ -20,14 +24,14 @@ pub fn complete<'db>(
.iter()
.filter(|label| label.kind == LabelKind::Definition)
{
match util::label::render(context.workspace, &context.project, label) {
match render_label(context.workspace, &context.project, label) {
Some(rendered_label) => {
let kind = match &rendered_label.object {
LabeledObject::Section { .. } => Structure::Section,
LabeledObject::Float { .. } => Structure::Float,
LabeledObject::Theorem { .. } => Structure::Theorem,
LabeledObject::Equation => Structure::Equation,
LabeledObject::EnumItem => Structure::Item,
RenderedObject::Section { .. } => Structure::Section,
RenderedObject::Float { .. } => Structure::Float,
RenderedObject::Theorem { .. } => Structure::Theorem,
RenderedObject::Equation => Structure::Equation,
RenderedObject::EnumItem => Structure::Item,
};
if is_math && kind != Structure::Equation {
@ -36,7 +40,7 @@ pub fn complete<'db>(
let header = rendered_label.detail();
let footer = match &rendered_label.object {
LabeledObject::Float { caption, .. } => Some(caption.clone()),
RenderedObject::Float { caption, .. } => Some(caption.clone()),
_ => None,
};

View file

@ -1,6 +1,6 @@
use base_db::{semantics::tex::LabelKind, DocumentData};
use base_db::{semantics::tex::LabelKind, util::render_label, DocumentData};
use crate::util::{self, cursor::CursorContext};
use crate::util::cursor::CursorContext;
use super::DefinitionResult;
@ -22,7 +22,7 @@ pub(super) fn goto_definition<'a>(
.find(|label| label.name.text == name_text) else { continue };
let target_selection_range = label.name.range;
let target_range = util::label::render(context.workspace, &context.project, label)
let target_range = render_label(context.workspace, &context.project, label)
.map_or(target_selection_range, |label| label.range);
return Some(vec![DefinitionResult {

View file

@ -1,4 +1,4 @@
use base_db::{Document, LineIndex, Workspace};
use base_db::{util::LineIndex, Document, Workspace};
use lsp_types::{FormattingOptions, TextEdit};
use rowan::{ast::AstNode, NodeOrToken};
use syntax::bibtex::{self, HasName, HasType, HasValue};

View file

@ -1,7 +1,7 @@
use base_db::semantics::tex::LabelKind;
use base_db::{semantics::tex::LabelKind, util::render_label};
use lsp_types::MarkupKind;
use crate::util::{self, cursor::CursorContext};
use crate::util::cursor::CursorContext;
use super::HoverResult;
@ -17,7 +17,7 @@ pub(super) fn find_hover(context: &CursorContext) -> Option<HoverResult> {
.filter_map(|document| document.data.as_tex())
.flat_map(|data| data.semantics.labels.iter())
.find(|label| label.kind == LabelKind::Definition && label.name.text == name_text)
.and_then(|label| util::label::render(context.workspace, &context.project, label))
.and_then(|label| render_label(context.workspace, &context.project, label))
.map(|label| HoverResult {
range,
value: label.reference(),

View file

@ -1,6 +1,8 @@
use base_db::{semantics::tex::LabelKind, DocumentData};
use crate::util::{self, label::LabeledObject};
use base_db::{
semantics::tex::LabelKind,
util::{render_label, RenderedObject},
DocumentData,
};
use super::InlayHintBuilder;
@ -15,21 +17,21 @@ pub(super) fn find_hints(builder: &mut InlayHintBuilder) -> Option<()> {
.filter(|label| label.kind == LabelKind::Definition)
.filter(|label| label.name.range.intersect(range).is_some())
{
let Some(rendered) = util::label::render(builder.workspace, &builder.project, label) else { continue };
let Some(rendered) = render_label(builder.workspace, &builder.project, label) else { continue };
let Some(number) = &rendered.number else { continue };
let text = match &rendered.object {
LabeledObject::Section { prefix, .. } => {
RenderedObject::Section { prefix, .. } => {
format!("{} {}", prefix, number)
}
LabeledObject::Float { kind, .. } => {
RenderedObject::Float { kind, .. } => {
format!("{} {}", kind.as_str(), number)
}
LabeledObject::Theorem { kind, .. } => {
RenderedObject::Theorem { kind, .. } => {
format!("{} {}", kind, number)
}
LabeledObject::Equation => format!("Equation ({})", number),
LabeledObject::EnumItem => format!("Item {}", number),
RenderedObject::Equation => format!("Equation ({})", number),
RenderedObject::EnumItem => format!("Item {}", number),
};
builder.push(label.name.range.end(), text);

View file

@ -1,129 +0,0 @@
mod bibtex;
mod latex;
mod project_order;
mod types;
use std::cmp::Reverse;
use base_db::Workspace;
use lsp_types::{
ClientCapabilities, DocumentSymbolResponse, SymbolInformation, Url, WorkspaceSymbolParams,
};
use crate::util::capabilities::ClientCapabilitiesExt;
use self::{project_order::ProjectOrdering, types::InternalSymbol};
pub fn find_document_symbols(
workspace: &Workspace,
uri: &Url,
client_capabilties: &ClientCapabilities,
) -> Option<DocumentSymbolResponse> {
let document = workspace.lookup(uri)?;
let project = workspace.project(document);
let mut buf = Vec::new();
latex::find_symbols(workspace, &project, document, &mut buf);
bibtex::find_symbols(document, &mut buf);
let config = &workspace.config().symbols;
InternalSymbol::filter(&mut buf, config);
if client_capabilties.has_hierarchical_document_symbol_support() {
let symbols = buf
.into_iter()
.map(|symbol| symbol.into_document_symbol())
.collect();
Some(DocumentSymbolResponse::Nested(symbols))
} else {
let mut new_buf = Vec::new();
for symbol in buf {
symbol.flatten(&mut new_buf);
}
let mut new_buf: Vec<_> = new_buf
.into_iter()
.map(|symbol| symbol.into_symbol_info(uri.clone()))
.collect();
sort_symbols(workspace, &mut new_buf);
Some(DocumentSymbolResponse::Flat(new_buf))
}
}
#[derive(Debug, Clone)]
struct WorkspaceSymbol {
info: SymbolInformation,
search_text: String,
}
#[must_use]
pub fn find_workspace_symbols(
workspace: &Workspace,
params: &WorkspaceSymbolParams,
) -> Vec<SymbolInformation> {
let mut symbols = Vec::new();
for document in workspace.iter() {
let project = workspace.project(document);
let mut buf = Vec::new();
latex::find_symbols(workspace, &project, document, &mut buf);
bibtex::find_symbols(document, &mut buf);
let mut new_buf = Vec::new();
for symbol in buf {
symbol.flatten(&mut new_buf);
}
for symbol in new_buf {
symbols.push(WorkspaceSymbol {
search_text: symbol.search_text(),
info: symbol.into_symbol_info(document.uri.clone()),
});
}
}
let query_words: Vec<_> = params
.query
.split_whitespace()
.map(str::to_lowercase)
.collect();
let mut filtered = Vec::new();
for symbol in symbols {
let mut included = true;
for word in &query_words {
if !symbol.search_text.contains(word) {
included = false;
break;
}
}
if included {
filtered.push(symbol.info);
}
}
sort_symbols(workspace, &mut filtered);
filtered
}
fn sort_symbols(workspace: &Workspace, symbols: &mut [SymbolInformation]) {
let ordering = ProjectOrdering::new(workspace);
symbols.sort_by(|left, right| {
let left_key = (
ordering.get(&left.location.uri),
left.location.range.start,
Reverse(left.location.range.end),
);
let right_key = (
ordering.get(&right.location.uri),
right.location.range.start,
Reverse(right.location.range.end),
);
left_key.cmp(&right_key)
});
}

View file

@ -1,80 +0,0 @@
use base_db::{
data::{BibtexEntryType, BibtexEntryTypeCategory},
Document, DocumentData, LineIndex,
};
use rowan::ast::AstNode;
use syntax::bibtex::{self, HasName, HasType};
use crate::util::line_index_ext::LineIndexExt;
use super::types::{InternalSymbol, InternalSymbolKind};
pub fn find_symbols(document: &Document, buf: &mut Vec<InternalSymbol>) {
let DocumentData::Bib(data) = &document.data else { return };
let line_index = &document.line_index;
for node in data.root_node().children() {
process_string(node.clone(), line_index, buf)
.or_else(|| process_entry(node, line_index, buf));
}
}
fn process_string(
node: bibtex::SyntaxNode,
line_index: &LineIndex,
buf: &mut Vec<InternalSymbol>,
) -> Option<()> {
let string = bibtex::StringDef::cast(node)?;
let name = string.name_token()?;
buf.push(InternalSymbol {
name: name.text().into(),
label: None,
kind: InternalSymbolKind::String,
deprecated: false,
full_range: line_index.line_col_lsp_range(string.syntax().text_range()),
selection_range: line_index.line_col_lsp_range(name.text_range()),
children: Vec::new(),
});
Some(())
}
fn process_entry(
node: bibtex::SyntaxNode,
line_index: &LineIndex,
buf: &mut Vec<InternalSymbol>,
) -> Option<()> {
let entry = bibtex::Entry::cast(node)?;
let ty = entry.type_token()?;
let key = entry.name_token()?;
let mut children = Vec::new();
for field in entry.fields() {
if let Some(name) = field.name_token() {
let symbol = InternalSymbol {
name: name.text().to_string(),
label: None,
kind: InternalSymbolKind::Field,
deprecated: false,
full_range: line_index.line_col_lsp_range(field.syntax().text_range()),
selection_range: line_index.line_col_lsp_range(name.text_range()),
children: Vec::new(),
};
children.push(symbol);
}
}
let category = BibtexEntryType::find(&ty.text()[1..])
.map_or(BibtexEntryTypeCategory::Misc, |ty| ty.category);
buf.push(InternalSymbol {
name: key.to_string(),
label: None,
kind: InternalSymbolKind::Entry(category),
deprecated: false,
full_range: line_index.line_col_lsp_range(entry.syntax().text_range()),
selection_range: line_index.line_col_lsp_range(key.text_range()),
children,
});
Some(())
}

View file

@ -1,431 +0,0 @@
use std::str::FromStr;
use base_db::{semantics::Span, Document, DocumentData, Project, Workspace};
use lsp_types::Range;
use rowan::{ast::AstNode, TextRange};
use syntax::latex::{self, HasBrack, HasCurly};
use titlecase::titlecase;
use crate::util::{
label::{find_caption_by_parent, LabeledFloatKind},
line_index_ext::LineIndexExt,
};
use super::types::{InternalSymbol, InternalSymbolKind};
pub fn find_symbols(
workspace: &Workspace,
project: &Project,
document: &Document,
buf: &mut Vec<InternalSymbol>,
) {
let DocumentData::Tex(data) = &document.data else { return };
let mut symbols = visit(workspace, project, document, data.root_node());
buf.append(&mut symbols);
}
fn visit(
workspace: &Workspace,
project: &Project,
document: &Document,
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(project, document, node.clone()),
latex::ENUM_ITEM => visit_enum_item(workspace, project, document, node.clone()),
latex::EQUATION => visit_equation(project, document, node.clone()),
latex::ENVIRONMENT => latex::Environment::cast(node.clone())
.and_then(|env| env.begin())
.and_then(|begin| begin.name())
.and_then(|name| name.key())
.map(|name| name.to_string())
.and_then(|name| {
let config = &workspace.config().syntax;
if config.math_environments.contains(&name) {
visit_equation_environment(project, document, node.clone())
} else if config.enum_environments.contains(&name) {
visit_enumeration(project, document, node.clone(), &name)
} else if let Ok(float_kind) = LabeledFloatKind::from_str(&name) {
visit_float(project, document, node.clone(), float_kind)
} else {
visit_theorem(project, document, node.clone(), &name)
}
}),
_ => None,
};
match symbol {
Some(mut parent) => {
for child in node.children() {
parent
.children
.append(&mut visit(workspace, project, document, child));
}
vec![parent]
}
None => {
let mut symbols = Vec::new();
for child in node.children() {
symbols.append(&mut visit(workspace, project, document, child));
}
symbols
}
}
}
fn visit_section(
project: &Project,
document: &Document,
node: latex::SyntaxNode,
) -> Option<InternalSymbol> {
let section = latex::Section::cast(node)?;
let full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&section));
let group = section.name()?;
let group_text = group.content_text()?;
let label = NumberedLabel::find(project, section.syntax());
let symbol = match label {
Some(label) => {
let name = match label.number {
Some(number) => format!("{} {}", number, group_text),
None => group_text,
};
InternalSymbol {
name,
label: Some(label.name.text),
kind: InternalSymbolKind::Section,
deprecated: false,
full_range,
selection_range: document.line_index.line_col_lsp_range(label.range),
children: Vec::new(),
}
}
None => InternalSymbol {
name: group_text,
label: None,
kind: InternalSymbolKind::Section,
deprecated: false,
full_range,
selection_range: full_range,
children: Vec::new(),
},
};
Some(symbol)
}
fn visit_enum_item(
workspace: &Workspace,
project: &Project,
document: &Document,
node: latex::SyntaxNode,
) -> Option<InternalSymbol> {
let enum_envs = &workspace.config().syntax.enum_environments;
let enum_item = latex::EnumItem::cast(node.clone())?;
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 full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&enum_item));
let name = enum_item
.label()
.and_then(|label| label.content_text())
.unwrap_or_else(|| "Item".to_string());
let symbol = match NumberedLabel::find(project, &node) {
Some(label) => InternalSymbol {
name: label.number.map_or_else(|| name.clone(), String::from),
label: Some(label.name.text),
kind: InternalSymbolKind::EnumerationItem,
deprecated: false,
full_range,
selection_range: document.line_index.line_col_lsp_range(label.range),
children: Vec::new(),
},
None => InternalSymbol {
name,
label: None,
kind: InternalSymbolKind::EnumerationItem,
deprecated: false,
full_range,
selection_range: full_range,
children: Vec::new(),
},
};
Some(symbol)
}
fn visit_equation(
project: &Project,
document: &Document,
node: latex::SyntaxNode,
) -> Option<InternalSymbol> {
let equation = latex::Equation::cast(node)?;
let full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&equation));
make_equation_symbol(project, document, equation.syntax(), full_range)
}
fn visit_equation_environment(
project: &Project,
document: &Document,
node: latex::SyntaxNode,
) -> Option<InternalSymbol> {
let environment = latex::Environment::cast(node)?;
let full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&environment));
make_equation_symbol(project, document, environment.syntax(), full_range)
}
fn make_equation_symbol(
project: &Project,
document: &Document,
node: &latex::SyntaxNode,
full_range: Range,
) -> Option<InternalSymbol> {
let symbol = match NumberedLabel::find(project, node) {
Some(label) => {
let name = match label.number {
Some(number) => format!("Equation ({})", number),
None => "Equation".to_string(),
};
InternalSymbol {
name,
label: Some(label.name.text),
kind: InternalSymbolKind::Equation,
deprecated: false,
full_range,
selection_range: document.line_index.line_col_lsp_range(label.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(
project: &Project,
document: &Document,
node: latex::SyntaxNode,
env_name: &str,
) -> Option<InternalSymbol> {
let environment = latex::Environment::cast(node)?;
let full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&environment));
let name = titlecase(env_name);
let symbol = match NumberedLabel::find(project, environment.syntax()) {
Some(label) => {
let name = match label.number {
Some(number) => format!("{} {}", name, number),
None => name,
};
InternalSymbol {
name,
label: Some(label.name.text),
kind: InternalSymbolKind::Enumeration,
deprecated: false,
full_range,
selection_range: document.line_index.line_col_lsp_range(label.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(
project: &Project,
document: &Document,
node: latex::SyntaxNode,
float_kind: LabeledFloatKind,
) -> Option<InternalSymbol> {
let environment = latex::Environment::cast(node)?;
let full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&environment));
let (float_kind, symbol_kind) = match float_kind {
LabeledFloatKind::Algorithm => ("Algorithm", InternalSymbolKind::Algorithm),
LabeledFloatKind::Figure => ("Figure", InternalSymbolKind::Figure),
LabeledFloatKind::Listing => ("Listing", InternalSymbolKind::Listing),
LabeledFloatKind::Table => ("Table", InternalSymbolKind::Table),
};
let caption = find_caption_by_parent(environment.syntax())?;
let symbol = match NumberedLabel::find(project, environment.syntax()) {
Some(label) => {
let name = match label.number {
Some(number) => format!("{} {}: {}", float_kind, number, caption),
None => format!("{}: {}", float_kind, caption),
};
InternalSymbol {
name,
label: Some(label.name.text),
kind: symbol_kind,
deprecated: false,
full_range,
selection_range: document.line_index.line_col_lsp_range(label.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(
project: &Project,
document: &Document,
node: latex::SyntaxNode,
environment_name: &str,
) -> Option<InternalSymbol> {
let definition = project
.documents
.iter()
.filter_map(|document| document.data.as_tex())
.flat_map(|data| data.semantics.theorem_definitions.iter())
.find(|theorem| theorem.name.text == environment_name)?;
let node = latex::Environment::cast(node)?;
let theorem_description = node
.begin()?
.options()
.and_then(|option| option.content_text());
let full_range = document
.line_index
.line_col_lsp_range(latex::small_range(&node));
let symbol = match NumberedLabel::find(project, node.syntax()) {
Some(label) => {
let name = match (label.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.name.text),
kind: InternalSymbolKind::Theorem,
deprecated: false,
full_range,
selection_range: document.line_index.line_col_lsp_range(label.range),
children: Vec::new(),
}
}
None => {
let name = match theorem_description {
Some(desc) => format!("{} ({})", definition.description, desc),
None => definition.description.clone(),
};
InternalSymbol {
name,
label: None,
kind: InternalSymbolKind::Theorem,
deprecated: false,
full_range,
selection_range: full_range,
children: Vec::new(),
}
}
};
Some(symbol)
}
#[derive(Debug)]
struct NumberedLabel<'a> {
name: Span,
range: TextRange,
number: Option<&'a str>,
}
impl<'a> NumberedLabel<'a> {
fn find(project: &Project<'a>, parent: &latex::SyntaxNode) -> Option<Self> {
let label = parent.children().find_map(latex::LabelDefinition::cast)?;
let name = Span::from(&label.name()?.key()?);
let number = project
.documents
.iter()
.filter_map(|document| document.data.as_aux())
.find_map(|data| data.semantics.label_numbers.get(&name.text))
.map(|number| number.as_str());
Some(NumberedLabel {
name,
range: latex::small_range(&label),
number,
})
}
}

View file

@ -1,133 +0,0 @@
use base_db::{data::BibtexEntryTypeCategory, SymbolConfig};
use lsp_types::{DocumentSymbol, Location, Range, SymbolInformation, SymbolKind, Url};
use crate::util::{self, lsp_enums::Structure};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum InternalSymbolKind {
Section,
Figure,
Algorithm,
Table,
Listing,
Enumeration,
EnumerationItem,
Theorem,
Equation,
Entry(BibtexEntryTypeCategory),
Field,
String,
}
impl InternalSymbolKind {
pub fn into_symbol_kind(self) -> SymbolKind {
match self {
Self::Section => Structure::Section.symbol_kind(),
Self::Figure | Self::Algorithm | Self::Table | Self::Listing => {
Structure::Float.symbol_kind()
}
Self::Enumeration => Structure::Environment.symbol_kind(),
Self::EnumerationItem => Structure::Item.symbol_kind(),
Self::Theorem => Structure::Theorem.symbol_kind(),
Self::Equation => Structure::Equation.symbol_kind(),
Self::Entry(category) => Structure::Entry(category).symbol_kind(),
Self::Field => Structure::Field.symbol_kind(),
Self::String => Structure::Entry(BibtexEntryTypeCategory::String).symbol_kind(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct InternalSymbol {
pub name: String,
pub label: Option<String>,
pub kind: InternalSymbolKind,
pub deprecated: bool,
pub full_range: Range,
pub selection_range: Range,
pub children: Vec<InternalSymbol>,
}
impl InternalSymbol {
pub fn search_text(&self) -> String {
let kind = match self.kind {
InternalSymbolKind::Section => "latex section",
InternalSymbolKind::Figure => "latex float figure",
InternalSymbolKind::Algorithm => "latex float algorithm",
InternalSymbolKind::Table => "latex float table",
InternalSymbolKind::Listing => "latex float listing",
InternalSymbolKind::Enumeration => "latex enumeration",
InternalSymbolKind::EnumerationItem => "latex enumeration item",
InternalSymbolKind::Theorem => "latex math",
InternalSymbolKind::Equation => "latex math equation",
InternalSymbolKind::Entry(_) => "bibtex entry",
InternalSymbolKind::Field => "bibtex field",
InternalSymbolKind::String => "bibtex string",
};
format!("{} {}", kind, self.name).to_lowercase()
}
pub fn flatten(mut self, buffer: &mut Vec<Self>) {
if self.kind == InternalSymbolKind::Field {
return;
}
for symbol in self.children.drain(..) {
symbol.flatten(buffer);
}
buffer.push(self);
}
pub fn filter(container: &mut Vec<InternalSymbol>, config: &SymbolConfig) {
let mut i = 0;
while i < container.len() {
let symbol = &mut container[i];
if !symbol.name.is_empty()
&& util::regex_filter::filter(
&symbol.name,
&config.allowed_patterns,
&config.ignored_patterns,
)
{
Self::filter(&mut symbol.children, config);
i += 1;
} else {
drop(symbol);
let mut symbol = container.remove(i);
container.append(&mut symbol.children);
}
}
}
pub fn into_document_symbol(self) -> DocumentSymbol {
let children = self
.children
.into_iter()
.map(|child| child.into_document_symbol())
.collect();
#[allow(deprecated)]
DocumentSymbol {
name: self.name,
detail: self.label,
kind: self.kind.into_symbol_kind(),
deprecated: Some(self.deprecated),
range: self.full_range,
selection_range: self.selection_range,
children: Some(children),
tags: None,
}
}
pub fn into_symbol_info(self, uri: Url) -> SymbolInformation {
#[allow(deprecated)]
SymbolInformation {
name: self.name,
kind: self.kind.into_symbol_kind(),
deprecated: Some(self.deprecated),
location: Location::new(uri, self.full_range),
container_name: None,
tags: None,
}
}
}

View file

@ -0,0 +1,107 @@
use base_db::{data::BibtexEntryTypeCategory, Document, Workspace};
use lsp_types::{
ClientCapabilities, DocumentSymbol, DocumentSymbolResponse, Location, WorkspaceSymbolResponse,
};
use crate::util::{capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt};
pub fn document_symbols(
workspace: &Workspace,
document: &Document,
capabilities: &ClientCapabilities,
) -> DocumentSymbolResponse {
let symbols = symbols::document_symbols(workspace, document);
if capabilities.has_hierarchical_document_symbol_support() {
let results = symbols
.into_iter()
.map(|symbol| convert_to_nested_symbol(symbol, document))
.collect();
DocumentSymbolResponse::Nested(results)
} else {
let mut results = Vec::new();
for symbol in symbols {
convert_to_flat_symbols(symbol, document, &mut results);
}
DocumentSymbolResponse::Flat(results)
}
}
pub fn workspace_symbols(workspace: &Workspace, query: &str) -> WorkspaceSymbolResponse {
let symbols = symbols::workspace_symbols(workspace, query);
let mut results = Vec::new();
for symbols::SymbolLocation { symbol, document } in symbols {
convert_to_flat_symbols(symbol, document, &mut results);
}
WorkspaceSymbolResponse::Flat(results)
}
fn convert_to_nested_symbol(symbol: symbols::Symbol, document: &Document) -> DocumentSymbol {
let children = symbol
.children
.into_iter()
.map(|child| convert_to_nested_symbol(child, document))
.collect();
#[allow(deprecated)]
DocumentSymbol {
name: symbol.name,
detail: symbol.label.map(|label| label.text),
kind: convert_symbol_kind(symbol.kind),
deprecated: Some(false),
range: document.line_index.line_col_lsp_range(symbol.full_range),
selection_range: document
.line_index
.line_col_lsp_range(symbol.selection_range),
children: Some(children),
tags: None,
}
}
fn convert_to_flat_symbols(
symbol: symbols::Symbol,
document: &Document,
results: &mut Vec<lsp_types::SymbolInformation>,
) {
let range = document.line_index.line_col_lsp_range(symbol.full_range);
#[allow(deprecated)]
results.push(lsp_types::SymbolInformation {
name: symbol.name,
kind: convert_symbol_kind(symbol.kind),
deprecated: Some(false),
location: Location::new(document.uri.clone(), range),
tags: None,
container_name: None,
});
for child in symbol.children {
convert_to_flat_symbols(child, document, results);
}
}
fn convert_symbol_kind(value: symbols::SymbolKind) -> lsp_types::SymbolKind {
match value {
symbols::SymbolKind::Section => lsp_types::SymbolKind::MODULE,
symbols::SymbolKind::Figure => lsp_types::SymbolKind::METHOD,
symbols::SymbolKind::Algorithm => lsp_types::SymbolKind::METHOD,
symbols::SymbolKind::Table => lsp_types::SymbolKind::METHOD,
symbols::SymbolKind::Listing => lsp_types::SymbolKind::METHOD,
symbols::SymbolKind::Enumeration => lsp_types::SymbolKind::ENUM,
symbols::SymbolKind::EnumerationItem => lsp_types::SymbolKind::ENUM_MEMBER,
symbols::SymbolKind::Theorem => lsp_types::SymbolKind::VARIABLE,
symbols::SymbolKind::Equation => lsp_types::SymbolKind::CONSTANT,
symbols::SymbolKind::Entry(category) => match category {
BibtexEntryTypeCategory::Misc => lsp_types::SymbolKind::INTERFACE,
BibtexEntryTypeCategory::String => lsp_types::SymbolKind::STRING,
BibtexEntryTypeCategory::Article => lsp_types::SymbolKind::EVENT,
BibtexEntryTypeCategory::Thesis => lsp_types::SymbolKind::OBJECT,
BibtexEntryTypeCategory::Book => lsp_types::SymbolKind::STRUCT,
BibtexEntryTypeCategory::Part => lsp_types::SymbolKind::OPERATOR,
BibtexEntryTypeCategory::Collection => lsp_types::SymbolKind::TYPE_PARAMETER,
},
symbols::SymbolKind::Field => lsp_types::SymbolKind::FIELD,
}
}

View file

@ -28,7 +28,7 @@ use crate::{
features::{
completion::{self, builder::CompletionItemData},
definition, folding, formatting, highlight, hover, inlay_hint, link, reference, rename,
symbol,
symbols,
},
util::{
self, capabilities::ClientCapabilitiesExt, components::COMPONENT_DATABASE,
@ -467,15 +467,24 @@ impl Server {
fn document_symbols(&self, id: RequestId, params: DocumentSymbolParams) -> Result<()> {
let mut uri = params.text_document.uri;
normalize_uri(&mut uri);
let client_capabilities = Arc::clone(&self.client_capabilities);
let capabilities = Arc::clone(&self.client_capabilities);
self.run_query(id, move |workspace| {
symbol::find_document_symbols(workspace, &uri, &client_capabilities)
let Some(document) = workspace.lookup(&uri) else {
return DocumentSymbolResponse::Flat(vec![]);
};
symbols::document_symbols(workspace, document, &capabilities)
});
Ok(())
}
fn workspace_symbols(&self, id: RequestId, params: WorkspaceSymbolParams) -> Result<()> {
self.run_query(id, move |db| symbol::find_workspace_symbols(db, &params));
self.run_query(id, move |workspace| {
symbols::workspace_symbols(workspace, &params.query)
});
Ok(())
}

View file

@ -3,10 +3,8 @@ pub mod chktex;
pub mod components;
pub mod cursor;
pub mod diagnostics;
pub mod label;
pub mod line_index_ext;
pub mod lsp_enums;
pub mod regex_filter;
use std::path::PathBuf;

View file

@ -1,11 +1,9 @@
use base_db::{diagnostics::ErrorCode, Document, Workspace};
use base_db::{diagnostics::ErrorCode, util::filter_regex_patterns, Document, Workspace};
use distro::Language;
use lsp_types::{DiagnosticSeverity, NumberOrString};
use rustc_hash::FxHashMap;
use syntax::BuildErrorLevel;
use crate::util;
use super::line_index_ext::LineIndexExt;
pub fn collect(workspace: &Workspace) -> FxHashMap<&Document, Vec<lsp_types::Diagnostic>> {
@ -110,7 +108,7 @@ pub fn filter(
let config = &workspace.config().diagnostics;
for diagnostics in all_diagnostics.values_mut() {
diagnostics.retain(|diagnostic| {
util::regex_filter::filter(
filter_regex_patterns(
&diagnostic.message,
&config.allowed_patterns,
&config.ignored_patterns,

View file

@ -1,4 +1,4 @@
use base_db::{LineCol, LineColUtf16, LineIndex};
use base_db::util::{LineCol, LineColUtf16, LineIndex};
use lsp_types::{Position, Range};
use rowan::{TextRange, TextSize};

View file

@ -1,4 +1,4 @@
use base_db::LineIndex;
use base_db::util::LineIndex;
use insta::assert_snapshot;
use lsp_types::{
request::Formatting, ClientCapabilities, DocumentFormattingParams, FormattingOptions,