Skip to content

Commit

Permalink
[red-knot] Support untitled files in the server
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvmanila committed Aug 22, 2024
1 parent b7c8fe2 commit a2a7562
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 42 deletions.
18 changes: 13 additions & 5 deletions crates/red_knot_server/src/server/api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{server::schedule::Task, session::Session, system::url_to_system_path};
use lsp_server as server;

use crate::server::schedule::Task;
use crate::session::Session;
use crate::system::{url_to_any_system_path, AnySystemPath};

mod diagnostics;
mod notifications;
mod requests;
Expand Down Expand Up @@ -82,12 +85,17 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
Ok(Task::background(schedule, move |session: &Session| {
let url = R::document_url(&params).into_owned();

let Ok(path) = url_to_system_path(&url) else {
let Ok(path) = url_to_any_system_path(&url) else {
return Box::new(|_, _| {});
};
let db = match session.workspace_db_for_path(path.as_std_path()) {
Some(db) => db.snapshot(),
None => session.default_workspace_db().snapshot(),
let db = match path {
AnySystemPath::System(path) => {
match session.workspace_db_for_path(path.as_std_path()) {
Some(db) => db.snapshot(),
None => session.default_workspace_db().snapshot(),
}
}
AnySystemPath::SystemVirtual(_) => session.default_workspace_db().snapshot(),
};

let Some(snapshot) = session.take_snapshot(url) else {
Expand Down
22 changes: 15 additions & 7 deletions crates/red_knot_server/src/server/api/notifications/did_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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;
use crate::system::{url_to_any_system_path, AnySystemPath};

pub(crate) struct DidChangeTextDocumentHandler;

Expand All @@ -24,7 +24,7 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler {
_requester: &mut Requester,
params: DidChangeTextDocumentParams,
) -> Result<()> {
let Ok(path) = url_to_system_path(&params.text_document.uri) else {
let Ok(path) = url_to_any_system_path(&params.text_document.uri) else {
return Ok(());
};

Expand All @@ -34,11 +34,19 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler {
.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);
match path {
AnySystemPath::System(path) => {
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);
}
AnySystemPath::SystemVirtual(virtual_path) => {
let db = session.default_workspace_db_mut();
db.apply_changes(vec![ChangeEvent::ChangedVirtual(virtual_path)], None);
}
}

// TODO(dhruvmanila): Publish diagnostics if the client doesnt support pull diagnostics

Expand Down
10 changes: 8 additions & 2 deletions crates/red_knot_server/src/server/api/notifications/did_close.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use lsp_server::ErrorCode;
use lsp_types::notification::DidCloseTextDocument;
use lsp_types::DidCloseTextDocumentParams;
use red_knot_workspace::watch::ChangeEvent;

use crate::server::api::diagnostics::clear_diagnostics;
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;
use crate::system::{url_to_any_system_path, AnySystemPath};

pub(crate) struct DidCloseTextDocumentHandler;

Expand All @@ -23,7 +24,7 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler {
_requester: &mut Requester,
params: DidCloseTextDocumentParams,
) -> Result<()> {
let Ok(_path) = url_to_system_path(&params.text_document.uri) else {
let Ok(path) = url_to_any_system_path(&params.text_document.uri) else {
return Ok(());
};

Expand All @@ -32,6 +33,11 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler {
.close_document(&key)
.with_failure_code(ErrorCode::InternalError)?;

if let AnySystemPath::SystemVirtual(virtual_path) = path {
let db = session.default_workspace_db_mut();
db.apply_changes(vec![ChangeEvent::DeletedVirtual(virtual_path)], None);
}

clear_diagnostics(key.url(), &notifier)?;

Ok(())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use lsp_types::notification::DidCloseNotebookDocument;
use lsp_types::DidCloseNotebookDocumentParams;

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;
use crate::system::{url_to_any_system_path, AnySystemPath};

pub(crate) struct DidCloseNotebookHandler;

Expand All @@ -21,7 +23,7 @@ impl SyncNotificationHandler for DidCloseNotebookHandler {
_requester: &mut Requester,
params: DidCloseNotebookDocumentParams,
) -> Result<()> {
let Ok(_path) = url_to_system_path(&params.notebook_document.uri) else {
let Ok(path) = url_to_any_system_path(&params.notebook_document.uri) else {
return Ok(());
};

Expand All @@ -30,6 +32,11 @@ impl SyncNotificationHandler for DidCloseNotebookHandler {
.close_document(&key)
.with_failure_code(lsp_server::ErrorCode::InternalError)?;

if let AnySystemPath::SystemVirtual(virtual_path) = path {
let db = session.default_workspace_db_mut();
db.apply_changes(vec![ChangeEvent::DeletedVirtual(virtual_path)], None);
}

Ok(())
}
}
23 changes: 16 additions & 7 deletions crates/red_knot_server/src/server/api/notifications/did_open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use lsp_types::notification::DidOpenTextDocument;
use lsp_types::DidOpenTextDocumentParams;

use red_knot_workspace::watch::ChangeEvent;
use ruff_db::Db;

use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler};
use crate::server::client::{Notifier, Requester};
use crate::server::Result;
use crate::session::Session;
use crate::system::url_to_system_path;
use crate::system::{url_to_any_system_path, AnySystemPath};
use crate::TextDocument;

pub(crate) struct DidOpenTextDocumentHandler;
Expand All @@ -23,18 +24,26 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler {
_requester: &mut Requester,
params: DidOpenTextDocumentParams,
) -> Result<()> {
let Ok(path) = url_to_system_path(&params.text_document.uri) else {
let Ok(path) = url_to_any_system_path(&params.text_document.uri) else {
return Ok(());
};

let document = TextDocument::new(params.text_document.text, params.text_document.version);
session.open_text_document(params.text_document.uri, document);

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_created(path)], None);
match path {
AnySystemPath::System(path) => {
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_created(path)], None);
}
AnySystemPath::SystemVirtual(virtual_path) => {
let db = session.default_workspace_db_mut();
db.files().virtual_file(db, &virtual_path);
}
}

// TODO(dhruvmanila): Publish diagnostics if the client doesn't support pull diagnostics

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use lsp_types::notification::DidOpenNotebookDocument;
use lsp_types::DidOpenNotebookDocumentParams;

use red_knot_workspace::watch::ChangeEvent;
use ruff_db::Db;

use crate::edit::NotebookDocument;
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;
use crate::system::{url_to_any_system_path, AnySystemPath};

pub(crate) struct DidOpenNotebookHandler;

Expand All @@ -25,7 +26,7 @@ impl SyncNotificationHandler for DidOpenNotebookHandler {
_requester: &mut Requester,
params: DidOpenNotebookDocumentParams,
) -> Result<()> {
let Ok(path) = url_to_system_path(&params.notebook_document.uri) else {
let Ok(path) = url_to_any_system_path(&params.notebook_document.uri) else {
return Ok(());
};

Expand All @@ -38,11 +39,19 @@ impl SyncNotificationHandler for DidOpenNotebookHandler {
.with_failure_code(ErrorCode::InternalError)?;
session.open_notebook_document(params.notebook_document.uri.clone(), notebook);

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_created(path)], None);
match path {
AnySystemPath::System(path) => {
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_created(path)], None);
}
AnySystemPath::SystemVirtual(virtual_path) => {
let db = session.default_workspace_db_mut();
db.files().virtual_file(db, &virtual_path);
}
}

// TODO(dhruvmanila): Publish diagnostics if the client doesn't support pull diagnostics

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fn to_lsp_diagnostic(message: &str) -> Diagnostic {
let words = message.split(':').collect::<Vec<_>>();

let (range, message) = match words.as_slice() {
[_filename, line, column, message] => {
[_, _, line, column, message] | [_, line, column, message] => {
let line = line.parse::<u32>().unwrap_or_default().saturating_sub(1);
let column = column.parse::<u32>().unwrap_or_default();
(
Expand Down
13 changes: 10 additions & 3 deletions crates/red_knot_server/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ 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 ruff_db::Db;

use crate::edit::{DocumentKey, DocumentVersion, NotebookDocument};
use crate::system::{url_to_system_path, LSPSystem};
use crate::system::{url_to_any_system_path, AnySystemPath, LSPSystem};
use crate::{PositionEncoding, TextDocument};

pub(crate) use self::capabilities::ResolvedClientCapabilities;
Expand Down Expand Up @@ -242,6 +243,7 @@ impl Drop for MutIndexGuard<'_> {

/// An immutable snapshot of `Session` that references
/// a specific document.
#[derive(Debug)]
pub struct DocumentSnapshot {
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
document_ref: index::DocumentQuery,
Expand All @@ -262,7 +264,12 @@ impl DocumentSnapshot {
}

pub(crate) fn file(&self, db: &RootDatabase) -> Option<File> {
let path = url_to_system_path(self.document_ref.file_url()).ok()?;
system_path_to_file(db, path).ok()
match url_to_any_system_path(self.document_ref.file_url()).ok()? {
AnySystemPath::System(path) => system_path_to_file(db, path).ok(),
AnySystemPath::SystemVirtual(virtual_path) => db
.files()
.try_virtual_file(&virtual_path)
.map(|virtual_file| virtual_file.file()),
}
}
}
25 changes: 19 additions & 6 deletions crates/red_knot_server/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,40 @@ use ruff_db::file_revision::FileRevision;
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
use ruff_db::system::{
DirectoryEntry, FileType, Metadata, OsSystem, Result, System, SystemPath, SystemPathBuf,
SystemVirtualPath,
SystemVirtualPath, SystemVirtualPathBuf,
};
use ruff_notebook::{Notebook, NotebookError};

use crate::session::index::Index;
use crate::DocumentQuery;

/// Converts the given [`Url`] to a [`SystemPathBuf`].
/// Converts the given [`Url`] to an [`AnySystemPath`].
///
/// If the URL scheme is `file`, then the path is converted to a [`SystemPathBuf`]. Otherwise, the
/// URL is converted to a [`SystemVirtualPathBuf`].
///
/// This fails in the following cases:
/// * The URL scheme is not `file`.
/// * The URL cannot be converted to a file path (refer to [`Url::to_file_path`]).
/// * If the URL is not a valid UTF-8 string.
pub(crate) fn url_to_system_path(url: &Url) -> std::result::Result<SystemPathBuf, ()> {
pub(crate) fn url_to_any_system_path(url: &Url) -> std::result::Result<AnySystemPath, ()> {
if url.scheme() == "file" {
Ok(SystemPathBuf::from_path_buf(url.to_file_path()?).map_err(|_| ())?)
Ok(AnySystemPath::System(
SystemPathBuf::from_path_buf(url.to_file_path()?).map_err(|_| ())?,
))
} else {
Err(())
Ok(AnySystemPath::SystemVirtual(
SystemVirtualPath::new(url.as_str()).to_path_buf(),
))
}
}

/// Represents either a [`SystemPath`] or a [`SystemVirtualPath`].
#[derive(Debug)]
pub(crate) enum AnySystemPath {
System(SystemPathBuf),
SystemVirtual(SystemVirtualPathBuf),
}

#[derive(Debug)]
pub(crate) struct LSPSystem {
/// A read-only copy of the index where the server stores all the open documents and settings.
Expand Down
11 changes: 11 additions & 0 deletions crates/red_knot_workspace/src/db/changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ impl RootDatabase {
}
}

watch::ChangeEvent::CreatedVirtual(path)
| watch::ChangeEvent::ChangedVirtual(path) => {
File::sync_virtual_path(self, &path);
}

watch::ChangeEvent::DeletedVirtual(path) => {
if let Some(virtual_file) = self.files().try_virtual_file(&path) {
virtual_file.close(self);
}
}

watch::ChangeEvent::Rescan => {
workspace_change = true;
Files::sync_all(self);
Expand Down
13 changes: 11 additions & 2 deletions crates/red_knot_workspace/src/watch.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_db::system::{SystemPath, SystemPathBuf, SystemVirtualPathBuf};
pub use watcher::{directory_watcher, EventHandler, Watcher};
pub use workspace_watcher::WorkspaceWatcher;

Expand Down Expand Up @@ -38,6 +38,15 @@ pub enum ChangeEvent {
kind: DeletedKind,
},

/// A new virtual path was created.
CreatedVirtual(SystemVirtualPathBuf),

/// The content of a virtual path was changed.
ChangedVirtual(SystemVirtualPathBuf),

/// A virtual path was deleted.
DeletedVirtual(SystemVirtualPathBuf),

/// The file watcher failed to observe some changes and now is out of sync with the file system.
///
/// This can happen if many files are changed at once. The consumer should rescan all files to catch up
Expand Down Expand Up @@ -75,7 +84,7 @@ impl ChangeEvent {
ChangeEvent::Created { path, .. }
| ChangeEvent::Changed { path, .. }
| ChangeEvent::Deleted { path, .. } => Some(path),
ChangeEvent::Rescan => None,
_ => None,
}
}
}
Expand Down
Loading

0 comments on commit a2a7562

Please sign in to comment.