ruff server: Support Jupyter Notebook (*.ipynb) files (#11206)

## Summary

Closes https://github.com/astral-sh/ruff/issues/10858.

`ruff server` now supports `*.ipynb` (aka Jupyter Notebook) files.
Extensive internal changes have been made to facilitate this, which I've
done some work to contextualize with documentation and an pre-review
that highlights notable sections of the code.

`*.ipynb` cells should behave similarly to `*.py` documents, with one
major exception. The format command `ruff.applyFormat` will only apply
to the currently selected notebook cell - if you want to format an
entire notebook document, use `Format Notebook` from the VS Code context
menu.

## Test Plan

The VS Code extension does not yet have Jupyter Notebook support
enabled, so you'll first need to enable it manually. To do this,
checkout the `pre-release` branch and modify `src/common/server.ts` as
follows:

Before:
![Screenshot 2024-05-13 at 10 59
06 PM](c6a3c604-c405-4968-b8a2-5d670de89172)

After:
![Screenshot 2024-05-13 at 10 58
24 PM](94ab2e3d-0609-448d-9c8c-cd07c69a513b)

I recommend testing this PR with large, complicated notebook files. I
used notebook files from [this popular
repository](https://github.com/jakevdp/PythonDataScienceHandbook/tree/master/notebooks)
in my preliminary testing.

The main thing to test is ensuring that notebook cells behave the same
as Python documents, besides the aforementioned issue with
`ruff.applyFormat`. You should also test adding and deleting cells (in
particular, deleting all the code cells and ensure that doesn't break
anything), changing the kind of a cell (i.e. from markup -> code or vice
versa), and creating a new notebook file from scratch. Finally, you
should also test that source actions work as expected (and across the
entire notebook).

Note: `ruff.applyAutofix` and `ruff.applyOrganizeImports` are currently
broken for notebook files, and I suspect it has something to do with
https://github.com/astral-sh/ruff/issues/11248. Once this is fixed, I
will update the test plan accordingly.

---------

Co-authored-by: nolan <nolan.king90@gmail.com>
This commit is contained in:
Jane Lewis 2024-05-21 15:29:30 -07:00 committed by GitHub
parent 84531d1644
commit b0731ef9cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 1584 additions and 622 deletions

View file

@ -1,6 +1,7 @@
//! Scheduling, I/O, and API endpoints.
use std::num::NonZeroUsize;
use std::path::PathBuf;
use lsp_server as lsp;
use lsp_types as types;
@ -10,6 +11,9 @@ use types::CodeActionOptions;
use types::DiagnosticOptions;
use types::DidChangeWatchedFilesRegistrationOptions;
use types::FileSystemWatcher;
use types::NotebookCellSelector;
use types::NotebookDocumentSyncOptions;
use types::NotebookSelector;
use types::OneOf;
use types::TextDocumentSyncCapability;
use types::TextDocumentSyncKind;
@ -67,26 +71,26 @@ impl Server {
mut workspace_settings,
} = AllSettings::from_value(init_params.initialization_options.unwrap_or_default());
let mut workspace_for_uri = |uri| {
let mut workspace_for_path = |path: PathBuf| {
let Some(workspace_settings) = workspace_settings.as_mut() else {
return (uri, ClientSettings::default());
return (path, ClientSettings::default());
};
let settings = workspace_settings.remove(&uri).unwrap_or_else(|| {
tracing::warn!("No workspace settings found for {uri}");
let settings = workspace_settings.remove(&path).unwrap_or_else(|| {
tracing::warn!("No workspace settings found for {}", path.display());
ClientSettings::default()
});
(uri, settings)
(path, settings)
};
let workspaces = init_params
.workspace_folders
.map(|folders| folders.into_iter().map(|folder| {
workspace_for_uri(folder.uri)
workspace_for_path(folder.uri.to_file_path().unwrap())
}).collect())
.or_else(|| {
tracing::debug!("No workspace(s) were provided during initialization. Using the current working directory as a default workspace...");
let uri = types::Url::from_file_path(std::env::current_dir().ok()?).ok()?;
Some(vec![workspace_for_uri(uri)])
Some(vec![workspace_for_path(uri.to_file_path().unwrap())])
})
.ok_or_else(|| {
anyhow::anyhow!("Failed to get the current working directory while creating a default workspace.")
@ -100,7 +104,7 @@ impl Server {
position_encoding,
global_settings,
workspaces,
)?,
),
client_capabilities,
})
}
@ -252,6 +256,16 @@ impl Server {
},
)),
hover_provider: Some(types::HoverProviderCapability::Simple(true)),
notebook_document_sync: Some(types::OneOf::Left(NotebookDocumentSyncOptions {
save: Some(false),
notebook_selector: [NotebookSelector::ByCells {
notebook: None,
cells: vec![NotebookCellSelector {
language: "python".to_string(),
}],
}]
.to_vec(),
})),
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
@ -278,8 +292,15 @@ pub(crate) enum SupportedCodeAction {
SourceFixAll,
/// Maps to `source.organizeImports` and `source.organizeImports.ruff` code action kinds.
/// This is a source action that applies import sorting fixes to the currently open document.
#[allow(dead_code)] // TODO: remove
SourceOrganizeImports,
/// Maps to the `notebook.source.fixAll` and `notebook.source.fixAll.ruff` code action kinds.
/// This is a source action, specifically for notebooks, that applies all safe fixes
/// to the currently open document.
NotebookSourceFixAll,
/// Maps to `source.organizeImports` and `source.organizeImports.ruff` code action kinds.
/// This is a source action, specifically for notebooks, that applies import sorting fixes
/// to the currently open document.
NotebookSourceOrganizeImports,
}
impl SupportedCodeAction {
@ -289,6 +310,8 @@ impl SupportedCodeAction {
Self::QuickFix => CodeActionKind::QUICKFIX,
Self::SourceFixAll => crate::SOURCE_FIX_ALL_RUFF,
Self::SourceOrganizeImports => crate::SOURCE_ORGANIZE_IMPORTS_RUFF,
Self::NotebookSourceFixAll => crate::NOTEBOOK_SOURCE_FIX_ALL_RUFF,
Self::NotebookSourceOrganizeImports => crate::NOTEBOOK_SOURCE_ORGANIZE_IMPORTS_RUFF,
}
}
@ -304,6 +327,8 @@ impl SupportedCodeAction {
Self::QuickFix,
Self::SourceFixAll,
Self::SourceOrganizeImports,
Self::NotebookSourceFixAll,
Self::NotebookSourceOrganizeImports,
]
.into_iter()
}