use std::collections::VecDeque;
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::Context;
use lsp_server::ErrorCode;
use lsp_types::request::WorkspaceConfiguration;
use lsp_types::{ClientCapabilities, ConfigurationItem, ConfigurationParams};
use serde_json::Value;
use tracing::{debug, error, warn};
use crate::ide::analysis_progress::AnalysisProgressController;
use crate::lang::db::AnalysisDatabase;
use crate::lang::proc_macros::controller::ProcMacroClientController;
use crate::lsp::capabilities::client::ClientCapabilitiesExt;
use crate::lsp::result::{LSPResult, LSPResultEx};
use crate::server::client::Requester;
use crate::server::schedule::Task;
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub unmanaged_core_path: Option<PathBuf>,
pub trace_macro_diagnostics: bool,
pub enable_proc_macros: bool,
pub enable_linter: bool,
pub run_test_command: String,
pub test_runner: TestRunner,
}
impl Default for Config {
fn default() -> Self {
Self {
unmanaged_core_path: None,
trace_macro_diagnostics: false,
enable_proc_macros: Self::ENABLE_PROC_MACROS_DEFAULT,
enable_linter: true,
run_test_command: String::new(),
test_runner: TestRunner::Auto,
}
}
}
impl Config {
pub const ENABLE_PROC_MACROS_DEFAULT: bool = true;
pub fn reload_on_start(
&mut self,
requester: &mut Requester<'_>,
db: &mut AnalysisDatabase,
proc_macro_controller: &mut ProcMacroClientController,
analysis_progress_controller: &mut AnalysisProgressController,
client_capabilities: &ClientCapabilities,
) -> LSPResult<()> {
if !client_capabilities.workspace_configuration_support() {
warn!(
"client does not support `workspace/configuration` requests, config will not be \
reloaded"
);
self.apply_changes(db, proc_macro_controller, analysis_progress_controller);
return Ok(());
}
self.reload_inner(requester)
}
pub fn reload(
&mut self,
requester: &mut Requester<'_>,
client_capabilities: &ClientCapabilities,
) -> LSPResult<()> {
if !client_capabilities.workspace_configuration_support() {
warn!(
"client does not support `workspace/configuration` requests, config will not be \
reloaded"
);
return Ok(());
}
self.reload_inner(requester)
}
fn reload_inner(&mut self, requester: &mut Requester<'_>) -> LSPResult<()> {
let items = vec![
ConfigurationItem { scope_uri: None, section: Some("cairo1.corelibPath".to_owned()) },
ConfigurationItem {
scope_uri: None,
section: Some("cairo1.traceMacroDiagnostics".to_owned()),
},
ConfigurationItem {
scope_uri: None,
section: Some("cairo1.enableProcMacros".to_owned()),
},
ConfigurationItem { scope_uri: None, section: Some("cairo1.enableLinter".to_owned()) },
ConfigurationItem {
scope_uri: None,
section: Some("cairo1.runTestCommand".to_owned()),
},
ConfigurationItem { scope_uri: None, section: Some("cairo1.testRunner".to_owned()) },
];
let expected_len = items.len();
let handler = move |response: Vec<Value>| {
let response_len = response.len();
if response_len != expected_len {
error!(
"server returned unexpected number of configuration items, expected: \
{expected_len}, got: {response_len}"
);
return Task::nothing();
}
let mut response = VecDeque::from(response);
Task::local_mut(move |state, _, _, _| {
*state.config = Config::default();
state.config.unmanaged_core_path = response
.pop_front()
.as_ref()
.and_then(Value::as_str)
.filter(|s| !s.is_empty())
.map(Into::into);
if let Some(value) = response.pop_front().as_ref().and_then(Value::as_bool) {
state.config.trace_macro_diagnostics = value;
}
if let Some(value) = response.pop_front().as_ref().and_then(Value::as_bool) {
state.config.enable_proc_macros = value;
}
if let Some(value) = response.pop_front().as_ref().and_then(Value::as_bool) {
state.config.enable_linter = value;
}
if let Some(value) = response.pop_front().as_ref().and_then(Value::as_str) {
state.config.run_test_command = value.to_string();
}
if let Some(value) = response.pop_front().as_ref().and_then(Value::as_str)
&& let Ok(value) = value.parse()
{
state.config.test_runner = value;
}
debug!("reloaded configuration: {:#?}", state.config);
state.config.apply_changes(
&mut state.db,
&mut state.proc_macro_controller,
&mut state.analysis_progress_controller,
);
})
};
requester
.request::<WorkspaceConfiguration>(ConfigurationParams { items }, handler)
.context("failed to query language client for configuration items")
.with_failure_code(ErrorCode::RequestFailed)
.inspect_err(|e| warn!("{e:?}"))
}
fn apply_changes(
&self,
db: &mut AnalysisDatabase,
proc_macro_controller: &mut ProcMacroClientController,
analysis_progress_controller: &mut AnalysisProgressController,
) {
proc_macro_controller.handle_config_update(db, self);
analysis_progress_controller.on_config_change(self);
}
}
#[derive(Debug, Default, Clone, PartialEq)]
pub enum TestRunner {
#[default]
Auto,
Snforge,
CairoTest,
Custom,
}
impl FromStr for TestRunner {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"auto" => Ok(Self::Auto),
"snforge" => Ok(Self::Snforge),
"cairo-test" => Ok(Self::CairoTest),
"custom" => Ok(Self::Custom),
_ => Err(()),
}
}
}