mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[ty] Add basic file watching to server (#17912)
This commit is contained in:
parent
51e2effd2d
commit
51386b3c7a
7 changed files with 203 additions and 14 deletions
|
@ -7,7 +7,8 @@ use std::panic::PanicInfo;
|
|||
|
||||
use lsp_server::Message;
|
||||
use lsp_types::{
|
||||
ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, HoverProviderCapability,
|
||||
ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities,
|
||||
DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher, HoverProviderCapability,
|
||||
InlayHintOptions, InlayHintServerCapabilities, MessageType, ServerCapabilities,
|
||||
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
|
||||
TypeDefinitionProviderCapability, Url,
|
||||
|
@ -24,6 +25,7 @@ mod connection;
|
|||
mod schedule;
|
||||
|
||||
use crate::message::try_show_message;
|
||||
use crate::server::schedule::Task;
|
||||
pub(crate) use connection::ClientSender;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, api::Error>;
|
||||
|
@ -170,13 +172,80 @@ impl Server {
|
|||
|
||||
fn event_loop(
|
||||
connection: &Connection,
|
||||
_client_capabilities: &ClientCapabilities,
|
||||
client_capabilities: &ClientCapabilities,
|
||||
mut session: Session,
|
||||
worker_threads: NonZeroUsize,
|
||||
) -> crate::Result<()> {
|
||||
let mut scheduler =
|
||||
schedule::Scheduler::new(&mut session, worker_threads, connection.make_sender());
|
||||
|
||||
let fs_watcher = client_capabilities
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| workspace.did_change_watched_files?.dynamic_registration)
|
||||
.unwrap_or_default();
|
||||
|
||||
if fs_watcher {
|
||||
let registration = lsp_types::Registration {
|
||||
id: "workspace/didChangeWatchedFiles".to_owned(),
|
||||
method: "workspace/didChangeWatchedFiles".to_owned(),
|
||||
register_options: Some(
|
||||
serde_json::to_value(DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: vec![
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String("**/ty.toml".into()),
|
||||
kind: None,
|
||||
},
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String(
|
||||
"**/.gitignore".into(),
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String("**/.ignore".into()),
|
||||
kind: None,
|
||||
},
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String(
|
||||
"**/pyproject.toml".into(),
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String("**/*.py".into()),
|
||||
kind: None,
|
||||
},
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String("**/*.pyi".into()),
|
||||
kind: None,
|
||||
},
|
||||
FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String("**/*.ipynb".into()),
|
||||
kind: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
};
|
||||
let response_handler = |()| {
|
||||
tracing::info!("File watcher successfully registered");
|
||||
Task::nothing()
|
||||
};
|
||||
|
||||
if let Err(err) = scheduler.request::<lsp_types::request::RegisterCapability>(
|
||||
lsp_types::RegistrationParams {
|
||||
registrations: vec![registration],
|
||||
},
|
||||
response_handler,
|
||||
) {
|
||||
tracing::error!("An error occurred when trying to register the configuration file watcher: {err}");
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("The client does not support file system watching.");
|
||||
}
|
||||
|
||||
for msg in connection.incoming() {
|
||||
if connection.handle_shutdown(&msg)? {
|
||||
break;
|
||||
|
|
|
@ -74,6 +74,9 @@ pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> {
|
|||
notification::DidCloseNotebookHandler::METHOD => {
|
||||
local_notification_task::<notification::DidCloseNotebookHandler>(notif)
|
||||
}
|
||||
notification::DidChangeWatchedFiles::METHOD => {
|
||||
local_notification_task::<notification::DidChangeWatchedFiles>(notif)
|
||||
}
|
||||
lsp_types::notification::SetTrace::METHOD => {
|
||||
tracing::trace!("Ignoring `setTrace` notification");
|
||||
return Task::nothing();
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
mod did_change;
|
||||
mod did_change_watched_files;
|
||||
mod did_close;
|
||||
mod did_close_notebook;
|
||||
mod did_open;
|
||||
mod did_open_notebook;
|
||||
|
||||
pub(super) use did_change::DidChangeTextDocumentHandler;
|
||||
pub(super) use did_change_watched_files::DidChangeWatchedFiles;
|
||||
pub(super) use did_close::DidCloseTextDocumentHandler;
|
||||
pub(super) use did_close_notebook::DidCloseNotebookHandler;
|
||||
pub(super) use did_open::DidOpenTextDocumentHandler;
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler};
|
||||
use crate::server::api::LSPResult;
|
||||
use crate::server::client::{Notifier, Requester};
|
||||
use crate::server::schedule::Task;
|
||||
use crate::server::Result;
|
||||
use crate::session::Session;
|
||||
use crate::system::{url_to_any_system_path, AnySystemPath};
|
||||
use lsp_types as types;
|
||||
use lsp_types::{notification as notif, FileChangeType};
|
||||
use rustc_hash::FxHashMap;
|
||||
use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind};
|
||||
use ty_project::Db;
|
||||
|
||||
pub(crate) struct DidChangeWatchedFiles;
|
||||
|
||||
impl NotificationHandler for DidChangeWatchedFiles {
|
||||
type NotificationType = notif::DidChangeWatchedFiles;
|
||||
}
|
||||
|
||||
impl SyncNotificationHandler for DidChangeWatchedFiles {
|
||||
fn run(
|
||||
session: &mut Session,
|
||||
_notifier: Notifier,
|
||||
requester: &mut Requester,
|
||||
params: types::DidChangeWatchedFilesParams,
|
||||
) -> Result<()> {
|
||||
let mut events_by_db: FxHashMap<_, Vec<ChangeEvent>> = FxHashMap::default();
|
||||
|
||||
for change in params.changes {
|
||||
let path = match url_to_any_system_path(&change.uri) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to convert URI '{}` to system path: {err:?}",
|
||||
change.uri
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let system_path = match path {
|
||||
AnySystemPath::System(system) => system,
|
||||
AnySystemPath::SystemVirtual(path) => {
|
||||
tracing::debug!("Ignoring virtual path from change event: `{path}`");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(db) = session.project_db_for_path(system_path.as_std_path()) else {
|
||||
tracing::trace!(
|
||||
"Ignoring change event for `{system_path}` because it's not in any workspace"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let change_event = match change.typ {
|
||||
FileChangeType::CREATED => ChangeEvent::Created {
|
||||
path: system_path,
|
||||
kind: CreatedKind::Any,
|
||||
},
|
||||
FileChangeType::CHANGED => ChangeEvent::Changed {
|
||||
path: system_path,
|
||||
kind: ChangedKind::Any,
|
||||
},
|
||||
FileChangeType::DELETED => ChangeEvent::Deleted {
|
||||
path: system_path,
|
||||
kind: DeletedKind::Any,
|
||||
},
|
||||
_ => {
|
||||
tracing::debug!(
|
||||
"Ignoring unsupported change event type: `{:?}` for {system_path}",
|
||||
change.typ
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
events_by_db
|
||||
.entry(db.project().root(db).to_path_buf())
|
||||
.or_default()
|
||||
.push(change_event);
|
||||
}
|
||||
|
||||
if events_by_db.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (root, changes) in events_by_db {
|
||||
tracing::debug!("Applying changes to `{root}`");
|
||||
let db = session.project_db_for_path_mut(&*root).unwrap();
|
||||
|
||||
db.apply_changes(changes, None);
|
||||
}
|
||||
|
||||
let client_capabilities = session.client_capabilities();
|
||||
|
||||
if client_capabilities.diagnostics_refresh {
|
||||
requester
|
||||
.request::<types::request::WorkspaceDiagnosticRefresh>((), |()| Task::nothing())
|
||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||
}
|
||||
|
||||
if client_capabilities.inlay_refresh {
|
||||
requester
|
||||
.request::<types::request::InlayHintRefreshRequest>((), |()| Task::nothing())
|
||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -57,7 +57,6 @@ impl<'s> Scheduler<'s> {
|
|||
/// Immediately sends a request of kind `R` to the client, with associated parameters.
|
||||
/// The task provided by `response_handler` will be dispatched as soon as the response
|
||||
/// comes back from the client.
|
||||
#[expect(dead_code)]
|
||||
pub(super) fn request<R>(
|
||||
&mut self,
|
||||
params: R::Params,
|
||||
|
|
|
@ -210,6 +210,10 @@ impl Session {
|
|||
index,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn client_capabilities(&self) -> &ResolvedClientCapabilities {
|
||||
&self.resolved_client_capabilities
|
||||
}
|
||||
}
|
||||
|
||||
/// A guard that holds the only reference to the index and allows modifying it.
|
||||
|
|
|
@ -6,7 +6,8 @@ pub(crate) struct ResolvedClientCapabilities {
|
|||
pub(crate) code_action_deferred_edit_resolution: bool,
|
||||
pub(crate) apply_edit: bool,
|
||||
pub(crate) document_changes: bool,
|
||||
pub(crate) workspace_refresh: bool,
|
||||
pub(crate) diagnostics_refresh: bool,
|
||||
pub(crate) inlay_refresh: bool,
|
||||
pub(crate) pull_diagnostics: bool,
|
||||
/// Whether `textDocument.typeDefinition.linkSupport` is `true`
|
||||
pub(crate) type_definition_link_support: bool,
|
||||
|
@ -47,18 +48,17 @@ impl ResolvedClientCapabilities {
|
|||
.and_then(|document| document.type_definition?.link_support)
|
||||
.unwrap_or_default();
|
||||
|
||||
let workspace_refresh = true;
|
||||
|
||||
// TODO(jane): Once the bug involving workspace.diagnostic(s) deserialization has been fixed,
|
||||
// uncomment this.
|
||||
/*
|
||||
let workspace_refresh = client_capabilities
|
||||
let diagnostics_refresh = client_capabilities
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| workspace.diagnostic.as_ref())
|
||||
.and_then(|diagnostic| diagnostic.refresh_support)
|
||||
.and_then(|workspace| workspace.diagnostics.as_ref()?.refresh_support)
|
||||
.unwrap_or_default();
|
||||
|
||||
let inlay_refresh = client_capabilities
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| workspace.inlay_hint.as_ref()?.refresh_support)
|
||||
.unwrap_or_default();
|
||||
*/
|
||||
|
||||
let pull_diagnostics = client_capabilities
|
||||
.text_document
|
||||
|
@ -86,7 +86,8 @@ impl ResolvedClientCapabilities {
|
|||
&& code_action_edit_resolution,
|
||||
apply_edit,
|
||||
document_changes,
|
||||
workspace_refresh,
|
||||
diagnostics_refresh,
|
||||
inlay_refresh,
|
||||
pull_diagnostics,
|
||||
type_definition_link_support: declaration_link_support,
|
||||
hover_prefer_markdown,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue