mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 22:31:23 +00:00
[ty] Make sure to always respond to client requests (#19277)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary This PR fixes a bug that didn't return a response to the client if the document snapshotting failed. This is resolved by making sure that the server always creates the document snapshot and embed the any failures inside the snapshot. Closes: astral-sh/ty#798 ## Test Plan Using the test case as described in the linked issue: https://github.com/user-attachments/assets/f32833f8-03e5-4641-8c7f-2a536fe2e270
This commit is contained in:
parent
39c6364545
commit
fd69533fe5
21 changed files with 243 additions and 144 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4272,6 +4272,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
|
"thiserror 2.0.12",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"ty_ide",
|
"ty_ide",
|
||||||
|
|
|
@ -31,6 +31,7 @@ salsa = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
shellexpand = { workspace = true }
|
shellexpand = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true, features = ["chrono"] }
|
tracing-subscriber = { workspace = true, features = ["chrono"] }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::server::{ConnectionInitializer, Server};
|
use crate::server::{ConnectionInitializer, Server};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
pub use document::{NotebookDocument, PositionEncoding, TextDocument};
|
pub use document::{NotebookDocument, PositionEncoding, TextDocument};
|
||||||
pub use session::{DocumentQuery, DocumentSnapshot, Session};
|
pub(crate) use session::{DocumentQuery, Session};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
mod document;
|
mod document;
|
||||||
|
|
|
@ -218,8 +218,22 @@ where
|
||||||
let url = R::document_url(¶ms).into_owned();
|
let url = R::document_url(¶ms).into_owned();
|
||||||
|
|
||||||
let Ok(path) = AnySystemPath::try_from_url(&url) else {
|
let Ok(path) = AnySystemPath::try_from_url(&url) else {
|
||||||
tracing::warn!("Ignoring request for invalid `{url}`");
|
let reason = format!("URL `{url}` isn't a valid system path");
|
||||||
return Box::new(|_| {});
|
tracing::warn!(
|
||||||
|
"Ignoring request id={id} method={} because {reason}",
|
||||||
|
R::METHOD
|
||||||
|
);
|
||||||
|
return Box::new(|client| {
|
||||||
|
respond_silent_error(
|
||||||
|
id,
|
||||||
|
client,
|
||||||
|
lsp_server::ResponseError {
|
||||||
|
code: lsp_server::ErrorCode::InvalidParams as i32,
|
||||||
|
message: reason,
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = match &path {
|
let db = match &path {
|
||||||
|
@ -230,10 +244,7 @@ where
|
||||||
AnySystemPath::SystemVirtual(_) => session.default_project_db().clone(),
|
AnySystemPath::SystemVirtual(_) => session.default_project_db().clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(snapshot) = session.take_document_snapshot(url) else {
|
let snapshot = session.take_document_snapshot(url);
|
||||||
tracing::warn!("Ignoring request because snapshot for path `{path:?}` doesn't exist");
|
|
||||||
return Box::new(|_| {});
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::new(move |client| {
|
Box::new(move |client| {
|
||||||
let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered();
|
let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered();
|
||||||
|
@ -331,12 +342,7 @@ where
|
||||||
let (id, params) = cast_notification::<N>(req)?;
|
let (id, params) = cast_notification::<N>(req)?;
|
||||||
Ok(Task::background(schedule, move |session: &Session| {
|
Ok(Task::background(schedule, move |session: &Session| {
|
||||||
let url = N::document_url(¶ms);
|
let url = N::document_url(¶ms);
|
||||||
let Some(snapshot) = session.take_document_snapshot((*url).clone()) else {
|
let snapshot = session.take_document_snapshot((*url).clone());
|
||||||
tracing::debug!(
|
|
||||||
"Ignoring notification because snapshot for url `{url}` doesn't exist."
|
|
||||||
);
|
|
||||||
return Box::new(|_| {});
|
|
||||||
};
|
|
||||||
Box::new(move |client| {
|
Box::new(move |client| {
|
||||||
let _span = tracing::debug_span!("notification", method = N::METHOD).entered();
|
let _span = tracing::debug_span!("notification", method = N::METHOD).entered();
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,10 @@ use ruff_db::files::FileRange;
|
||||||
use ruff_db::source::{line_index, source_text};
|
use ruff_db::source::{line_index, source_text};
|
||||||
use ty_project::{Db, ProjectDatabase};
|
use ty_project::{Db, ProjectDatabase};
|
||||||
|
|
||||||
use super::LSPResult;
|
|
||||||
use crate::document::{DocumentKey, FileRangeExt, ToRangeExt};
|
use crate::document::{DocumentKey, FileRangeExt, ToRangeExt};
|
||||||
use crate::server::Result;
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use crate::{DocumentSnapshot, PositionEncoding, Session};
|
use crate::{PositionEncoding, Session};
|
||||||
|
|
||||||
/// Represents the diagnostics for a text document or a notebook document.
|
/// Represents the diagnostics for a text document or a notebook document.
|
||||||
pub(super) enum Diagnostics {
|
pub(super) enum Diagnostics {
|
||||||
|
@ -64,30 +63,29 @@ pub(super) fn clear_diagnostics(key: &DocumentKey, client: &Client) {
|
||||||
/// This function is a no-op if the client supports pull diagnostics.
|
/// This function is a no-op if the client supports pull diagnostics.
|
||||||
///
|
///
|
||||||
/// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics
|
/// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics
|
||||||
pub(super) fn publish_diagnostics(
|
pub(super) fn publish_diagnostics(session: &Session, key: &DocumentKey, client: &Client) {
|
||||||
session: &Session,
|
|
||||||
key: &DocumentKey,
|
|
||||||
client: &Client,
|
|
||||||
) -> Result<()> {
|
|
||||||
if session.client_capabilities().pull_diagnostics {
|
if session.client_capabilities().pull_diagnostics {
|
||||||
return Ok(());
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(url) = key.to_url() else {
|
let Some(url) = key.to_url() else {
|
||||||
return Ok(());
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = key.path();
|
let snapshot = session.take_document_snapshot(url.clone());
|
||||||
|
|
||||||
let snapshot = session
|
let document = match snapshot.document() {
|
||||||
.take_document_snapshot(url.clone())
|
Ok(document) => document,
|
||||||
.ok_or_else(|| anyhow::anyhow!("Unable to take snapshot for document with URL {url}"))
|
Err(err) => {
|
||||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
tracing::debug!("Failed to resolve document for URL `{}`: {}", url, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let db = session.project_db_or_default(path);
|
let db = session.project_db_or_default(key.path());
|
||||||
|
|
||||||
let Some(diagnostics) = compute_diagnostics(db, &snapshot) else {
|
let Some(diagnostics) = compute_diagnostics(db, &snapshot) else {
|
||||||
return Ok(());
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sends a notification to the client with the diagnostics for the document.
|
// Sends a notification to the client with the diagnostics for the document.
|
||||||
|
@ -95,7 +93,7 @@ pub(super) fn publish_diagnostics(
|
||||||
client.send_notification::<PublishDiagnostics>(PublishDiagnosticsParams {
|
client.send_notification::<PublishDiagnostics>(PublishDiagnosticsParams {
|
||||||
uri,
|
uri,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
version: Some(snapshot.query().version()),
|
version: Some(document.version()),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,25 +107,28 @@ pub(super) fn publish_diagnostics(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn compute_diagnostics(
|
pub(super) fn compute_diagnostics(
|
||||||
db: &ProjectDatabase,
|
db: &ProjectDatabase,
|
||||||
snapshot: &DocumentSnapshot,
|
snapshot: &DocumentSnapshot,
|
||||||
) -> Option<Diagnostics> {
|
) -> Option<Diagnostics> {
|
||||||
let Some(file) = snapshot.file(db) else {
|
let document = match snapshot.document() {
|
||||||
tracing::info!(
|
Ok(document) => document,
|
||||||
"No file found for snapshot for `{}`",
|
Err(err) => {
|
||||||
snapshot.query().file_url()
|
tracing::info!("Failed to resolve document for snapshot: {}", err);
|
||||||
);
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(file) = document.file(db) else {
|
||||||
|
tracing::info!("No file found for snapshot for `{}`", document.file_path());
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let diagnostics = db.check_file(file);
|
let diagnostics = db.check_file(file);
|
||||||
|
|
||||||
if let Some(notebook) = snapshot.query().as_notebook() {
|
if let Some(notebook) = document.as_notebook() {
|
||||||
let mut cell_diagnostics: FxHashMap<Url, Vec<Diagnostic>> = FxHashMap::default();
|
let mut cell_diagnostics: FxHashMap<Url, Vec<Diagnostic>> = FxHashMap::default();
|
||||||
|
|
||||||
// Populates all relevant URLs with an empty diagnostic list. This ensures that documents
|
// Populates all relevant URLs with an empty diagnostic list. This ensures that documents
|
||||||
|
|
|
@ -28,9 +28,12 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler {
|
||||||
content_changes,
|
content_changes,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let Ok(key) = session.key_from_url(uri.clone()) else {
|
let key = match session.key_from_url(uri) {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(uri) => {
|
||||||
tracing::debug!("Failed to create document key from URI: {}", uri);
|
tracing::debug!("Failed to create document key from URI: {}", uri);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
session
|
session
|
||||||
|
@ -54,6 +57,8 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publish_diagnostics(session, &key, client)
|
publish_diagnostics(session, &key, client);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
for key in session.text_document_keys() {
|
for key in session.text_document_keys() {
|
||||||
publish_diagnostics(session, &key, client)?;
|
publish_diagnostics(session, &key, client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ use crate::session::Session;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use crate::system::AnySystemPath;
|
use crate::system::AnySystemPath;
|
||||||
use lsp_server::ErrorCode;
|
use lsp_server::ErrorCode;
|
||||||
use lsp_types::DidCloseTextDocumentParams;
|
|
||||||
use lsp_types::notification::DidCloseTextDocument;
|
use lsp_types::notification::DidCloseTextDocument;
|
||||||
|
use lsp_types::{DidCloseTextDocumentParams, TextDocumentIdentifier};
|
||||||
use ty_project::watch::ChangeEvent;
|
use ty_project::watch::ChangeEvent;
|
||||||
|
|
||||||
pub(crate) struct DidCloseTextDocumentHandler;
|
pub(crate) struct DidCloseTextDocumentHandler;
|
||||||
|
@ -22,13 +22,18 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler {
|
||||||
client: &Client,
|
client: &Client,
|
||||||
params: DidCloseTextDocumentParams,
|
params: DidCloseTextDocumentParams,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Ok(key) = session.key_from_url(params.text_document.uri.clone()) else {
|
let DidCloseTextDocumentParams {
|
||||||
tracing::debug!(
|
text_document: TextDocumentIdentifier { uri },
|
||||||
"Failed to create document key from URI: {}",
|
} = params;
|
||||||
params.text_document.uri
|
|
||||||
);
|
let key = match session.key_from_url(uri) {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(uri) => {
|
||||||
|
tracing::debug!("Failed to create document key from URI: {}", uri);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
session
|
session
|
||||||
.close_document(&key)
|
.close_document(&key)
|
||||||
.with_failure_code(ErrorCode::InternalError)?;
|
.with_failure_code(ErrorCode::InternalError)?;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use lsp_types::DidCloseNotebookDocumentParams;
|
|
||||||
use lsp_types::notification::DidCloseNotebookDocument;
|
use lsp_types::notification::DidCloseNotebookDocument;
|
||||||
|
use lsp_types::{DidCloseNotebookDocumentParams, NotebookDocumentIdentifier};
|
||||||
|
|
||||||
use crate::server::Result;
|
use crate::server::Result;
|
||||||
use crate::server::api::LSPResult;
|
use crate::server::api::LSPResult;
|
||||||
|
@ -21,13 +21,19 @@ impl SyncNotificationHandler for DidCloseNotebookHandler {
|
||||||
_client: &Client,
|
_client: &Client,
|
||||||
params: DidCloseNotebookDocumentParams,
|
params: DidCloseNotebookDocumentParams,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Ok(key) = session.key_from_url(params.notebook_document.uri.clone()) else {
|
let DidCloseNotebookDocumentParams {
|
||||||
tracing::debug!(
|
notebook_document: NotebookDocumentIdentifier { uri },
|
||||||
"Failed to create document key from URI: {}",
|
..
|
||||||
params.notebook_document.uri
|
} = params;
|
||||||
);
|
|
||||||
|
let key = match session.key_from_url(uri) {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(uri) => {
|
||||||
|
tracing::debug!("Failed to create document key from URI: {}", uri);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
session
|
session
|
||||||
.close_document(&key)
|
.close_document(&key)
|
||||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||||
|
|
|
@ -21,7 +21,9 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler {
|
||||||
fn run(
|
fn run(
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
DidOpenTextDocumentParams {
|
params: DidOpenTextDocumentParams,
|
||||||
|
) -> Result<()> {
|
||||||
|
let DidOpenTextDocumentParams {
|
||||||
text_document:
|
text_document:
|
||||||
TextDocumentItem {
|
TextDocumentItem {
|
||||||
uri,
|
uri,
|
||||||
|
@ -29,11 +31,14 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler {
|
||||||
version,
|
version,
|
||||||
language_id,
|
language_id,
|
||||||
},
|
},
|
||||||
}: DidOpenTextDocumentParams,
|
} = params;
|
||||||
) -> Result<()> {
|
|
||||||
let Ok(key) = session.key_from_url(uri.clone()) else {
|
let key = match session.key_from_url(uri) {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(uri) => {
|
||||||
tracing::debug!("Failed to create document key from URI: {}", uri);
|
tracing::debug!("Failed to create document key from URI: {}", uri);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let document = TextDocument::new(text, version).with_language_id(&language_id);
|
let document = TextDocument::new(text, version).with_language_id(&language_id);
|
||||||
|
@ -53,6 +58,8 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publish_diagnostics(session, &key, client)
|
publish_diagnostics(session, &key, client);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ use ty_ide::completion;
|
||||||
use ty_project::ProjectDatabase;
|
use ty_project::ProjectDatabase;
|
||||||
use ty_python_semantic::CompletionKind;
|
use ty_python_semantic::CompletionKind;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::document::PositionExt;
|
use crate::document::PositionExt;
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
|
|
||||||
pub(crate) struct CompletionRequestHandler;
|
pub(crate) struct CompletionRequestHandler;
|
||||||
|
@ -38,8 +38,7 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ use ruff_db::source::{line_index, source_text};
|
||||||
use ty_ide::goto_type_definition;
|
use ty_ide::goto_type_definition;
|
||||||
use ty_project::ProjectDatabase;
|
use ty_project::ProjectDatabase;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::document::{PositionExt, ToLink};
|
use crate::document::{PositionExt, ToLink};
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
|
|
||||||
pub(crate) struct GotoTypeDefinitionRequestHandler;
|
pub(crate) struct GotoTypeDefinitionRequestHandler;
|
||||||
|
@ -34,8 +34,7 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::document::{PositionExt, ToRangeExt};
|
use crate::document::{PositionExt, ToRangeExt};
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use lsp_types::request::HoverRequest;
|
use lsp_types::request::HoverRequest;
|
||||||
use lsp_types::{HoverContents, HoverParams, MarkupContent, Url};
|
use lsp_types::{HoverContents, HoverParams, MarkupContent, Url};
|
||||||
|
@ -34,8 +34,7 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::document::{RangeExt, TextSizeExt};
|
use crate::document::{RangeExt, TextSizeExt};
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use lsp_types::request::InlayHintRequest;
|
use lsp_types::request::InlayHintRequest;
|
||||||
use lsp_types::{InlayHintParams, Url};
|
use lsp_types::{InlayHintParams, Url};
|
||||||
|
@ -33,8 +33,7 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::server::api::semantic_tokens::generate_semantic_tokens;
|
use crate::server::api::semantic_tokens::generate_semantic_tokens;
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use lsp_types::{SemanticTokens, SemanticTokensParams, SemanticTokensResult, Url};
|
use lsp_types::{SemanticTokens, SemanticTokensParams, SemanticTokensResult, Url};
|
||||||
use ty_project::ProjectDatabase;
|
use ty_project::ProjectDatabase;
|
||||||
|
@ -24,14 +24,13 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRequestHandler {
|
||||||
db: &ProjectDatabase,
|
db: &ProjectDatabase,
|
||||||
snapshot: DocumentSnapshot,
|
snapshot: DocumentSnapshot,
|
||||||
_client: &Client,
|
_client: &Client,
|
||||||
params: SemanticTokensParams,
|
_params: SemanticTokensParams,
|
||||||
) -> crate::server::Result<Option<SemanticTokensResult>> {
|
) -> crate::server::Result<Option<SemanticTokensResult>> {
|
||||||
if snapshot.client_settings().is_language_services_disabled() {
|
if snapshot.client_settings().is_language_services_disabled() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::document::RangeExt;
|
use crate::document::RangeExt;
|
||||||
use crate::server::api::semantic_tokens::generate_semantic_tokens;
|
use crate::server::api::semantic_tokens::generate_semantic_tokens;
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use lsp_types::{SemanticTokens, SemanticTokensRangeParams, SemanticTokensRangeResult, Url};
|
use lsp_types::{SemanticTokens, SemanticTokensRangeParams, SemanticTokensRangeResult, Url};
|
||||||
use ruff_db::source::{line_index, source_text};
|
use ruff_db::source::{line_index, source_text};
|
||||||
|
@ -32,8 +32,7 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRangeRequestHandler {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::DocumentSnapshot;
|
|
||||||
use crate::document::{PositionEncoding, PositionExt};
|
use crate::document::{PositionEncoding, PositionExt};
|
||||||
use crate::server::api::traits::{
|
use crate::server::api::traits::{
|
||||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||||
};
|
};
|
||||||
|
use crate::session::DocumentSnapshot;
|
||||||
use crate::session::client::Client;
|
use crate::session::client::Client;
|
||||||
use lsp_types::request::SignatureHelpRequest;
|
use lsp_types::request::SignatureHelpRequest;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
|
@ -36,8 +36,7 @@ impl BackgroundDocumentRequestHandler for SignatureHelpRequestHandler {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file) = snapshot.file(db) else {
|
let Some(file) = snapshot.file_ok(db) else {
|
||||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl BackgroundRequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||||
let version = index
|
let version = index
|
||||||
.key_from_url(url.clone())
|
.key_from_url(url.clone())
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|key| index.make_document_ref(&key))
|
.and_then(|key| index.make_document_ref(key).ok())
|
||||||
.map(|doc| i64::from(doc.version()));
|
.map(|doc| i64::from(doc.version()));
|
||||||
|
|
||||||
// Convert diagnostics to LSP format
|
// Convert diagnostics to LSP format
|
||||||
|
|
|
@ -5,23 +5,25 @@ use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Context, anyhow};
|
use anyhow::{Context, anyhow};
|
||||||
|
use index::DocumentQueryError;
|
||||||
use lsp_server::Message;
|
use lsp_server::Message;
|
||||||
use lsp_types::{ClientCapabilities, TextDocumentContentChangeEvent, Url};
|
use lsp_types::{ClientCapabilities, TextDocumentContentChangeEvent, Url};
|
||||||
use options::GlobalOptions;
|
use options::GlobalOptions;
|
||||||
use ruff_db::Db;
|
use ruff_db::Db;
|
||||||
use ruff_db::files::{File, system_path_to_file};
|
use ruff_db::files::File;
|
||||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||||
use ty_project::metadata::Options;
|
use ty_project::metadata::Options;
|
||||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||||
|
|
||||||
pub(crate) use self::capabilities::ResolvedClientCapabilities;
|
pub(crate) use self::capabilities::ResolvedClientCapabilities;
|
||||||
pub use self::index::DocumentQuery;
|
pub(crate) use self::index::DocumentQuery;
|
||||||
pub(crate) use self::options::{AllOptions, ClientOptions, DiagnosticMode};
|
pub(crate) use self::options::{AllOptions, ClientOptions, DiagnosticMode};
|
||||||
pub(crate) use self::settings::ClientSettings;
|
pub(crate) use self::settings::ClientSettings;
|
||||||
use crate::document::{DocumentKey, DocumentVersion, NotebookDocument};
|
use crate::document::{DocumentKey, DocumentVersion, NotebookDocument};
|
||||||
use crate::session::request_queue::RequestQueue;
|
use crate::session::request_queue::RequestQueue;
|
||||||
use crate::system::{AnySystemPath, LSPSystem};
|
use crate::system::{AnySystemPath, LSPSystem};
|
||||||
use crate::{PositionEncoding, TextDocument};
|
use crate::{PositionEncoding, TextDocument};
|
||||||
|
use index::Index;
|
||||||
|
|
||||||
mod capabilities;
|
mod capabilities;
|
||||||
pub(crate) mod client;
|
pub(crate) mod client;
|
||||||
|
@ -31,7 +33,7 @@ mod request_queue;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
/// The global state for the LSP
|
/// The global state for the LSP
|
||||||
pub struct Session {
|
pub(crate) struct Session {
|
||||||
/// Used to retrieve information about open documents and settings.
|
/// Used to retrieve information about open documents and settings.
|
||||||
///
|
///
|
||||||
/// This will be [`None`] when a mutable reference is held to the index via [`index_mut`]
|
/// This will be [`None`] when a mutable reference is held to the index via [`index_mut`]
|
||||||
|
@ -39,7 +41,7 @@ pub struct Session {
|
||||||
/// when the mutable reference ([`MutIndexGuard`]) is dropped.
|
/// when the mutable reference ([`MutIndexGuard`]) is dropped.
|
||||||
///
|
///
|
||||||
/// [`index_mut`]: Session::index_mut
|
/// [`index_mut`]: Session::index_mut
|
||||||
index: Option<Arc<index::Index>>,
|
index: Option<Arc<Index>>,
|
||||||
|
|
||||||
/// Maps workspace folders to their respective workspace.
|
/// Maps workspace folders to their respective workspace.
|
||||||
workspaces: Workspaces,
|
workspaces: Workspaces,
|
||||||
|
@ -71,7 +73,7 @@ impl Session {
|
||||||
global_options: GlobalOptions,
|
global_options: GlobalOptions,
|
||||||
workspace_folders: Vec<(Url, ClientOptions)>,
|
workspace_folders: Vec<(Url, ClientOptions)>,
|
||||||
) -> crate::Result<Self> {
|
) -> crate::Result<Self> {
|
||||||
let index = Arc::new(index::Index::new(global_options.into_settings()));
|
let index = Arc::new(Index::new(global_options.into_settings()));
|
||||||
|
|
||||||
let mut workspaces = Workspaces::default();
|
let mut workspaces = Workspaces::default();
|
||||||
for (url, options) in workspace_folders {
|
for (url, options) in workspace_folders {
|
||||||
|
@ -219,7 +221,10 @@ impl Session {
|
||||||
.chain(std::iter::once(&mut self.default_project))
|
.chain(std::iter::once(&mut self.default_project))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn key_from_url(&self, url: Url) -> crate::Result<DocumentKey> {
|
/// Returns the [`DocumentKey`] for the given URL.
|
||||||
|
///
|
||||||
|
/// Refer to [`Index::key_from_url`] for more details.
|
||||||
|
pub(crate) fn key_from_url(&self, url: Url) -> Result<DocumentKey, Url> {
|
||||||
self.index().key_from_url(url)
|
self.index().key_from_url(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,16 +283,17 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a document snapshot with the URL referencing the document to snapshot.
|
/// Creates a document snapshot with the URL referencing the document to snapshot.
|
||||||
///
|
pub(crate) fn take_document_snapshot(&self, url: Url) -> DocumentSnapshot {
|
||||||
/// Returns `None` if the url can't be converted to a document key or if the document isn't open.
|
let index = self.index();
|
||||||
pub(crate) fn take_document_snapshot(&self, url: Url) -> Option<DocumentSnapshot> {
|
DocumentSnapshot {
|
||||||
let key = self.key_from_url(url).ok()?;
|
|
||||||
Some(DocumentSnapshot {
|
|
||||||
resolved_client_capabilities: self.resolved_client_capabilities.clone(),
|
resolved_client_capabilities: self.resolved_client_capabilities.clone(),
|
||||||
client_settings: self.index().global_settings(),
|
client_settings: index.global_settings(),
|
||||||
document_ref: self.index().make_document_ref(&key)?,
|
|
||||||
position_encoding: self.position_encoding,
|
position_encoding: self.position_encoding,
|
||||||
})
|
document_query_result: self
|
||||||
|
.key_from_url(url)
|
||||||
|
.map_err(DocumentQueryError::InvalidUrl)
|
||||||
|
.and_then(|key| index.make_document_ref(key)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a snapshot of the current state of the [`Session`].
|
/// Creates a snapshot of the current state of the [`Session`].
|
||||||
|
@ -350,7 +356,7 @@ impl Session {
|
||||||
/// Panics if there's a mutable reference to the index via [`index_mut`].
|
/// Panics if there's a mutable reference to the index via [`index_mut`].
|
||||||
///
|
///
|
||||||
/// [`index_mut`]: Session::index_mut
|
/// [`index_mut`]: Session::index_mut
|
||||||
fn index(&self) -> &index::Index {
|
fn index(&self) -> &Index {
|
||||||
self.index.as_ref().unwrap()
|
self.index.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,11 +400,11 @@ impl Session {
|
||||||
/// When dropped, this guard restores all references to the index.
|
/// When dropped, this guard restores all references to the index.
|
||||||
struct MutIndexGuard<'a> {
|
struct MutIndexGuard<'a> {
|
||||||
session: &'a mut Session,
|
session: &'a mut Session,
|
||||||
index: Option<index::Index>,
|
index: Option<Index>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for MutIndexGuard<'_> {
|
impl Deref for MutIndexGuard<'_> {
|
||||||
type Target = index::Index;
|
type Target = Index;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.index.as_ref().unwrap()
|
self.index.as_ref().unwrap()
|
||||||
|
@ -428,48 +434,69 @@ impl Drop for MutIndexGuard<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An immutable snapshot of `Session` that references
|
/// An immutable snapshot of [`Session`] that references a specific document.
|
||||||
/// a specific document.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DocumentSnapshot {
|
pub(crate) struct DocumentSnapshot {
|
||||||
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
|
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
|
||||||
client_settings: Arc<ClientSettings>,
|
client_settings: Arc<ClientSettings>,
|
||||||
document_ref: index::DocumentQuery,
|
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
|
document_query_result: Result<DocumentQuery, DocumentQueryError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentSnapshot {
|
impl DocumentSnapshot {
|
||||||
|
/// Returns the resolved client capabilities that were captured during initialization.
|
||||||
pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities {
|
pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities {
|
||||||
&self.resolved_client_capabilities
|
&self.resolved_client_capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn query(&self) -> &index::DocumentQuery {
|
/// Returns the position encoding that was negotiated during initialization.
|
||||||
&self.document_ref
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn encoding(&self) -> PositionEncoding {
|
pub(crate) fn encoding(&self) -> PositionEncoding {
|
||||||
self.position_encoding
|
self.position_encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the client settings for this document.
|
||||||
pub(crate) fn client_settings(&self) -> &ClientSettings {
|
pub(crate) fn client_settings(&self) -> &ClientSettings {
|
||||||
&self.client_settings
|
&self.client_settings
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn file(&self, db: &dyn Db) -> Option<File> {
|
/// Returns the result of the document query for this snapshot.
|
||||||
match AnySystemPath::try_from_url(self.document_ref.file_url()).ok()? {
|
pub(crate) fn document(&self) -> Result<&DocumentQuery, &DocumentQueryError> {
|
||||||
AnySystemPath::System(path) => system_path_to_file(db, path).ok(),
|
self.document_query_result.as_ref()
|
||||||
AnySystemPath::SystemVirtual(virtual_path) => db
|
}
|
||||||
.files()
|
|
||||||
.try_virtual_file(&virtual_path)
|
pub(crate) fn file_ok(&self, db: &dyn Db) -> Option<File> {
|
||||||
.map(|virtual_file| virtual_file.file()),
|
match self.file(db) {
|
||||||
|
Ok(file) => Some(file),
|
||||||
|
Err(err) => {
|
||||||
|
tracing::debug!("Failed to resolve file: {}", err);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file(&self, db: &dyn Db) -> Result<File, FileLookupError> {
|
||||||
|
let document = match self.document() {
|
||||||
|
Ok(document) => document,
|
||||||
|
Err(err) => return Err(FileLookupError::DocumentQuery(err.clone())),
|
||||||
|
};
|
||||||
|
document
|
||||||
|
.file(db)
|
||||||
|
.ok_or_else(|| FileLookupError::NotFound(document.file_path().clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub(crate) enum FileLookupError {
|
||||||
|
#[error("file not found for path `{0}`")]
|
||||||
|
NotFound(AnySystemPath),
|
||||||
|
#[error(transparent)]
|
||||||
|
DocumentQuery(DocumentQueryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An immutable snapshot of the current state of [`Session`].
|
/// An immutable snapshot of the current state of [`Session`].
|
||||||
pub(crate) struct SessionSnapshot {
|
pub(crate) struct SessionSnapshot {
|
||||||
projects: Vec<ProjectDatabase>,
|
projects: Vec<ProjectDatabase>,
|
||||||
index: Arc<index::Index>,
|
index: Arc<Index>,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +505,7 @@ impl SessionSnapshot {
|
||||||
&self.projects
|
&self.projects
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn index(&self) -> &index::Index {
|
pub(crate) fn index(&self) -> &Index {
|
||||||
&self.index
|
&self.index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
|
use ruff_db::Db;
|
||||||
|
use ruff_db::files::{File, system_path_to_file};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::session::settings::ClientSettings;
|
use crate::session::settings::ClientSettings;
|
||||||
|
@ -68,16 +70,17 @@ impl Index {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn key_from_url(&self, url: Url) -> crate::Result<DocumentKey> {
|
/// Returns the [`DocumentKey`] corresponding to the given URL.
|
||||||
|
///
|
||||||
|
/// It returns [`Err`] with the original URL if it cannot be converted to a [`AnySystemPath`].
|
||||||
|
pub(crate) fn key_from_url(&self, url: Url) -> Result<DocumentKey, Url> {
|
||||||
if let Some(notebook_path) = self.notebook_cells.get(&url) {
|
if let Some(notebook_path) = self.notebook_cells.get(&url) {
|
||||||
Ok(DocumentKey::NotebookCell {
|
Ok(DocumentKey::NotebookCell {
|
||||||
cell_url: url,
|
cell_url: url,
|
||||||
notebook_path: notebook_path.clone(),
|
notebook_path: notebook_path.clone(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let path = AnySystemPath::try_from_url(&url)
|
let path = AnySystemPath::try_from_url(&url).map_err(|()| url)?;
|
||||||
.map_err(|()| anyhow::anyhow!("Failed to convert URL to system path: {}", url))?;
|
|
||||||
|
|
||||||
if path
|
if path
|
||||||
.extension()
|
.extension()
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("ipynb"))
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("ipynb"))
|
||||||
|
@ -122,17 +125,27 @@ impl Index {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_document_ref(&self, key: &DocumentKey) -> Option<DocumentQuery> {
|
/// Create a document reference corresponding to the given document key.
|
||||||
|
///
|
||||||
|
/// Returns an error if the document is not found or if the path cannot be converted to a URL.
|
||||||
|
pub(crate) fn make_document_ref(
|
||||||
|
&self,
|
||||||
|
key: DocumentKey,
|
||||||
|
) -> Result<DocumentQuery, DocumentQueryError> {
|
||||||
let path = key.path();
|
let path = key.path();
|
||||||
let controller = self.documents.get(path)?;
|
let Some(controller) = self.documents.get(path) else {
|
||||||
let (cell_url, file_url) = match &key {
|
return Err(DocumentQueryError::NotFound(key));
|
||||||
|
};
|
||||||
|
// TODO: The `to_url` conversion shouldn't be an error because the paths themselves are
|
||||||
|
// constructed from the URLs but the `Index` APIs don't maintain this invariant.
|
||||||
|
let (cell_url, file_path) = match key {
|
||||||
DocumentKey::NotebookCell {
|
DocumentKey::NotebookCell {
|
||||||
cell_url,
|
cell_url,
|
||||||
notebook_path,
|
notebook_path,
|
||||||
} => (Some(cell_url.clone()), notebook_path.to_url()?),
|
} => (Some(cell_url), notebook_path),
|
||||||
DocumentKey::Notebook(path) | DocumentKey::Text(path) => (None, path.to_url()?),
|
DocumentKey::Notebook(path) | DocumentKey::Text(path) => (None, path),
|
||||||
};
|
};
|
||||||
Some(controller.make_ref(cell_url, file_url))
|
Ok(controller.make_ref(cell_url, file_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn open_text_document(&mut self, path: &AnySystemPath, document: TextDocument) {
|
pub(super) fn open_text_document(&mut self, path: &AnySystemPath, document: TextDocument) {
|
||||||
|
@ -207,15 +220,15 @@ impl DocumentController {
|
||||||
Self::Notebook(Arc::new(document))
|
Self::Notebook(Arc::new(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_ref(&self, cell_url: Option<Url>, file_url: Url) -> DocumentQuery {
|
fn make_ref(&self, cell_url: Option<Url>, file_path: AnySystemPath) -> DocumentQuery {
|
||||||
match &self {
|
match &self {
|
||||||
Self::Notebook(notebook) => DocumentQuery::Notebook {
|
Self::Notebook(notebook) => DocumentQuery::Notebook {
|
||||||
cell_url,
|
cell_url,
|
||||||
file_url,
|
file_path,
|
||||||
notebook: notebook.clone(),
|
notebook: notebook.clone(),
|
||||||
},
|
},
|
||||||
Self::Text(document) => DocumentQuery::Text {
|
Self::Text(document) => DocumentQuery::Text {
|
||||||
file_url,
|
file_path,
|
||||||
document: document.clone(),
|
document: document.clone(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -251,26 +264,27 @@ impl DocumentController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A read-only query to an open document.
|
/// A read-only query to an open document.
|
||||||
|
///
|
||||||
/// This query can 'select' a text document, full notebook, or a specific notebook cell.
|
/// This query can 'select' a text document, full notebook, or a specific notebook cell.
|
||||||
/// It also includes document settings.
|
/// It also includes document settings.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DocumentQuery {
|
pub(crate) enum DocumentQuery {
|
||||||
Text {
|
Text {
|
||||||
file_url: Url,
|
file_path: AnySystemPath,
|
||||||
document: Arc<TextDocument>,
|
document: Arc<TextDocument>,
|
||||||
},
|
},
|
||||||
Notebook {
|
Notebook {
|
||||||
/// The selected notebook cell, if it exists.
|
/// The selected notebook cell, if it exists.
|
||||||
cell_url: Option<Url>,
|
cell_url: Option<Url>,
|
||||||
/// The URL of the notebook.
|
/// The path to the notebook.
|
||||||
file_url: Url,
|
file_path: AnySystemPath,
|
||||||
notebook: Arc<NotebookDocument>,
|
notebook: Arc<NotebookDocument>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentQuery {
|
impl DocumentQuery {
|
||||||
/// Attempts to access the underlying notebook document that this query is selecting.
|
/// Attempts to access the underlying notebook document that this query is selecting.
|
||||||
pub fn as_notebook(&self) -> Option<&NotebookDocument> {
|
pub(crate) fn as_notebook(&self) -> Option<&NotebookDocument> {
|
||||||
match self {
|
match self {
|
||||||
Self::Notebook { notebook, .. } => Some(notebook),
|
Self::Notebook { notebook, .. } => Some(notebook),
|
||||||
Self::Text { .. } => None,
|
Self::Text { .. } => None,
|
||||||
|
@ -285,10 +299,10 @@ impl DocumentQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the URL for the document selected by this query.
|
/// Get the system path for the document selected by this query.
|
||||||
pub(crate) fn file_url(&self) -> &Url {
|
pub(crate) fn file_path(&self) -> &AnySystemPath {
|
||||||
match self {
|
match self {
|
||||||
Self::Text { file_url, .. } | Self::Notebook { file_url, .. } => file_url,
|
Self::Text { file_path, .. } | Self::Notebook { file_path, .. } => file_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,4 +321,27 @@ impl DocumentQuery {
|
||||||
.and_then(|cell_uri| notebook.cell_document_by_uri(cell_uri)),
|
.and_then(|cell_uri| notebook.cell_document_by_uri(cell_uri)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the salsa interned [`File`] for the document selected by this query.
|
||||||
|
///
|
||||||
|
/// It returns [`None`] for the following cases:
|
||||||
|
/// - For virtual file, if it's not yet opened
|
||||||
|
/// - For regular file, if it does not exists or is a directory
|
||||||
|
pub(crate) fn file(&self, db: &dyn Db) -> Option<File> {
|
||||||
|
match self.file_path() {
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
pub(crate) enum DocumentQueryError {
|
||||||
|
#[error("invalid URL: {0}")]
|
||||||
|
InvalidUrl(Url),
|
||||||
|
#[error("document not found for key: {0}")]
|
||||||
|
NotFound(DocumentKey),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::fmt;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -97,6 +98,15 @@ impl AnySystemPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AnySystemPath {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
AnySystemPath::System(system_path) => write!(f, "{system_path}"),
|
||||||
|
AnySystemPath::SystemVirtual(virtual_path) => write!(f, "{virtual_path}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct LSPSystem {
|
pub(crate) struct LSPSystem {
|
||||||
/// A read-only copy of the index where the server stores all the open documents and settings.
|
/// A read-only copy of the index where the server stores all the open documents and settings.
|
||||||
|
@ -145,7 +155,7 @@ impl LSPSystem {
|
||||||
fn make_document_ref(&self, path: AnySystemPath) -> Option<DocumentQuery> {
|
fn make_document_ref(&self, path: AnySystemPath) -> Option<DocumentQuery> {
|
||||||
let index = self.index();
|
let index = self.index();
|
||||||
let key = DocumentKey::from_path(path);
|
let key = DocumentKey::from_path(path);
|
||||||
index.make_document_ref(&key)
|
index.make_document_ref(key).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn system_path_to_document_ref(&self, path: &SystemPath) -> Option<DocumentQuery> {
|
fn system_path_to_document_ref(&self, path: &SystemPath) -> Option<DocumentQuery> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue