diff --git a/crates/emmylua_ls/src/context/mod.rs b/crates/emmylua_ls/src/context/mod.rs index 28caee900..bd1850e1d 100644 --- a/crates/emmylua_ls/src/context/mod.rs +++ b/crates/emmylua_ls/src/context/mod.rs @@ -10,6 +10,7 @@ pub use client_id::{ClientId, get_client_id}; use emmylua_code_analysis::EmmyLuaAnalysis; pub use file_diagnostic::FileDiagnostic; use lsp_server::{Connection, ErrorCode, Message, RequestId, Response}; +use lsp_types::ClientCapabilities; pub use snapshot::ServerContextSnapshot; pub use status_bar::ProgressTask; pub use status_bar::StatusBar; @@ -29,10 +30,11 @@ pub struct ServerContext { file_diagnostic: Arc, workspace_manager: Arc>, status_bar: Arc, + client_capabilities: Arc, } impl ServerContext { - pub fn new(conn: Connection) -> Self { + pub fn new(conn: Connection, client_capabilities: Arc) -> Self { let client = Arc::new(ClientProxy::new(Connection { sender: conn.sender.clone(), receiver: conn.receiver.clone(), @@ -60,6 +62,7 @@ impl ServerContext { cancllations: Arc::new(Mutex::new(HashMap::new())), workspace_manager, status_bar, + client_capabilities, } } @@ -70,6 +73,7 @@ impl ServerContext { file_diagnostic: self.file_diagnostic.clone(), workspace_manager: self.workspace_manager.clone(), status_bar: self.status_bar.clone(), + client_capabilities: self.client_capabilities.clone(), } } diff --git a/crates/emmylua_ls/src/context/snapshot.rs b/crates/emmylua_ls/src/context/snapshot.rs index 53120e6ca..528fe994e 100644 --- a/crates/emmylua_ls/src/context/snapshot.rs +++ b/crates/emmylua_ls/src/context/snapshot.rs @@ -1,3 +1,4 @@ +use lsp_types::ClientCapabilities; use std::sync::Arc; use tokio::sync::RwLock; @@ -15,4 +16,5 @@ pub struct ServerContextSnapshot { pub file_diagnostic: Arc, pub workspace_manager: Arc>, pub status_bar: Arc, + pub client_capabilities: Arc, } diff --git a/crates/emmylua_ls/src/handlers/configuration/mod.rs b/crates/emmylua_ls/src/handlers/configuration/mod.rs index c641548eb..2c26cde88 100644 --- a/crates/emmylua_ls/src/handlers/configuration/mod.rs +++ b/crates/emmylua_ls/src/handlers/configuration/mod.rs @@ -22,8 +22,15 @@ pub async fn on_did_change_configuration( let client_id = workspace_manager.client_config.client_id; drop(workspace_manager); + let supports_config_request = context + .client_capabilities + .workspace + .as_ref()? + .configuration + .unwrap_or_default(); + log::info!("change config client_id: {:?}", client_id); - let new_client_config = get_client_config(&context, client_id).await; + let new_client_config = get_client_config(&context, client_id, supports_config_request).await; let mut config_manager = context.workspace_manager.write().await; config_manager.client_config = new_client_config; diff --git a/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs b/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs new file mode 100644 index 000000000..5dd71d6ec --- /dev/null +++ b/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs @@ -0,0 +1,82 @@ +use std::time::Duration; + +use log::info; +use serde_json::Value; + +use crate::{context::ServerContextSnapshot, util::time_cancel_token}; +use emmylua_code_analysis::file_path_to_uri; + +use super::ClientConfig; + +pub async fn get_client_config_default( + context: &ServerContextSnapshot, + config: &mut ClientConfig, + scopes: Option<&[&str]>, +) -> Option<()> { + let workspace_folders = context + .workspace_manager + .read() + .await + .workspace_folders + .clone(); + let main_workspace_folder = workspace_folders.get(0); + let client = &context.client; + let scope_uri = main_workspace_folder.map(|p| file_path_to_uri(p).unwrap()); + + let mut configs = Vec::new(); + let mut used_scope = None; + for scope in scopes.unwrap_or(&["emmylua"]) { + let params = lsp_types::ConfigurationParams { + items: vec![lsp_types::ConfigurationItem { + scope_uri: scope_uri.clone(), + section: Some(scope.to_string()), + }], + }; + let cancel_token = time_cancel_token(Duration::from_secs(5)); + let fetched_configs: Vec<_> = client + .get_configuration::(params, cancel_token) + .await? + .into_iter() + .filter(|config| !config.is_null()) + .collect(); + if !fetched_configs.is_empty() { + info!("found client config in scope {scope:?}"); + configs = fetched_configs; + used_scope = Some(scope.to_string()); + } + } + + if let Some(used_scope) = used_scope { + info!( + "using client config from scope {used_scope:?}: {}", + serde_json::to_string_pretty(&configs) + .as_deref() + .unwrap_or("") + ); + } else { + info!("no client config found"); + } + + for config in &mut configs { + // VSCode always sends default values for all options, even those that weren't + // explicitly configured by user. This results in `null`s being sent for + // every option. Naturally, serde chokes on these nulls when applying partial + // configuration. + // + // Because of this, we have to ignore them here. + skip_nulls(config); + } + + config.partial_emmyrcs = Some(configs); + + Some(()) +} + +fn skip_nulls(v: &mut Value) { + if let Value::Object(obj) = v { + obj.retain(|_, v| !v.is_null()); + for (_, v) in obj { + skip_nulls(v); + } + } +} diff --git a/crates/emmylua_ls/src/handlers/initialized/client_config/mod.rs b/crates/emmylua_ls/src/handlers/initialized/client_config/mod.rs index 3c53b2fce..db7f8adf8 100644 --- a/crates/emmylua_ls/src/handlers/initialized/client_config/mod.rs +++ b/crates/emmylua_ls/src/handlers/initialized/client_config/mod.rs @@ -1,7 +1,7 @@ -mod neovim_config; +mod default_config; mod vscode_config; -use neovim_config::get_client_config_neovim; +use default_config::get_client_config_default; use serde_json::Value; use vscode_config::get_client_config_vscode; @@ -20,6 +20,7 @@ pub struct ClientConfig { pub async fn get_client_config( context: &ServerContextSnapshot, client_id: ClientId, + supports_config_request: bool, ) -> ClientConfig { let mut config = ClientConfig { client_id, @@ -29,9 +30,16 @@ pub async fn get_client_config( partial_emmyrcs: None, }; match client_id { - ClientId::VSCode => get_client_config_vscode(context, &mut config).await, - ClientId::Neovim => get_client_config_neovim(context, &mut config).await, - _ => Some(()), + ClientId::VSCode => { + get_client_config_vscode(context, &mut config).await; + } + ClientId::Neovim => { + get_client_config_default(context, &mut config, Some(&["Lua", "emmylua"])).await; + } + _ if supports_config_request => { + get_client_config_default(context, &mut config, None).await; + } + _ => {} }; config diff --git a/crates/emmylua_ls/src/handlers/initialized/client_config/neovim_config.rs b/crates/emmylua_ls/src/handlers/initialized/client_config/neovim_config.rs deleted file mode 100644 index 504dd86bd..000000000 --- a/crates/emmylua_ls/src/handlers/initialized/client_config/neovim_config.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::time::Duration; - -use log::info; -use serde_json::Value; - -use crate::{context::ServerContextSnapshot, util::time_cancel_token}; -use emmylua_code_analysis::file_path_to_uri; - -use super::ClientConfig; - -pub async fn get_client_config_neovim( - context: &ServerContextSnapshot, - config: &mut ClientConfig, -) -> Option<()> { - let workspace_folders = context - .workspace_manager - .read() - .await - .workspace_folders - .clone(); - - let main_workspace_folder = workspace_folders.get(0)?; - let client = &context.client; - let scope_uri = file_path_to_uri(main_workspace_folder); - let params = lsp_types::ConfigurationParams { - items: vec![lsp_types::ConfigurationItem { - scope_uri: scope_uri, - section: Some("Lua".to_string()), - }], - }; - let cancel_token = time_cancel_token(Duration::from_secs(5)); - let configs = client - .get_configuration::(params, cancel_token) - .await? - .into_iter() - .filter(|config| !config.is_null()) - .collect(); - - if let Some(pretty_json) = serde_json::to_string_pretty(&configs).ok() { - info!("load neovim client config: {}", pretty_json); - } else { - info!("not found neovim client config"); - } - - config.partial_emmyrcs = Some(configs); - Some(()) -} diff --git a/crates/emmylua_ls/src/handlers/initialized/client_config/vscode_config.rs b/crates/emmylua_ls/src/handlers/initialized/client_config/vscode_config.rs index 4a1bd761d..da8e4af3c 100644 --- a/crates/emmylua_ls/src/handlers/initialized/client_config/vscode_config.rs +++ b/crates/emmylua_ls/src/handlers/initialized/client_config/vscode_config.rs @@ -1,10 +1,8 @@ -use std::{collections::HashMap, time::Duration}; - -use serde::{Deserialize, Serialize}; - -use crate::{context::ServerContextSnapshot, util::time_cancel_token}; - use super::ClientConfig; +use crate::handlers::initialized::client_config::default_config::get_client_config_default; +use crate::{context::ServerContextSnapshot, util::time_cancel_token}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, time::Duration}; #[derive(Debug, Deserialize, Serialize)] struct VscodeFilesConfig { @@ -17,6 +15,8 @@ pub async fn get_client_config_vscode( context: &ServerContextSnapshot, config: &mut ClientConfig, ) -> Option<()> { + get_client_config_default(context, config, None).await; + let client = &context.client; let params = lsp_types::ConfigurationParams { items: vec![lsp_types::ConfigurationItem { diff --git a/crates/emmylua_ls/src/handlers/initialized/mod.rs b/crates/emmylua_ls/src/handlers/initialized/mod.rs index 7fd5cd58c..e879a4955 100644 --- a/crates/emmylua_ls/src/handlers/initialized/mod.rs +++ b/crates/emmylua_ls/src/handlers/initialized/mod.rs @@ -42,6 +42,12 @@ pub async fn initialized_handler( log::info!("main root: {:?}", main_root); let client_id = get_client_id(¶ms.client_info); + let supports_config_request = params + .capabilities + .workspace + .as_ref()? + .configuration + .unwrap_or_default(); log::info!("client_id: {:?}", client_id); { @@ -51,7 +57,7 @@ pub async fn initialized_handler( log::info!("workspace folders set"); } - let client_config = get_client_config(&context, client_id).await; + let client_config = get_client_config(&context, client_id, supports_config_request).await; log::info!("client_config: {:?}", client_config); let params_json = serde_json::to_string_pretty(¶ms).unwrap(); diff --git a/crates/emmylua_ls/src/lib.rs b/crates/emmylua_ls/src/lib.rs index d224d7a19..f43db09a0 100644 --- a/crates/emmylua_ls/src/lib.rs +++ b/crates/emmylua_ls/src/lib.rs @@ -5,17 +5,17 @@ mod logger; mod meta_text; mod util; +use crate::handlers::{ + initialized_handler, on_notification_handler, on_req_handler, on_response_handler, +}; pub use clap::Parser; pub use cmd_args::*; use handlers::server_capabilities; use lsp_server::{Connection, Message}; use lsp_types::InitializeParams; +use std::sync::Arc; use std::{env, error::Error}; -use crate::handlers::{ - initialized_handler, on_notification_handler, on_req_handler, on_response_handler, -}; - #[macro_use] extern crate rust_i18n; rust_i18n::i18n!("./locales", fallback = "en"); @@ -60,10 +60,13 @@ async fn main_loop( params: InitializeParams, cmd_args: CmdArgs, ) -> Result<(), Box> { - let mut server_context = context::ServerContext::new(Connection { - sender: connection.sender.clone(), - receiver: connection.receiver.clone(), - }); + let mut server_context = context::ServerContext::new( + Connection { + sender: connection.sender.clone(), + receiver: connection.receiver.clone(), + }, + Arc::new(params.capabilities.clone()), + ); let server_context_snapshot = server_context.snapshot(); tokio::spawn(async move {