Skip to content

Commit 5b500fc

Browse files
T-256T-256MichaReiser
authored
ruff server: Add support for documents not exist on disk (#11588)
Co-authored-by: T-256 <[email protected]> Co-authored-by: Micha Reiser <[email protected]>
1 parent 685d11a commit 5b500fc

20 files changed

+379
-365
lines changed

crates/ruff_server/src/edit.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ mod range;
55
mod replacement;
66
mod text_document;
77

8-
use std::{collections::HashMap, path::PathBuf};
8+
use std::collections::HashMap;
99

10-
use lsp_types::PositionEncodingKind;
10+
use lsp_types::{PositionEncodingKind, Url};
1111
pub(crate) use notebook::NotebookDocument;
1212
pub(crate) use range::{NotebookRange, RangeExt, ToRangeExt};
1313
pub(crate) use replacement::Replacement;
@@ -35,29 +35,26 @@ pub enum PositionEncoding {
3535
/// This document ID can point to either be a standalone Python file, a full notebook, or a cell within a notebook.
3636
#[derive(Clone, Debug)]
3737
pub(crate) enum DocumentKey {
38-
Notebook(PathBuf),
39-
NotebookCell(lsp_types::Url),
40-
Text(PathBuf),
38+
Notebook(Url),
39+
NotebookCell(Url),
40+
Text(Url),
4141
}
4242

4343
impl DocumentKey {
4444
/// Converts the key back into its original URL.
45-
pub(crate) fn into_url(self) -> lsp_types::Url {
45+
pub(crate) fn into_url(self) -> Url {
4646
match self {
47-
DocumentKey::NotebookCell(url) => url,
48-
DocumentKey::Notebook(path) | DocumentKey::Text(path) => {
49-
lsp_types::Url::from_file_path(path)
50-
.expect("file path originally from URL should convert back to URL")
51-
}
47+
DocumentKey::NotebookCell(url)
48+
| DocumentKey::Notebook(url)
49+
| DocumentKey::Text(url) => url,
5250
}
5351
}
5452
}
5553

5654
impl std::fmt::Display for DocumentKey {
5755
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5856
match self {
59-
Self::NotebookCell(url) => url.fmt(f),
60-
Self::Notebook(path) | Self::Text(path) => path.display().fmt(f),
57+
Self::NotebookCell(url) | Self::Notebook(url) | Self::Text(url) => url.fmt(f),
6158
}
6259
}
6360
}
@@ -67,7 +64,7 @@ impl std::fmt::Display for DocumentKey {
6764
#[derive(Debug)]
6865
pub(crate) enum WorkspaceEditTracker {
6966
DocumentChanges(Vec<lsp_types::TextDocumentEdit>),
70-
Changes(HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>),
67+
Changes(HashMap<Url, Vec<lsp_types::TextEdit>>),
7168
}
7269

7370
impl From<PositionEncoding> for lsp_types::PositionEncodingKind {
@@ -122,7 +119,7 @@ impl WorkspaceEditTracker {
122119
/// multiple times.
123120
pub(crate) fn set_edits_for_document(
124121
&mut self,
125-
uri: lsp_types::Url,
122+
uri: Url,
126123
_version: DocumentVersion,
127124
edits: Vec<lsp_types::TextEdit>,
128125
) -> crate::Result<()> {

crates/ruff_server/src/fix.rs

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,37 @@ pub(crate) fn fix_all(
2626
linter_settings: &LinterSettings,
2727
encoding: PositionEncoding,
2828
) -> crate::Result<Fixes> {
29-
let document_path = query.file_path();
3029
let source_kind = query.make_source_kind();
3130

3231
let file_resolver_settings = query.settings().file_resolver();
32+
let document_path = query.file_path();
3333

3434
// If the document is excluded, return an empty list of fixes.
35-
if let Some(exclusion) = match_any_exclusion(
36-
document_path,
37-
&file_resolver_settings.exclude,
38-
&file_resolver_settings.extend_exclude,
39-
Some(&linter_settings.exclude),
40-
None,
41-
) {
42-
tracing::debug!(
43-
"Ignored path via `{}`: {}",
44-
exclusion,
45-
document_path.display()
46-
);
47-
return Ok(Fixes::default());
48-
}
35+
let package = if let Some(document_path) = document_path.as_ref() {
36+
if let Some(exclusion) = match_any_exclusion(
37+
document_path,
38+
&file_resolver_settings.exclude,
39+
&file_resolver_settings.extend_exclude,
40+
Some(&linter_settings.exclude),
41+
None,
42+
) {
43+
tracing::debug!(
44+
"Ignored path via `{}`: {}",
45+
exclusion,
46+
document_path.display()
47+
);
48+
return Ok(Fixes::default());
49+
}
4950

50-
let package = detect_package_root(
51-
document_path
52-
.parent()
53-
.expect("a path to a document should have a parent path"),
54-
&linter_settings.namespace_packages,
55-
);
51+
detect_package_root(
52+
document_path
53+
.parent()
54+
.expect("a path to a document should have a parent path"),
55+
&linter_settings.namespace_packages,
56+
)
57+
} else {
58+
None
59+
};
5660

5761
let source_type = query.source_type();
5862

@@ -67,7 +71,7 @@ pub(crate) fn fix_all(
6771
result: LinterResult { error, .. },
6872
..
6973
} = ruff_linter::linter::lint_fix(
70-
document_path,
74+
query.virtual_file_path(),
7175
package,
7276
flags::Noqa::Enabled,
7377
UnsafeFixes::Disabled,

crates/ruff_server/src/lint.rs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Access to the Ruff linting API for the LSP
22
3+
use rustc_hash::FxHashMap;
4+
use serde::{Deserialize, Serialize};
5+
36
use ruff_diagnostics::{Applicability, Diagnostic, DiagnosticKind, Edit, Fix};
47
use ruff_linter::{
58
directives::{extract_directives, Flags},
@@ -17,8 +20,6 @@ use ruff_python_parser::AsMode;
1720
use ruff_source_file::{LineIndex, Locator};
1821
use ruff_text_size::{Ranged, TextRange};
1922
use ruff_workspace::resolver::match_any_exclusion;
20-
use rustc_hash::FxHashMap;
21-
use serde::{Deserialize, Serialize};
2223

2324
use crate::{
2425
edit::{NotebookRange, ToRangeExt},
@@ -60,33 +61,37 @@ pub(crate) struct DiagnosticFix {
6061
pub(crate) type Diagnostics = FxHashMap<lsp_types::Url, Vec<lsp_types::Diagnostic>>;
6162

6263
pub(crate) fn check(query: &DocumentQuery, encoding: PositionEncoding) -> Diagnostics {
63-
let document_path = query.file_path();
6464
let source_kind = query.make_source_kind();
6565
let file_resolver_settings = query.settings().file_resolver();
6666
let linter_settings = query.settings().linter();
67+
let document_path = query.file_path();
6768

6869
// If the document is excluded, return an empty list of diagnostics.
69-
if let Some(exclusion) = match_any_exclusion(
70-
document_path,
71-
&file_resolver_settings.exclude,
72-
&file_resolver_settings.extend_exclude,
73-
Some(&linter_settings.exclude),
74-
None,
75-
) {
76-
tracing::debug!(
77-
"Ignored path via `{}`: {}",
78-
exclusion,
79-
document_path.display()
80-
);
81-
return Diagnostics::default();
82-
}
70+
let package = if let Some(document_path) = document_path.as_ref() {
71+
if let Some(exclusion) = match_any_exclusion(
72+
document_path,
73+
&file_resolver_settings.exclude,
74+
&file_resolver_settings.extend_exclude,
75+
Some(&linter_settings.exclude),
76+
None,
77+
) {
78+
tracing::debug!(
79+
"Ignored path via `{}`: {}",
80+
exclusion,
81+
document_path.display()
82+
);
83+
return Diagnostics::default();
84+
}
8385

84-
let package = detect_package_root(
85-
document_path
86-
.parent()
87-
.expect("a path to a document should have a parent path"),
88-
&linter_settings.namespace_packages,
89-
);
86+
detect_package_root(
87+
document_path
88+
.parent()
89+
.expect("a path to a document should have a parent path"),
90+
&linter_settings.namespace_packages,
91+
)
92+
} else {
93+
None
94+
};
9095

9196
let source_type = query.source_type();
9297

@@ -109,7 +114,7 @@ pub(crate) fn check(query: &DocumentQuery, encoding: PositionEncoding) -> Diagno
109114

110115
// Generate checks.
111116
let LinterResult { data, .. } = check_path(
112-
document_path,
117+
query.virtual_file_path(),
113118
package,
114119
&locator,
115120
&stylist,
@@ -123,7 +128,7 @@ pub(crate) fn check(query: &DocumentQuery, encoding: PositionEncoding) -> Diagno
123128
);
124129

125130
let noqa_edits = generate_noqa_edits(
126-
document_path,
131+
query.virtual_file_path(),
127132
data.as_slice(),
128133
&locator,
129134
indexer.comment_ranges(),

crates/ruff_server/src/server.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Scheduling, I/O, and API endpoints.
22
33
use std::num::NonZeroUsize;
4-
use std::path::PathBuf;
54

65
use lsp_server as lsp;
76
use lsp_types as types;
@@ -75,27 +74,27 @@ impl Server {
7574
.unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())),
7675
);
7776

78-
let mut workspace_for_path = |path: PathBuf| {
77+
let mut workspace_for_url = |url: lsp_types::Url| {
7978
let Some(workspace_settings) = workspace_settings.as_mut() else {
80-
return (path, ClientSettings::default());
79+
return (url, ClientSettings::default());
8180
};
82-
let settings = workspace_settings.remove(&path).unwrap_or_else(|| {
83-
tracing::warn!("No workspace settings found for {}", path.display());
81+
let settings = workspace_settings.remove(&url).unwrap_or_else(|| {
82+
tracing::warn!("No workspace settings found for {}", url);
8483
ClientSettings::default()
8584
});
86-
(path, settings)
85+
(url, settings)
8786
};
8887

8988
let workspaces = init_params
9089
.workspace_folders
9190
.filter(|folders| !folders.is_empty())
9291
.map(|folders| folders.into_iter().map(|folder| {
93-
workspace_for_path(folder.uri.to_file_path().unwrap())
92+
workspace_for_url(folder.uri)
9493
}).collect())
9594
.or_else(|| {
9695
tracing::warn!("No workspace(s) were provided during initialization. Using the current working directory as a default workspace...");
9796
let uri = types::Url::from_file_path(std::env::current_dir().ok()?).ok()?;
98-
Some(vec![workspace_for_path(uri.to_file_path().unwrap())])
97+
Some(vec![workspace_for_url(uri)])
9998
})
10099
.ok_or_else(|| {
101100
anyhow::anyhow!("Failed to get the current working directory while creating a default workspace.")
@@ -109,7 +108,7 @@ impl Server {
109108
position_encoding,
110109
global_settings,
111110
workspaces,
112-
),
111+
)?,
113112
client_capabilities,
114113
})
115114
}

crates/ruff_server/src/server/api.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
122122
let (id, params) = cast_request::<R>(req)?;
123123
Ok(Task::background(schedule, move |session: &Session| {
124124
// TODO(jane): we should log an error if we can't take a snapshot.
125-
let Some(snapshot) = session.take_snapshot(&R::document_url(&params)) else {
125+
let Some(snapshot) = session.take_snapshot(R::document_url(&params).into_owned()) else {
126126
return Box::new(|_, _| {});
127127
};
128128
Box::new(move |notifier, responder| {
@@ -152,7 +152,7 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH
152152
let (id, params) = cast_notification::<N>(req)?;
153153
Ok(Task::background(schedule, move |session: &Session| {
154154
// TODO(jane): we should log an error if we can't take a snapshot.
155-
let Some(snapshot) = session.take_snapshot(&N::document_url(&params)) else {
155+
let Some(snapshot) = session.take_snapshot(N::document_url(&params).into_owned()) else {
156156
return Box::new(|_, _| {});
157157
};
158158
Box::new(move |notifier, _| {

crates/ruff_server/src/server/api/notifications/did_change.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,15 @@ impl super::SyncNotificationHandler for DidChange {
2727
content_changes,
2828
}: types::DidChangeTextDocumentParams,
2929
) -> Result<()> {
30-
let key = session
31-
.key_from_url(&uri)
32-
.with_failure_code(ErrorCode::InternalError)?;
30+
let key = session.key_from_url(uri);
3331

3432
session
3533
.update_text_document(&key, content_changes, new_version)
3634
.with_failure_code(ErrorCode::InternalError)?;
3735

3836
// Publish diagnostics if the client doesnt support pull diagnostics
3937
if !session.resolved_client_capabilities().pull_diagnostics {
40-
let snapshot = session.take_snapshot(&uri).unwrap();
38+
let snapshot = session.take_snapshot(key.into_url()).unwrap();
4139
publish_diagnostics_for_document(&snapshot, &notifier)?;
4240
}
4341

crates/ruff_server/src/server/api/notifications/did_change_notebook.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,14 @@ impl super::SyncNotificationHandler for DidChangeNotebook {
2323
change: types::NotebookDocumentChangeEvent { cells, metadata },
2424
}: types::DidChangeNotebookDocumentParams,
2525
) -> Result<()> {
26-
let key = session
27-
.key_from_url(&uri)
28-
.with_failure_code(ErrorCode::InternalError)?;
26+
let key = session.key_from_url(uri);
2927
session
3028
.update_notebook_document(&key, cells, metadata, version)
3129
.with_failure_code(ErrorCode::InternalError)?;
3230

3331
// publish new diagnostics
3432
let snapshot = session
35-
.take_snapshot(&uri)
33+
.take_snapshot(key.into_url())
3634
.expect("snapshot should be available");
3735
publish_diagnostics_for_document(&snapshot, &notifier)?;
3836

crates/ruff_server/src/server/api/notifications/did_change_watched_files.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
2121
params: types::DidChangeWatchedFilesParams,
2222
) -> Result<()> {
2323
for change in &params.changes {
24-
session.reload_settings(&change.uri.to_file_path().unwrap());
24+
session.reload_settings(&change.uri);
2525
}
2626

2727
if !params.changes.is_empty() {
@@ -33,7 +33,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
3333
// publish diagnostics for text documents
3434
for url in session.text_document_urls() {
3535
let snapshot = session
36-
.take_snapshot(&url)
36+
.take_snapshot(url.clone())
3737
.expect("snapshot should be available");
3838
publish_diagnostics_for_document(&snapshot, &notifier)?;
3939
}
@@ -42,7 +42,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
4242
// always publish diagnostics for notebook files (since they don't use pull diagnostics)
4343
for url in session.notebook_document_urls() {
4444
let snapshot = session
45-
.take_snapshot(&url)
45+
.take_snapshot(url.clone())
4646
.expect("snapshot should be available");
4747
publish_diagnostics_for_document(&snapshot, &notifier)?;
4848
}

0 commit comments

Comments
 (0)