ruff/crates/ruff_server/src/session.rs
Jane Lewis 35ca887e02
ruff server: Ruff configuration from client settings overrides project configuration (#11062)
## Summary

This is a follow-up to https://github.com/astral-sh/ruff/pull/10984 that
implements configuration resolution for editor configuration. By 'editor
configuration', I'm referring to the client settings that correspond to
Ruff configuration/options, like `preview`, `select`, and so on. These
will be combined with 'project configuration' (configuration taken from
project files such as `pyproject.toml`) to generate the final linter and
formatter settings used by `RuffSettings`. Editor configuration takes
priority over project configuration.

In a follow-up pull request, I'll implement a new client setting that
allows project configuration to override editor configuration, as per
[this issue](https://github.com/astral-sh/ruff-vscode/issues/425).

## Review guide

The first commit, e38966d8843becc7234fa7d46009c16af4ba41e9, is just
doing re-arrangement so that we can pass the right things to
`RuffSettings::resolve`. The actual resolution logic is in the second
commit, 0eec9ee75c10e5ec423bd9f5ce1764f4d7a5ad86. It might help to look
at these comments individually since the diff is rather messy.

## Test Plan

For the settings to show up in VS Code, you'll need to checkout this
branch: https://github.com/astral-sh/ruff-vscode/pull/456.

To test that the resolution for a specific setting works as expected,
run through the following scenarios, setting it in project and editor
configuration as needed:

| Set in project configuration? | Set in editor configuration? |
Expected Outcome |

|-------------------------------|--------------------------------------------------|------------------------------------------------------------------------------------------|
| No | No | The editor should behave as if the setting was set to its
default value. |
| Yes | No | The editor should behave as if the setting was set to the
value in project configuration. |
| No | Yes | The editor should behave as if the setting was set to the
value in editor configuration. |
| Yes | Yes (but distinctive from project configuration) | The editor
should behave as if the setting was set to the value in editor
configuration. |

An exception to this is `extendSelect`, which does not have an analog in
TOML configuration. Instead, you should verify that `extendSelect`
amends the `select` setting. If `select` is set in both editor and
project configuration, `extendSelect` will only append to the `select`
value in editor configuration, so make sure to un-set it there if you're
testing `extendSelect` with `select` in project configuration.
2024-04-23 11:19:17 -07:00

133 lines
4.1 KiB
Rust

//! Data model, state management, and configuration resolution.
mod capabilities;
mod settings;
mod workspace;
use std::sync::Arc;
use anyhow::anyhow;
use lsp_types::{ClientCapabilities, Url};
use crate::edit::DocumentVersion;
use crate::PositionEncoding;
pub(crate) use self::capabilities::ResolvedClientCapabilities;
pub(crate) use self::settings::{AllSettings, ClientSettings};
/// The global state for the LSP
pub(crate) struct Session {
/// Workspace folders in the current session, which contain the state of all open files.
workspaces: workspace::Workspaces,
/// The global position encoding, negotiated during LSP initialization.
position_encoding: PositionEncoding,
/// Global settings provided by the client.
global_settings: ClientSettings,
/// Tracks what LSP features the client supports and doesn't support.
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
}
/// An immutable snapshot of `Session` that references
/// a specific document.
pub(crate) struct DocumentSnapshot {
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
client_settings: settings::ResolvedClientSettings,
document_ref: workspace::DocumentRef,
position_encoding: PositionEncoding,
url: Url,
}
impl Session {
pub(crate) fn new(
client_capabilities: &ClientCapabilities,
position_encoding: PositionEncoding,
global_settings: ClientSettings,
workspaces: Vec<(Url, ClientSettings)>,
) -> crate::Result<Self> {
Ok(Self {
position_encoding,
workspaces: workspace::Workspaces::new(workspaces, &global_settings)?,
global_settings,
resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new(
client_capabilities,
)),
})
}
pub(crate) fn take_snapshot(&self, url: &Url) -> Option<DocumentSnapshot> {
Some(DocumentSnapshot {
resolved_client_capabilities: self.resolved_client_capabilities.clone(),
client_settings: self.workspaces.client_settings(url, &self.global_settings),
document_ref: self.workspaces.snapshot(url)?,
position_encoding: self.position_encoding,
url: url.clone(),
})
}
pub(crate) fn open_document(&mut self, url: &Url, contents: String, version: DocumentVersion) {
self.workspaces.open(url, contents, version);
}
pub(crate) fn close_document(&mut self, url: &Url) -> crate::Result<()> {
self.workspaces.close(url)?;
Ok(())
}
pub(crate) fn document_controller(
&mut self,
url: &Url,
) -> crate::Result<&mut workspace::DocumentController> {
self.workspaces
.controller(url)
.ok_or_else(|| anyhow!("Tried to open unavailable document `{url}`"))
}
pub(crate) fn reload_settings(&mut self, url: &Url) -> crate::Result<()> {
self.workspaces.reload_settings(url)
}
pub(crate) fn open_workspace_folder(&mut self, url: &Url) -> crate::Result<()> {
self.workspaces
.open_workspace_folder(url, &self.global_settings)?;
Ok(())
}
pub(crate) fn close_workspace_folder(&mut self, url: &Url) -> crate::Result<()> {
self.workspaces.close_workspace_folder(url)?;
Ok(())
}
pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities {
&self.resolved_client_capabilities
}
pub(crate) fn encoding(&self) -> PositionEncoding {
self.position_encoding
}
}
impl DocumentSnapshot {
pub(crate) fn settings(&self) -> &workspace::RuffSettings {
self.document().settings()
}
pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities {
&self.resolved_client_capabilities
}
pub(crate) fn client_settings(&self) -> &settings::ResolvedClientSettings {
&self.client_settings
}
pub(crate) fn document(&self) -> &workspace::DocumentRef {
&self.document_ref
}
pub(crate) fn encoding(&self) -> PositionEncoding {
self.position_encoding
}
pub(crate) fn url(&self) -> &Url {
&self.url
}
}