mirror of
https://github.com/latex-lsp/texlab.git
synced 2025-08-04 10:49:55 +00:00
Refactor document and workspace symbol requests
This commit is contained in:
parent
e6fe836ed6
commit
edee410a66
34 changed files with 765 additions and 877 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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::*};
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
9
crates/base-db/src/util.rs
Normal file
9
crates/base-db/src/util.rs
Normal 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,
|
||||
};
|
|
@ -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())
|
||||
}
|
|
@ -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
19
crates/symbols/Cargo.toml
Normal 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
|
48
crates/symbols/src/document.rs
Normal file
48
crates/symbols/src/document.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
64
crates/symbols/src/document/bib.rs
Normal file
64
crates/symbols/src/document/bib.rs
Normal 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(),
|
||||
))
|
||||
}
|
||||
}
|
257
crates/symbols/src/document/tex.rs
Normal file
257
crates/symbols/src/document/tex.rs
Normal 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(§ion)
|
||||
} 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)
|
||||
}
|
||||
}
|
9
crates/symbols/src/lib.rs
Normal file
9
crates/symbols/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod document;
|
||||
mod types;
|
||||
mod workspace;
|
||||
|
||||
pub use self::{
|
||||
document::document_symbols,
|
||||
types::{Symbol, SymbolKind, SymbolLocation},
|
||||
workspace::workspace_symbols,
|
||||
};
|
89
crates/symbols/src/types.rs
Normal file
89
crates/symbols/src/types.rs
Normal 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,
|
||||
}
|
44
crates/symbols/src/workspace.rs
Normal file
44
crates/symbols/src/workspace.rs
Normal 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
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -8,4 +8,4 @@ pub mod inlay_hint;
|
|||
pub mod link;
|
||||
pub mod reference;
|
||||
pub mod rename;
|
||||
pub mod symbol;
|
||||
pub mod symbols;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(§ion));
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
107
crates/texlab/src/features/symbols.rs
Normal file
107
crates/texlab/src/features/symbols.rs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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, ¶ms));
|
||||
self.run_query(id, move |workspace| {
|
||||
symbols::workspace_symbols(workspace, ¶ms.query)
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue