#![warn(missing_docs)]
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct LspLimits {
pub workspace_symbol_cap: usize,
pub references_cap: usize,
pub completion_cap: usize,
pub document_symbol_cap: usize,
pub code_lens_cap: usize,
pub diagnostics_per_file_cap: usize,
pub inlay_hints_cap: usize,
pub ast_cache_max_entries: usize,
pub ast_cache_ttl_secs: u64,
pub symbol_cache_max_entries: usize,
pub max_indexed_files: usize,
pub max_symbols_per_file: usize,
pub max_total_symbols: usize,
pub parse_storm_threshold: usize,
pub max_file_size_bytes: usize,
pub workspace_scan_deadline: Duration,
pub file_index_deadline: Duration,
pub reference_search_deadline: Duration,
pub regex_scan_deadline: Duration,
pub fs_operation_deadline: Duration,
pub semantic_tokens_deadline: Duration,
pub code_lens_resolve_deadline: Duration,
pub completion_deadline: Duration,
pub return_partial_on_timeout: bool,
pub include_open_docs_when_degraded: bool,
}
impl Default for LspLimits {
fn default() -> Self {
Self {
workspace_symbol_cap: 200,
references_cap: 500,
completion_cap: 100,
document_symbol_cap: 500,
code_lens_cap: 100,
diagnostics_per_file_cap: 200,
inlay_hints_cap: 500,
ast_cache_max_entries: 100,
ast_cache_ttl_secs: 300,
symbol_cache_max_entries: 1000,
max_indexed_files: 10_000,
max_symbols_per_file: 5_000,
max_total_symbols: 500_000,
parse_storm_threshold: 10,
max_file_size_bytes: 1_024 * 1_024,
workspace_scan_deadline: Duration::from_secs(30),
file_index_deadline: Duration::from_secs(5),
reference_search_deadline: Duration::from_secs(2),
regex_scan_deadline: Duration::from_secs(1),
fs_operation_deadline: Duration::from_millis(500),
semantic_tokens_deadline: Duration::from_secs(2),
code_lens_resolve_deadline: Duration::from_secs(1),
completion_deadline: Duration::from_millis(500),
return_partial_on_timeout: true,
include_open_docs_when_degraded: true,
}
}
}
impl LspLimits {
pub fn large_workspace() -> Self {
Self {
max_indexed_files: 50_000,
max_total_symbols: 2_000_000,
workspace_scan_deadline: Duration::from_secs(120),
..Default::default()
}
}
pub fn constrained() -> Self {
Self {
ast_cache_max_entries: 50,
max_indexed_files: 5_000,
max_total_symbols: 100_000,
workspace_scan_deadline: Duration::from_secs(15),
reference_search_deadline: Duration::from_secs(1),
..Default::default()
}
}
pub fn update_from_value(&mut self, settings: &serde_json::Value) {
if let Some(limits) = settings.get("limits") {
if let Some(v) = limits.get("workspaceSymbolCap").and_then(|v| v.as_u64()) {
self.workspace_symbol_cap = v as usize;
}
if let Some(v) = limits.get("referencesCap").and_then(|v| v.as_u64()) {
self.references_cap = v as usize;
}
if let Some(v) = limits.get("completionCap").and_then(|v| v.as_u64()) {
self.completion_cap = v as usize;
}
if let Some(v) = limits.get("astCacheMaxEntries").and_then(|v| v.as_u64()) {
self.ast_cache_max_entries = v as usize;
}
if let Some(v) = limits.get("maxIndexedFiles").and_then(|v| v.as_u64()) {
self.max_indexed_files = v as usize;
}
if let Some(v) = limits.get("maxTotalSymbols").and_then(|v| v.as_u64()) {
self.max_total_symbols = v as usize;
}
if let Some(v) = limits.get("maxFileSizeBytes").and_then(|v| v.as_u64()) {
self.max_file_size_bytes = v as usize;
}
if let Some(v) = limits.get("workspaceScanDeadlineMs").and_then(|v| v.as_u64()) {
self.workspace_scan_deadline = Duration::from_millis(v);
}
if let Some(v) = limits.get("referenceSearchDeadlineMs").and_then(|v| v.as_u64()) {
self.reference_search_deadline = Duration::from_millis(v);
}
}
}
}
pub static LSP_LIMITS: std::sync::LazyLock<std::sync::RwLock<LspLimits>> =
std::sync::LazyLock::new(|| std::sync::RwLock::new(LspLimits::default()));
#[inline]
pub fn workspace_symbol_cap() -> usize {
LSP_LIMITS.read().map(|l| l.workspace_symbol_cap).unwrap_or(200)
}
#[inline]
pub fn references_cap() -> usize {
LSP_LIMITS.read().map(|l| l.references_cap).unwrap_or(500)
}
#[inline]
pub fn completion_cap() -> usize {
LSP_LIMITS.read().map(|l| l.completion_cap).unwrap_or(100)
}
#[inline]
pub fn reference_search_deadline() -> Duration {
LSP_LIMITS.read().map(|l| l.reference_search_deadline).unwrap_or(Duration::from_secs(2))
}
#[inline]
pub fn regex_scan_deadline() -> Duration {
LSP_LIMITS.read().map(|l| l.regex_scan_deadline).unwrap_or(Duration::from_secs(1))
}
#[inline]
pub fn code_lens_cap() -> usize {
LSP_LIMITS.read().map(|l| l.code_lens_cap).unwrap_or(100)
}
#[inline]
pub fn document_symbol_cap() -> usize {
LSP_LIMITS.read().map(|l| l.document_symbol_cap).unwrap_or(500)
}
#[inline]
pub fn semantic_tokens_deadline() -> Duration {
LSP_LIMITS.read().map(|l| l.semantic_tokens_deadline).unwrap_or(Duration::from_secs(2))
}
#[inline]
pub fn code_lens_resolve_deadline() -> Duration {
LSP_LIMITS.read().map(|l| l.code_lens_resolve_deadline).unwrap_or(Duration::from_secs(1))
}
#[inline]
pub fn completion_deadline() -> Duration {
LSP_LIMITS.read().map(|l| l.completion_deadline).unwrap_or(Duration::from_millis(500))
}
#[inline]
pub fn inlay_hints_cap() -> usize {
LSP_LIMITS.read().map(|l| l.inlay_hints_cap).unwrap_or(500)
}
#[inline]
pub fn diagnostics_per_file_cap() -> usize {
LSP_LIMITS.read().map(|l| l.diagnostics_per_file_cap).unwrap_or(200)
}
#[inline]
pub fn max_file_size_bytes() -> usize {
LSP_LIMITS.read().map(|l| l.max_file_size_bytes).unwrap_or(1_024 * 1_024)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_limits() {
let limits = LspLimits::default();
assert_eq!(limits.workspace_symbol_cap, 200);
assert_eq!(limits.references_cap, 500);
assert_eq!(limits.max_indexed_files, 10_000);
assert_eq!(limits.max_file_size_bytes, 1_024 * 1_024);
}
#[test]
fn test_large_workspace_limits() {
let limits = LspLimits::large_workspace();
assert_eq!(limits.max_indexed_files, 50_000);
assert_eq!(limits.max_total_symbols, 2_000_000);
}
#[test]
fn test_constrained_limits() {
let limits = LspLimits::constrained();
assert_eq!(limits.max_indexed_files, 5_000);
assert_eq!(limits.ast_cache_max_entries, 50);
}
#[test]
fn test_update_from_value() {
let mut limits = LspLimits::default();
let settings = serde_json::json!({
"limits": {
"workspaceSymbolCap": 300,
"maxIndexedFiles": 20000
}
});
limits.update_from_value(&settings);
assert_eq!(limits.workspace_symbol_cap, 300);
assert_eq!(limits.max_indexed_files, 20_000);
}
}