mod calls;
mod files;
mod grep;
mod index;
mod refs;
mod rename;
mod resolve;
mod session;
mod show;
use std::collections::HashSet;
use std::path::Path;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use fastrace::trace;
use leta_fs::{get_lines_around, read_file_content, uri_to_path};
use leta_lsp::lsp_types::{DocumentSymbol, DocumentSymbolResponse, Location, SymbolInformation};
use leta_types::{CacheStats, LocationInfo, SymbolInfo, SymbolKind};
pub use calls::handle_calls;
pub use files::{handle_files, handle_files_streaming};
pub use grep::{get_file_symbols, handle_grep, handle_grep_streaming};
pub use index::handle_add_workspace;
pub use refs::{
handle_declaration, handle_implementations, handle_references, handle_subtypes,
handle_supertypes,
};
pub use rename::{handle_move_file, handle_rename};
pub use resolve::handle_resolve_symbol;
pub use session::{handle_describe_session, handle_remove_workspace, handle_restart_workspace};
pub use show::handle_show;
use crate::session::Session;
use leta_cache::LmdbCache;
#[derive(Default)]
pub struct CacheStatsTracker {
pub symbol_hits: AtomicU32,
pub symbol_misses: AtomicU32,
pub hover_hits: AtomicU32,
pub hover_misses: AtomicU32,
}
impl CacheStatsTracker {
pub fn to_cache_stats(&self) -> CacheStats {
CacheStats {
symbol_hits: self.symbol_hits.load(Ordering::Relaxed),
symbol_misses: self.symbol_misses.load(Ordering::Relaxed),
hover_hits: self.hover_hits.load(Ordering::Relaxed),
hover_misses: self.hover_misses.load(Ordering::Relaxed),
}
}
pub fn reset(&self) {
self.symbol_hits.store(0, Ordering::Relaxed);
self.symbol_misses.store(0, Ordering::Relaxed);
self.hover_hits.store(0, Ordering::Relaxed);
self.hover_misses.store(0, Ordering::Relaxed);
}
}
#[derive(Clone)]
pub struct HandlerContext {
pub session: Arc<Session>,
pub hover_cache: Arc<LmdbCache>,
pub symbol_cache: Arc<LmdbCache>,
pub cache_stats: Arc<CacheStatsTracker>,
}
impl HandlerContext {
pub fn new(
session: Arc<Session>,
hover_cache: Arc<LmdbCache>,
symbol_cache: Arc<LmdbCache>,
) -> Self {
Self {
session,
hover_cache,
symbol_cache,
cache_stats: Arc::new(CacheStatsTracker::default()),
}
}
pub fn with_shared_stats(&self) -> Self {
Self {
session: Arc::clone(&self.session),
hover_cache: Arc::clone(&self.hover_cache),
symbol_cache: Arc::clone(&self.symbol_cache),
cache_stats: Arc::clone(&self.cache_stats),
}
}
}
pub fn relative_path(path: &Path, workspace_root: &Path) -> String {
path.strip_prefix(workspace_root)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| path.to_string_lossy().to_string())
}
pub fn find_source_files_with_extension(
workspace_root: &Path,
extension: &str,
) -> Vec<std::path::PathBuf> {
let mut files = Vec::new();
let walker = ignore::WalkBuilder::new(workspace_root)
.hidden(true)
.git_ignore(true)
.build();
for entry in walker.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if ext == extension {
files.push(path.to_path_buf());
}
}
}
}
files
}
pub fn flatten_document_symbols(
symbols: &DocumentSymbolResponse,
file_path: &str,
) -> Vec<SymbolInfo> {
let mut result = Vec::new();
match symbols {
DocumentSymbolResponse::Flat(syms) => {
for sym in syms {
result.push(symbol_info_from_symbol_information(sym, file_path));
}
}
DocumentSymbolResponse::Nested(syms) => {
flatten_nested_symbols(syms, file_path, None, &mut result);
}
}
result
}
fn flatten_nested_symbols(
symbols: &[DocumentSymbol],
file_path: &str,
container: Option<&str>,
output: &mut Vec<SymbolInfo>,
) {
for sym in symbols {
let kind = SymbolKind::from_lsp(sym.kind);
let mut info = SymbolInfo::new(
sym.name.clone(),
kind,
file_path.to_string(),
sym.selection_range.start.line + 1,
);
info.column = sym.selection_range.start.character;
info.container = container.map(String::from);
info.detail = sym.detail.clone();
info.range_start_line = Some(sym.range.start.line + 1);
info.range_end_line = Some(sym.range.end.line + 1);
output.push(info);
if let Some(children) = &sym.children {
flatten_nested_symbols(children, file_path, Some(&sym.name), output);
}
}
}
fn symbol_info_from_symbol_information(sym: &SymbolInformation, file_path: &str) -> SymbolInfo {
let kind = SymbolKind::from_lsp(sym.kind);
let mut info = SymbolInfo::new(
sym.name.clone(),
kind,
file_path.to_string(),
sym.location.range.start.line + 1,
);
info.column = sym.location.range.start.character;
info.container = sym.container_name.clone();
info.range_start_line = Some(sym.location.range.start.line + 1);
info.range_end_line = Some(sym.location.range.end.line + 1);
info
}
pub fn format_locations(
locations: &[Location],
workspace_root: &Path,
context: u32,
) -> Vec<LocationInfo> {
let mut result = Vec::new();
for loc in locations {
let file_path = uri_to_path(loc.uri.as_str());
let rel_path = relative_path(&file_path, workspace_root);
let line = loc.range.start.line + 1;
let mut info = LocationInfo::new(rel_path, line);
info.column = loc.range.start.character;
if context > 0 && file_path.exists() {
if let Ok(content) = read_file_content(&file_path) {
let (lines, start, _) =
get_lines_around(&content, loc.range.start.line as usize, context as usize);
info.context_lines = Some(lines);
info.context_start = Some(start as u32 + 1);
}
}
result.push(info);
}
result.sort_by(|a, b| (&a.path, a.line).cmp(&(&b.path, b.line)));
result
}
pub fn format_type_hierarchy_items_from_json(
items: &[serde_json::Value],
workspace_root: &Path,
context: u32,
) -> Vec<LocationInfo> {
let mut result = Vec::new();
let mut seen = std::collections::HashSet::new();
for item in items {
let uri = match item.get("uri").and_then(|v| v.as_str()) {
Some(u) => u,
None => continue,
};
let name = match item.get("name").and_then(|v| v.as_str()) {
Some(n) => n.to_string(),
None => continue,
};
let kind_num = item.get("kind").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let selection_range = match item.get("selectionRange") {
Some(r) => r,
None => continue,
};
let start_line = selection_range
.get("start")
.and_then(|s| s.get("line"))
.and_then(|l| l.as_u64())
.unwrap_or(0) as u32;
let start_char = selection_range
.get("start")
.and_then(|s| s.get("character"))
.and_then(|c| c.as_u64())
.unwrap_or(0) as u32;
let detail = item
.get("detail")
.and_then(|v| v.as_str())
.map(String::from);
let file_path = uri_to_path(uri);
let rel_path = relative_path(&file_path, workspace_root);
let line = start_line + 1;
let key = (rel_path.clone(), line);
if seen.contains(&key) {
continue;
}
seen.insert(key);
let lsp_kind = match kind_num {
1 => leta_lsp::lsp_types::SymbolKind::FILE,
2 => leta_lsp::lsp_types::SymbolKind::MODULE,
3 => leta_lsp::lsp_types::SymbolKind::NAMESPACE,
4 => leta_lsp::lsp_types::SymbolKind::PACKAGE,
5 => leta_lsp::lsp_types::SymbolKind::CLASS,
6 => leta_lsp::lsp_types::SymbolKind::METHOD,
7 => leta_lsp::lsp_types::SymbolKind::PROPERTY,
8 => leta_lsp::lsp_types::SymbolKind::FIELD,
9 => leta_lsp::lsp_types::SymbolKind::CONSTRUCTOR,
10 => leta_lsp::lsp_types::SymbolKind::ENUM,
11 => leta_lsp::lsp_types::SymbolKind::INTERFACE,
12 => leta_lsp::lsp_types::SymbolKind::FUNCTION,
13 => leta_lsp::lsp_types::SymbolKind::VARIABLE,
14 => leta_lsp::lsp_types::SymbolKind::CONSTANT,
15 => leta_lsp::lsp_types::SymbolKind::STRING,
16 => leta_lsp::lsp_types::SymbolKind::NUMBER,
17 => leta_lsp::lsp_types::SymbolKind::BOOLEAN,
18 => leta_lsp::lsp_types::SymbolKind::ARRAY,
19 => leta_lsp::lsp_types::SymbolKind::OBJECT,
20 => leta_lsp::lsp_types::SymbolKind::KEY,
21 => leta_lsp::lsp_types::SymbolKind::NULL,
22 => leta_lsp::lsp_types::SymbolKind::ENUM_MEMBER,
23 => leta_lsp::lsp_types::SymbolKind::STRUCT,
24 => leta_lsp::lsp_types::SymbolKind::EVENT,
25 => leta_lsp::lsp_types::SymbolKind::OPERATOR,
26 => leta_lsp::lsp_types::SymbolKind::TYPE_PARAMETER,
_ => leta_lsp::lsp_types::SymbolKind::VARIABLE,
};
let mut info = LocationInfo::new(rel_path, line);
info.column = start_char;
info.name = Some(name);
info.kind = Some(SymbolKind::from_lsp(lsp_kind).to_string());
info.detail = detail;
if context > 0 && file_path.exists() {
if let Ok(content) = read_file_content(&file_path) {
let (lines, start, _) =
get_lines_around(&content, start_line as usize, context as usize);
info.context_lines = Some(lines);
info.context_start = Some(start as u32 + 1);
}
}
result.push(info);
}
result.sort_by(|a, b| (&a.path, a.line).cmp(&(&b.path, b.line)));
result
}
#[trace]
pub async fn collect_all_workspace_symbols(
ctx: &HandlerContext,
workspace_root: &Path,
) -> Result<Vec<SymbolInfo>, String> {
let config = ctx.session.config().await;
let excluded_languages: HashSet<String> = config
.workspaces
.excluded_languages
.iter()
.cloned()
.collect();
let files = grep::enumerate_source_files(workspace_root, &excluded_languages);
grep::collect_symbols_smart(ctx, workspace_root, &files, None, &excluded_languages).await
}