ruff/crates/ruff_server/src/session.rs
Jane Lewis cffc55576f
ruff server: Resolve configuration for each document individually (#10950)
## Summary

Configuration is no longer the property of a workspace but rather of
individual documents. Just like the Ruff CLI, each document is
configured based on the 'nearest' project configuration. See [the Ruff
documentation](https://docs.astral.sh/ruff/configuration/#config-file-discovery)
for more details.

To reduce the amount of times we resolve configuration for a file, we
have an index for each workspace that stores a reference-counted pointer
to a configuration for a given folder. If another file in the same
folder is opened, the configuration is simply re-used rather than us
re-resolving it.

## Guide for reviewing

The first commit is just the restructuring work, which adds some noise
to the diff. If you want to quickly understand what's actually changed,
I recommend looking at the two commits that come after it.
f7c073d441 makes configuration a property
of `DocumentController`/`DocumentRef`, moving it out of `Workspace`, and
it also sets up the `ConfigurationIndex`, though it doesn't implement
its key function, `get_or_insert`. In the commit after it,
fc35618f17, we implement `get_or_insert`.

## Test Plan

The best way to test this would be to ensure that the behavior matches
the Ruff CLI. Open a project with multiple configuration files (or add
them yourself), and then introduce problems in certain files that won't
show due to their configuration. Add those same problems to a section of
the project where those rules are run. Confirm that the lint rules are
run as expected with `ruff check`. Then, open your editor and confirm
that the diagnostics shown match the CLI output.

As an example - I have a workspace with two separate folders, `pandas`
and `scipy`. I created a `pyproject.toml` file in `pandas/pandas/io` and
a `ruff.toml` file in `pandas/pandas/api`. I changed the `select` and
`preview` settings in the sub-folder configuration files and confirmed
that these were reflected in the diagnostics. I also confirmed that this
did not change the diagnostics for the `scipy` folder whatsoever.
2024-04-16 18:15:02 +00:00

132 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,
global_settings,
resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new(
client_capabilities,
)),
workspaces: workspace::Workspaces::new(workspaces)?,
})
}
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)?;
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
}
}