Skip to content

Commit eb35460

Browse files
[ty] Add LSP debug information command (#20379)
Co-authored-by: Micha Reiser <[email protected]>
1 parent 12086df commit eb35460

File tree

10 files changed

+458
-1
lines changed

10 files changed

+458
-1
lines changed

crates/ty_python_semantic/src/lint.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ impl From<&'static LintMetadata> for LintEntry {
463463
}
464464
}
465465

466-
#[derive(Debug, Clone, Default, PartialEq, Eq, get_size2::GetSize)]
466+
#[derive(Clone, Default, PartialEq, Eq, get_size2::GetSize)]
467467
pub struct RuleSelection {
468468
/// Map with the severity for each enabled lint rule.
469469
///
@@ -541,6 +541,35 @@ impl RuleSelection {
541541
}
542542
}
543543

544+
// The default `LintId` debug implementation prints the entire lint metadata.
545+
// This is way too verbose.
546+
impl fmt::Debug for RuleSelection {
547+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
548+
let lints = self.lints.iter().sorted_by_key(|(lint, _)| lint.name);
549+
550+
if f.alternate() {
551+
let mut f = f.debug_map();
552+
553+
for (lint, (severity, source)) in lints {
554+
f.entry(
555+
&lint.name().as_str(),
556+
&format_args!("{severity:?} ({source:?})"),
557+
);
558+
}
559+
560+
f.finish()
561+
} else {
562+
let mut f = f.debug_set();
563+
564+
for (lint, _) in lints {
565+
f.entry(&lint.name());
566+
}
567+
568+
f.finish()
569+
}
570+
}
571+
}
572+
544573
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
545574
pub enum LintSource {
546575
/// The user didn't enable the rule explicitly, instead it's enabled by default.

crates/ty_server/src/capabilities.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use lsp_types::{
1010

1111
use crate::PositionEncoding;
1212
use crate::session::GlobalSettings;
13+
use lsp_types as types;
14+
use std::str::FromStr;
1315

1416
bitflags::bitflags! {
1517
/// Represents the resolved client capabilities for the language server.
@@ -37,6 +39,36 @@ bitflags::bitflags! {
3739
}
3840
}
3941

42+
#[derive(Clone, Copy, Debug, PartialEq)]
43+
pub(crate) enum SupportedCommand {
44+
Debug,
45+
}
46+
47+
impl SupportedCommand {
48+
/// Returns the identifier of the command.
49+
const fn identifier(self) -> &'static str {
50+
match self {
51+
SupportedCommand::Debug => "ty.printDebugInformation",
52+
}
53+
}
54+
55+
/// Returns all the commands that the server currently supports.
56+
const fn all() -> [SupportedCommand; 1] {
57+
[SupportedCommand::Debug]
58+
}
59+
}
60+
61+
impl FromStr for SupportedCommand {
62+
type Err = anyhow::Error;
63+
64+
fn from_str(name: &str) -> anyhow::Result<Self, Self::Err> {
65+
Ok(match name {
66+
"ty.printDebugInformation" => Self::Debug,
67+
_ => return Err(anyhow::anyhow!("Invalid command `{name}`")),
68+
})
69+
}
70+
}
71+
4072
impl ResolvedClientCapabilities {
4173
/// Returns `true` if the client supports workspace diagnostic refresh.
4274
pub(crate) const fn supports_workspace_diagnostic_refresh(self) -> bool {
@@ -319,6 +351,15 @@ pub(crate) fn server_capabilities(
319351

320352
ServerCapabilities {
321353
position_encoding: Some(position_encoding.into()),
354+
execute_command_provider: Some(types::ExecuteCommandOptions {
355+
commands: SupportedCommand::all()
356+
.map(|command| command.identifier().to_string())
357+
.to_vec(),
358+
work_done_progress_options: WorkDoneProgressOptions {
359+
work_done_progress: Some(false),
360+
},
361+
}),
362+
322363
diagnostic_provider,
323364
text_document_sync: Some(TextDocumentSyncCapability::Options(
324365
TextDocumentSyncOptions {

crates/ty_server/src/server/api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub(super) fn request(req: server::Request) -> Task {
3232
let id = req.id.clone();
3333

3434
match req.method.as_str() {
35+
requests::ExecuteCommand::METHOD => sync_request_task::<requests::ExecuteCommand>(req),
3536
requests::DocumentDiagnosticRequestHandler::METHOD => background_document_request_task::<
3637
requests::DocumentDiagnosticRequestHandler,
3738
>(

crates/ty_server/src/server/api/requests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod completion;
22
mod diagnostic;
33
mod doc_highlights;
44
mod document_symbols;
5+
mod execute_command;
56
mod goto_declaration;
67
mod goto_definition;
78
mod goto_references;
@@ -22,6 +23,7 @@ pub(super) use completion::CompletionRequestHandler;
2223
pub(super) use diagnostic::DocumentDiagnosticRequestHandler;
2324
pub(super) use doc_highlights::DocumentHighlightRequestHandler;
2425
pub(super) use document_symbols::DocumentSymbolRequestHandler;
26+
pub(super) use execute_command::ExecuteCommand;
2527
pub(super) use goto_declaration::GotoDeclarationRequestHandler;
2628
pub(super) use goto_definition::GotoDefinitionRequestHandler;
2729
pub(super) use goto_references::ReferencesRequestHandler;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use crate::capabilities::SupportedCommand;
2+
use crate::server;
3+
use crate::server::api::LSPResult;
4+
use crate::server::api::RequestHandler;
5+
use crate::server::api::traits::SyncRequestHandler;
6+
use crate::session::Session;
7+
use crate::session::client::Client;
8+
use lsp_server::ErrorCode;
9+
use lsp_types::{self as types, request as req};
10+
use std::fmt::Write;
11+
use std::str::FromStr;
12+
use ty_project::Db;
13+
14+
pub(crate) struct ExecuteCommand;
15+
16+
impl RequestHandler for ExecuteCommand {
17+
type RequestType = req::ExecuteCommand;
18+
}
19+
20+
impl SyncRequestHandler for ExecuteCommand {
21+
fn run(
22+
session: &mut Session,
23+
_client: &Client,
24+
params: types::ExecuteCommandParams,
25+
) -> server::Result<Option<serde_json::Value>> {
26+
let command = SupportedCommand::from_str(&params.command)
27+
.with_failure_code(ErrorCode::InvalidParams)?;
28+
29+
match command {
30+
SupportedCommand::Debug => Ok(Some(serde_json::Value::String(
31+
debug_information(session).with_failure_code(ErrorCode::InternalError)?,
32+
))),
33+
}
34+
}
35+
}
36+
37+
/// Returns a string with detailed memory usage.
38+
fn debug_information(session: &Session) -> crate::Result<String> {
39+
let mut buffer = String::new();
40+
41+
writeln!(
42+
buffer,
43+
"Client capabilities: {:#?}",
44+
session.client_capabilities()
45+
)?;
46+
writeln!(
47+
buffer,
48+
"Position encoding: {:#?}",
49+
session.position_encoding()
50+
)?;
51+
writeln!(buffer, "Global settings: {:#?}", session.global_settings())?;
52+
writeln!(
53+
buffer,
54+
"Open text documents: {}",
55+
session.text_document_keys().count()
56+
)?;
57+
writeln!(buffer)?;
58+
59+
for (root, workspace) in session.workspaces() {
60+
writeln!(buffer, "Workspace {root} ({})", workspace.url())?;
61+
writeln!(buffer, "Settings: {:#?}", workspace.settings())?;
62+
writeln!(buffer)?;
63+
}
64+
65+
for db in session.project_dbs() {
66+
writeln!(buffer, "Project at {}", db.project().root(db))?;
67+
writeln!(buffer, "Settings: {:#?}", db.project().settings(db))?;
68+
writeln!(buffer)?;
69+
writeln!(
70+
buffer,
71+
"Memory report:\n{}",
72+
db.salsa_memory_dump().display_full()
73+
)?;
74+
}
75+
Ok(buffer)
76+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use anyhow::Result;
2+
use lsp_types::{ExecuteCommandParams, WorkDoneProgressParams, request::ExecuteCommand};
3+
use ruff_db::system::SystemPath;
4+
5+
use crate::{TestServer, TestServerBuilder};
6+
7+
// Sends an executeCommand request to the TestServer
8+
fn execute_command(
9+
server: &mut TestServer,
10+
command: String,
11+
arguments: Vec<serde_json::Value>,
12+
) -> anyhow::Result<Option<serde_json::Value>> {
13+
let params = ExecuteCommandParams {
14+
command,
15+
arguments,
16+
work_done_progress_params: WorkDoneProgressParams::default(),
17+
};
18+
let id = server.send_request::<ExecuteCommand>(params);
19+
server.await_response::<ExecuteCommand>(&id)
20+
}
21+
22+
#[test]
23+
fn debug_command() -> Result<()> {
24+
let workspace_root = SystemPath::new("src");
25+
let foo = SystemPath::new("src/foo.py");
26+
let foo_content = "\
27+
def foo() -> str:
28+
return 42
29+
";
30+
31+
let mut server = TestServerBuilder::new()?
32+
.with_workspace(workspace_root, None)?
33+
.with_file(foo, foo_content)?
34+
.enable_pull_diagnostics(false)
35+
.build()?
36+
.wait_until_workspaces_are_initialized()?;
37+
38+
let response = execute_command(&mut server, "ty.printDebugInformation".to_string(), vec![])?;
39+
let response = response.expect("expect server response");
40+
41+
let response = response
42+
.as_str()
43+
.expect("debug command to return a string response");
44+
45+
insta::with_settings!({
46+
filters =>vec![
47+
(r"\b[0-9]+.[0-9]+MB\b","[X.XXMB]"),
48+
(r"Workspace .+\)","Workspace XXX"),
49+
(r"Project at .+","Project at XXX"),
50+
]}, {
51+
insta::assert_snapshot!(response);
52+
});
53+
54+
Ok(())
55+
}

crates/ty_server/tests/e2e/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
//! [`await_request`]: TestServer::await_request
2828
//! [`await_notification`]: TestServer::await_notification
2929
30+
mod commands;
3031
mod initialize;
3132
mod inlay_hints;
3233
mod publish_diagnostics;

0 commit comments

Comments
 (0)