From ba3d95ab4dbdd3f0456c1979bfb2ca51a77526a1 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sat, 5 Jul 2025 10:28:05 +0200 Subject: [PATCH 1/6] Simplify LSP settings --- Cargo.lock | 21 ---- crates/ark/Cargo.toml | 1 - crates/ark/src/lsp/config.rs | 145 ++++++++++++--------------- crates/ark/src/lsp/handlers.rs | 41 ++------ crates/ark/src/lsp/state_handlers.rs | 119 +++------------------- 5 files changed, 88 insertions(+), 239 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f1dcf4a8..0f6dcd098 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,7 +319,6 @@ dependencies = [ "serde", "serde_json", "stdext", - "struct-field-names-as-array", "strum 0.26.2", "strum_macros 0.26.4", "tempfile", @@ -2744,26 +2743,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "struct-field-names-as-array" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ba4bae771f9cc992c4f403636c54d2ef13acde6367583e99d06bb336674dd9" -dependencies = [ - "struct-field-names-as-array-derive", -] - -[[package]] -name = "struct-field-names-as-array-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2dbf8b57f3ce20e4bb171a11822b283bdfab6c4bb0fe64fa729f045f23a0938" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", -] - [[package]] name = "strum" version = "0.24.1" diff --git a/crates/ark/Cargo.toml b/crates/ark/Cargo.toml index 3893dd3d1..2a8b1b6e3 100644 --- a/crates/ark/Cargo.toml +++ b/crates/ark/Cargo.toml @@ -53,7 +53,6 @@ url = "2.4.1" walkdir = "2" yaml-rust = "0.4.5" winsafe = { version = "0.0.19", features = ["kernel"] } -struct-field-names-as-array = "0.3.0" strum = "0.26.2" strum_macros = "0.26.2" futures = "0.3.30" diff --git a/crates/ark/src/lsp/config.rs b/crates/ark/src/lsp/config.rs index e7041b048..79b8ee886 100644 --- a/crates/ark/src/lsp/config.rs +++ b/crates/ark/src/lsp/config.rs @@ -1,15 +1,75 @@ use serde::Deserialize; use serde::Serialize; -use struct_field_names_as_array::FieldNamesAsArray; +use serde_json::Value; -use crate::lsp; use crate::lsp::diagnostics::DiagnosticsConfig; +pub struct Setting { + pub key: &'static str, + pub set: fn(&mut LspConfig, Value), +} + +// List of LSP settings for which clients can send `didChangeConfiguration` +// notifications. We register our interest in watching over these settings in +// our `initialized` handler. The `set` methods convert from a json `Value` to +// the expected type, using a default value if the conversion fails. +pub static SETTINGS: &[Setting] = &[ + Setting { + key: "editor.insertSpaces", + set: |cfg, v| { + let default_style = IndentationConfig::default().indent_style; + cfg.document.indent.indent_style = if v + .as_bool() + .unwrap_or_else(|| default_style == IndentStyle::Space) + { + IndentStyle::Space + } else { + IndentStyle::Tab + } + }, + }, + Setting { + key: "editor.indentSize", + set: |cfg, v| { + cfg.document.indent.indent_size = v + .as_u64() + .map(|n| n as usize) + .unwrap_or_else(|| IndentationConfig::default().indent_size) + }, + }, + Setting { + key: "editor.tabSize", + set: |cfg, v| { + cfg.document.indent.tab_width = v + .as_u64() + .map(|n| n as usize) + .unwrap_or_else(|| IndentationConfig::default().tab_width) + }, + }, + Setting { + key: "positron.r.diagnostics.enable", + set: |cfg, v| { + cfg.diagnostics.enable = v + .as_bool() + .unwrap_or_else(|| DiagnosticsConfig::default().enable) + }, + }, + Setting { + key: "positron.r.symbols.includeAssignmentsInBlocks", + set: |cfg, v| { + cfg.symbols.include_assignments_in_blocks = v + .as_bool() + .unwrap_or_else(|| SymbolsConfig::default().include_assignments_in_blocks) + }, + }, +]; + /// Configuration of the LSP #[derive(Clone, Default, Debug)] pub(crate) struct LspConfig { pub(crate) diagnostics: DiagnosticsConfig, pub(crate) symbols: SymbolsConfig, + pub(crate) document: DocumentConfig, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -39,14 +99,14 @@ pub struct IndentationConfig { pub tab_width: usize, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(PartialEq, Serialize, Deserialize, Clone, Debug)] pub enum IndentStyle { Tab, Space, } /// VS Code representation of a document configuration -#[derive(Serialize, Deserialize, FieldNamesAsArray, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub(crate) struct VscDocumentConfig { // DEV NOTE: Update `section_from_key()` method after adding a field pub insert_spaces: bool, @@ -54,13 +114,13 @@ pub(crate) struct VscDocumentConfig { pub tab_size: usize, } -#[derive(Serialize, Deserialize, FieldNamesAsArray, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub(crate) struct VscDiagnosticsConfig { // DEV NOTE: Update `section_from_key()` method after adding a field pub enable: bool, } -#[derive(Serialize, Deserialize, FieldNamesAsArray, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub(crate) struct VscSymbolsConfig { // DEV NOTE: Update `section_from_key()` method after adding a field pub include_assignments_in_blocks: bool, @@ -91,79 +151,6 @@ impl Default for IndentationConfig { } } -impl VscDocumentConfig { - pub(crate) fn section_from_key(key: &str) -> &str { - match key { - "insert_spaces" => "editor.insertSpaces", - "indent_size" => "editor.indentSize", - "tab_size" => "editor.tabSize", - _ => "unknown", // To be caught via downstream errors - } - } -} - -/// Convert from VS Code representation of a document config to our own -/// representation. Currently one-to-one. -impl From for DocumentConfig { - fn from(x: VscDocumentConfig) -> Self { - let indent_style = indent_style_from_lsp(x.insert_spaces); - - let indent_size = match x.indent_size { - VscIndentSize::Size(size) => size, - VscIndentSize::Alias(var) => { - if var == "tabSize" { - x.tab_size - } else { - lsp::log_warn!("Unknown indent alias {var}, using default"); - 2 - } - }, - }; - - Self { - indent: IndentationConfig { - indent_style, - indent_size, - tab_width: x.tab_size, - }, - } - } -} - -impl VscDiagnosticsConfig { - pub(crate) fn section_from_key(key: &str) -> &str { - match key { - "enable" => "positron.r.diagnostics.enable", - _ => "unknown", // To be caught via downstream errors - } - } -} - -impl From for DiagnosticsConfig { - fn from(value: VscDiagnosticsConfig) -> Self { - Self { - enable: value.enable, - } - } -} - -impl VscSymbolsConfig { - pub(crate) fn section_from_key(key: &str) -> &str { - match key { - "include_assignments_in_blocks" => "positron.r.symbols.includeAssignmentsInBlocks", - _ => "unknown", // To be caught via downstream errors - } - } -} - -impl From for SymbolsConfig { - fn from(value: VscSymbolsConfig) -> Self { - Self { - include_assignments_in_blocks: value.include_assignments_in_blocks, - } - } -} - pub(crate) fn indent_style_from_lsp(insert_spaces: bool) -> IndentStyle { if insert_spaces { IndentStyle::Space diff --git a/crates/ark/src/lsp/handlers.rs b/crates/ark/src/lsp/handlers.rs index 7cb115e7c..c111cfb82 100644 --- a/crates/ark/src/lsp/handlers.rs +++ b/crates/ark/src/lsp/handlers.rs @@ -9,7 +9,6 @@ use anyhow::anyhow; use serde_json::Value; use stdext::unwrap; use stdext::unwrap::IntoResult; -use struct_field_names_as_array::FieldNamesAsArray; use tower_lsp::lsp_types::CodeActionParams; use tower_lsp::lsp_types::CodeActionResponse; use tower_lsp::lsp_types::CompletionItem; @@ -46,9 +45,6 @@ use crate::lsp; use crate::lsp::code_action::code_actions; use crate::lsp::completions::provide_completions; use crate::lsp::completions::resolve_completion; -use crate::lsp::config::VscDiagnosticsConfig; -use crate::lsp::config::VscDocumentConfig; -use crate::lsp::config::VscSymbolsConfig; use crate::lsp::definitions::goto_definition; use crate::lsp::document_context::DocumentContext; use crate::lsp::encoding::convert_lsp_range_to_tree_sitter_range; @@ -106,22 +102,16 @@ pub(crate) async fn handle_initialized( // Note that some settings, such as editor indentation properties, may be // changed by extensions or by the user without changing the actual // underlying setting. Unfortunately we don't receive updates in that case. - let mut config_document_regs = collect_regs( - VscDocumentConfig::FIELD_NAMES_AS_ARRAY.to_vec(), - VscDocumentConfig::section_from_key, - ); - let mut config_symbols_regs: Vec = collect_regs( - VscSymbolsConfig::FIELD_NAMES_AS_ARRAY.to_vec(), - VscSymbolsConfig::section_from_key, - ); - let mut config_diagnostics_regs: Vec = collect_regs( - VscDiagnosticsConfig::FIELD_NAMES_AS_ARRAY.to_vec(), - VscDiagnosticsConfig::section_from_key, - ); - - regs.append(&mut config_document_regs); - regs.append(&mut config_symbols_regs); - regs.append(&mut config_diagnostics_regs); + + use crate::lsp::config::SETTINGS; + + for setting in SETTINGS { + regs.push(Registration { + id: uuid::Uuid::new_v4().to_string(), + method: String::from("workspace/didChangeConfiguration"), + register_options: Some(serde_json::json!({ "section": setting.key })), + }); + } } client @@ -131,17 +121,6 @@ pub(crate) async fn handle_initialized( Ok(()) } -fn collect_regs(fields: Vec<&str>, into_section: impl Fn(&str) -> &str) -> Vec { - fields - .into_iter() - .map(|field| Registration { - id: uuid::Uuid::new_v4().to_string(), - method: String::from("workspace/didChangeConfiguration"), - register_options: Some(serde_json::json!({ "section": into_section(field) })), - }) - .collect() -} - #[tracing::instrument(level = "info", skip_all)] pub(crate) fn handle_symbol( params: WorkspaceSymbolParams, diff --git a/crates/ark/src/lsp/state_handlers.rs b/crates/ark/src/lsp/state_handlers.rs index 1bca6fcc0..b748d038d 100644 --- a/crates/ark/src/lsp/state_handlers.rs +++ b/crates/ark/src/lsp/state_handlers.rs @@ -8,11 +8,8 @@ use std::path::Path; use anyhow::anyhow; -use serde_json::Value; -use struct_field_names_as_array::FieldNamesAsArray; use tower_lsp::lsp_types::CompletionOptions; use tower_lsp::lsp_types::CompletionOptionsCompletionItem; -use tower_lsp::lsp_types::ConfigurationItem; use tower_lsp::lsp_types::DidChangeConfigurationParams; use tower_lsp::lsp_types::DidChangeTextDocumentParams; use tower_lsp::lsp_types::DidCloseTextDocumentParams; @@ -42,12 +39,6 @@ use url::Url; use crate::lsp; use crate::lsp::capabilities::Capabilities; use crate::lsp::config::indent_style_from_lsp; -use crate::lsp::config::DocumentConfig; -use crate::lsp::config::SymbolsConfig; -use crate::lsp::config::VscDiagnosticsConfig; -use crate::lsp::config::VscDocumentConfig; -use crate::lsp::config::VscSymbolsConfig; -use crate::lsp::diagnostics::DiagnosticsConfig; use crate::lsp::documents::Document; use crate::lsp::encoding::get_position_encoding_kind; use crate::lsp::indexer; @@ -284,121 +275,35 @@ pub(crate) fn did_change_formatting_options( // `insert_final_newline` } +use crate::lsp::config::SETTINGS; + async fn update_config( - uris: Vec, + _uris: Vec, client: &tower_lsp::Client, state: &mut WorldState, ) -> anyhow::Result<()> { - let mut items: Vec = vec![]; - - let diagnostics_keys = VscDiagnosticsConfig::FIELD_NAMES_AS_ARRAY; - let mut diagnostics_items: Vec = diagnostics_keys - .iter() - .map(|key| ConfigurationItem { - scope_uri: None, - section: Some(VscDiagnosticsConfig::section_from_key(key).into()), - }) - .collect(); - items.append(&mut diagnostics_items); - - let symbols_keys = VscSymbolsConfig::FIELD_NAMES_AS_ARRAY; - let mut symbols_items: Vec = symbols_keys + // Build the configuration request for global settings + let items: Vec<_> = SETTINGS .iter() - .map(|key| ConfigurationItem { + .map(|mapping| tower_lsp::lsp_types::ConfigurationItem { scope_uri: None, - section: Some(VscSymbolsConfig::section_from_key(key).into()), + section: Some(mapping.key.to_string()), }) .collect(); - items.append(&mut symbols_items); - - // For document configs we collect all pairs of URIs and config keys of - // interest in a flat vector - let document_keys = VscDocumentConfig::FIELD_NAMES_AS_ARRAY; - let mut document_items: Vec = - itertools::iproduct!(uris.iter(), document_keys.iter()) - .map(|(uri, key)| ConfigurationItem { - scope_uri: Some(uri.clone()), - section: Some(VscDocumentConfig::section_from_key(key).into()), - }) - .collect(); - items.append(&mut document_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_symbols_items = symbols_keys.len(); - let n_items = n_diagnostics_items + n_symbols_items + (n_document_items * uris.len()); - - if configs.len() != n_items { + if configs.len() != SETTINGS.len() { return Err(anyhow!( "Unexpected number of retrieved configurations: {}/{}", configs.len(), - n_items + SETTINGS.len() )); } - let mut configs = configs.into_iter(); - - // --- Diagnostics - let keys = diagnostics_keys.into_iter(); - let items: Vec = configs.by_ref().take(n_diagnostics_items).collect(); - - // 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. - 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 config: VscDiagnosticsConfig = serde_json::from_value(serde_json::Value::Object(map))?; - let config: DiagnosticsConfig = config.into(); - - let changed = state.config.diagnostics != config; - state.config.diagnostics = config; - - if changed { - lsp::spawn_diagnostics_refresh_all(state.clone()); - } - - // --- Symbols - let keys = symbols_keys.into_iter(); - let items: Vec = configs.by_ref().take(n_symbols_items).collect(); - - let mut map = serde_json::Map::new(); - std::iter::zip(keys, items).for_each(|(key, item)| { - map.insert(key.into(), item); - }); - - let config: VscSymbolsConfig = serde_json::from_value(serde_json::Value::Object(map))?; - let config: SymbolsConfig = config.into(); - state.config.symbols = config; - - // --- Documents - // For each document, deserialise the vector of JSON values into a typed config - for uri in uris.into_iter() { - let keys = document_keys.into_iter(); - let items: Vec = configs.by_ref().take(n_document_items).collect(); - - 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 config: VscDocumentConfig = serde_json::from_value(serde_json::Value::Object(map))?; - - // Now convert the VS Code specific type into our own type - let config: DocumentConfig = config.into(); - - // Finally, update the document's config - state.get_document_mut(&uri)?.config = config; + // Apply each config value using its update closure + for (mapping, value) in SETTINGS.iter().zip(configs) { + (mapping.set)(&mut state.config, value); } Ok(()) From 3ea926b3f89c5183a99e122fa298c6a0ea3dfd66 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Mon, 21 Jul 2025 14:47:32 +0200 Subject: [PATCH 2/6] Add copilot instruction about `::` --- .github/copilot-instructions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ebb815a6e..a257f6e7e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -22,3 +22,5 @@ judgement to produce the clearest code organization. Keep the main logic as unnested as possible. Favour Rust's `let ... else` syntax to return early or continue a loop in the `else` clause, over `if let`. + +Always prefer importing with `use` instead of qualifying with `::`, unless specifically requested in these instructions or by the user, or you see existing `::` usages in the file you're editing. From b442b13574ea05e969fa8db0a322040010069171 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Mon, 21 Jul 2025 14:54:02 +0200 Subject: [PATCH 3/6] Move import up top --- crates/ark/src/lsp/handlers.rs | 4 +--- crates/ark/src/lsp/state_handlers.rs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/ark/src/lsp/handlers.rs b/crates/ark/src/lsp/handlers.rs index c111cfb82..cf28007da 100644 --- a/crates/ark/src/lsp/handlers.rs +++ b/crates/ark/src/lsp/handlers.rs @@ -103,9 +103,7 @@ pub(crate) async fn handle_initialized( // changed by extensions or by the user without changing the actual // underlying setting. Unfortunately we don't receive updates in that case. - use crate::lsp::config::SETTINGS; - - for setting in SETTINGS { + for setting in crate::lsp::config::SETTINGS { regs.push(Registration { id: uuid::Uuid::new_v4().to_string(), method: String::from("workspace/didChangeConfiguration"), diff --git a/crates/ark/src/lsp/state_handlers.rs b/crates/ark/src/lsp/state_handlers.rs index b748d038d..f33dedb8a 100644 --- a/crates/ark/src/lsp/state_handlers.rs +++ b/crates/ark/src/lsp/state_handlers.rs @@ -39,6 +39,7 @@ use url::Url; use crate::lsp; use crate::lsp::capabilities::Capabilities; use crate::lsp::config::indent_style_from_lsp; +use crate::lsp::config::SETTINGS; use crate::lsp::documents::Document; use crate::lsp::encoding::get_position_encoding_kind; use crate::lsp::indexer; @@ -275,8 +276,6 @@ pub(crate) fn did_change_formatting_options( // `insert_final_newline` } -use crate::lsp::config::SETTINGS; - async fn update_config( _uris: Vec, client: &tower_lsp::Client, From 6e29d7c5291a37347ff7f5cca2d5e4f073af68f2 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 22 Jul 2025 10:14:40 +0200 Subject: [PATCH 4/6] Split global and document settings --- crates/ark/src/lsp/config.rs | 61 ++++++++++++++------------ crates/ark/src/lsp/handlers.rs | 9 +++- crates/ark/src/lsp/state_handlers.rs | 64 +++++++++++++++++++++++----- 3 files changed, 96 insertions(+), 38 deletions(-) diff --git a/crates/ark/src/lsp/config.rs b/crates/ark/src/lsp/config.rs index 79b8ee886..06b70a367 100644 --- a/crates/ark/src/lsp/config.rs +++ b/crates/ark/src/lsp/config.rs @@ -4,21 +4,45 @@ use serde_json::Value; use crate::lsp::diagnostics::DiagnosticsConfig; -pub struct Setting { +pub struct Setting { pub key: &'static str, - pub set: fn(&mut LspConfig, Value), + pub set: fn(&mut T, Value), } -// List of LSP settings for which clients can send `didChangeConfiguration` -// notifications. We register our interest in watching over these settings in -// our `initialized` handler. The `set` methods convert from a json `Value` to -// the expected type, using a default value if the conversion fails. -pub static SETTINGS: &[Setting] = &[ +/// List of LSP settings for which clients can send `didChangeConfiguration` +/// notifications. We register our interest in watching over these settings in +/// our `initialized` handler. The `set` methods convert from a json `Value` to +/// the expected type, using a default value if the conversion fails. +/// +/// This array is for global settings. If the setting should only affect a given +/// document URI, add it to `DOCUMENT_SETTINGS` instead. +pub static GLOBAL_SETTINGS: &[Setting] = &[ + Setting { + key: "positron.r.diagnostics.enable", + set: |cfg, v| { + cfg.diagnostics.enable = v + .as_bool() + .unwrap_or_else(|| DiagnosticsConfig::default().enable) + }, + }, + Setting { + key: "positron.r.symbols.includeAssignmentsInBlocks", + set: |cfg, v| { + cfg.symbols.include_assignments_in_blocks = v + .as_bool() + .unwrap_or_else(|| SymbolsConfig::default().include_assignments_in_blocks) + }, + }, +]; + +/// These document settings are updated on a URI basis. Each document has its +/// own value of the setting. +pub static DOCUMENT_SETTINGS: &[Setting] = &[ Setting { key: "editor.insertSpaces", set: |cfg, v| { let default_style = IndentationConfig::default().indent_style; - cfg.document.indent.indent_style = if v + cfg.indent.indent_style = if v .as_bool() .unwrap_or_else(|| default_style == IndentStyle::Space) { @@ -31,7 +55,7 @@ pub static SETTINGS: &[Setting] = &[ Setting { key: "editor.indentSize", set: |cfg, v| { - cfg.document.indent.indent_size = v + cfg.indent.indent_size = v .as_u64() .map(|n| n as usize) .unwrap_or_else(|| IndentationConfig::default().indent_size) @@ -40,28 +64,12 @@ pub static SETTINGS: &[Setting] = &[ Setting { key: "editor.tabSize", set: |cfg, v| { - cfg.document.indent.tab_width = v + cfg.indent.tab_width = v .as_u64() .map(|n| n as usize) .unwrap_or_else(|| IndentationConfig::default().tab_width) }, }, - Setting { - key: "positron.r.diagnostics.enable", - set: |cfg, v| { - cfg.diagnostics.enable = v - .as_bool() - .unwrap_or_else(|| DiagnosticsConfig::default().enable) - }, - }, - Setting { - key: "positron.r.symbols.includeAssignmentsInBlocks", - set: |cfg, v| { - cfg.symbols.include_assignments_in_blocks = v - .as_bool() - .unwrap_or_else(|| SymbolsConfig::default().include_assignments_in_blocks) - }, - }, ]; /// Configuration of the LSP @@ -69,7 +77,6 @@ pub static SETTINGS: &[Setting] = &[ pub(crate) struct LspConfig { pub(crate) diagnostics: DiagnosticsConfig, pub(crate) symbols: SymbolsConfig, - pub(crate) document: DocumentConfig, } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/crates/ark/src/lsp/handlers.rs b/crates/ark/src/lsp/handlers.rs index cf28007da..64bca86f3 100644 --- a/crates/ark/src/lsp/handlers.rs +++ b/crates/ark/src/lsp/handlers.rs @@ -103,7 +103,14 @@ pub(crate) async fn handle_initialized( // changed by extensions or by the user without changing the actual // underlying setting. Unfortunately we don't receive updates in that case. - for setting in crate::lsp::config::SETTINGS { + for setting in crate::lsp::config::GLOBAL_SETTINGS { + regs.push(Registration { + id: uuid::Uuid::new_v4().to_string(), + method: String::from("workspace/didChangeConfiguration"), + register_options: Some(serde_json::json!({ "section": setting.key })), + }); + } + for setting in crate::lsp::config::DOCUMENT_SETTINGS { regs.push(Registration { id: uuid::Uuid::new_v4().to_string(), method: String::from("workspace/didChangeConfiguration"), diff --git a/crates/ark/src/lsp/state_handlers.rs b/crates/ark/src/lsp/state_handlers.rs index f33dedb8a..0f3b9263e 100644 --- a/crates/ark/src/lsp/state_handlers.rs +++ b/crates/ark/src/lsp/state_handlers.rs @@ -8,6 +8,7 @@ use std::path::Path; use anyhow::anyhow; +use tower_lsp::lsp_types; use tower_lsp::lsp_types::CompletionOptions; use tower_lsp::lsp_types::CompletionOptionsCompletionItem; use tower_lsp::lsp_types::DidChangeConfigurationParams; @@ -39,7 +40,8 @@ use url::Url; use crate::lsp; use crate::lsp::capabilities::Capabilities; use crate::lsp::config::indent_style_from_lsp; -use crate::lsp::config::SETTINGS; +use crate::lsp::config::DOCUMENT_SETTINGS; +use crate::lsp::config::GLOBAL_SETTINGS; use crate::lsp::documents::Document; use crate::lsp::encoding::get_position_encoding_kind; use crate::lsp::indexer; @@ -277,34 +279,76 @@ pub(crate) fn did_change_formatting_options( } async fn update_config( - _uris: Vec, + uris: Vec, client: &tower_lsp::Client, state: &mut WorldState, ) -> anyhow::Result<()> { - // Build the configuration request for global settings - let items: Vec<_> = SETTINGS + // Build the configuration request for global and document settings + let mut items: Vec<_> = vec![]; + + // This should be first because we first handle the global settings below, + // splitting them off the response array + let mut global_items: Vec<_> = GLOBAL_SETTINGS .iter() - .map(|mapping| tower_lsp::lsp_types::ConfigurationItem { + .map(|mapping| lsp_types::ConfigurationItem { scope_uri: None, section: Some(mapping.key.to_string()), }) .collect(); - let configs = client.configuration(items).await?; + // For document items we create a n_uris * n_document_settings array that we'll + // handle by batch in a double loop over URIs and document settings + let mut document_items: Vec<_> = uris + .iter() + .flat_map(|uri| { + DOCUMENT_SETTINGS + .iter() + .map(|mapping| lsp_types::ConfigurationItem { + scope_uri: Some(uri.clone()), + section: Some(mapping.key.to_string()), + }) + }) + .collect(); + + // Concatenate everything into a flat array that we'll send in one request + items.append(&mut global_items); + items.append(&mut document_items); + + // The response better match the number of items we send in + let n_items = items.len(); + + let mut configs = client.configuration(items).await?; - if configs.len() != SETTINGS.len() { + if configs.len() != n_items { return Err(anyhow!( "Unexpected number of retrieved configurations: {}/{}", configs.len(), - SETTINGS.len() + n_items )); } - // Apply each config value using its update closure - for (mapping, value) in SETTINGS.iter().zip(configs) { + let document_configs = configs.split_off(GLOBAL_SETTINGS.len()); + let global_configs = configs; + + for (mapping, value) in GLOBAL_SETTINGS.into_iter().zip(global_configs) { (mapping.set)(&mut state.config, value); } + let mut remaining = document_configs; + + for uri in uris.into_iter() { + // Need to juggle a bit because `split_off()` returns the tail of the + // split and updates the vector with the head + let tail = remaining.split_off(DOCUMENT_SETTINGS.len()); + let head = std::mem::replace(&mut remaining, tail); + + for (mapping, value) in DOCUMENT_SETTINGS.iter().zip(head) { + if let Ok(doc) = state.get_document_mut(&uri) { + (mapping.set)(&mut doc.config, value); + } + } + } + Ok(()) } From 06187c16a30916c040d7d788efaa432c7354ab73 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 22 Jul 2025 11:34:16 +0200 Subject: [PATCH 5/6] Remove dangling types --- crates/ark/src/lsp/config.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/crates/ark/src/lsp/config.rs b/crates/ark/src/lsp/config.rs index 06b70a367..0628ec8c2 100644 --- a/crates/ark/src/lsp/config.rs +++ b/crates/ark/src/lsp/config.rs @@ -112,34 +112,6 @@ pub enum IndentStyle { Space, } -/// VS Code representation of a document configuration -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(crate) struct VscDocumentConfig { - // DEV NOTE: Update `section_from_key()` method after adding a field - pub insert_spaces: bool, - pub indent_size: VscIndentSize, - pub tab_size: usize, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(crate) struct VscDiagnosticsConfig { - // DEV NOTE: Update `section_from_key()` method after adding a field - pub enable: bool, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub(crate) struct VscSymbolsConfig { - // DEV NOTE: Update `section_from_key()` method after adding a field - pub include_assignments_in_blocks: bool, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(untagged)] -pub(crate) enum VscIndentSize { - Alias(String), - Size(usize), -} - impl Default for SymbolsConfig { fn default() -> Self { Self { From eccee9211502843139b19416f6bb2c73017cf01c Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 22 Jul 2025 11:39:20 +0200 Subject: [PATCH 6/6] Refresh diagnostics if config changed --- crates/ark/src/lsp/state_handlers.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/ark/src/lsp/state_handlers.rs b/crates/ark/src/lsp/state_handlers.rs index 0f3b9263e..2fb24ac55 100644 --- a/crates/ark/src/lsp/state_handlers.rs +++ b/crates/ark/src/lsp/state_handlers.rs @@ -283,6 +283,9 @@ async fn update_config( client: &tower_lsp::Client, state: &mut WorldState, ) -> anyhow::Result<()> { + // Keep track of existing config to detect whether it was changed + let diagnostics_config = state.config.diagnostics.clone(); + // Build the configuration request for global and document settings let mut items: Vec<_> = vec![]; @@ -349,6 +352,12 @@ async fn update_config( } } + // Refresh diagnostics if the configuration changed + if state.config.diagnostics != diagnostics_config { + tracing::info!("Refreshing diagnostics after configuration changed"); + lsp::spawn_diagnostics_refresh_all(state.clone()); + } + Ok(()) }