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 db66417dc0..be4801e099 100644 --- a/crates/ruff_server/src/server/api/requests/execute_command.rs +++ b/crates/ruff_server/src/server/api/requests/execute_command.rs @@ -1,3 +1,4 @@ +use std::fmt::Write; use std::str::FromStr; use crate::edit::WorkspaceEditTracker; @@ -5,10 +6,10 @@ use crate::server::api::LSPResult; use crate::server::schedule::Task; use crate::server::{client, SupportedCommand}; use crate::session::Session; -use crate::DIAGNOSTIC_NAME; use crate::{edit::DocumentVersion, server}; +use crate::{DocumentKey, DIAGNOSTIC_NAME}; use lsp_server::ErrorCode; -use lsp_types::{self as types, request as req}; +use lsp_types::{self as types, request as req, TextDocumentIdentifier}; use serde::Deserialize; pub(crate) struct ExecuteCommand; @@ -19,6 +20,17 @@ struct Argument { version: DocumentVersion, } +/// The argument schema for the `ruff.printDebugInformation` command. +#[derive(Default, Deserialize)] +#[serde(rename_all = "camelCase")] +struct DebugCommandArgument { + /// The URI of the document to print debug information for. + /// + /// When provided, both document-specific debug information and global information are printed. + /// If not provided ([None]), only global debug information is printed. + text_document: Option, +} + impl super::RequestHandler for ExecuteCommand { type RequestType = req::ExecuteCommand; } @@ -34,7 +46,12 @@ impl super::SyncRequestHandler for ExecuteCommand { .with_failure_code(ErrorCode::InvalidParams)?; if command == SupportedCommand::Debug { - let output = debug_information(session); + let argument: DebugCommandArgument = params.arguments.into_iter().next().map_or_else( + || Ok(DebugCommandArgument::default()), + |value| serde_json::from_value(value).with_failure_code(ErrorCode::InvalidParams), + )?; + let output = debug_information(session, argument.text_document) + .with_failure_code(ErrorCode::InternalError)?; notifier .notify::(types::LogMessageParams { message: output.clone(), @@ -134,23 +151,71 @@ fn apply_edit( ) } -fn debug_information(session: &Session) -> String { +/// Returns a string with debug information about the session and the document at the given URI. +fn debug_information( + session: &Session, + text_document: Option, +) -> crate::Result { let executable = std::env::current_exe() .map(|path| format!("{}", path.display())) .unwrap_or_else(|_| "".to_string()); - format!( - "executable = {executable} + + let mut buffer = String::new(); + + writeln!( + buffer, + "Global: +executable = {executable} version = {version} -encoding = {encoding:?} -open_document_count = {doc_count} -active_workspace_count = {workspace_count} -configuration_files = {config_files:?} -{client_capabilities}", +position_encoding = {encoding:?} +workspace_root_folders = {workspace_folders:#?} +indexed_configuration_files = {config_files:#?} +open_documents_len = {open_documents_len} +client_capabilities = {client_capabilities:#?} +", version = crate::version(), encoding = session.encoding(), + workspace_folders = session.workspace_root_folders().collect::>(), + config_files = session.config_file_paths().collect::>(), + open_documents_len = session.open_documents_len(), client_capabilities = session.resolved_client_capabilities(), - doc_count = session.num_documents(), - workspace_count = session.num_workspaces(), - config_files = session.list_config_files() - ) + )?; + + if let Some(TextDocumentIdentifier { uri }) = text_document { + let Some(snapshot) = session.take_snapshot(uri.clone()) else { + writeln!(buffer, "Unable to take a snapshot of the document at {uri}")?; + return Ok(buffer); + }; + let query = snapshot.query(); + + writeln!( + buffer, + "Open document: +uri = {uri} +kind = {kind} +version = {version} +client_settings = {client_settings:#?} +config_path = {config_path:?} +{settings} + ", + uri = uri.clone(), + kind = match session.key_from_url(uri) { + DocumentKey::Notebook(_) => "Notebook", + DocumentKey::NotebookCell(_) => "NotebookCell", + DocumentKey::Text(_) => "Text", + }, + version = query.version(), + client_settings = snapshot.client_settings(), + config_path = query.settings().path(), + settings = query.settings(), + )?; + } else { + writeln!( + buffer, + "global_client_settings = {:#?}", + session.global_client_settings() + )?; + } + + Ok(buffer) } diff --git a/crates/ruff_server/src/session.rs b/crates/ruff_server/src/session.rs index 743fae4ecf..d79502dba9 100644 --- a/crates/ruff_server/src/session.rs +++ b/crates/ruff_server/src/session.rs @@ -1,8 +1,10 @@ //! Data model, state management, and configuration resolution. +use std::path::Path; use std::sync::Arc; use lsp_types::{ClientCapabilities, FileEvent, NotebookDocumentCellChange, Url}; +use settings::ResolvedClientSettings; use crate::edit::{DocumentKey, DocumentVersion, NotebookDocument}; use crate::server::Workspaces; @@ -147,18 +149,6 @@ 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 } @@ -166,6 +156,26 @@ impl Session { pub(crate) fn encoding(&self) -> PositionEncoding { self.position_encoding } + + /// Returns an iterator over the paths to the configuration files in the index. + pub(crate) fn config_file_paths(&self) -> impl Iterator { + self.index.config_file_paths() + } + + /// Returns the resolved global client settings. + pub(crate) fn global_client_settings(&self) -> ResolvedClientSettings { + ResolvedClientSettings::global(&self.global_settings) + } + + /// Returns the number of open documents in the session. + pub(crate) fn open_documents_len(&self) -> usize { + self.index.open_documents_len() + } + + /// Returns an iterator over the workspace root folders in the session. + pub(crate) fn workspace_root_folders(&self) -> impl Iterator { + self.index.workspace_root_folders() + } } impl DocumentSnapshot { diff --git a/crates/ruff_server/src/session/index.rs b/crates/ruff_server/src/session/index.rs index 4067d6be18..3ca1ac5ba2 100644 --- a/crates/ruff_server/src/session/index.rs +++ b/crates/ruff_server/src/session/index.rs @@ -177,21 +177,6 @@ impl Index { .register_workspace(&Workspace::new(url), 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() - } - pub(super) fn close_workspace_folder(&mut self, workspace_url: &Url) -> crate::Result<()> { let workspace_path = workspace_url.to_file_path().map_err(|()| { anyhow!("Failed to convert workspace URL to file path: {workspace_url}") @@ -404,6 +389,23 @@ impl Index { .next_back() .map(|(_, settings)| settings) } + + /// Returns an iterator over the workspace root folders contained in this index. + pub(super) fn workspace_root_folders(&self) -> impl Iterator { + self.settings.keys().map(PathBuf::as_path) + } + + /// Returns the number of open documents. + pub(super) fn open_documents_len(&self) -> usize { + self.documents.len() + } + + /// Returns an iterator over the paths to the configuration files in the index. + pub(super) fn config_file_paths(&self) -> impl Iterator { + self.settings + .values() + .flat_map(|WorkspaceSettings { ruff_settings, .. }| ruff_settings.config_file_paths()) + } } /// Maps a workspace folder root to its settings. diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index 5edc1c97de..a9962c5fa7 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -20,6 +20,7 @@ use ruff_workspace::{ use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings}; +#[derive(Debug)] pub struct RuffSettings { /// The path to this configuration file, used for debugging. /// The default fallback configuration does not have a file path. @@ -28,6 +29,12 @@ pub struct RuffSettings { settings: Settings, } +impl RuffSettings { + pub(crate) fn path(&self) -> Option<&Path> { + self.path.as_deref() + } +} + impl Deref for RuffSettings { type Target = Settings; @@ -298,15 +305,16 @@ impl RuffSettingsIndex { .clone() } - pub(crate) fn list_files(&self) -> impl Iterator { + pub(super) fn fallback(&self) -> Arc { + self.fallback.clone() + } + + /// Returns an iterator over the paths to the configuration files in the index. + pub(crate) fn config_file_paths(&self) -> impl Iterator { self.index .values() .filter_map(|settings| settings.path.as_deref()) } - - pub(super) fn fallback(&self) -> Arc { - self.fallback.clone() - } } struct EditorConfigurationTransformer<'a>(&'a ResolvedEditorSettings, &'a Path);