pub use notification_params::*;
use {
super::LspVersion,
crate::{
Uri,
database::PartitionWriteContextRef,
protocol::{
lsp::{LSPObject, PositionEncodingKind},
macros::lsp_enum,
},
scheduler::task::TaskContext,
},
serde::{
Deserialize,
Serialize,
},
};
fn deserialize_lsp_version<'de, D>(deserializer: D) -> Result<LspVersion, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = i32::deserialize(deserializer)?;
Ok(value.into())
}
fn serialize_lsp_version<S>(version: &LspVersion, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i32(version.raw())
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocument {
pub uri: Uri,
pub notebook_type: String,
#[serde(deserialize_with = "deserialize_lsp_version", serialize_with = "serialize_lsp_version")]
pub version: LspVersion,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<LSPObject>,
pub cells: Vec<NotebookCell>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookCell {
pub kind: NotebookCellKind,
pub document: Uri,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<LSPObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_summary: Option<ExecutionSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecutionSummary {
pub execution_order: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub success: Option<bool>,
}
#[derive(Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct NotebookCellKind(i32);
lsp_enum! {
impl NotebookCellKind {
const MARKUP = 1;
const CODE = 2;
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentClientCapabilities {
pub synchronization: NotebookDocumentSyncClientCapabilities,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentSyncClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_registration: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_summary_report: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentSyncOptions {
pub notebook_selector: Vec<NotebookSelector>,
#[serde(skip_serializing_if = "Option::is_none")]
pub save: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentSyncRegistrationOptions {
pub notebook_selector: Vec<NotebookSelector>,
#[serde(skip_serializing_if = "Option::is_none")]
pub save: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookCellTextDocumentFilter {
pub notebook: Notebook,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum NotebookSelector {
ByNotebook {
notebook: Notebook,
#[serde(skip_serializing_if = "Option::is_none")]
cells: Option<Vec<NotebookCellSelector>>,
},
ByCells {
#[serde(skip_serializing_if = "Option::is_none")]
notebook: Option<Notebook>,
cells: Vec<NotebookCellSelector>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookCellSelector {
pub language: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Notebook {
String(String),
NotebookDocumentFilter(NotebookDocumentFilter),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum NotebookDocumentFilter {
ByType {
notebook_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
scheme: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pattern: Option<String>,
},
ByScheme {
#[serde(skip_serializing_if = "Option::is_none")]
notebook_type: Option<String>,
scheme: String,
#[serde(skip_serializing_if = "Option::is_none")]
pattern: Option<String>,
},
ByPattern {
#[serde(skip_serializing_if = "Option::is_none")]
notebook_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
scheme: Option<String>,
pattern: String,
},
}
mod notification_params {
use {
crate::{
protocol::lsp::{
LSPObject,
LspVersion,
NotebookCell,
NotebookDocument,
TextDocumentContentChangeEvent,
TextDocumentIdentifier,
TextDocumentItem,
Uri,
VersionedTextDocumentIdentifier,
},
},
serde::{
Deserialize,
Serialize,
},
};
fn deserialize_lsp_version<'de, D>(deserializer: D) -> Result<LspVersion, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = i32::deserialize(deserializer)?;
Ok(value.into())
}
fn serialize_lsp_version<S>(version: &LspVersion, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i32(version.raw())
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DidOpenNotebookDocumentParams {
pub notebook_document: NotebookDocument,
pub cell_text_documents: Vec<TextDocumentItem>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DidChangeNotebookDocumentParams {
pub notebook_document: VersionedNotebookDocumentIdentifier,
pub change: NotebookDocumentChangeEvent,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VersionedNotebookDocumentIdentifier {
#[serde(deserialize_with = "deserialize_lsp_version", serialize_with = "serialize_lsp_version")]
pub version: LspVersion,
pub uri: Uri,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentChangeEvent {
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<LSPObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cells: Option<NotebookDocumentCellChange>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentCellChange {
#[serde(skip_serializing_if = "Option::is_none")]
pub structure: Option<NotebookDocumentCellChangeStructure>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<NotebookCell>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_content: Option<Vec<NotebookDocumentChangeTextContent>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentChangeTextContent {
pub document: VersionedTextDocumentIdentifier,
pub changes: Vec<TextDocumentContentChangeEvent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentCellChangeStructure {
pub array: NotebookCellArrayChange,
#[serde(skip_serializing_if = "Option::is_none")]
pub did_open: Option<Vec<TextDocumentItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub did_close: Option<Vec<TextDocumentIdentifier>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookCellArrayChange {
pub start: u32,
pub delete_count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub cells: Option<Vec<NotebookCell>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DidSaveNotebookDocumentParams {
pub notebook_document: NotebookDocumentIdentifier,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NotebookDocumentIdentifier {
pub uri: Uri,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DidCloseNotebookDocumentParams {
pub notebook_document: NotebookDocumentIdentifier,
pub cell_text_documents: Vec<TextDocumentIdentifier>,
}
}
pub trait NotebookDocumentService<
P: crate::database::storage::Partitions,
T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
fn notebook_did_open(
&self,
params: DidOpenNotebookDocumentParams,
ctx: &mut TaskContext<P, T>,
writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<Output = ()> + Send {
let client_id = ctx.client_id();
async move {
let _ = writer;
let notebook_uri = params.notebook_document.uri.clone();
let version = params.notebook_document.version;
let source_cache = ctx.source_cache();
let mut cache = source_cache.write();
if let Err(e) = cache.open_notebook(params.notebook_document) {
otel::error!(
"notebook_open_failed",
format!("Failed to open notebook {}: {:?}", notebook_uri, e)
);
return;
}
for cell in params.cell_text_documents {
match cache.upsert_with_version(
cell.uri.clone(),
cell.text,
cell.version,
client_id,
) {
| Ok(_) => {
cache.mark_file_open(cell.uri, client_id);
},
| Err(e) => {
otel::error!(
"notebook_cell_upsert_failed",
format!("Failed to upsert notebook cell {}: {:?}", cell.uri, e)
);
},
}
}
}
}
const NOTEBOOK_DID_OPEN_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
fn notebook_did_change(
&self,
params: DidChangeNotebookDocumentParams,
ctx: &mut TaskContext<P, T>,
writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<Output = ()> + Send {
let client_id = ctx.client_id();
let encoding = ctx.position_encoding();
async move {
let _ = writer;
let notebook_uri = params.notebook_document.uri.clone();
let version = params.notebook_document.version;
let source_cache = ctx.source_cache();
let mut cache = source_cache.write();
{
let mut notebook = match cache.get_notebook_mut(¬ebook_uri) {
| Some(nb) => nb,
| None => {
return;
},
};
notebook.version = version;
if let Some(metadata) = params.change.metadata {
notebook.metadata = Some(metadata);
}
}
if let Some(cells_change) = params.change.cells {
if let Some(structure) = cells_change.structure {
let start = structure.array.start as usize;
let delete_count = structure.array.delete_count as usize;
let mut removed_cells = Vec::new();
{
let Some(mut notebook) = cache.get_notebook_mut(¬ebook_uri) else {
otel::error!(
"notebook_not_found",
format!("Notebook {} not found during cell removal", notebook_uri)
);
return;
};
for _i in 0..delete_count {
if start < notebook.cells.len() {
removed_cells.push(notebook.cells.remove(start));
}
}
}
drop(removed_cells);
if let Some(new_cells) = structure.array.cells {
let Some(mut notebook) = cache.get_notebook_mut(¬ebook_uri) else {
otel::error!(
"notebook_not_found",
format!("Notebook {} not found during cell insertion", notebook_uri)
);
return;
};
for (i, cell) in new_cells.into_iter().enumerate() {
notebook.cells.insert(start + i, cell);
}
}
if let Some(opened_cells) = structure.did_open {
for cell_doc in opened_cells {
match cache.upsert_with_version(
cell_doc.uri.clone(),
cell_doc.text,
cell_doc.version,
client_id,
) {
| Ok(_) => {
cache.mark_file_open(cell_doc.uri, client_id);
},
| Err(e) => {
otel::error!(
"notebook_cell_upsert_failed",
format!(
"Failed to upsert newly opened notebook cell {}: {:?}",
cell_doc.uri, e
)
);
},
}
}
}
if let Some(closed_cells) = structure.did_close {
for cell_id in closed_cells {
cache.mark_file_closed(&cell_id.uri, client_id);
}
}
}
if let Some(data_changes) = cells_change.data {
let Some(mut notebook) = cache.get_notebook_mut(¬ebook_uri) else {
otel::error!(
"notebook_not_found",
format!("Notebook {} not found during data change", notebook_uri)
);
return;
};
for updated_cell in data_changes {
if let Some(cell) = notebook
.cells
.iter_mut()
.find(|c| c.document == updated_cell.document)
{
*cell = updated_cell;
}
}
}
if let Some(text_changes) = cells_change.text_content {
for text_change in text_changes {
if let Err(e) = cache.apply_content_changes(
&text_change.document.uri,
text_change.document.version,
text_change.changes,
&encoding,
client_id,
) {
otel::error!(
"notebook_cell_content_changes_failed",
format!(
"Failed to apply content changes to notebook cell {}: {:?}",
text_change.document.uri, e
)
);
}
}
}
}
}
}
const NOTEBOOK_DID_CHANGE_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
fn notebook_did_save(
&self,
params: DidSaveNotebookDocumentParams,
ctx: &mut TaskContext<P, T>,
writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<Output = ()> + Send {
async move {
let _ = ctx;
let _ = writer;
let notebook_uri = params.notebook_document.uri;
}
}
const NOTEBOOK_DID_SAVE_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
fn notebook_did_close(
&self,
params: DidCloseNotebookDocumentParams,
ctx: &mut TaskContext<P, T>,
writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<Output = ()> + Send {
let client_id = ctx.client_id();
async move {
let _ = writer;
let notebook_uri = params.notebook_document.uri;
let source_cache = ctx.source_cache();
let mut cache = source_cache.write();
for cell_id in params.cell_text_documents {
cache.mark_file_closed(&cell_id.uri, client_id);
}
if let Err(e) = cache.close_notebook(¬ebook_uri) {
otel::error!(
"notebook_close_failed",
format!("Failed to close notebook {}: {:?}", notebook_uri, e)
);
}
}
}
const NOTEBOOK_DID_CLOSE_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
}