diff --git a/crates/ruff_server/src/server/api/requests/execute_command.rs b/crates/ruff_server/src/server/api/requests/execute_command.rs index 1cf06cf1024b0..570b16cba012b 100644 --- a/crates/ruff_server/src/server/api/requests/execute_command.rs +++ b/crates/ruff_server/src/server/api/requests/execute_command.rs @@ -11,8 +11,9 @@ use lsp_server::ErrorCode; use lsp_types::{self as types, request as req}; use serde::Deserialize; -#[derive(Debug)] +#[derive(Debug, PartialEq)] enum Command { + Debug, Format, FixAll, OrganizeImports, @@ -33,13 +34,24 @@ impl super::RequestHandler for ExecuteCommand { impl super::SyncRequestHandler for ExecuteCommand { fn run( session: &mut Session, - _notifier: client::Notifier, + notifier: client::Notifier, requester: &mut client::Requester, params: types::ExecuteCommandParams, ) -> server::Result> { let command = Command::from_str(¶ms.command).with_failure_code(ErrorCode::InvalidParams)?; + if command == Command::Debug { + let output = debug_information(session); + notifier + .notify::(types::LogMessageParams { + message: output, + typ: types::MessageType::INFO, + }) + .with_failure_code(ErrorCode::InternalError)?; + return Ok(None); + } + // check if we can apply a workspace edit if !session.resolved_client_capabilities().apply_edit { return Err(anyhow::anyhow!("Cannot execute the '{}' command: the client does not support `workspace/applyEdit`", command.label())).with_failure_code(ErrorCode::InternalError); @@ -87,6 +99,9 @@ impl super::SyncRequestHandler for ExecuteCommand { .set_fixes_for_document(fixes, snapshot.query().version()) .with_failure_code(ErrorCode::InternalError)?; } + Command::Debug => { + unreachable!("The debug command should have already been handled") + } } } @@ -109,6 +124,7 @@ impl Command { Self::FixAll => "Fix all auto-fixable problems", Self::Format => "Format document", Self::OrganizeImports => "Format imports", + Self::Debug => "Print debug information", } } } @@ -121,6 +137,7 @@ impl FromStr for Command { "ruff.applyAutofix" => Self::FixAll, "ruff.applyFormat" => Self::Format, "ruff.applyOrganizeImports" => Self::OrganizeImports, + "ruff.printDebugInformation" => Self::Debug, _ => return Err(anyhow::anyhow!("Invalid command `{name}`")), }) } @@ -148,3 +165,25 @@ fn apply_edit( }, ) } + +fn debug_information(session: &Session) -> String { + let executable = std::env::current_exe() + .map(|path| format!("{}", path.display())) + .unwrap_or_else(|_| "".to_string()); + format!( + r#"executable = {executable} +version = {version} +encoding = {encoding:?} +open_document_count = {doc_count} +active_workspace_count = {workspace_count} +configuration_files = {config_files:?} +{client_capabilities} + "#, + version = crate::version(), + encoding = session.encoding(), + client_capabilities = session.resolved_client_capabilities(), + doc_count = session.num_documents(), + workspace_count = session.num_workspaces(), + config_files = session.list_config_files() + ) +} diff --git a/crates/ruff_server/src/session.rs b/crates/ruff_server/src/session.rs index d1327bb8e3a48..a6072fb6c1f59 100644 --- a/crates/ruff_server/src/session.rs +++ b/crates/ruff_server/src/session.rs @@ -145,6 +145,18 @@ impl Session { Ok(()) } + pub(crate) fn num_documents(&self) -> usize { + self.index.num_documents() + } + + pub(crate) fn num_workspaces(&self) -> usize { + self.index.num_workspaces() + } + + pub(crate) fn list_config_files(&self) -> Vec<&std::path::Path> { + self.index.list_config_files() + } + pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities { &self.resolved_client_capabilities } diff --git a/crates/ruff_server/src/session/capabilities.rs b/crates/ruff_server/src/session/capabilities.rs index 27d5d09ce7adc..001931f9e8bae 100644 --- a/crates/ruff_server/src/session/capabilities.rs +++ b/crates/ruff_server/src/session/capabilities.rs @@ -1,4 +1,5 @@ use lsp_types::ClientCapabilities; +use ruff_linter::display_settings; #[derive(Debug, Clone, PartialEq, Eq, Default)] #[allow(clippy::struct_excessive_bools)] @@ -65,3 +66,20 @@ impl ResolvedClientCapabilities { } } } + +impl std::fmt::Display for ResolvedClientCapabilities { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + display_settings! { + formatter = f, + namespace = "capabilities", + fields = [ + self.code_action_deferred_edit_resolution, + self.apply_edit, + self.document_changes, + self.workspace_refresh, + self.pull_diagnostics, + ] + }; + Ok(()) + } +} diff --git a/crates/ruff_server/src/session/index.rs b/crates/ruff_server/src/session/index.rs index 2b68e282d3558..6d60f148953a6 100644 --- a/crates/ruff_server/src/session/index.rs +++ b/crates/ruff_server/src/session/index.rs @@ -187,6 +187,21 @@ impl Index { Self::register_workspace_settings(&mut self.settings, url, None, global_settings) } + pub(super) fn num_documents(&self) -> usize { + self.documents.len() + } + + pub(super) fn num_workspaces(&self) -> usize { + self.settings.len() + } + + pub(super) fn list_config_files(&self) -> Vec<&Path> { + self.settings + .values() + .flat_map(|WorkspaceSettings { ruff_settings, .. }| ruff_settings.list_files()) + .collect() + } + fn register_workspace_settings( settings_index: &mut SettingsIndex, workspace_url: &Url, diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index 1939a91aca3c0..abb02a463e606 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -19,6 +19,9 @@ use walkdir::WalkDir; use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings}; pub(crate) struct RuffSettings { + /// The path to this configuration file, used for debugging. + /// The default fallback configuration does not have a file path. + path: Option, /// Settings used to manage file inclusion and exclusion. file_resolver: ruff_workspace::FileResolverSettings, /// Settings to pass into the Ruff linter. @@ -48,14 +51,17 @@ impl std::fmt::Display for RuffSettings { impl RuffSettings { pub(crate) fn fallback(editor_settings: &ResolvedEditorSettings, root: &Path) -> RuffSettings { + let mut path = None; let fallback = find_user_settings_toml() .and_then(|user_settings| { - ruff_workspace::resolver::resolve_root_settings( + let settings = ruff_workspace::resolver::resolve_root_settings( &user_settings, Relativity::Cwd, &EditorConfigurationTransformer(editor_settings, root), ) - .ok() + .ok(); + path = Some(user_settings); + settings }) .unwrap_or_else(|| { let default_configuration = Configuration::default(); @@ -68,6 +74,7 @@ impl RuffSettings { }); RuffSettings { + path, file_resolver: fallback.file_resolver, formatter: fallback.formatter, linter: fallback.linter, @@ -108,6 +115,7 @@ impl RuffSettingsIndex { index.insert( directory.to_path_buf(), Arc::new(RuffSettings { + path: Some(pyproject), file_resolver: settings.file_resolver, linter: settings.linter, formatter: settings.formatter, @@ -176,6 +184,7 @@ impl RuffSettingsIndex { index.insert( directory, Arc::new(RuffSettings { + path: Some(pyproject), file_resolver: settings.file_resolver, linter: settings.linter, formatter: settings.formatter, @@ -198,6 +207,12 @@ impl RuffSettingsIndex { .clone() } + pub(crate) fn list_files(&self) -> impl Iterator { + self.index + .values() + .filter_map(|settings| settings.path.as_deref()) + } + pub(super) fn fallback(&self) -> Arc { self.fallback.clone() }