mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Add ty.experimental.rename
server setting (#19800)
## Summary This PR is a follow-up from https://github.com/astral-sh/ruff/pull/19551 and adds a new `ty.experimental.rename` setting to conditionally register for the rename capability. The complementary PR in ty VS Code extension is https://github.com/astral-sh/ty-vscode/pull/111. This is done using dynamic registration after the settings have been resolved. The experimental group is part of the global settings because they're applied for all workspaces that are managed by the client. ## Test Plan Add E2E tests. In VS Code, with the following setting: ```json { "ty.experimental.rename": "true", "python.languageServer": "None" } ``` I get the relevant log entry: ``` 2025-08-07 16:05:40.598709000 DEBUG client_response{id=3 method="client/registerCapability"}: Registered rename capability ``` And, I'm able to rename a symbol. Once I set it to `false`, then I can see this log entry: ``` 2025-08-07 16:08:39.027876000 DEBUG Rename capability is disabled in the client settings ``` And, I don't see the "Rename Symbol" open in the VS Code dropdown. https://github.com/user-attachments/assets/501659df-ba96-4252-bf51-6f22acb4920b
This commit is contained in:
parent
b005cdb7ff
commit
7b6abfb030
9 changed files with 259 additions and 32 deletions
|
@ -9,6 +9,7 @@ use lsp_types::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::PositionEncoding;
|
use crate::PositionEncoding;
|
||||||
|
use crate::session::GlobalSettings;
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
/// Represents the resolved client capabilities for the language server.
|
/// Represents the resolved client capabilities for the language server.
|
||||||
|
@ -31,6 +32,7 @@ bitflags::bitflags! {
|
||||||
const FILE_WATCHER_SUPPORT = 1 << 12;
|
const FILE_WATCHER_SUPPORT = 1 << 12;
|
||||||
const DIAGNOSTIC_DYNAMIC_REGISTRATION = 1 << 13;
|
const DIAGNOSTIC_DYNAMIC_REGISTRATION = 1 << 13;
|
||||||
const WORKSPACE_CONFIGURATION = 1 << 14;
|
const WORKSPACE_CONFIGURATION = 1 << 14;
|
||||||
|
const RENAME_DYNAMIC_REGISTRATION = 1 << 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +112,11 @@ impl ResolvedClientCapabilities {
|
||||||
self.contains(Self::DIAGNOSTIC_DYNAMIC_REGISTRATION)
|
self.contains(Self::DIAGNOSTIC_DYNAMIC_REGISTRATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the client supports dynamic registration for rename capabilities.
|
||||||
|
pub(crate) const fn supports_rename_dynamic_registration(self) -> bool {
|
||||||
|
self.contains(Self::RENAME_DYNAMIC_REGISTRATION)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn new(client_capabilities: &ClientCapabilities) -> Self {
|
pub(super) fn new(client_capabilities: &ClientCapabilities) -> Self {
|
||||||
let mut flags = Self::empty();
|
let mut flags = Self::empty();
|
||||||
|
|
||||||
|
@ -246,6 +253,13 @@ impl ResolvedClientCapabilities {
|
||||||
flags |= Self::HIERARCHICAL_DOCUMENT_SYMBOL_SUPPORT;
|
flags |= Self::HIERARCHICAL_DOCUMENT_SYMBOL_SUPPORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if text_document
|
||||||
|
.and_then(|text_document| text_document.rename.as_ref()?.dynamic_registration)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
flags |= Self::RENAME_DYNAMIC_REGISTRATION;
|
||||||
|
}
|
||||||
|
|
||||||
if client_capabilities
|
if client_capabilities
|
||||||
.window
|
.window
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -259,9 +273,12 @@ impl ResolvedClientCapabilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates the server capabilities based on the resolved client capabilities and resolved global
|
||||||
|
/// settings from the initialization options.
|
||||||
pub(crate) fn server_capabilities(
|
pub(crate) fn server_capabilities(
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
resolved_client_capabilities: ResolvedClientCapabilities,
|
resolved_client_capabilities: ResolvedClientCapabilities,
|
||||||
|
global_settings: &GlobalSettings,
|
||||||
) -> ServerCapabilities {
|
) -> ServerCapabilities {
|
||||||
let diagnostic_provider =
|
let diagnostic_provider =
|
||||||
if resolved_client_capabilities.supports_diagnostic_dynamic_registration() {
|
if resolved_client_capabilities.supports_diagnostic_dynamic_registration() {
|
||||||
|
@ -275,6 +292,18 @@ pub(crate) fn server_capabilities(
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let rename_provider = if resolved_client_capabilities.supports_rename_dynamic_registration() {
|
||||||
|
// If the client supports dynamic registration, we will register the rename capabilities
|
||||||
|
// dynamically based on the `ty.experimental.rename` setting.
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Otherwise, we check whether user has enabled rename support via the resolved settings
|
||||||
|
// from initialization options.
|
||||||
|
global_settings
|
||||||
|
.is_rename_enabled()
|
||||||
|
.then(|| OneOf::Right(server_rename_options()))
|
||||||
|
};
|
||||||
|
|
||||||
ServerCapabilities {
|
ServerCapabilities {
|
||||||
position_encoding: Some(position_encoding.into()),
|
position_encoding: Some(position_encoding.into()),
|
||||||
diagnostic_provider,
|
diagnostic_provider,
|
||||||
|
@ -289,10 +318,7 @@ pub(crate) fn server_capabilities(
|
||||||
definition_provider: Some(OneOf::Left(true)),
|
definition_provider: Some(OneOf::Left(true)),
|
||||||
declaration_provider: Some(DeclarationCapability::Simple(true)),
|
declaration_provider: Some(DeclarationCapability::Simple(true)),
|
||||||
references_provider: Some(OneOf::Left(true)),
|
references_provider: Some(OneOf::Left(true)),
|
||||||
rename_provider: Some(OneOf::Right(RenameOptions {
|
rename_provider,
|
||||||
prepare_provider: Some(true),
|
|
||||||
work_done_progress_options: WorkDoneProgressOptions::default(),
|
|
||||||
})),
|
|
||||||
document_highlight_provider: Some(OneOf::Left(true)),
|
document_highlight_provider: Some(OneOf::Left(true)),
|
||||||
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
||||||
signature_help_provider: Some(SignatureHelpOptions {
|
signature_help_provider: Some(SignatureHelpOptions {
|
||||||
|
@ -344,3 +370,10 @@ pub(crate) fn server_diagnostic_options(workspace_diagnostics: bool) -> Diagnost
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn server_rename_options() -> RenameOptions {
|
||||||
|
RenameOptions {
|
||||||
|
prepare_provider: Some(true),
|
||||||
|
work_done_progress_options: WorkDoneProgressOptions::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -69,8 +69,15 @@ impl Server {
|
||||||
|
|
||||||
let resolved_client_capabilities = ResolvedClientCapabilities::new(&client_capabilities);
|
let resolved_client_capabilities = ResolvedClientCapabilities::new(&client_capabilities);
|
||||||
let position_encoding = Self::find_best_position_encoding(&client_capabilities);
|
let position_encoding = Self::find_best_position_encoding(&client_capabilities);
|
||||||
let server_capabilities =
|
let server_capabilities = server_capabilities(
|
||||||
server_capabilities(position_encoding, resolved_client_capabilities);
|
position_encoding,
|
||||||
|
resolved_client_capabilities,
|
||||||
|
&initialization_options
|
||||||
|
.options
|
||||||
|
.global
|
||||||
|
.clone()
|
||||||
|
.into_settings(),
|
||||||
|
);
|
||||||
|
|
||||||
let version = ruff_db::program_version().unwrap_or("Unknown");
|
let version = ruff_db::program_version().unwrap_or("Unknown");
|
||||||
tracing::debug!("Version: {version}");
|
tracing::debug!("Version: {version}");
|
||||||
|
@ -102,7 +109,7 @@ impl Server {
|
||||||
{
|
{
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Received unknown options during initialization: {}",
|
"Received unknown options during initialization: {}",
|
||||||
serde_json::to_string_pretty(unknown_options)
|
serde_json::to_string_pretty(&unknown_options)
|
||||||
.unwrap_or_else(|_| format!("{unknown_options:?}"))
|
.unwrap_or_else(|_| format!("{unknown_options:?}"))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use index::DocumentQueryError;
|
||||||
use lsp_server::{Message, RequestId};
|
use lsp_server::{Message, RequestId};
|
||||||
use lsp_types::notification::{Exit, Notification};
|
use lsp_types::notification::{Exit, Notification};
|
||||||
use lsp_types::request::{
|
use lsp_types::request::{
|
||||||
DocumentDiagnosticRequest, RegisterCapability, Request, Shutdown, UnregisterCapability,
|
DocumentDiagnosticRequest, RegisterCapability, Rename, Request, Shutdown, UnregisterCapability,
|
||||||
WorkspaceDiagnosticRequest,
|
WorkspaceDiagnosticRequest,
|
||||||
};
|
};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
|
@ -16,8 +16,7 @@ use options::GlobalOptions;
|
||||||
use ruff_db::Db;
|
use ruff_db::Db;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||||
use settings::GlobalSettings;
|
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||||
use std::collections::{BTreeMap, VecDeque};
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -29,8 +28,10 @@ use ty_project::{ChangeResult, CheckMode, Db as _, ProjectDatabase, ProjectMetad
|
||||||
pub(crate) use self::index::DocumentQuery;
|
pub(crate) use self::index::DocumentQuery;
|
||||||
pub(crate) use self::options::InitializationOptions;
|
pub(crate) use self::options::InitializationOptions;
|
||||||
pub use self::options::{ClientOptions, DiagnosticMode};
|
pub use self::options::{ClientOptions, DiagnosticMode};
|
||||||
pub(crate) use self::settings::WorkspaceSettings;
|
pub(crate) use self::settings::{GlobalSettings, WorkspaceSettings};
|
||||||
use crate::capabilities::{ResolvedClientCapabilities, server_diagnostic_options};
|
use crate::capabilities::{
|
||||||
|
ResolvedClientCapabilities, server_diagnostic_options, server_rename_options,
|
||||||
|
};
|
||||||
use crate::document::{DocumentKey, DocumentVersion, NotebookDocument};
|
use crate::document::{DocumentKey, DocumentVersion, NotebookDocument};
|
||||||
use crate::server::{Action, publish_settings_diagnostics};
|
use crate::server::{Action, publish_settings_diagnostics};
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
|
@ -91,8 +92,6 @@ pub(crate) struct Session {
|
||||||
shutdown_requested: bool,
|
shutdown_requested: bool,
|
||||||
|
|
||||||
/// Whether the server has dynamically registered the diagnostic capability with the client.
|
/// Whether the server has dynamically registered the diagnostic capability with the client.
|
||||||
diagnostic_capability_registered: bool,
|
|
||||||
|
|
||||||
/// Is the connected client a `TestServer` instance.
|
/// Is the connected client a `TestServer` instance.
|
||||||
in_test: bool,
|
in_test: bool,
|
||||||
|
|
||||||
|
@ -107,6 +106,10 @@ pub(crate) struct Session {
|
||||||
/// We'll re-run the request after every change to `Session` (see `revision`)
|
/// We'll re-run the request after every change to `Session` (see `revision`)
|
||||||
/// to see if there are now changes and, if so, respond to the client.
|
/// to see if there are now changes and, if so, respond to the client.
|
||||||
suspended_workspace_diagnostics_request: Option<SuspendedWorkspaceDiagnosticRequest>,
|
suspended_workspace_diagnostics_request: Option<SuspendedWorkspaceDiagnosticRequest>,
|
||||||
|
|
||||||
|
/// Registrations is a set of LSP methods that have been dynamically registered with the
|
||||||
|
/// client.
|
||||||
|
registrations: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// LSP State for a Project
|
/// LSP State for a Project
|
||||||
|
@ -166,10 +169,10 @@ impl Session {
|
||||||
resolved_client_capabilities,
|
resolved_client_capabilities,
|
||||||
request_queue: RequestQueue::new(),
|
request_queue: RequestQueue::new(),
|
||||||
shutdown_requested: false,
|
shutdown_requested: false,
|
||||||
diagnostic_capability_registered: false,
|
|
||||||
in_test,
|
in_test,
|
||||||
suspended_workspace_diagnostics_request: None,
|
suspended_workspace_diagnostics_request: None,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
registrations: HashSet::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,6 +571,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.register_diagnostic_capability(client);
|
self.register_diagnostic_capability(client);
|
||||||
|
self.register_rename_capability(client);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
self.workspaces.all_initialized(),
|
self.workspaces.all_initialized(),
|
||||||
|
@ -583,11 +587,13 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Merge the following two methods as `register_capability` and `unregister_capability`
|
||||||
|
|
||||||
/// Sends a registration notification to the client to enable / disable workspace diagnostics
|
/// Sends a registration notification to the client to enable / disable workspace diagnostics
|
||||||
/// as per the `diagnostic_mode`.
|
/// as per the `ty.diagnosticMode` global setting.
|
||||||
///
|
///
|
||||||
/// This method is a no-op if the client doesn't support dynamic registration of diagnostic
|
/// This method is a no-op if the client doesn't support dynamic registration of diagnostic
|
||||||
/// capabilities.
|
/// capability.
|
||||||
fn register_diagnostic_capability(&mut self, client: &Client) {
|
fn register_diagnostic_capability(&mut self, client: &Client) {
|
||||||
static DIAGNOSTIC_REGISTRATION_ID: &str = "ty/textDocument/diagnostic";
|
static DIAGNOSTIC_REGISTRATION_ID: &str = "ty/textDocument/diagnostic";
|
||||||
|
|
||||||
|
@ -598,9 +604,11 @@ impl Session {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let diagnostic_mode = self.global_settings.diagnostic_mode;
|
let registered = self
|
||||||
|
.registrations
|
||||||
|
.contains(DocumentDiagnosticRequest::METHOD);
|
||||||
|
|
||||||
if self.diagnostic_capability_registered {
|
if registered {
|
||||||
client.send_request::<UnregisterCapability>(
|
client.send_request::<UnregisterCapability>(
|
||||||
self,
|
self,
|
||||||
UnregistrationParams {
|
UnregistrationParams {
|
||||||
|
@ -615,6 +623,8 @@ impl Session {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let diagnostic_mode = self.global_settings.diagnostic_mode;
|
||||||
|
|
||||||
let registration = Registration {
|
let registration = Registration {
|
||||||
id: DIAGNOSTIC_REGISTRATION_ID.into(),
|
id: DIAGNOSTIC_REGISTRATION_ID.into(),
|
||||||
method: DocumentDiagnosticRequest::METHOD.into(),
|
method: DocumentDiagnosticRequest::METHOD.into(),
|
||||||
|
@ -643,7 +653,68 @@ impl Session {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
self.diagnostic_capability_registered = true;
|
if !registered {
|
||||||
|
self.registrations
|
||||||
|
.insert(DocumentDiagnosticRequest::METHOD.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a registration notification to the client to enable / disable rename capability as
|
||||||
|
/// per the `ty.experimental.rename` global setting.
|
||||||
|
///
|
||||||
|
/// This method is a no-op if the client doesn't support dynamic registration of rename
|
||||||
|
/// capability.
|
||||||
|
fn register_rename_capability(&mut self, client: &Client) {
|
||||||
|
static RENAME_REGISTRATION_ID: &str = "ty/textDocument/rename";
|
||||||
|
|
||||||
|
if !self
|
||||||
|
.resolved_client_capabilities
|
||||||
|
.supports_rename_dynamic_registration()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let registered = self.registrations.contains(Rename::METHOD);
|
||||||
|
|
||||||
|
if registered {
|
||||||
|
client.send_request::<UnregisterCapability>(
|
||||||
|
self,
|
||||||
|
UnregistrationParams {
|
||||||
|
unregisterations: vec![Unregistration {
|
||||||
|
id: RENAME_REGISTRATION_ID.into(),
|
||||||
|
method: Rename::METHOD.into(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
move |_: &Client, ()| {
|
||||||
|
tracing::debug!("Unregistered rename capability");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.global_settings.experimental.rename {
|
||||||
|
tracing::debug!("Rename capability is disabled in the client settings");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let registration = Registration {
|
||||||
|
id: RENAME_REGISTRATION_ID.into(),
|
||||||
|
method: Rename::METHOD.into(),
|
||||||
|
register_options: Some(serde_json::to_value(server_rename_options()).unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.send_request::<RegisterCapability>(
|
||||||
|
self,
|
||||||
|
RegistrationParams {
|
||||||
|
registrations: vec![registration],
|
||||||
|
},
|
||||||
|
move |_: &Client, ()| {
|
||||||
|
tracing::debug!("Registered rename capability");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if !registered {
|
||||||
|
self.registrations.insert(Rename::METHOD.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a document snapshot with the URL referencing the document to snapshot.
|
/// Creates a document snapshot with the URL referencing the document to snapshot.
|
||||||
|
|
|
@ -14,7 +14,7 @@ use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||||
|
|
||||||
use crate::logging::LogLevel;
|
use crate::logging::LogLevel;
|
||||||
|
|
||||||
use super::settings::{GlobalSettings, WorkspaceSettings};
|
use super::settings::{ExperimentalSettings, GlobalSettings, WorkspaceSettings};
|
||||||
|
|
||||||
/// Initialization options that are set once at server startup that never change.
|
/// Initialization options that are set once at server startup that never change.
|
||||||
///
|
///
|
||||||
|
@ -106,6 +106,12 @@ impl ClientOptions {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_experimental_rename(mut self, enabled: bool) -> Self {
|
||||||
|
self.global.experimental.get_or_insert_default().rename = Some(enabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_unknown(mut self, unknown: HashMap<String, Value>) -> Self {
|
pub fn with_unknown(mut self, unknown: HashMap<String, Value>) -> Self {
|
||||||
self.unknown = unknown;
|
self.unknown = unknown;
|
||||||
|
@ -122,12 +128,23 @@ impl ClientOptions {
|
||||||
pub(crate) struct GlobalOptions {
|
pub(crate) struct GlobalOptions {
|
||||||
/// Diagnostic mode for the language server.
|
/// Diagnostic mode for the language server.
|
||||||
diagnostic_mode: Option<DiagnosticMode>,
|
diagnostic_mode: Option<DiagnosticMode>,
|
||||||
|
|
||||||
|
/// Experimental features that the server provides on an opt-in basis.
|
||||||
|
pub(crate) experimental: Option<Experimental>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalOptions {
|
impl GlobalOptions {
|
||||||
pub(crate) fn into_settings(self) -> GlobalSettings {
|
pub(crate) fn into_settings(self) -> GlobalSettings {
|
||||||
|
let experimental = self
|
||||||
|
.experimental
|
||||||
|
.map(|experimental| ExperimentalSettings {
|
||||||
|
rename: experimental.rename.unwrap_or_default(),
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
GlobalSettings {
|
GlobalSettings {
|
||||||
diagnostic_mode: self.diagnostic_mode.unwrap_or_default(),
|
diagnostic_mode: self.diagnostic_mode.unwrap_or_default(),
|
||||||
|
experimental,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,6 +255,13 @@ impl Combine for DiagnosticMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Combine, Debug, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct Experimental {
|
||||||
|
/// Whether to enable the experimental symbol rename feature.
|
||||||
|
pub(crate) rename: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct PythonExtension {
|
struct PythonExtension {
|
||||||
|
|
|
@ -6,6 +6,13 @@ use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
pub(crate) struct GlobalSettings {
|
pub(crate) struct GlobalSettings {
|
||||||
pub(super) diagnostic_mode: DiagnosticMode,
|
pub(super) diagnostic_mode: DiagnosticMode,
|
||||||
|
pub(super) experimental: ExperimentalSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalSettings {
|
||||||
|
pub(crate) fn is_rename_enabled(&self) -> bool {
|
||||||
|
self.experimental.rename
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalSettings {
|
impl GlobalSettings {
|
||||||
|
@ -14,6 +21,11 @@ impl GlobalSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
|
pub(crate) struct ExperimentalSettings {
|
||||||
|
pub(super) rename: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolved client settings for a specific workspace.
|
/// Resolved client settings for a specific workspace.
|
||||||
///
|
///
|
||||||
/// These settings are meant to be used directly by the server, and are *not* a 1:1 representation
|
/// These settings are meant to be used directly by the server, and are *not* a 1:1 representation
|
||||||
|
|
|
@ -433,3 +433,78 @@ fn unknown_options_in_workspace_configuration() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that the server sends a registration request for the rename capability if the client
|
||||||
|
/// setting is set to true and dynamic registration is enabled.
|
||||||
|
#[test]
|
||||||
|
fn register_rename_capability_when_enabled() -> Result<()> {
|
||||||
|
let workspace_root = SystemPath::new("foo");
|
||||||
|
let mut server = TestServerBuilder::new()?
|
||||||
|
.with_workspace(workspace_root, None)?
|
||||||
|
.with_initialization_options(ClientOptions::default().with_experimental_rename(true))
|
||||||
|
.enable_rename_dynamic_registration(true)
|
||||||
|
.build()?
|
||||||
|
.wait_until_workspaces_are_initialized()?;
|
||||||
|
|
||||||
|
let (_, params) = server.await_request::<RegisterCapability>()?;
|
||||||
|
let [registration] = params.registrations.as_slice() else {
|
||||||
|
panic!(
|
||||||
|
"Expected a single registration, got: {:#?}",
|
||||||
|
params.registrations
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
insta::assert_json_snapshot!(registration, @r#"
|
||||||
|
{
|
||||||
|
"id": "ty/textDocument/rename",
|
||||||
|
"method": "textDocument/rename",
|
||||||
|
"registerOptions": {
|
||||||
|
"prepareProvider": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that rename capability is statically registered during initialization if the client
|
||||||
|
/// doesn't support dynamic registration, but the server is configured to support it.
|
||||||
|
#[test]
|
||||||
|
fn rename_available_without_dynamic_registration() -> Result<()> {
|
||||||
|
let workspace_root = SystemPath::new("foo");
|
||||||
|
|
||||||
|
let server = TestServerBuilder::new()?
|
||||||
|
.with_workspace(workspace_root, None)?
|
||||||
|
.with_initialization_options(ClientOptions::default().with_experimental_rename(true))
|
||||||
|
.enable_rename_dynamic_registration(false)
|
||||||
|
.build()?
|
||||||
|
.wait_until_workspaces_are_initialized()?;
|
||||||
|
|
||||||
|
let initialization_result = server.initialization_result().unwrap();
|
||||||
|
insta::assert_json_snapshot!(initialization_result.capabilities.rename_provider, @r#"
|
||||||
|
{
|
||||||
|
"prepareProvider": true
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that the server does not send a registration request for the rename capability if the
|
||||||
|
/// client setting is set to false and dynamic registration is enabled.
|
||||||
|
#[test]
|
||||||
|
fn not_register_rename_capability_when_disabled() -> Result<()> {
|
||||||
|
let workspace_root = SystemPath::new("foo");
|
||||||
|
|
||||||
|
TestServerBuilder::new()?
|
||||||
|
.with_workspace(workspace_root, None)?
|
||||||
|
.with_initialization_options(ClientOptions::default().with_experimental_rename(false))
|
||||||
|
.enable_rename_dynamic_registration(true)
|
||||||
|
.build()?
|
||||||
|
.wait_until_workspaces_are_initialized()?;
|
||||||
|
|
||||||
|
// The `Drop` implementation will make sure that the client did not receive any registration
|
||||||
|
// request.
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -868,7 +868,7 @@ impl TestServerBuilder {
|
||||||
pub(crate) fn enable_pull_diagnostics(mut self, enabled: bool) -> Self {
|
pub(crate) fn enable_pull_diagnostics(mut self, enabled: bool) -> Self {
|
||||||
self.client_capabilities
|
self.client_capabilities
|
||||||
.text_document
|
.text_document
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_default()
|
||||||
.diagnostic = if enabled {
|
.diagnostic = if enabled {
|
||||||
Some(DiagnosticClientCapabilities::default())
|
Some(DiagnosticClientCapabilities::default())
|
||||||
} else {
|
} else {
|
||||||
|
@ -881,9 +881,20 @@ impl TestServerBuilder {
|
||||||
pub(crate) fn enable_diagnostic_dynamic_registration(mut self, enabled: bool) -> Self {
|
pub(crate) fn enable_diagnostic_dynamic_registration(mut self, enabled: bool) -> Self {
|
||||||
self.client_capabilities
|
self.client_capabilities
|
||||||
.text_document
|
.text_document
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_default()
|
||||||
.diagnostic
|
.diagnostic
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_default()
|
||||||
|
.dynamic_registration = Some(enabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable dynamic registration of rename capability
|
||||||
|
pub(crate) fn enable_rename_dynamic_registration(mut self, enabled: bool) -> Self {
|
||||||
|
self.client_capabilities
|
||||||
|
.text_document
|
||||||
|
.get_or_insert_default()
|
||||||
|
.rename
|
||||||
|
.get_or_insert_default()
|
||||||
.dynamic_registration = Some(enabled);
|
.dynamic_registration = Some(enabled);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -892,7 +903,7 @@ impl TestServerBuilder {
|
||||||
pub(crate) fn enable_workspace_configuration(mut self, enabled: bool) -> Self {
|
pub(crate) fn enable_workspace_configuration(mut self, enabled: bool) -> Self {
|
||||||
self.client_capabilities
|
self.client_capabilities
|
||||||
.workspace
|
.workspace
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_default()
|
||||||
.configuration = Some(enabled);
|
.configuration = Some(enabled);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -902,7 +913,7 @@ impl TestServerBuilder {
|
||||||
pub(crate) fn enable_did_change_watched_files(mut self, enabled: bool) -> Self {
|
pub(crate) fn enable_did_change_watched_files(mut self, enabled: bool) -> Self {
|
||||||
self.client_capabilities
|
self.client_capabilities
|
||||||
.workspace
|
.workspace
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_default()
|
||||||
.did_change_watched_files = if enabled {
|
.did_change_watched_files = if enabled {
|
||||||
Some(DidChangeWatchedFilesClientCapabilities::default())
|
Some(DidChangeWatchedFilesClientCapabilities::default())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -31,9 +31,6 @@ expression: initialization_result
|
||||||
"documentHighlightProvider": true,
|
"documentHighlightProvider": true,
|
||||||
"documentSymbolProvider": true,
|
"documentSymbolProvider": true,
|
||||||
"workspaceSymbolProvider": true,
|
"workspaceSymbolProvider": true,
|
||||||
"renameProvider": {
|
|
||||||
"prepareProvider": true
|
|
||||||
},
|
|
||||||
"declarationProvider": true,
|
"declarationProvider": true,
|
||||||
"semanticTokensProvider": {
|
"semanticTokensProvider": {
|
||||||
"legend": {
|
"legend": {
|
||||||
|
|
|
@ -31,9 +31,6 @@ expression: initialization_result
|
||||||
"documentHighlightProvider": true,
|
"documentHighlightProvider": true,
|
||||||
"documentSymbolProvider": true,
|
"documentSymbolProvider": true,
|
||||||
"workspaceSymbolProvider": true,
|
"workspaceSymbolProvider": true,
|
||||||
"renameProvider": {
|
|
||||||
"prepareProvider": true
|
|
||||||
},
|
|
||||||
"declarationProvider": true,
|
"declarationProvider": true,
|
||||||
"semanticTokensProvider": {
|
"semanticTokensProvider": {
|
||||||
"legend": {
|
"legend": {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue