Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dynamic updates of the log level #139

Merged
merged 2 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions crates/lsp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use serde::Deserialize;
use serde::Serialize;
use struct_field_names_as_array::FieldNamesAsArray;

use crate::logging::LogLevel;

/// Configuration of the LSP
#[derive(Clone, Debug, Default)]
pub(crate) struct LspConfig {}
Expand Down Expand Up @@ -55,6 +57,13 @@ pub(crate) struct VscDiagnosticsConfig {
pub enable: bool,
}

#[derive(Deserialize, FieldNamesAsArray, Clone, Debug)]
pub(crate) struct VscLogConfig {
// DEV NOTE: Update `section_from_key()` method after adding a field
pub log_level: Option<LogLevel>,
pub dependency_log_levels: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub(crate) enum VscIndentSize {
Expand Down Expand Up @@ -127,3 +136,13 @@ pub(crate) fn indent_style_from_lsp(insert_spaces: bool) -> IndentStyle {
IndentStyle::Tab
}
}

impl VscLogConfig {
pub(crate) fn section_from_key(key: &str) -> &str {
match key {
"log_level" => "air.logLevel",
"dependency_log_levels" => "air.dependencyLogLevels",
_ => "unknown", // To be caught via downstream errors
}
}
}
6 changes: 6 additions & 0 deletions crates/lsp/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use tracing::Instrument;

use crate::config::VscDiagnosticsConfig;
use crate::config::VscDocumentConfig;
use crate::config::VscLogConfig;
use crate::main_loop::LspState;

// Handlers that do not mutate the world state. They take a sharing reference or
Expand Down Expand Up @@ -46,9 +47,14 @@ pub(crate) async fn handle_initialized(
VscDiagnosticsConfig::FIELD_NAMES_AS_ARRAY.to_vec(),
VscDiagnosticsConfig::section_from_key,
);
let mut config_log_registrations: Vec<lsp_types::Registration> = collect_regs(
VscLogConfig::FIELD_NAMES_AS_ARRAY.to_vec(),
VscLogConfig::section_from_key,
);

registrations.append(&mut config_document_registrations);
registrations.append(&mut config_diagnostics_registrations);
registrations.append(&mut config_log_registrations);
}

if lsp_state
Expand Down
93 changes: 80 additions & 13 deletions crates/lsp/src/handlers_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
//
//

use std::array::IntoIter;

use anyhow::anyhow;
use anyhow::Context;
use biome_lsp_converters::PositionEncoding;
use biome_lsp_converters::WideEncoding;
use serde_json::Value;
Expand Down Expand Up @@ -36,6 +39,7 @@ use crate::config::indent_style_from_lsp;
use crate::config::DocumentConfig;
use crate::config::VscDiagnosticsConfig;
use crate::config::VscDocumentConfig;
use crate::config::VscLogConfig;
use crate::documents::Document;
use crate::logging;
use crate::logging::LogMessageSender;
Expand Down Expand Up @@ -78,12 +82,12 @@ pub(crate) fn initialize(
InitializationOptions::from_value,
);

logging::init_logging(
lsp_state.log_state = Some(logging::init_logging(
log_tx,
log_level,
dependency_log_levels,
params.client_info.as_ref(),
);
));

// Initialize the workspace settings resolver using the initial set of client provided `workspace_folders`
lsp_state.workspace_settings_resolver = WorkspaceSettingsResolver::from_workspace_folders(
Expand Down Expand Up @@ -182,13 +186,15 @@ pub(crate) fn did_close(
pub(crate) async fn did_change_configuration(
_params: DidChangeConfigurationParams,
client: &tower_lsp::Client,
lsp_state: &mut LspState,
state: &mut WorldState,
) -> anyhow::Result<()> {
// The notification params sometimes contain data but it seems in practice
// we should just ignore it. Instead we need to pull the settings again for
// all URI of interest.
// The LSP deprecated usage of `DidChangeConfigurationParams`, but still allows
// servers to use the notification itself as a way to get notified that some
// configuration that we watch has changed. When we detect any changes, we re-pull
// everything we are interested in.

update_config(workspace_uris(state), client, state)
update_config(workspace_uris(state), client, lsp_state, state)
.instrument(tracing::info_span!("did_change_configuration"))
.await
}
Expand Down Expand Up @@ -252,6 +258,7 @@ pub(crate) fn did_change_formatting_options(
async fn update_config(
uris: Vec<Url>,
client: &tower_lsp::Client,
lsp_state: &mut LspState,
state: &mut WorldState,
) -> anyhow::Result<()> {
let mut items: Vec<ConfigurationItem> = vec![];
Expand All @@ -278,15 +285,26 @@ async fn update_config(
.collect();
items.append(&mut document_items);

let log_keys = VscLogConfig::FIELD_NAMES_AS_ARRAY;
let mut log_items: Vec<ConfigurationItem> = log_keys
.iter()
.map(|key| ConfigurationItem {
scope_uri: None,
section: Some(VscLogConfig::section_from_key(key).into()),
})
.collect();
items.append(&mut log_items);

let configs = client.configuration(items).await?;

// We got the config items in a flat vector that's guaranteed to be
// ordered in the same way it was sent in. Be defensive and check that
// we've got the expected number of items before we process them chunk
// by chunk
let n_document_items = document_keys.len();
let n_diagnostics_items = diagnostics_keys.len();
let n_items = n_diagnostics_items + (n_document_items * uris.len());
let n_document_items = document_keys.len() * uris.len();
let n_log_items = log_keys.len();
let n_items = n_diagnostics_items + n_document_items + n_log_items;

if configs.len() != n_items {
return Err(anyhow!(
Expand All @@ -300,8 +318,26 @@ async fn update_config(

// --- Diagnostics
let keys = diagnostics_keys.into_iter();
let items: Vec<Value> = configs.by_ref().take(n_diagnostics_items).collect();
let items = configs.by_ref().take(n_diagnostics_items);
update_diagnostics_config(keys, items)?;

// --- Documents
let keys = document_keys.into_iter();
let items = configs.by_ref().take(n_document_items);
update_documents_config(keys, items, uris, state)?;

// --- Logs
let keys = log_keys.into_iter();
let items = configs.by_ref().take(n_log_items);
update_log_config(keys, items, lsp_state)?;

Ok(())
}

fn update_diagnostics_config(
keys: IntoIter<&str, 1>,
items: impl Iterator<Item = Value>,
) -> anyhow::Result<()> {
// Create a new `serde_json::Value::Object` manually to convert it
// to a `VscDocumentConfig` with `from_value()`. This way serde_json
// can type-check the dynamic JSON value we got from the client.
Expand All @@ -321,14 +357,22 @@ async fn update_config(
// lsp::spawn_diagnostics_refresh_all(state.clone());
// }

// --- Documents
Ok(())
}

fn update_documents_config(
keys: IntoIter<&str, 3>,
mut items: impl Iterator<Item = Value>,
uris: Vec<Url>,
state: &mut WorldState,
) -> anyhow::Result<()> {
// For each document, deserialise the vector of JSON values into a typed config
for uri in uris {
let keys = document_keys.into_iter();
let items: Vec<Value> = configs.by_ref().take(n_document_items).collect();
let uri_keys = keys.clone();
let uri_items = items.by_ref().take(keys.len());

let mut map = serde_json::Map::new();
std::iter::zip(keys, items).for_each(|(key, item)| {
std::iter::zip(uri_keys, uri_items).for_each(|(key, item)| {
map.insert(key.into(), item);
});

Expand All @@ -344,3 +388,26 @@ async fn update_config(

Ok(())
}

fn update_log_config(
keys: IntoIter<&str, 2>,
items: impl Iterator<Item = Value>,
lsp_state: &mut LspState,
) -> anyhow::Result<()> {
let log_state = lsp_state.log_state.as_mut().context("Missing log state")?;

let mut map = serde_json::Map::new();
std::iter::zip(keys, items).for_each(|(key, item)| {
map.insert(key.into(), item);
});

// Deserialise the VS Code configuration
let VscLogConfig {
log_level,
dependency_log_levels,
} = serde_json::from_value(serde_json::Value::Object(map))?;

log_state.reload(log_level, dependency_log_levels);

Ok(())
}
Loading
Loading