#[derive(Debug, Clone)]
pub struct DiagnosticsConfig {
pub enabled: bool,
pub undefined_variables: bool,
pub undefined_functions: bool,
pub undefined_classes: bool,
pub arity_errors: bool,
pub type_errors: bool,
pub deprecated_calls: bool,
pub duplicate_declarations: bool,
pub unused_symbols: bool,
}
impl Default for DiagnosticsConfig {
fn default() -> Self {
DiagnosticsConfig {
enabled: true,
undefined_variables: true,
undefined_functions: true,
undefined_classes: true,
arity_errors: true,
type_errors: true,
deprecated_calls: true,
duplicate_declarations: true,
unused_symbols: false,
}
}
}
impl DiagnosticsConfig {
#[cfg(test)]
pub fn all_enabled() -> Self {
DiagnosticsConfig {
enabled: true,
..DiagnosticsConfig::default()
}
}
pub(crate) fn from_value(v: &serde_json::Value) -> Self {
let mut cfg = DiagnosticsConfig::default();
let Some(obj) = v.as_object() else { return cfg };
let flag = |key: &str| obj.get(key).and_then(|x| x.as_bool()).unwrap_or(true);
cfg.enabled = obj.get("enabled").and_then(|x| x.as_bool()).unwrap_or(true);
cfg.undefined_variables = flag("undefinedVariables");
cfg.undefined_functions = flag("undefinedFunctions");
cfg.undefined_classes = flag("undefinedClasses");
cfg.arity_errors = flag("arityErrors");
cfg.type_errors = flag("typeErrors");
cfg.deprecated_calls = flag("deprecatedCalls");
cfg.duplicate_declarations = flag("duplicateDeclarations");
cfg.unused_symbols = obj
.get("unusedSymbols")
.and_then(|x| x.as_bool())
.unwrap_or(false);
cfg
}
}
#[derive(Debug, Clone)]
pub struct FeaturesConfig {
pub completion: bool,
pub hover: bool,
pub definition: bool,
pub declaration: bool,
pub references: bool,
pub document_symbols: bool,
pub workspace_symbols: bool,
pub rename: bool,
pub signature_help: bool,
pub inlay_hints: bool,
pub semantic_tokens: bool,
pub selection_range: bool,
pub call_hierarchy: bool,
pub document_highlight: bool,
pub implementation: bool,
pub code_action: bool,
pub type_definition: bool,
pub code_lens: bool,
pub formatting: bool,
pub range_formatting: bool,
pub on_type_formatting: bool,
pub document_link: bool,
pub linked_editing_range: bool,
pub inline_values: bool,
}
impl Default for FeaturesConfig {
fn default() -> Self {
FeaturesConfig {
completion: true,
hover: true,
definition: true,
declaration: true,
references: true,
document_symbols: true,
workspace_symbols: true,
rename: true,
signature_help: true,
inlay_hints: true,
semantic_tokens: true,
selection_range: true,
call_hierarchy: true,
document_highlight: true,
implementation: true,
code_action: true,
type_definition: true,
code_lens: true,
formatting: true,
range_formatting: true,
on_type_formatting: true,
document_link: true,
linked_editing_range: true,
inline_values: true,
}
}
}
impl FeaturesConfig {
pub(crate) fn from_value(v: &serde_json::Value) -> Self {
let mut cfg = FeaturesConfig::default();
let Some(obj) = v.as_object() else { return cfg };
let flag = |key: &str| obj.get(key).and_then(|x| x.as_bool()).unwrap_or(true);
cfg.completion = flag("completion");
cfg.hover = flag("hover");
cfg.definition = flag("definition");
cfg.declaration = flag("declaration");
cfg.references = flag("references");
cfg.document_symbols = flag("documentSymbols");
cfg.workspace_symbols = flag("workspaceSymbols");
cfg.rename = flag("rename");
cfg.signature_help = flag("signatureHelp");
cfg.inlay_hints = flag("inlayHints");
cfg.semantic_tokens = flag("semanticTokens");
cfg.selection_range = flag("selectionRange");
cfg.call_hierarchy = flag("callHierarchy");
cfg.document_highlight = flag("documentHighlight");
cfg.implementation = flag("implementation");
cfg.code_action = flag("codeAction");
cfg.type_definition = flag("typeDefinition");
cfg.code_lens = flag("codeLens");
cfg.formatting = flag("formatting");
cfg.range_formatting = flag("rangeFormatting");
cfg.on_type_formatting = flag("onTypeFormatting");
cfg.document_link = flag("documentLink");
cfg.linked_editing_range = flag("linkedEditingRange");
cfg.inline_values = flag("inlineValues");
cfg
}
}
pub const MAX_INDEXED_FILES: usize = 50_000;
#[derive(Debug, Clone)]
pub struct LspConfig {
pub php_version: Option<String>,
pub exclude_paths: Vec<String>,
pub diagnostics: DiagnosticsConfig,
pub features: FeaturesConfig,
pub max_indexed_files: usize,
}
impl Default for LspConfig {
fn default() -> Self {
LspConfig {
php_version: None,
exclude_paths: Vec::new(),
diagnostics: DiagnosticsConfig::default(),
features: FeaturesConfig::default(),
max_indexed_files: MAX_INDEXED_FILES,
}
}
}
impl LspConfig {
pub(crate) fn merge_project_configs(
file: Option<&serde_json::Value>,
editor: Option<&serde_json::Value>,
) -> serde_json::Value {
let mut merged = file
.cloned()
.unwrap_or(serde_json::Value::Object(Default::default()));
let Some(editor_obj) = editor.and_then(|e| e.as_object()) else {
return merged;
};
let merged_obj = merged
.as_object_mut()
.expect("merged base is always an object");
for (key, val) in editor_obj {
if key == "excludePaths" {
let file_arr = merged_obj
.get("excludePaths")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let editor_arr = val.as_array().cloned().unwrap_or_default();
merged_obj.insert(
key.clone(),
serde_json::Value::Array([file_arr, editor_arr].concat()),
);
} else {
merged_obj.insert(key.clone(), val.clone());
}
}
merged
}
pub(crate) fn from_value(v: &serde_json::Value) -> Self {
let mut cfg = LspConfig::default();
if let Some(ver) = v.get("phpVersion").and_then(|x| x.as_str()) {
if crate::autoload::is_valid_php_version(ver) {
cfg.php_version = Some(ver.to_string());
} else {
cfg.php_version = Some(crate::autoload::PHP_8_5.to_string());
}
}
if let Some(arr) = v.get("excludePaths").and_then(|x| x.as_array()) {
cfg.exclude_paths = arr
.iter()
.filter_map(|x| x.as_str().map(str::to_string))
.collect();
}
if let Some(diag_val) = v.get("diagnostics") {
cfg.diagnostics = DiagnosticsConfig::from_value(diag_val);
}
if let Some(feat_val) = v.get("features") {
cfg.features = FeaturesConfig::from_value(feat_val);
}
if let Some(n) = v.get("maxIndexedFiles").and_then(|x| x.as_u64()) {
cfg.max_indexed_files = n as usize;
}
cfg
}
}