ruff server: Add support for documents not exist on disk (#11588)

Co-authored-by: T-256 <Tester@test.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
T-256 2024-05-31 10:04:10 +03:30 committed by GitHub
parent 685d11a909
commit 5b500fc4dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 380 additions and 366 deletions

View file

@ -122,7 +122,7 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
let (id, params) = cast_request::<R>(req)?;
Ok(Task::background(schedule, move |session: &Session| {
// TODO(jane): we should log an error if we can't take a snapshot.
let Some(snapshot) = session.take_snapshot(&R::document_url(&params)) else {
let Some(snapshot) = session.take_snapshot(R::document_url(&params).into_owned()) else {
return Box::new(|_, _| {});
};
Box::new(move |notifier, responder| {
@ -152,7 +152,7 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH
let (id, params) = cast_notification::<N>(req)?;
Ok(Task::background(schedule, move |session: &Session| {
// TODO(jane): we should log an error if we can't take a snapshot.
let Some(snapshot) = session.take_snapshot(&N::document_url(&params)) else {
let Some(snapshot) = session.take_snapshot(N::document_url(&params).into_owned()) else {
return Box::new(|_, _| {});
};
Box::new(move |notifier, _| {

View file

@ -27,9 +27,7 @@ impl super::SyncNotificationHandler for DidChange {
content_changes,
}: types::DidChangeTextDocumentParams,
) -> Result<()> {
let key = session
.key_from_url(&uri)
.with_failure_code(ErrorCode::InternalError)?;
let key = session.key_from_url(uri);
session
.update_text_document(&key, content_changes, new_version)
@ -37,7 +35,7 @@ impl super::SyncNotificationHandler for DidChange {
// Publish diagnostics if the client doesnt support pull diagnostics
if !session.resolved_client_capabilities().pull_diagnostics {
let snapshot = session.take_snapshot(&uri).unwrap();
let snapshot = session.take_snapshot(key.into_url()).unwrap();
publish_diagnostics_for_document(&snapshot, &notifier)?;
}

View file

@ -23,16 +23,14 @@ impl super::SyncNotificationHandler for DidChangeNotebook {
change: types::NotebookDocumentChangeEvent { cells, metadata },
}: types::DidChangeNotebookDocumentParams,
) -> Result<()> {
let key = session
.key_from_url(&uri)
.with_failure_code(ErrorCode::InternalError)?;
let key = session.key_from_url(uri);
session
.update_notebook_document(&key, cells, metadata, version)
.with_failure_code(ErrorCode::InternalError)?;
// publish new diagnostics
let snapshot = session
.take_snapshot(&uri)
.take_snapshot(key.into_url())
.expect("snapshot should be available");
publish_diagnostics_for_document(&snapshot, &notifier)?;

View file

@ -21,7 +21,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
params: types::DidChangeWatchedFilesParams,
) -> Result<()> {
for change in &params.changes {
session.reload_settings(&change.uri.to_file_path().unwrap());
session.reload_settings(&change.uri);
}
if !params.changes.is_empty() {
@ -33,7 +33,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
// publish diagnostics for text documents
for url in session.text_document_urls() {
let snapshot = session
.take_snapshot(&url)
.take_snapshot(url.clone())
.expect("snapshot should be available");
publish_diagnostics_for_document(&snapshot, &notifier)?;
}
@ -42,7 +42,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
// always publish diagnostics for notebook files (since they don't use pull diagnostics)
for url in session.notebook_document_urls() {
let snapshot = session
.take_snapshot(&url)
.take_snapshot(url.clone())
.expect("snapshot should be available");
publish_diagnostics_for_document(&snapshot, &notifier)?;
}

View file

@ -2,8 +2,6 @@ use crate::server::api::LSPResult;
use crate::server::client::{Notifier, Requester};
use crate::server::Result;
use crate::session::Session;
use anyhow::anyhow;
use lsp_server::ErrorCode;
use lsp_types as types;
use lsp_types::notification as notif;
@ -20,21 +18,14 @@ impl super::SyncNotificationHandler for DidChangeWorkspace {
_requester: &mut Requester,
params: types::DidChangeWorkspaceFoldersParams,
) -> Result<()> {
for types::WorkspaceFolder { ref uri, .. } in params.event.added {
let workspace_path = uri
.to_file_path()
.map_err(|()| anyhow!("expected document URI {uri} to be a valid file path"))
.with_failure_code(ErrorCode::InvalidParams)?;
session.open_workspace_folder(workspace_path);
}
for types::WorkspaceFolder { ref uri, .. } in params.event.removed {
let workspace_path = uri
.to_file_path()
.map_err(|()| anyhow!("expected document URI {uri} to be a valid file path"))
.with_failure_code(ErrorCode::InvalidParams)?;
for types::WorkspaceFolder { uri, .. } in params.event.added {
session
.close_workspace_folder(&workspace_path)
.open_workspace_folder(&uri)
.with_failure_code(lsp_server::ErrorCode::InvalidParams)?;
}
for types::WorkspaceFolder { uri, .. } in params.event.removed {
session
.close_workspace_folder(&uri)
.with_failure_code(lsp_server::ErrorCode::InvalidParams)?;
}
Ok(())

View file

@ -24,7 +24,7 @@ impl super::SyncNotificationHandler for DidClose {
) -> Result<()> {
// Publish an empty diagnostic report for the document. This will de-register any existing diagnostics.
let snapshot = session
.take_snapshot(&uri)
.take_snapshot(uri.clone())
.ok_or_else(|| anyhow::anyhow!("Unable to take snapshot for document with URL {uri}"))
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
clear_diagnostics_for_document(snapshot.query(), &notifier)?;

View file

@ -22,9 +22,7 @@ impl super::SyncNotificationHandler for DidCloseNotebook {
..
}: types::DidCloseNotebookDocumentParams,
) -> Result<()> {
let key = session
.key_from_url(&uri)
.with_failure_code(ErrorCode::InternalError)?;
let key = session.key_from_url(uri);
session
.close_document(&key)

View file

@ -4,8 +4,6 @@ use crate::server::client::{Notifier, Requester};
use crate::server::Result;
use crate::session::Session;
use crate::TextDocument;
use anyhow::anyhow;
use lsp_server::ErrorCode;
use lsp_types as types;
use lsp_types::notification as notif;
@ -23,26 +21,18 @@ impl super::SyncNotificationHandler for DidOpen {
types::DidOpenTextDocumentParams {
text_document:
types::TextDocumentItem {
ref uri,
text,
version,
..
uri, text, version, ..
},
}: types::DidOpenTextDocumentParams,
) -> Result<()> {
let document_path: std::path::PathBuf = uri
.to_file_path()
.map_err(|()| anyhow!("expected document URI {uri} to be a valid file path"))
.with_failure_code(ErrorCode::InvalidParams)?;
let document = TextDocument::new(text, version);
session.open_text_document(document_path, document);
session.open_text_document(uri.clone(), document);
// Publish diagnostics if the client doesnt support pull diagnostics
if !session.resolved_client_capabilities().pull_diagnostics {
let snapshot = session
.take_snapshot(uri)
.take_snapshot(uri.clone())
.ok_or_else(|| {
anyhow::anyhow!("Unable to take snapshot for document with URL {uri}")
})

View file

@ -4,7 +4,6 @@ use crate::server::api::LSPResult;
use crate::server::client::{Notifier, Requester};
use crate::server::Result;
use crate::session::Session;
use anyhow::anyhow;
use lsp_server::ErrorCode;
use lsp_types as types;
use lsp_types::notification as notif;
@ -40,16 +39,11 @@ impl super::SyncNotificationHandler for DidOpenNotebook {
)
.with_failure_code(ErrorCode::InternalError)?;
let notebook_path = uri
.to_file_path()
.map_err(|()| anyhow!("expected notebook URI {uri} to be a valid file path"))
.with_failure_code(ErrorCode::InvalidParams)?;
session.open_notebook_document(notebook_path, notebook);
session.open_notebook_document(uri.clone(), notebook);
// publish diagnostics
let snapshot = session
.take_snapshot(&uri)
.take_snapshot(uri)
.expect("snapshot should be available");
publish_diagnostics_for_document(&snapshot, &notifier)?;

View file

@ -57,7 +57,7 @@ impl super::SyncRequestHandler for ExecuteCommand {
let mut edit_tracker = WorkspaceEditTracker::new(session.resolved_client_capabilities());
for Argument { uri, version } in arguments {
let snapshot = session
.take_snapshot(&uri)
.take_snapshot(uri.clone())
.ok_or(anyhow::anyhow!("Document snapshot not available for {uri}",))
.with_failure_code(ErrorCode::InternalError)?;
match command {

View file

@ -1,18 +1,14 @@
use std::path::Path;
use lsp_types::{self as types, request as req};
use types::TextEdit;
use ruff_python_ast::PySourceType;
use ruff_source_file::LineIndex;
use ruff_workspace::resolver::match_any_exclusion;
use ruff_workspace::{FileResolverSettings, FormatterSettings};
use crate::edit::{Replacement, ToRangeExt};
use crate::fix::Fixes;
use crate::server::api::LSPResult;
use crate::server::{client::Notifier, Result};
use crate::session::DocumentSnapshot;
use crate::session::{DocumentQuery, DocumentSnapshot};
use crate::{PositionEncoding, TextDocument};
pub(crate) struct Format;
@ -37,34 +33,25 @@ pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result<Fixes>
let mut fixes = Fixes::default();
let query = snapshot.query();
if let Some(notebook) = snapshot.query().as_notebook() {
for (url, text_document) in notebook
.urls()
.map(|url| (url.clone(), notebook.cell_document_by_uri(url).unwrap()))
{
if let Some(changes) = format_text_document(
text_document,
query.source_type(),
query.file_path(),
query.settings().file_resolver(),
query.settings().formatter(),
snapshot.encoding(),
true,
)? {
fixes.insert(url, changes);
match snapshot.query() {
DocumentQuery::Notebook { notebook, .. } => {
for (url, text_document) in notebook
.urls()
.map(|url| (url.clone(), notebook.cell_document_by_uri(url).unwrap()))
{
if let Some(changes) =
format_text_document(text_document, query, snapshot.encoding(), true)?
{
fixes.insert(url, changes);
}
}
}
} else {
if let Some(changes) = format_text_document(
query.as_single_document().unwrap(),
query.source_type(),
query.file_path(),
query.settings().file_resolver(),
query.settings().formatter(),
snapshot.encoding(),
false,
)? {
fixes.insert(snapshot.query().make_key().into_url(), changes);
DocumentQuery::Text { document, .. } => {
if let Some(changes) =
format_text_document(document, query, snapshot.encoding(), false)?
{
fixes.insert(snapshot.query().make_key().into_url(), changes);
}
}
}
@ -81,10 +68,7 @@ pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result<super::Form
let query = snapshot.query();
format_text_document(
text_document,
query.source_type(),
query.file_path(),
query.settings().file_resolver(),
query.settings().formatter(),
query,
snapshot.encoding(),
query.as_notebook().is_some(),
)
@ -92,28 +76,31 @@ pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result<super::Form
fn format_text_document(
text_document: &TextDocument,
source_type: PySourceType,
file_path: &Path,
file_resolver_settings: &FileResolverSettings,
formatter_settings: &FormatterSettings,
query: &DocumentQuery,
encoding: PositionEncoding,
is_notebook: bool,
) -> Result<super::FormatResponse> {
let file_resolver_settings = query.settings().file_resolver();
let formatter_settings = query.settings().formatter();
// If the document is excluded, return early.
if let Some(exclusion) = match_any_exclusion(
file_path,
&file_resolver_settings.exclude,
&file_resolver_settings.extend_exclude,
None,
Some(&formatter_settings.exclude),
) {
tracing::debug!("Ignored path via `{}`: {}", exclusion, file_path.display());
return Ok(None);
if let Some(file_path) = query.file_path() {
if let Some(exclusion) = match_any_exclusion(
&file_path,
&file_resolver_settings.exclude,
&file_resolver_settings.extend_exclude,
None,
Some(&formatter_settings.exclude),
) {
tracing::debug!("Ignored path via `{}`: {}", exclusion, file_path.display());
return Ok(None);
}
}
let source = text_document.contents();
let mut formatted = crate::format::format(text_document, source_type, formatter_settings)
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
let mut formatted =
crate::format::format(text_document, query.source_type(), formatter_settings)
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
// fast path - if the code is the same, return early
if formatted == source {
return Ok(None);

View file

@ -1,15 +1,11 @@
use std::path::Path;
use lsp_types::{self as types, request as req, Range};
use ruff_python_ast::PySourceType;
use ruff_workspace::resolver::match_any_exclusion;
use ruff_workspace::{FileResolverSettings, FormatterSettings};
use crate::edit::{RangeExt, ToRangeExt};
use crate::server::api::LSPResult;
use crate::server::{client::Notifier, Result};
use crate::session::DocumentSnapshot;
use crate::session::{DocumentQuery, DocumentSnapshot};
use crate::{PositionEncoding, TextDocument};
pub(crate) struct FormatRange;
@ -39,45 +35,43 @@ fn format_document_range(
.as_single_document()
.expect("format should only be called on text documents or notebook cells");
let query = snapshot.query();
format_text_document_range(
text_document,
range,
query.source_type(),
query.file_path(),
query.settings().file_resolver(),
query.settings().formatter(),
snapshot.encoding(),
)
format_text_document_range(text_document, range, query, snapshot.encoding())
}
/// Formats the specified [`Range`] in the [`TextDocument`].
fn format_text_document_range(
text_document: &TextDocument,
range: Range,
source_type: PySourceType,
file_path: &Path,
file_resolver_settings: &FileResolverSettings,
formatter_settings: &FormatterSettings,
query: &DocumentQuery,
encoding: PositionEncoding,
) -> Result<super::FormatResponse> {
let file_resolver_settings = query.settings().file_resolver();
let formatter_settings = query.settings().formatter();
// If the document is excluded, return early.
if let Some(exclusion) = match_any_exclusion(
file_path,
&file_resolver_settings.exclude,
&file_resolver_settings.extend_exclude,
None,
Some(&formatter_settings.exclude),
) {
tracing::debug!("Ignored path via `{}`: {}", exclusion, file_path.display());
return Ok(None);
if let Some(file_path) = query.file_path() {
if let Some(exclusion) = match_any_exclusion(
&file_path,
&file_resolver_settings.exclude,
&file_resolver_settings.extend_exclude,
None,
Some(&formatter_settings.exclude),
) {
tracing::debug!("Ignored path via `{}`: {}", exclusion, file_path.display());
return Ok(None);
}
}
let text = text_document.contents();
let index = text_document.index();
let range = range.to_text_range(text, index, encoding);
let formatted_range =
crate::format::format_range(text_document, source_type, formatter_settings, range)
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
let formatted_range = crate::format::format_range(
text_document,
query.source_type(),
formatter_settings,
range,
)
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
Ok(Some(vec![types::TextEdit {
range: formatted_range