Automatic configuration reloading for ruff server (#10404)

## Summary

Fixes #10366.

`ruff server` now registers a file watcher on the client side using the
LSP protocol, and listen for events on configuration files. On such an
event, it reloads the configuration in the 'nearest' workspace to the
file that was changed.

## Test Plan

N/A
This commit is contained in:
Jane Lewis 2024-03-21 13:17:07 -07:00 committed by GitHub
parent 5062572aca
commit 4f06d59ff6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 131 additions and 13 deletions

View file

@ -111,6 +111,10 @@ impl Session {
.ok_or_else(|| anyhow!("Tried to open unavailable document `{url}`"))
}
pub(crate) fn reload_configuration(&mut self, url: &Url) -> crate::Result<()> {
self.workspaces.reload_configuration(url)
}
pub(crate) fn open_workspace_folder(&mut self, url: &Url) -> crate::Result<()> {
self.workspaces.open_workspace_folder(url)?;
Ok(())
@ -231,23 +235,32 @@ impl Workspaces {
}
fn snapshot(&self, document_url: &Url) -> Option<DocumentRef> {
self.workspace_for_url(document_url)
.and_then(|w| w.open_documents.snapshot(document_url))
self.workspace_for_url(document_url)?
.open_documents
.snapshot(document_url)
}
fn controller(&mut self, document_url: &Url) -> Option<&mut DocumentController> {
self.workspace_for_url_mut(document_url)
.and_then(|w| w.open_documents.controller(document_url))
self.workspace_for_url_mut(document_url)?
.open_documents
.controller(document_url)
}
fn configuration(&self, document_url: &Url) -> Option<&Arc<RuffConfiguration>> {
self.workspace_for_url(document_url)
.map(|w| &w.configuration)
Some(&self.workspace_for_url(document_url)?.configuration)
}
fn reload_configuration(&mut self, changed_url: &Url) -> crate::Result<()> {
let (path, workspace) = self
.entry_for_url_mut(changed_url)
.ok_or_else(|| anyhow!("Workspace not found for {changed_url}"))?;
workspace.reload_configuration(path);
Ok(())
}
fn open(&mut self, url: &Url, contents: String, version: DocumentVersion) {
if let Some(w) = self.workspace_for_url_mut(url) {
w.open_documents.open(url, contents, version);
if let Some(workspace) = self.workspace_for_url_mut(url) {
workspace.open_documents.open(url, contents, version);
}
}
@ -259,19 +272,27 @@ impl Workspaces {
}
fn workspace_for_url(&self, url: &Url) -> Option<&Workspace> {
Some(self.entry_for_url(url)?.1)
}
fn workspace_for_url_mut(&mut self, url: &Url) -> Option<&mut Workspace> {
Some(self.entry_for_url_mut(url)?.1)
}
fn entry_for_url(&self, url: &Url) -> Option<(&Path, &Workspace)> {
let path = url.to_file_path().ok()?;
self.0
.range(..path)
.next_back()
.map(|(_, workspace)| workspace)
.map(|(path, workspace)| (path.as_path(), workspace))
}
fn workspace_for_url_mut(&mut self, url: &Url) -> Option<&mut Workspace> {
fn entry_for_url_mut(&mut self, url: &Url) -> Option<(&Path, &mut Workspace)> {
let path = url.to_file_path().ok()?;
self.0
.range_mut(..path)
.next_back()
.map(|(_, workspace)| workspace)
.map(|(path, workspace)| (path.as_path(), workspace))
}
}
@ -292,6 +313,10 @@ impl Workspace {
))
}
fn reload_configuration(&mut self, path: &Path) {
self.configuration = Arc::new(Self::find_configuration_or_fallback(path));
}
fn find_configuration_or_fallback(root: &Path) -> RuffConfiguration {
find_configuration_from_root(root).unwrap_or_else(|err| {
tracing::error!("The following error occurred when trying to find a configuration file at `{}`:\n{err}", root.display());