mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Add ty.inlayHints.variableTypes
server option (#19780)
## Summary This PR adds a new `ty.inlayHints.variableTypes` server setting to configure ty to include / exclude inlay hints at variable position. Currently, we only support inlay hints at this position so this option basically translates to enabling / disabling inlay hints for now :) The VS Code extension PR is https://github.com/astral-sh/ty-vscode/pull/112. closes: astral-sh/ty#472 ## Test Plan Add E2E tests.
This commit is contained in:
parent
c401a6d86e
commit
b22586fa0e
8 changed files with 181 additions and 20 deletions
|
@ -51,8 +51,13 @@ impl fmt::Display for DisplayInlayHint<'_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn inlay_hints(db: &dyn Db, file: File, range: TextRange) -> Vec<InlayHint<'_>> {
|
||||
let mut visitor = InlayHintVisitor::new(db, file, range);
|
||||
pub fn inlay_hints<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
range: TextRange,
|
||||
settings: &InlayHintSettings,
|
||||
) -> Vec<InlayHint<'db>> {
|
||||
let mut visitor = InlayHintVisitor::new(db, file, range, settings);
|
||||
|
||||
let ast = parsed_module(db, file).load(db);
|
||||
|
||||
|
@ -61,20 +66,34 @@ pub fn inlay_hints(db: &dyn Db, file: File, range: TextRange) -> Vec<InlayHint<'
|
|||
visitor.hints
|
||||
}
|
||||
|
||||
struct InlayHintVisitor<'db> {
|
||||
/// Settings to control the behavior of inlay hints.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct InlayHintSettings {
|
||||
/// Whether to show variable type hints.
|
||||
///
|
||||
/// For example, this would enable / disable hints like the ones quoted below:
|
||||
/// ```python
|
||||
/// x": Literal[1]" = 1
|
||||
/// ```
|
||||
pub variable_types: bool,
|
||||
}
|
||||
|
||||
struct InlayHintVisitor<'a, 'db> {
|
||||
model: SemanticModel<'db>,
|
||||
hints: Vec<InlayHint<'db>>,
|
||||
in_assignment: bool,
|
||||
range: TextRange,
|
||||
settings: &'a InlayHintSettings,
|
||||
}
|
||||
|
||||
impl<'db> InlayHintVisitor<'db> {
|
||||
fn new(db: &'db dyn Db, file: File, range: TextRange) -> Self {
|
||||
impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
fn new(db: &'db dyn Db, file: File, range: TextRange, settings: &'a InlayHintSettings) -> Self {
|
||||
Self {
|
||||
model: SemanticModel::new(db, file),
|
||||
hints: Vec::new(),
|
||||
in_assignment: false,
|
||||
range,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +105,7 @@ impl<'db> InlayHintVisitor<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
impl SourceOrderVisitor<'_> for InlayHintVisitor<'_> {
|
||||
impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||
fn enter_node(&mut self, node: AnyNodeRef<'_>) -> TraversalSignal {
|
||||
if self.range.intersect(node.range()).is_some() {
|
||||
TraversalSignal::Traverse
|
||||
|
@ -104,6 +123,10 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_> {
|
|||
|
||||
match stmt {
|
||||
Stmt::Assign(assign) => {
|
||||
if !self.settings.variable_types {
|
||||
return;
|
||||
}
|
||||
|
||||
self.in_assignment = true;
|
||||
for target in &assign.targets {
|
||||
self.visit_expr(target);
|
||||
|
@ -213,8 +236,21 @@ mod tests {
|
|||
}
|
||||
|
||||
impl InlayHintTest {
|
||||
/// Returns the inlay hints for the given test case.
|
||||
///
|
||||
/// All inlay hints are generated using the applicable settings. Use
|
||||
/// [`inlay_hints_with_settings`] to generate hints with custom settings.
|
||||
///
|
||||
/// [`inlay_hints_with_settings`]: Self::inlay_hints_with_settings
|
||||
fn inlay_hints(&self) -> String {
|
||||
let hints = inlay_hints(&self.db, self.file, self.range);
|
||||
self.inlay_hints_with_settings(&InlayHintSettings {
|
||||
variable_types: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the inlay hints for the given test case with custom settings.
|
||||
fn inlay_hints_with_settings(&self, settings: &InlayHintSettings) -> String {
|
||||
let hints = inlay_hints(&self.db, self.file, self.range, settings);
|
||||
|
||||
let mut buf = source_text(&self.db, self.file).as_str().to_string();
|
||||
|
||||
|
@ -276,4 +312,18 @@ mod tests {
|
|||
y = 2
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_variable_types() {
|
||||
let test = inlay_hint_test("x = 1");
|
||||
|
||||
assert_snapshot!(
|
||||
test.inlay_hints_with_settings(&InlayHintSettings {
|
||||
variable_types: false,
|
||||
}),
|
||||
@r"
|
||||
x = 1
|
||||
"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ pub use document_symbols::{document_symbols, document_symbols_with_options};
|
|||
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
||||
pub use goto_references::goto_references;
|
||||
pub use hover::hover;
|
||||
pub use inlay_hints::inlay_hints;
|
||||
pub use inlay_hints::{InlayHintSettings, inlay_hints};
|
||||
pub use markup::MarkupKind;
|
||||
pub use references::ReferencesMode;
|
||||
pub use rename::{can_rename, rename};
|
||||
|
|
|
@ -47,7 +47,7 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
|||
.range
|
||||
.to_text_range(&source, &index, snapshot.encoding());
|
||||
|
||||
let inlay_hints = inlay_hints(db, file, range);
|
||||
let inlay_hints = inlay_hints(db, file, range, snapshot.workspace_settings().inlay_hints());
|
||||
|
||||
let inlay_hints = inlay_hints
|
||||
.into_iter()
|
||||
|
|
|
@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::Value;
|
||||
|
||||
use ty_combine::Combine;
|
||||
use ty_ide::InlayHintSettings;
|
||||
use ty_project::metadata::Options as TyOptions;
|
||||
use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||
|
@ -106,6 +107,15 @@ impl ClientOptions {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_variable_types_inlay_hints(mut self, variable_types: bool) -> Self {
|
||||
self.workspace
|
||||
.inlay_hints
|
||||
.get_or_insert_default()
|
||||
.variable_types = Some(variable_types);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_experimental_rename(mut self, enabled: bool) -> Self {
|
||||
self.global.experimental.get_or_insert_default().rename = Some(enabled);
|
||||
|
@ -138,7 +148,7 @@ impl GlobalOptions {
|
|||
let experimental = self
|
||||
.experimental
|
||||
.map(|experimental| ExperimentalSettings {
|
||||
rename: experimental.rename.unwrap_or_default(),
|
||||
rename: experimental.rename.unwrap_or(true),
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -158,6 +168,9 @@ pub(crate) struct WorkspaceOptions {
|
|||
/// Whether to disable language services like code completions, hover, etc.
|
||||
disable_language_services: Option<bool>,
|
||||
|
||||
/// Options to configure inlay hints.
|
||||
inlay_hints: Option<InlayHintOptions>,
|
||||
|
||||
/// Information about the currently active Python environment in the VS Code Python extension.
|
||||
///
|
||||
/// This is relevant only for VS Code and is populated by the ty VS Code extension.
|
||||
|
@ -211,11 +224,29 @@ impl WorkspaceOptions {
|
|||
|
||||
WorkspaceSettings {
|
||||
disable_language_services: self.disable_language_services.unwrap_or_default(),
|
||||
inlay_hints: self
|
||||
.inlay_hints
|
||||
.map(InlayHintOptions::into_settings)
|
||||
.unwrap_or_default(),
|
||||
overrides,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Combine, Debug, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InlayHintOptions {
|
||||
variable_types: Option<bool>,
|
||||
}
|
||||
|
||||
impl InlayHintOptions {
|
||||
fn into_settings(self) -> InlayHintSettings {
|
||||
InlayHintSettings {
|
||||
variable_types: self.variable_types.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic mode for the language server.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::options::DiagnosticMode;
|
||||
|
||||
use ty_ide::InlayHintSettings;
|
||||
use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||
|
||||
/// Resolved client settings that are shared across all workspaces.
|
||||
|
@ -33,6 +34,7 @@ pub(crate) struct ExperimentalSettings {
|
|||
#[derive(Clone, Default, Debug)]
|
||||
pub(crate) struct WorkspaceSettings {
|
||||
pub(super) disable_language_services: bool,
|
||||
pub(super) inlay_hints: InlayHintSettings,
|
||||
pub(super) overrides: Option<ProjectOptionsOverrides>,
|
||||
}
|
||||
|
||||
|
@ -44,4 +46,8 @@ impl WorkspaceSettings {
|
|||
pub(crate) fn project_options_overrides(&self) -> Option<&ProjectOptionsOverrides> {
|
||||
self.overrides.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn inlay_hints(&self) -> &InlayHintSettings {
|
||||
&self.inlay_hints
|
||||
}
|
||||
}
|
||||
|
|
38
crates/ty_server/tests/e2e/inlay_hints.rs
Normal file
38
crates/ty_server/tests/e2e/inlay_hints.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use anyhow::Result;
|
||||
use lsp_types::{Position, Range, notification::PublishDiagnostics};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ty_server::ClientOptions;
|
||||
|
||||
use crate::TestServerBuilder;
|
||||
|
||||
/// Tests that disabling variable types inlay hints works correctly.
|
||||
#[test]
|
||||
fn variable_inlay_hints_disabled() -> Result<()> {
|
||||
let workspace_root = SystemPath::new("src");
|
||||
let foo = SystemPath::new("src/foo.py");
|
||||
let foo_content = "x = 1";
|
||||
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_initialization_options(
|
||||
ClientOptions::default().with_variable_types_inlay_hints(false),
|
||||
)
|
||||
.with_workspace(workspace_root, None)?
|
||||
.with_file(foo, foo_content)?
|
||||
.enable_inlay_hints(true)
|
||||
.build()?
|
||||
.wait_until_workspaces_are_initialized()?;
|
||||
|
||||
server.open_text_document(foo, &foo_content, 1);
|
||||
let _ = server.await_notification::<PublishDiagnostics>()?;
|
||||
|
||||
let hints = server
|
||||
.inlay_hints_request(foo, Range::new(Position::new(0, 0), Position::new(0, 5)))?
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
hints.is_empty(),
|
||||
"Expected no inlay hints, but found: {hints:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -28,6 +28,7 @@
|
|||
//! [`await_notification`]: TestServer::await_notification
|
||||
|
||||
mod initialize;
|
||||
mod inlay_hints;
|
||||
mod publish_diagnostics;
|
||||
mod pull_diagnostics;
|
||||
|
||||
|
@ -48,20 +49,21 @@ use lsp_types::notification::{
|
|||
Initialized, Notification,
|
||||
};
|
||||
use lsp_types::request::{
|
||||
DocumentDiagnosticRequest, HoverRequest, Initialize, Request, Shutdown, WorkspaceConfiguration,
|
||||
WorkspaceDiagnosticRequest,
|
||||
DocumentDiagnosticRequest, HoverRequest, Initialize, InlayHintRequest, Request, Shutdown,
|
||||
WorkspaceConfiguration, WorkspaceDiagnosticRequest,
|
||||
};
|
||||
use lsp_types::{
|
||||
ClientCapabilities, ConfigurationParams, DiagnosticClientCapabilities,
|
||||
DidChangeTextDocumentParams, DidChangeWatchedFilesClientCapabilities,
|
||||
DidChangeWatchedFilesParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
|
||||
DocumentDiagnosticParams, DocumentDiagnosticReportResult, FileEvent, Hover, HoverParams,
|
||||
InitializeParams, InitializeResult, InitializedParams, NumberOrString, PartialResultParams,
|
||||
Position, PreviousResultId, PublishDiagnosticsClientCapabilities,
|
||||
TextDocumentClientCapabilities, TextDocumentContentChangeEvent, TextDocumentIdentifier,
|
||||
TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier,
|
||||
WorkDoneProgressParams, WorkspaceClientCapabilities, WorkspaceDiagnosticParams,
|
||||
WorkspaceDiagnosticReportResult, WorkspaceFolder,
|
||||
InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintClientCapabilities,
|
||||
InlayHintParams, NumberOrString, PartialResultParams, Position, PreviousResultId,
|
||||
PublishDiagnosticsClientCapabilities, Range, TextDocumentClientCapabilities,
|
||||
TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
|
||||
TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkDoneProgressParams,
|
||||
WorkspaceClientCapabilities, WorkspaceDiagnosticParams, WorkspaceDiagnosticReportResult,
|
||||
WorkspaceFolder,
|
||||
};
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf, TestSystem};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
@ -725,6 +727,23 @@ impl TestServer {
|
|||
let id = self.send_request::<HoverRequest>(params);
|
||||
self.await_response::<HoverRequest>(&id)
|
||||
}
|
||||
|
||||
/// Sends a `textDocument/inlayHint` request for the document at the given path and range.
|
||||
pub(crate) fn inlay_hints_request(
|
||||
&mut self,
|
||||
path: impl AsRef<SystemPath>,
|
||||
range: Range,
|
||||
) -> Result<Option<Vec<InlayHint>>> {
|
||||
let params = InlayHintParams {
|
||||
text_document: TextDocumentIdentifier {
|
||||
uri: self.file_uri(path),
|
||||
},
|
||||
range,
|
||||
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||
};
|
||||
let id = self.send_request::<InlayHintRequest>(params);
|
||||
self.await_response::<InlayHintRequest>(&id)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TestServer {
|
||||
|
@ -908,6 +927,19 @@ impl TestServerBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Enable or disable inlay hints capability
|
||||
pub(crate) fn enable_inlay_hints(mut self, enabled: bool) -> Self {
|
||||
self.client_capabilities
|
||||
.text_document
|
||||
.get_or_insert_default()
|
||||
.inlay_hint = if enabled {
|
||||
Some(InlayHintClientCapabilities::default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable or disable file watching capability
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn enable_did_change_watched_files(mut self, enabled: bool) -> Self {
|
||||
|
|
|
@ -16,8 +16,8 @@ use ruff_python_formatter::formatted_file;
|
|||
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_ide::{
|
||||
MarkupKind, RangedValue, document_highlights, goto_declaration, goto_definition,
|
||||
goto_references, goto_type_definition, hover, inlay_hints,
|
||||
InlayHintSettings, MarkupKind, RangedValue, document_highlights, goto_declaration,
|
||||
goto_definition, goto_references, goto_type_definition, hover, inlay_hints,
|
||||
};
|
||||
use ty_ide::{NavigationTargets, signature_help};
|
||||
use ty_project::metadata::options::Options;
|
||||
|
@ -435,6 +435,10 @@ impl Workspace {
|
|||
&self.db,
|
||||
file_id.file,
|
||||
range.to_text_range(&index, &source, self.position_encoding)?,
|
||||
// TODO: Provide a way to configure this
|
||||
&InlayHintSettings {
|
||||
variable_types: true,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(result
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue