Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions crates/oxc_language_server/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tower_lsp_server::{
DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DidSaveTextDocumentParams, DocumentFormattingParams, ExecuteCommandParams,
InitializeParams, InitializeResult, InitializedParams, Registration, ServerInfo, TextEdit,
Unregistration, Uri,
Uri,
},
};

Expand Down Expand Up @@ -218,7 +218,21 @@ impl LanguageServer for Backend {
///
/// See: <https://microsoft.github.io/language-server-protocol/specifications/specification-current/#shutdown>
async fn shutdown(&self) -> Result<()> {
self.clear_all_diagnostics().await;
let mut clearing_diagnostics = Vec::new();
let mut removed_registrations = Vec::new();

for worker in &*self.workspace_workers.read().await {
let (uris, unregistrations) = worker.shutdown().await;
clearing_diagnostics.extend(uris);
removed_registrations.extend(unregistrations);
}
self.clear_diagnostics(clearing_diagnostics).await;
if !removed_registrations.is_empty()
&& let Err(err) = self.client.unregister_capability(removed_registrations).await
{
warn!("sending unregisterCapability.didChangeWatchedFiles failed: {err}");
}

if self.capabilities.get().is_some_and(|option| option.dynamic_formatting) {
self.file_system.write().await.clear();
}
Expand Down Expand Up @@ -387,15 +401,13 @@ impl LanguageServer for Backend {
else {
continue;
};
cleared_diagnostics.extend(worker.get_clear_diagnostics().await);
removed_registrations.push(Unregistration {
id: format!("watcher-{}", worker.get_root_uri().as_str()),
method: "workspace/didChangeWatchedFiles".to_string(),
});
let (uris, unregistrations) = worker.shutdown().await;
cleared_diagnostics.extend(uris);
removed_registrations.extend(unregistrations);
workers.remove(index);
}

self.publish_all_diagnostics(&cleared_diagnostics).await;
self.clear_diagnostics(cleared_diagnostics).await;

// client support `workspace/configuration` request
if self.capabilities.get().is_some_and(|capabilities| capabilities.workspace_configuration)
Expand Down Expand Up @@ -632,14 +644,10 @@ impl Backend {
configs
}

/// Clears all diagnostics for workspace folders
async fn clear_all_diagnostics(&self) {
let mut cleared_diagnostics = vec![];
let workers = &*self.workspace_workers.read().await;
for worker in workers {
cleared_diagnostics.extend(worker.get_clear_diagnostics().await);
}
self.publish_all_diagnostics(&cleared_diagnostics).await;
async fn clear_diagnostics(&self, uris: Vec<Uri>) {
let diagnostics: Vec<(String, Vec<Diagnostic>)> =
uris.into_iter().map(|uri| (uri.to_string(), vec![])).collect();
self.publish_all_diagnostics(&diagnostics).await;
}

/// Publish diagnostics for all files.
Expand Down
10 changes: 8 additions & 2 deletions crates/oxc_language_server/src/linter/server_linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use oxc_linter::{
LintIgnoreMatcher, LintOptions, Oxlintrc,
};

use crate::tool::{Tool, ToolBuilder};
use crate::tool::{Tool, ToolBuilder, ToolShutdownChanges};
use crate::{
ConcurrentHashMap,
linter::{
Expand Down Expand Up @@ -245,6 +245,12 @@ pub struct ServerLinter {
}

impl Tool for ServerLinter {
fn shutdown(&self) -> ToolShutdownChanges {
ToolShutdownChanges {
uris_to_clear_diagnostics: Some(self.get_cached_files_of_diagnostics()),
}
}

/// # Panics
/// Panics if the root URI cannot be converted to a file path.
async fn handle_configuration_change(
Expand Down Expand Up @@ -522,7 +528,7 @@ impl ServerLinter {
None
}

pub fn get_cached_files_of_diagnostics(&self) -> Vec<Uri> {
fn get_cached_files_of_diagnostics(&self) -> Vec<Uri> {
self.diagnostics.pin().keys().filter_map(|s| Uri::from_str(s).ok()).collect()
}

Expand Down
10 changes: 10 additions & 0 deletions crates/oxc_language_server/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ pub trait Tool: Sized {
fn remove_diagnostics(&self, _uri: &Uri) {
// Default implementation does nothing.
}

/// Shutdown the tool and return any necessary changes to be made after shutdown.
fn shutdown(&self) -> ToolShutdownChanges {
ToolShutdownChanges { uris_to_clear_diagnostics: None }
}
}

pub struct ToolRestartChanges<T> {
Expand All @@ -125,3 +130,8 @@ pub struct ToolRestartChanges<T> {
/// Old patterns will be automatically unregistered
pub watch_patterns: Option<Vec<Pattern>>,
}

pub struct ToolShutdownChanges {
/// The URIs that need to have their diagnostics removed after the tool shutdown
pub uris_to_clear_diagnostics: Option<Vec<Uri>>,
}
45 changes: 25 additions & 20 deletions crates/oxc_language_server/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,26 +184,31 @@ impl WorkspaceWorker {
server_formatter.run_format(uri, content)
}

/// Get all clear diagnostics for the current workspace
/// This should be called when:
/// - The linter is disabled (not currently implemented)
/// - The workspace is closed
/// - The server is shut down
///
/// This will return a list of URIs that had diagnostics before, each with an empty diagnostics list
pub async fn get_clear_diagnostics(&self) -> Vec<(String, Vec<Diagnostic>)> {
self.server_linter
.read()
.await
.as_ref()
.map(|server_linter| {
server_linter
.get_cached_files_of_diagnostics()
.iter()
.map(|uri| (uri.to_string(), vec![]))
.collect::<Vec<_>>()
})
.unwrap_or_default()
/// Shutdown the worker and return any necessary changes to be made after shutdown.
/// This includes clearing diagnostics and unregistering file watchers.
pub async fn shutdown(
&self,
) -> (
// The URIs that need to have their diagnostics removed after shutdown
Vec<Uri>,
// Watchers that need to be unregistered
Vec<Unregistration>,
) {
let mut uris_to_clear_diagnostics = Vec::new();

if let Some(server_linter) = &*self.server_linter.read().await
&& let Some(uris) = server_linter.shutdown().uris_to_clear_diagnostics
{
uris_to_clear_diagnostics.extend(uris);
}

(
uris_to_clear_diagnostics,
vec![
unregistration_tool_watcher_id("linter", &self.root_uri),
unregistration_tool_watcher_id("formatter", &self.root_uri),
],
)
}

/// Get code actions or commands for the given range
Expand Down
Loading