use super::document::{Document, DocumentStore};
use super::types::*;
use std::sync::Arc;
pub struct SymbolProvider {
documents: Arc<DocumentStore>,
}
impl SymbolProvider {
pub fn new(documents: Arc<DocumentStore>) -> Self {
Self { documents }
}
pub fn document_symbols(&self, doc: &Document) -> Vec<DocumentSymbol> {
let mut symbols = Vec::new();
let lines: Vec<&str> = doc.content.lines().collect();
let mut i = 0;
while i < lines.len() {
if let Some((symbol, end_line)) = self.parse_symbol(&lines, i, doc) {
symbols.push(symbol);
i = end_line + 1;
} else {
i += 1;
}
}
symbols
}
fn parse_symbol(
&self,
lines: &[&str],
start: usize,
doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines.get(start)?;
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("/*") {
return None;
}
if let Some(symbol) = self.parse_function(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_struct(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_enum(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_trait(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_impl(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_const(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_type_alias(lines, start, doc) {
return Some(symbol);
}
if let Some(symbol) = self.parse_module(lines, start, doc) {
return Some(symbol);
}
None
}
fn parse_function(
&self,
lines: &[&str],
start: usize,
_doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.or_else(|| trimmed.strip_prefix("async "))
.or_else(|| trimmed.strip_prefix("pub async "))
.unwrap_or(trimmed);
let rest = rest.strip_prefix("fn ")?;
let paren_pos = rest.find('(')?;
let name = rest[..paren_pos].trim().to_string();
let brace_pos = line.find('{');
let detail = if let Some(pos) = brace_pos {
Some(line[..pos].trim().to_string())
} else {
Some(line.trim().to_string())
};
let end_line = if brace_pos.is_some() {
self.find_block_end(lines, start)
} else {
start
};
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Function,
Range::new(
Position::new(start as u32, 0),
Position::new(end_line as u32, lines[end_line].len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
)
.with_detail(detail.unwrap_or_default());
Some((symbol, end_line))
}
fn parse_struct(
&self,
lines: &[&str],
start: usize,
_doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.unwrap_or(trimmed)
.strip_prefix("struct ")?;
let name_end = rest
.find(|c: char| c == '<' || c == '{' || c == '(' || c == ' ')
.unwrap_or(rest.len());
let name = rest[..name_end].trim().to_string();
let end_line = if line.contains('{') {
self.find_block_end(lines, start)
} else {
start
};
let mut children = Vec::new();
if line.contains('{') {
for i in (start + 1)..end_line {
if let Some(field) = self.parse_field(lines[i], i as u32) {
children.push(field);
}
}
}
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Struct,
Range::new(
Position::new(start as u32, 0),
Position::new(end_line as u32, lines[end_line].len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
)
.with_children(children);
Some((symbol, end_line))
}
fn parse_field(&self, line: &str, line_num: u32) -> Option<DocumentSymbol> {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed == "}" {
return None;
}
let colon_pos = trimmed.find(':')?;
let name = trimmed[..colon_pos].trim();
let name = name.strip_prefix("pub ").unwrap_or(name);
if name.is_empty() {
return None;
}
let name_start = line.find(name).unwrap_or(0) as u32;
Some(DocumentSymbol::new(
name,
SymbolKind::Field,
Range::new(
Position::new(line_num, 0),
Position::new(line_num, line.len() as u32),
),
Range::new(
Position::new(line_num, name_start),
Position::new(line_num, name_start + name.len() as u32),
),
))
}
fn parse_enum(
&self,
lines: &[&str],
start: usize,
_doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.unwrap_or(trimmed)
.strip_prefix("enum ")?;
let name_end = rest
.find(|c: char| c == '<' || c == '{' || c == ' ')
.unwrap_or(rest.len());
let name = rest[..name_end].trim().to_string();
let end_line = if line.contains('{') {
self.find_block_end(lines, start)
} else {
start
};
let mut children = Vec::new();
if line.contains('{') {
for i in (start + 1)..end_line {
if let Some(variant) = self.parse_enum_variant(lines[i], i as u32) {
children.push(variant);
}
}
}
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Enum,
Range::new(
Position::new(start as u32, 0),
Position::new(end_line as u32, lines[end_line].len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
)
.with_children(children);
Some((symbol, end_line))
}
fn parse_enum_variant(&self, line: &str, line_num: u32) -> Option<DocumentSymbol> {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed == "}" {
return None;
}
let name_end = trimmed
.find(|c: char| c == '(' || c == '{' || c == ',' || c == ' ')
.unwrap_or(trimmed.len());
let name = trimmed[..name_end].trim();
if name.is_empty() {
return None;
}
let name_start = line.find(name).unwrap_or(0) as u32;
Some(DocumentSymbol::new(
name,
SymbolKind::EnumMember,
Range::new(
Position::new(line_num, 0),
Position::new(line_num, line.len() as u32),
),
Range::new(
Position::new(line_num, name_start),
Position::new(line_num, name_start + name.len() as u32),
),
))
}
fn parse_trait(
&self,
lines: &[&str],
start: usize,
doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.unwrap_or(trimmed)
.strip_prefix("trait ")?;
let name_end = rest
.find(|c: char| c == '<' || c == '{' || c == ':' || c == ' ')
.unwrap_or(rest.len());
let name = rest[..name_end].trim().to_string();
let end_line = if line.contains('{') {
self.find_block_end(lines, start)
} else {
start
};
let mut children = Vec::new();
if line.contains('{') {
let mut i = start + 1;
while i < end_line {
if let Some((method, method_end)) = self.parse_function(lines, i, doc) {
children.push(method);
i = method_end + 1;
} else {
i += 1;
}
}
}
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Interface,
Range::new(
Position::new(start as u32, 0),
Position::new(end_line as u32, lines[end_line].len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
)
.with_children(children);
Some((symbol, end_line))
}
fn parse_impl(
&self,
lines: &[&str],
start: usize,
doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed.strip_prefix("impl")?;
let brace_pos = rest.find('{').unwrap_or(rest.len());
let impl_part = rest[..brace_pos].trim();
let name = if impl_part.contains(" for ") {
let parts: Vec<&str> = impl_part.split(" for ").collect();
if parts.len() == 2 {
format!("impl {} for {}", parts[0].trim(), parts[1].trim())
} else {
format!("impl {}", impl_part)
}
} else {
format!(
"impl {}",
impl_part
.trim_start_matches('<')
.split('>')
.last()
.unwrap_or(impl_part)
.trim()
)
};
let end_line = if line.contains('{') {
self.find_block_end(lines, start)
} else {
start
};
let mut children = Vec::new();
if line.contains('{') {
let mut i = start + 1;
while i < end_line {
if let Some((method, method_end)) = self.parse_function(lines, i, doc) {
children.push(method);
i = method_end + 1;
} else {
i += 1;
}
}
}
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Class,
Range::new(
Position::new(start as u32, 0),
Position::new(end_line as u32, lines[end_line].len() as u32),
),
Range::new(
Position::new(start as u32, 0),
Position::new(start as u32, line.len() as u32),
),
)
.with_children(children);
Some((symbol, end_line))
}
fn parse_const(
&self,
lines: &[&str],
start: usize,
_doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.unwrap_or(trimmed)
.strip_prefix("const ")?;
let colon_pos = rest.find(':')?;
let name = rest[..colon_pos].trim().to_string();
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Constant,
Range::new(
Position::new(start as u32, 0),
Position::new(start as u32, line.len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
);
Some((symbol, start))
}
fn parse_type_alias(
&self,
lines: &[&str],
start: usize,
_doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.unwrap_or(trimmed)
.strip_prefix("type ")?;
let eq_pos = rest.find('=')?;
let name_part = rest[..eq_pos].trim();
let name_end = name_part.find('<').unwrap_or(name_part.len());
let name = name_part[..name_end].trim().to_string();
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::TypeParameter,
Range::new(
Position::new(start as u32, 0),
Position::new(start as u32, line.len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
);
Some((symbol, start))
}
fn parse_module(
&self,
lines: &[&str],
start: usize,
doc: &Document,
) -> Option<(DocumentSymbol, usize)> {
let line = lines[start];
let trimmed = line.trim();
let rest = trimmed
.strip_prefix("pub ")
.unwrap_or(trimmed)
.strip_prefix("mod ")?;
if rest.contains('{') {
let name_end = rest
.find(|c: char| c == '{' || c == ' ')
.unwrap_or(rest.len());
let name = rest[..name_end].trim().to_string();
let end_line = self.find_block_end(lines, start);
let mut children = Vec::new();
let mut i = start + 1;
while i < end_line {
if let Some((child, child_end)) = self.parse_symbol(lines, i, doc) {
children.push(child);
i = child_end + 1;
} else {
i += 1;
}
}
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Module,
Range::new(
Position::new(start as u32, 0),
Position::new(end_line as u32, lines[end_line].len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
)
.with_children(children);
Some((symbol, end_line))
} else {
let name_end = rest.find(';').unwrap_or(rest.len());
let name = rest[..name_end].trim().to_string();
let name_start = line.find(&name).unwrap_or(0) as u32;
let symbol = DocumentSymbol::new(
&name,
SymbolKind::Module,
Range::new(
Position::new(start as u32, 0),
Position::new(start as u32, line.len() as u32),
),
Range::new(
Position::new(start as u32, name_start),
Position::new(start as u32, name_start + name.len() as u32),
),
);
Some((symbol, start))
}
}
fn find_block_end(&self, lines: &[&str], start: usize) -> usize {
let mut depth = 0;
for i in start..lines.len() {
for c in lines[i].chars() {
if c == '{' {
depth += 1;
} else if c == '}' {
depth -= 1;
if depth == 0 {
return i;
}
}
}
}
lines.len().saturating_sub(1)
}
pub fn workspace_symbols(&self, query: &str) -> Vec<SymbolInformation> {
let mut symbols = Vec::new();
let query_lower = query.to_lowercase();
for uri in self.documents.uris() {
if let Some(doc) = self.documents.get(&uri) {
let doc_symbols = self.document_symbols(&doc);
self.collect_matching_symbols(&doc_symbols, &uri, &query_lower, &mut symbols);
}
}
symbols
}
fn collect_matching_symbols(
&self,
symbols: &[DocumentSymbol],
uri: &str,
query: &str,
result: &mut Vec<SymbolInformation>,
) {
for symbol in symbols {
if symbol.name.to_lowercase().contains(query) {
result.push(SymbolInformation {
name: symbol.name.clone(),
kind: symbol.kind,
tags: symbol.tags.clone(),
location: Location::new(uri.to_string(), symbol.selection_range),
container_name: None,
});
}
self.collect_matching_symbols(&symbol.children, uri, query, result);
}
}
}