Skip to content

Commit 3cb2e67

Browse files
authored
ruff.applyFormat now formats an entire notebook document (#11493)
## Summary Previously, `ruff.applyFormat`, seen in VS Code as the command `Ruff: Format Document`, would only format the currently active notebook cell inside a notebook document. This PR makes `ruff.applyFormat` format the entire notebook document at once, operating on each code cell in order. ## Test Plan 1. Open a notebook document that has multiple unformatted code cells. 2. Run `Ruff: Format Document` through the Command Palette (`Ctrl/Cmd+Shift+P` by default) 3. Observe that all code cells in the notebook have been formatted.
1 parent f0046ab commit 3cb2e67

File tree

2 files changed

+64
-14
lines changed

2 files changed

+64
-14
lines changed

crates/ruff_server/src/server/api/requests/execute_command.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,10 @@ impl super::SyncRequestHandler for ExecuteCommand {
7373
.with_failure_code(ErrorCode::InternalError)?;
7474
}
7575
Command::Format => {
76-
let response = super::format::format_document(&snapshot)?;
77-
if let Some(edits) = response {
78-
edit_tracker
79-
.set_edits_for_document(uri, version, edits)
80-
.with_failure_code(ErrorCode::InternalError)?;
81-
}
76+
let fixes = super::format::format_full_document(&snapshot)?;
77+
edit_tracker
78+
.set_fixes_for_document(fixes, version)
79+
.with_failure_code(ErrorCode::InternalError)?;
8280
}
8381
Command::OrganizeImports => {
8482
let fixes = super::code_action_resolve::organize_imports_edit(

crates/ruff_server/src/server/api/requests/format.rs

+60-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use crate::edit::{Replacement, ToRangeExt};
2+
use crate::fix::Fixes;
23
use crate::server::api::LSPResult;
34
use crate::server::{client::Notifier, Result};
45
use crate::session::DocumentSnapshot;
6+
use crate::{PositionEncoding, TextDocument};
57
use lsp_types::{self as types, request as req};
8+
use ruff_python_ast::PySourceType;
69
use ruff_source_file::LineIndex;
10+
use ruff_workspace::FormatterSettings;
711
use types::TextEdit;
812

913
pub(crate) struct Format;
@@ -23,25 +27,73 @@ impl super::BackgroundDocumentRequestHandler for Format {
2327
}
2428
}
2529

30+
/// Formats either a full text document or each individual cell in a single notebook document.
31+
pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result<Fixes> {
32+
let mut fixes = Fixes::default();
33+
34+
if let Some(notebook) = snapshot.query().as_notebook() {
35+
for (url, text_document) in notebook
36+
.urls()
37+
.map(|url| (url.clone(), notebook.cell_document_by_uri(url).unwrap()))
38+
{
39+
if let Some(changes) = format_text_document(
40+
text_document,
41+
snapshot.query().source_type(),
42+
snapshot.query().settings().formatter(),
43+
snapshot.encoding(),
44+
true,
45+
)? {
46+
fixes.insert(url, changes);
47+
}
48+
}
49+
} else {
50+
if let Some(changes) = format_text_document(
51+
snapshot.query().as_single_document().unwrap(),
52+
snapshot.query().source_type(),
53+
snapshot.query().settings().formatter(),
54+
snapshot.encoding(),
55+
false,
56+
)? {
57+
fixes.insert(snapshot.query().make_key().into_url(), changes);
58+
}
59+
}
60+
61+
Ok(fixes)
62+
}
63+
64+
/// Formats either a full text document or an specific notebook cell. If the query within the snapshot is a notebook document
65+
/// with no selected cell, this will throw an error.
2666
pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result<super::FormatResponse> {
27-
let doc = snapshot
67+
let text_document = snapshot
2868
.query()
2969
.as_single_document()
3070
.expect("format should only be called on text documents or notebook cells");
31-
let source = doc.contents();
32-
let mut formatted = crate::format::format(
33-
doc,
71+
format_text_document(
72+
text_document,
3473
snapshot.query().source_type(),
3574
snapshot.query().settings().formatter(),
75+
snapshot.encoding(),
76+
snapshot.query().as_notebook().is_some(),
3677
)
37-
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
78+
}
79+
80+
fn format_text_document(
81+
text_document: &TextDocument,
82+
source_type: PySourceType,
83+
formatter_settings: &FormatterSettings,
84+
encoding: PositionEncoding,
85+
is_notebook: bool,
86+
) -> Result<super::FormatResponse> {
87+
let source = text_document.contents();
88+
let mut formatted = crate::format::format(text_document, source_type, formatter_settings)
89+
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
3890
// fast path - if the code is the same, return early
3991
if formatted == source {
4092
return Ok(None);
4193
}
4294

4395
// special case - avoid adding a newline to a notebook cell if it didn't already exist
44-
if snapshot.query().as_notebook().is_some() {
96+
if is_notebook {
4597
let mut trimmed = formatted.as_str();
4698
if !source.ends_with("\r\n") {
4799
trimmed = trimmed.trim_end_matches("\r\n");
@@ -57,7 +109,7 @@ pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result<super::Form
57109

58110
let formatted_index: LineIndex = LineIndex::from_source_text(&formatted);
59111

60-
let unformatted_index = doc.index();
112+
let unformatted_index = text_document.index();
61113

62114
let Replacement {
63115
source_range,
@@ -70,7 +122,7 @@ pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result<super::Form
70122
);
71123

72124
Ok(Some(vec![TextEdit {
73-
range: source_range.to_range(source, unformatted_index, snapshot.encoding()),
125+
range: source_range.to_range(source, unformatted_index, encoding),
74126
new_text: formatted[formatted_range].to_owned(),
75127
}]))
76128
}

0 commit comments

Comments
 (0)