Skip to content

Commit

Permalink
Implement support for Jupyter Notebooks in ruff server
Browse files Browse the repository at this point in the history
  • Loading branch information
snowsignal committed May 9, 2024
1 parent 0213eb8 commit 3a721e2
Show file tree
Hide file tree
Showing 35 changed files with 823 additions and 341 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 25 additions & 1 deletion crates/ruff_notebook/src/notebook.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cmp::Ordering;
use std::collections::HashSet;
use std::collections::{BTreeMap, HashSet};
use std::fs::File;
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
Expand Down Expand Up @@ -85,6 +85,24 @@ impl Notebook {
Self::from_reader(Cursor::new(source_code))
}

pub fn from_cells(cells: Vec<Cell>) -> Result<Self, NotebookError> {
let raw_notebook = RawNotebook {
cells,
metadata: crate::RawNotebookMetadata {
authors: None,
kernelspec: None,
language_info: None,
orig_nbformat: None,
title: None,
extra: BTreeMap::default(),
},
nbformat: 4,
nbformat_minor: 5,
};

Self::from_raw(raw_notebook, false)
}

/// Read a Jupyter Notebook from a [`Read`] implementer.
///
/// See also the black implementation
Expand Down Expand Up @@ -113,7 +131,13 @@ impl Notebook {
});
}
};
Self::from_raw(raw_notebook, trailing_newline)
}

fn from_raw(
mut raw_notebook: RawNotebook,
trailing_newline: bool,
) -> Result<Self, NotebookError> {
// v4 is what everybody uses
if raw_notebook.nbformat != 4 {
// bail because we should have already failed at the json schema stage
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ruff_python_formatter = { path = "../ruff_python_formatter" }
ruff_python_index = { path = "../ruff_python_index" }
ruff_python_parser = { path = "../ruff_python_parser" }
ruff_source_file = { path = "../ruff_source_file" }
ruff_notebook = { path = "../ruff_notebook" }
ruff_text_size = { path = "../ruff_text_size" }
ruff_workspace = { path = "../ruff_workspace" }

Expand Down
120 changes: 118 additions & 2 deletions crates/ruff_server/src/edit.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
//! Types and utilities for working with text, modifying source files, and `Ruff <-> LSP` type conversion.

mod document;
mod notebook;
mod range;
mod replacement;

use std::collections::HashMap;
use std::{
collections::HashMap,
path::{Display, PathBuf},
};

pub use document::Document;
pub(crate) use document::DocumentVersion;
pub use document::TextDocument;
use lsp_types::PositionEncodingKind;
pub(crate) use notebook::NotebookDocument;
pub(crate) use range::{RangeExt, ToRangeExt};
pub(crate) use replacement::Replacement;
use ruff_linter::source_kind::SourceKind;
use ruff_source_file::LineIndex;

use crate::session::ResolvedClientCapabilities;

Expand All @@ -29,6 +36,115 @@ pub enum PositionEncoding {
UTF8,
}

/// A wrapper for a document. Can be either a text document (`.py`)
/// or a notebook document (`.ipynb`).
#[derive(Clone)]
pub(crate) enum Document {
Text(TextDocument),
Notebook(NotebookDocument),
}

#[derive(Clone, Debug)]
pub(crate) enum DocumentKey {
File(PathBuf),
Cell(lsp_types::Url),
}

impl Document {
pub(crate) fn version(&self) -> DocumentVersion {
match self {
Self::Notebook(notebook) => notebook.version(),
Self::Text(py) => py.version(),
}
}

pub(crate) fn make_source_kind(&self) -> SourceKind {
match self {
Self::Notebook(notebook) => {
let notebook = SourceKind::IpyNotebook(notebook.make_ruff_notebook());
tracing::info!("{notebook:?}");
notebook
}
Self::Text(text) => SourceKind::Python(text.contents().to_string()),
}
}

pub(crate) fn index(&self) -> &LineIndex {
match self {
Self::Notebook(notebook) => notebook.index(),
Self::Text(text) => text.index(),
}
}

pub(crate) fn as_notebook(&self) -> Option<&NotebookDocument> {
match self {
Self::Notebook(notebook) => Some(&notebook),
Self::Text(_) => None,
}
}

pub(crate) fn as_text(&self) -> Option<&TextDocument> {
match self {
Self::Text(py) => Some(&py),
Self::Notebook(_) => None,
}
}

pub(crate) fn as_notebook_mut(&mut self) -> Option<&mut NotebookDocument> {
match self {
Self::Notebook(ref mut notebook) => Some(notebook),
Self::Text(_) => None,
}
}

pub(crate) fn as_text_mut(&mut self) -> Option<&mut TextDocument> {
match self {
Self::Notebook(_) => None,
Self::Text(ref mut py) => Some(py),
}
}

pub(crate) fn as_notebook_cell(&self, uri: &lsp_types::Url) -> Option<&TextDocument> {
self.as_notebook()?.cell(uri)
}

pub(crate) fn as_notebook_cell_mut(
&mut self,
uri: &lsp_types::Url,
) -> Option<&mut TextDocument> {
self.as_notebook_mut()?.cell_mut(uri)
}
}

impl DocumentKey {
pub(crate) fn from_url(url: &lsp_types::Url) -> Self {
if url.scheme() != "file" {
return Self::Cell(url.clone());
}
url.to_file_path()
.map(Self::File)
.unwrap_or_else(|_| Self::Cell(url.clone()))
}

pub(crate) fn to_url(self) -> lsp_types::Url {
match self {
Self::Cell(url) => url,
Self::File(path) => {
lsp_types::Url::from_file_path(path).expect("path should convert to file URL")
}
}
}
}

impl std::fmt::Display for DocumentKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cell(url) => url.fmt(f),
Self::File(path) => path.display().fmt(f),
}
}
}

/// Tracks multi-document edits to eventually merge into a `WorkspaceEdit`.
/// Compatible with clients that don't support `workspace.workspaceEdit.documentChanges`.
#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions crates/ruff_server/src/edit/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) type DocumentVersion = i32;
/// The state for an individual document in the server. Stays up-to-date
/// with changes made by the user, including unsaved changes.
#[derive(Debug, Clone)]
pub struct Document {
pub struct TextDocument {
/// The string contents of the document.
contents: String,
/// A computed line index for the document. This should always reflect
Expand All @@ -22,7 +22,7 @@ pub struct Document {
version: DocumentVersion,
}

impl Document {
impl TextDocument {
pub fn new(contents: String, version: DocumentVersion) -> Self {
let index = LineIndex::from_source_text(&contents);
Self {
Expand Down
Loading

0 comments on commit 3a721e2

Please sign in to comment.