diff --git a/crates/red_knot_server/src/server.rs b/crates/red_knot_server/src/server.rs index ef19915a5be50..5f198cbb4286c 100644 --- a/crates/red_knot_server/src/server.rs +++ b/crates/red_knot_server/src/server.rs @@ -6,7 +6,8 @@ use std::panic::PanicInfo; use lsp_server::Message; use lsp_types::{ ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, MessageType, - ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncOptions, Url, + ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, + Url, }; use self::connection::{Connection, ConnectionInitializer}; @@ -220,6 +221,7 @@ impl Server { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { open_close: Some(true), + change: Some(TextDocumentSyncKind::INCREMENTAL), ..Default::default() }, )), diff --git a/crates/red_knot_server/src/server/api.rs b/crates/red_knot_server/src/server/api.rs index f1a838cba44e3..67e65e32ba080 100644 --- a/crates/red_knot_server/src/server/api.rs +++ b/crates/red_knot_server/src/server/api.rs @@ -42,6 +42,7 @@ pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { match notif.method.as_str() { notification::DidCloseTextDocumentHandler::METHOD => local_notification_task::(notif), notification::DidOpenTextDocumentHandler::METHOD => local_notification_task::(notif), + notification::DidChangeTextDocumentHandler::METHOD => local_notification_task::(notif), notification::DidOpenNotebookHandler::METHOD => { local_notification_task::(notif) } diff --git a/crates/red_knot_server/src/server/api/notifications.rs b/crates/red_knot_server/src/server/api/notifications.rs index eef4bd17a2f1a..33429547d4da6 100644 --- a/crates/red_knot_server/src/server/api/notifications.rs +++ b/crates/red_knot_server/src/server/api/notifications.rs @@ -1,9 +1,11 @@ +mod did_change; mod did_close; mod did_close_notebook; mod did_open; mod did_open_notebook; mod set_trace; +pub(super) use did_change::DidChangeTextDocumentHandler; pub(super) use did_close::DidCloseTextDocumentHandler; pub(super) use did_close_notebook::DidCloseNotebookHandler; pub(super) use did_open::DidOpenTextDocumentHandler; diff --git a/crates/red_knot_server/src/server/api/notifications/did_change.rs b/crates/red_knot_server/src/server/api/notifications/did_change.rs new file mode 100644 index 0000000000000..9fe67bb9826c1 --- /dev/null +++ b/crates/red_knot_server/src/server/api/notifications/did_change.rs @@ -0,0 +1,47 @@ +use lsp_server::ErrorCode; +use lsp_types::notification::DidChangeTextDocument; +use lsp_types::DidChangeTextDocumentParams; + +use red_knot_workspace::watch::ChangeEvent; + +use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; +use crate::server::api::LSPResult; +use crate::server::client::{Notifier, Requester}; +use crate::server::Result; +use crate::session::Session; +use crate::system::url_to_system_path; + +pub(crate) struct DidChangeTextDocumentHandler; + +impl NotificationHandler for DidChangeTextDocumentHandler { + type NotificationType = DidChangeTextDocument; +} + +impl SyncNotificationHandler for DidChangeTextDocumentHandler { + fn run( + session: &mut Session, + _notifier: Notifier, + _requester: &mut Requester, + params: DidChangeTextDocumentParams, + ) -> Result<()> { + let Ok(path) = url_to_system_path(¶ms.text_document.uri) else { + return Ok(()); + }; + + let key = session.key_from_url(params.text_document.uri); + + session + .update_text_document(&key, params.content_changes, params.text_document.version) + .with_failure_code(ErrorCode::InternalError)?; + + let db = match session.workspace_db_for_path_mut(path.as_std_path()) { + Some(db) => db, + None => session.default_workspace_db_mut(), + }; + db.apply_changes(vec![ChangeEvent::file_content_changed(path)], None); + + // TODO(dhruvmanila): Publish diagnostics if the client doesnt support pull diagnostics + + Ok(()) + } +} diff --git a/crates/red_knot_server/src/server/api/requests/diagnostic.rs b/crates/red_knot_server/src/server/api/requests/diagnostic.rs index 44998b435e591..c7496907c6ca6 100644 --- a/crates/red_knot_server/src/server/api/requests/diagnostic.rs +++ b/crates/red_knot_server/src/server/api/requests/diagnostic.rs @@ -46,10 +46,19 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &RootDatabase) -> Vec { let Some(file) = snapshot.file(db) else { + tracing::info!( + "No file found for snapshot for '{}'", + snapshot.query().file_url() + ); return vec![]; }; - let Ok(diagnostics) = db.check_file(file) else { - return vec![]; + + let diagnostics = match db.check_file(file) { + Ok(diagnostics) => diagnostics, + Err(cancelled) => { + tracing::info!("Diagnostics computation {cancelled}"); + return vec![]; + } }; diagnostics diff --git a/crates/red_knot_server/src/session.rs b/crates/red_knot_server/src/session.rs index bc4245c13215a..b1e489254eaf6 100644 --- a/crates/red_knot_server/src/session.rs +++ b/crates/red_knot_server/src/session.rs @@ -6,14 +6,14 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::anyhow; -use lsp_types::{ClientCapabilities, Url}; +use lsp_types::{ClientCapabilities, TextDocumentContentChangeEvent, Url}; use red_knot_workspace::db::RootDatabase; use red_knot_workspace::workspace::WorkspaceMetadata; use ruff_db::files::{system_path_to_file, File}; use ruff_db::system::SystemPath; -use crate::edit::{DocumentKey, NotebookDocument}; +use crate::edit::{DocumentKey, DocumentVersion, NotebookDocument}; use crate::system::{url_to_system_path, LSPSystem}; use crate::{PositionEncoding, TextDocument}; @@ -146,6 +146,20 @@ impl Session { self.index_mut().open_text_document(url, document); } + /// Updates a text document at the associated `key`. + /// + /// The document key must point to a text document, or this will throw an error. + pub(crate) fn update_text_document( + &mut self, + key: &DocumentKey, + content_changes: Vec, + new_version: DocumentVersion, + ) -> crate::Result<()> { + let position_encoding = self.position_encoding; + self.index_mut() + .update_text_document(key, content_changes, new_version, position_encoding) + } + /// De-registers a document, specified by its key. /// Calling this multiple times for the same document is a logic error. pub(crate) fn close_document(&mut self, key: &DocumentKey) -> crate::Result<()> { diff --git a/crates/red_knot_workspace/src/watch.rs b/crates/red_knot_workspace/src/watch.rs index b0e6c805e9e22..fbfe638d9832a 100644 --- a/crates/red_knot_workspace/src/watch.rs +++ b/crates/red_knot_workspace/src/watch.rs @@ -49,6 +49,16 @@ pub enum ChangeEvent { } impl ChangeEvent { + /// Creates a new [`Changed`] event for the file content at the given path. + /// + /// [`Changed`]: ChangeEvent::Changed + pub fn file_content_changed(path: SystemPathBuf) -> ChangeEvent { + ChangeEvent::Changed { + path, + kind: ChangedKind::FileContent, + } + } + pub fn file_name(&self) -> Option<&str> { self.path().and_then(|path| path.file_name()) }