Make completions an opt-in LSP feature (#17921)

## Summary

We now expect the client to send initialization options to opt-in to
experimental (but LSP-standardized) features, like completion support.
Specifically, the client should set `"experimental.completions.enable":
true`.

Closes https://github.com/astral-sh/ty/issues/74.
This commit is contained in:
Charlie Marsh 2025-05-07 12:39:35 -04:00 committed by GitHub
parent 82d31a6014
commit 51e2effd2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 49 additions and 18 deletions

View file

@ -6,6 +6,7 @@ use ruff_text_size::TextSize;
use crate::Db; use crate::Db;
#[derive(Debug, Clone)]
pub struct Completion { pub struct Completion {
pub label: String, pub label: String,
} }

View file

@ -15,7 +15,7 @@ use lsp_types::{
use self::connection::{Connection, ConnectionInitializer}; use self::connection::{Connection, ConnectionInitializer};
use self::schedule::event_loop_thread; use self::schedule::event_loop_thread;
use crate::session::{AllSettings, ClientSettings, Session}; use crate::session::{AllSettings, ClientSettings, Experimental, Session};
use crate::PositionEncoding; use crate::PositionEncoding;
mod api; mod api;
@ -41,19 +41,6 @@ impl Server {
let (id, init_params) = connection.initialize_start()?; let (id, init_params) = connection.initialize_start()?;
let client_capabilities = init_params.capabilities;
let position_encoding = Self::find_best_position_encoding(&client_capabilities);
let server_capabilities = Self::server_capabilities(position_encoding);
let connection = connection.initialize_finish(
id,
&server_capabilities,
crate::SERVER_NAME,
crate::version(),
)?;
crate::message::init_messenger(connection.make_sender());
let AllSettings { let AllSettings {
global_settings, global_settings,
mut workspace_settings, mut workspace_settings,
@ -63,6 +50,19 @@ impl Server {
.unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())), .unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())),
); );
let client_capabilities = init_params.capabilities;
let position_encoding = Self::find_best_position_encoding(&client_capabilities);
let server_capabilities =
Self::server_capabilities(position_encoding, global_settings.experimental.as_ref());
let connection = connection.initialize_finish(
id,
&server_capabilities,
crate::SERVER_NAME,
crate::version(),
)?;
crate::message::init_messenger(connection.make_sender());
crate::logging::init_logging( crate::logging::init_logging(
global_settings.tracing.log_level.unwrap_or_default(), global_settings.tracing.log_level.unwrap_or_default(),
global_settings.tracing.log_file.as_deref(), global_settings.tracing.log_file.as_deref(),
@ -206,7 +206,10 @@ impl Server {
.unwrap_or_default() .unwrap_or_default()
} }
fn server_capabilities(position_encoding: PositionEncoding) -> ServerCapabilities { fn server_capabilities(
position_encoding: PositionEncoding,
experimental: Option<&Experimental>,
) -> ServerCapabilities {
ServerCapabilities { ServerCapabilities {
position_encoding: Some(position_encoding.into()), position_encoding: Some(position_encoding.into()),
diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions { diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
@ -226,7 +229,9 @@ impl Server {
inlay_hint_provider: Some(lsp_types::OneOf::Right( inlay_hint_provider: Some(lsp_types::OneOf::Right(
InlayHintServerCapabilities::Options(InlayHintOptions::default()), InlayHintServerCapabilities::Options(InlayHintOptions::default()),
)), )),
completion_provider: Some(lsp_types::CompletionOptions { completion_provider: experimental
.is_some_and(Experimental::is_completions_enabled)
.then_some(lsp_types::CompletionOptions {
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()

View file

@ -21,6 +21,7 @@ pub(crate) use self::capabilities::ResolvedClientCapabilities;
pub use self::index::DocumentQuery; pub use self::index::DocumentQuery;
pub(crate) use self::settings::AllSettings; pub(crate) use self::settings::AllSettings;
pub use self::settings::ClientSettings; pub use self::settings::ClientSettings;
pub(crate) use self::settings::Experimental;
mod capabilities; mod capabilities;
pub(crate) mod index; pub(crate) mod index;

View file

@ -7,11 +7,35 @@ use serde::Deserialize;
/// Maps a workspace URI to its associated client settings. Used during server initialization. /// Maps a workspace URI to its associated client settings. Used during server initialization.
pub(crate) type WorkspaceSettingsMap = FxHashMap<Url, ClientSettings>; pub(crate) type WorkspaceSettingsMap = FxHashMap<Url, ClientSettings>;
#[derive(Debug, Deserialize, Default)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[serde(rename_all = "camelCase")]
struct Completions {
enable: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[serde(rename_all = "camelCase")]
pub(crate) struct Experimental {
completions: Option<Completions>,
}
impl Experimental {
/// Returns `true` if completions are enabled in the settings.
pub(crate) fn is_completions_enabled(&self) -> bool {
self.completions
.as_ref()
.is_some_and(|completions| completions.enable.unwrap_or_default())
}
}
/// This is a direct representation of the settings schema sent by the client. /// This is a direct representation of the settings schema sent by the client.
#[derive(Debug, Deserialize, Default)] #[derive(Debug, Deserialize, Default)]
#[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(test, derive(PartialEq, Eq))]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ClientSettings { pub struct ClientSettings {
pub(crate) experimental: Option<Experimental>,
// These settings are only needed for tracing, and are only read from the global configuration. // These settings are only needed for tracing, and are only read from the global configuration.
// These will not be in the resolved settings. // These will not be in the resolved settings.
#[serde(flatten)] #[serde(flatten)]