use {
crate::{
TRACER,
database::{
DynPartition,
PartitionKey,
PartitionWriteContextRef,
},
partitions::DocumentSymbols,
protocol::{
jsonrpc,
lsif::RangeBasedDocumentSymbol,
lsp::{
Location,
PartialResultParams,
PositionEncodingKind,
Range,
SymbolKindCapability,
TagSupport,
TextDocumentIdentifier,
WorkDoneProgressParams,
},
macros::lsp_enum,
},
record::LaburnumRecordRef,
scheduler::task::TaskContext,
},
opentelemetry::trace::FutureExt,
serde::{
Deserialize,
Serialize,
},
};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSymbol {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
pub kind: SymbolKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<SymbolTag>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use tags instead")]
pub deprecated: Option<bool>,
pub range: Range,
pub selection_range: Range,
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<DocumentSymbol>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct SymbolKind(i32);
lsp_enum! {
impl SymbolKind {
const FILE = 1;
const MODULE = 2;
const NAMESPACE = 3;
const PACKAGE = 4;
const CLASS = 5;
const METHOD = 6;
const PROPERTY = 7;
const FIELD = 8;
const CONSTRUCTOR = 9;
const ENUM = 10;
const INTERFACE = 11;
const FUNCTION = 12;
const VARIABLE = 13;
const CONSTANT = 14;
const STRING = 15;
const NUMBER = 16;
const BOOLEAN = 17;
const ARRAY = 18;
const OBJECT = 19;
const KEY = 20;
const NULL = 21;
const ENUM_MEMBER = 22;
const STRUCT = 23;
const EVENT = 24;
const OPERATOR = 25;
const TYPE_PARAMETER = 26;
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct SymbolTag(i32);
lsp_enum! {
impl SymbolTag {
const DEPRECATED = 1;
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSymbolClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_registration: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol_kind: Option<SymbolKindCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hierarchical_document_symbol_support: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "TagSupport::deserialize_compat"
)]
pub tag_support: Option<TagSupport<SymbolTag>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label_support: Option<bool>,
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum DocumentSymbolOrRangeBasedVec {
DocumentSymbol(Vec<DocumentSymbol>),
RangeBased(Vec<RangeBasedDocumentSymbol>),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum DocumentSymbolResponse {
Flat(Vec<SymbolInformation>),
Nested(Vec<DocumentSymbol>),
}
impl From<Vec<SymbolInformation>> for DocumentSymbolResponse {
fn from(info: Vec<SymbolInformation>) -> Self {
Self::Flat(info)
}
}
impl From<Vec<DocumentSymbol>> for DocumentSymbolResponse {
fn from(symbols: Vec<DocumentSymbol>) -> Self {
Self::Nested(symbols)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSymbolParams {
pub text_document: TextDocumentIdentifier,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
#[serde(flatten)]
pub partial_result_params: PartialResultParams,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SymbolInformation {
pub name: String,
pub kind: SymbolKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<SymbolTag>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use tags instead")]
pub deprecated: Option<bool>,
pub location: Location,
#[serde(skip_serializing_if = "Option::is_none")]
pub container_name: Option<String>,
}
pub trait DocumentSymbolService<
P: crate::database::storage::Partitions,
T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
fn document_symbol(
&self,
params: DocumentSymbolParams,
ctx: &mut TaskContext<P, T>,
writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<
Output = jsonrpc::Result<Option<DocumentSymbolResponse>>,
> + Send {
let cx = otel::span!(^
"laburnum.lsp.document_symbol",
"document.uri" = params.text_document.uri.to_string()
);
async move {
let uri = params.text_document.uri;
let source_key = match ctx.source_cache().read().latest_key(&uri) {
| Some(key) => key,
| None => return Ok(None),
};
let encoding = ctx.position_encoding();
let source_cache_reader = ctx.source_cache_reader();
let mut symbols = Vec::new();
let query_results = ctx
.query_client()
.query_partition(DocumentSymbols)
.sort_key_begins_with(crate::partitions::document_symbols::DocumentSymbolSortKey::FilePrefix { source_key })
.execute()
.await;
if !query_results.is_empty() {
for record_ref in query_results.iter() {
if let Some(symbol_record) = record_ref.as_document_symbol()
{
symbols.push(symbol_record.to_document_symbol(&source_cache_reader, &encoding));
}
}
} else {
let hook_symbols = T::get_document_symbols(source_key, ctx).await;
for symbol in hook_symbols {
symbols.push(symbol.to_document_symbol(&source_cache_reader, &encoding));
}
}
symbols.sort_by(|a, b| {
let a_pos = a.range.start;
let b_pos = b.range.start;
a_pos
.line
.cmp(&b_pos.line)
.then_with(|| a_pos.character.cmp(&b_pos.character))
});
if symbols.is_empty() {
Ok(None)
} else {
Ok(Some(DocumentSymbolResponse::Nested(symbols)))
}
}.with_context(cx)
}
const DOCUMENT_SYMBOL_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
}
pub use crate::partitions::document_symbols::SymbolKindVariant;
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
Ident,
SourceKey,
},
};
#[test]
fn test_document_symbol_sort_key_format() {
use crate::partitions::document_symbols::DocumentSymbolSortKey;
let source_key = SourceKey::new(10, 1);
let symbol_kind = SymbolKind::FUNCTION;
let ident = Ident::new("my_function");
let sort_key = DocumentSymbolSortKey::Symbol {
source_key,
symbol_kind,
ident,
}
.to_string();
assert!(sort_key.starts_with("10v1|12|"));
assert!(sort_key.contains("|12|"));
}
#[test]
fn test_document_symbol_sort_key_ordering() {
use crate::partitions::document_symbols::DocumentSymbolSortKey;
let source_key = SourceKey::new(10, 1);
let func_ident = Ident::new("func");
let var_ident = Ident::new("var");
let func_key = DocumentSymbolSortKey::Symbol {
source_key,
symbol_kind: SymbolKind::FUNCTION,
ident: func_ident,
}
.to_string();
let var_key = DocumentSymbolSortKey::Symbol {
source_key,
symbol_kind: SymbolKind::VARIABLE,
ident: var_ident,
}
.to_string();
assert!(func_key < var_key);
}
}