move/copy onDidSaveNotebookDocument-event into workspace, add onDidChangeNotebookDocument-event which fires for any notebook change: metadata, structure, cell output, cell metadata

This commit is contained in:
Johannes Rieken
2022-03-08 14:15:35 +01:00
parent 1a9016d0a4
commit 52faf21ef2
7 changed files with 145 additions and 14 deletions

View File

@@ -882,6 +882,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
return extHostNotebook.getNotebookDocument(uri).apiNotebook;
},
onDidSaveNotebookDocument(listener, thisArg, disposables) {
checkProposedApiEnabled(extension, 'notebookDocumentEvents');
return extHostNotebookDocuments.onDidSaveNotebookDocument(listener, thisArg, disposables);
},
onDidChangeNotebookDocument(listener, thisArg, disposables) {
checkProposedApiEnabled(extension, 'notebookDocumentEvents');
return extHostNotebookDocuments.onDidChangeNotebookDocument(listener, thisArg, disposables);
},
get onDidOpenNotebookDocument(): Event<vscode.NotebookDocument> {
return extHostNotebook.onDidOpenNotebookDocument;
},

View File

@@ -2005,7 +2005,7 @@ export type NotebookCellsChangedEventDto = {
};
export interface ExtHostNotebookDocumentsShape {
$acceptModelChanged(uriComponents: UriComponents, event: SerializableObjectWithBuffers<NotebookCellsChangedEventDto>, isDirty: boolean): void;
$acceptModelChanged(uriComponents: UriComponents, event: SerializableObjectWithBuffers<NotebookCellsChangedEventDto>, isDirty: boolean, newMetadata?: notebookCommon.NotebookDocumentMetadata): void;
$acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void;
$acceptModelSaved(uriComponents: UriComponents): void;
$acceptDocumentPropertiesChanged(uriComponents: UriComponents, data: INotebookDocumentPropertiesChangeData): void;

View File

@@ -10,12 +10,25 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { NotebookRange } from 'vs/workbench/api/common/extHostTypes';
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as vscode from 'vscode';
class RawContentChangeEvent {
constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: vscode.NotebookCell[], readonly items: ExtHostCell[]) { }
constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: vscode.NotebookCell[], readonly items: ExtHostCell[]) {
}
asApiEvent(): vscode.NotebookDocumentContentChange {
return {
range: new NotebookRange(this.start, this.start + this.deletedCount),
addedCells: this.items.map(cell => cell.apiCell),
removedCells: this.deletedItems,
};
}
static asApiEvents(events: RawContentChangeEvent[]): readonly vscode.NotebookCellsChangeData[] {
return events.map(event => {
@@ -157,7 +170,7 @@ export class ExtHostNotebookDocument {
) {
this._notebookType = data.viewType;
this._metadata = Object.freeze(data.metadata ?? Object.create(null));
this._spliceNotebookCells([[0, 0, data.cells]], true /* init -> no event*/);
this._spliceNotebookCells([[0, 0, data.cells]], true /* init -> no event*/, undefined);
this._versionId = data.versionId;
}
@@ -213,29 +226,67 @@ export class ExtHostNotebookDocument {
this._isDirty = isDirty;
}
acceptModelChanged(event: extHostProtocol.NotebookCellsChangedEventDto, isDirty: boolean): void {
acceptModelChanged(event: extHostProtocol.NotebookCellsChangedEventDto, isDirty: boolean, newMetadata: notebookCommon.NotebookDocumentMetadata | undefined): vscode.NotebookDocumentChangeEvent {
this._versionId = event.versionId;
this._isDirty = isDirty;
this.acceptDocumentPropertiesChanged({ metadata: newMetadata });
const result = {
notebook: this.apiNotebook,
metadata: newMetadata,
cellChanges: <vscode.NotebookDocumentContentCellChange[]>[],
contentChanges: <vscode.NotebookDocumentContentChange[]>[],
};
const cellOutputChanges = new Set<ExtHostCell>();
const cellMetadataChanges = new Set<ExtHostCell>();
// -- apply change and populate content changes
for (const rawEvent of event.rawEvents) {
if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ModelChange) {
this._spliceNotebookCells(rawEvent.changes, false);
this._spliceNotebookCells(rawEvent.changes, false, result.contentChanges);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.Move) {
this._moveCell(rawEvent.index, rawEvent.newIdx);
this._moveCell(rawEvent.index, rawEvent.newIdx, result.contentChanges);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.Output) {
this._setCellOutputs(rawEvent.index, rawEvent.outputs);
cellOutputChanges.add(this._cells[rawEvent.index]);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.OutputItem) {
this._setCellOutputItems(rawEvent.index, rawEvent.outputId, rawEvent.append, rawEvent.outputItems);
cellOutputChanges.add(this._cells[rawEvent.index]);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeLanguage) {
this._changeCellLanguage(rawEvent.index, rawEvent.language);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellMime) {
this._changeCellMime(rawEvent.index, rawEvent.mime);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellMetadata) {
this._changeCellMetadata(rawEvent.index, rawEvent.metadata);
cellMetadataChanges.add(this._cells[rawEvent.index]);
} else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellInternalMetadata) {
this._changeCellInternalMetadata(rawEvent.index, rawEvent.internalMetadata);
}
}
// -- populate cell changes
for (const cell of cellOutputChanges) {
result.cellChanges.push({
cell: cell.apiCell,
outputs: cell.apiCell.outputs,
metadata: cellMetadataChanges.has(cell) ? cell.apiCell.metadata : undefined,
executionSummary: undefined
});
cellMetadataChanges.delete(cell);
}
for (const cell of cellMetadataChanges) {
result.cellChanges.push({
cell: cell.apiCell,
metadata: cell.apiCell.metadata,
outputs: undefined,
executionSummary: undefined
});
}
return result;
}
private _validateIndex(index: number): number {
@@ -277,7 +328,7 @@ export class ExtHostNotebookDocument {
return this._proxy.$trySaveNotebook(this.uri);
}
private _spliceNotebookCells(splices: notebookCommon.NotebookCellTextModelSplice<extHostProtocol.NotebookCellDto>[], initialization: boolean): void {
private _spliceNotebookCells(splices: notebookCommon.NotebookCellTextModelSplice<extHostProtocol.NotebookCellDto>[], initialization: boolean, bucket: vscode.NotebookDocumentContentChange[] | undefined): void {
if (this._disposed) {
return;
}
@@ -303,7 +354,6 @@ export class ExtHostNotebookDocument {
removedCellDocuments.push(cell.uri);
changeEvent.deletedItems.push(cell.apiCell);
}
contentChangeEvents.push(changeEvent);
});
@@ -312,6 +362,12 @@ export class ExtHostNotebookDocument {
removedDocuments: removedCellDocuments
});
if (bucket) {
for (let changeEvent of contentChangeEvents) {
bucket.push(changeEvent.asApiEvent());
}
}
if (!initialization) {
this._emitter.emitModelChange(deepFreeze({
document: this.apiNotebook,
@@ -320,13 +376,16 @@ export class ExtHostNotebookDocument {
}
}
private _moveCell(index: number, newIdx: number): void {
private _moveCell(index: number, newIdx: number, bucket: vscode.NotebookDocumentContentChange[]): void {
const cells = this._cells.splice(index, 1);
this._cells.splice(newIdx, 0, ...cells);
const changes = [
new RawContentChangeEvent(index, 1, cells.map(c => c.apiCell), []),
new RawContentChangeEvent(newIdx, 0, [], cells)
];
for (const change of changes) {
bucket.push(change.asApiEvent());
}
this._emitter.emitModelChange(deepFreeze({
document: this.apiNotebook,
changes: RawContentChangeEvent.asApiEvents(changes)

View File

@@ -4,10 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { deepFreeze } from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import type * as vscode from 'vscode';
@@ -16,17 +18,21 @@ export class ExtHostNotebookDocuments implements extHostProtocol.ExtHostNotebook
private readonly _onDidChangeNotebookDocumentMetadata = new Emitter<vscode.NotebookDocumentMetadataChangeEvent>();
readonly onDidChangeNotebookDocumentMetadata = this._onDidChangeNotebookDocumentMetadata.event;
private _onDidSaveNotebookDocument = new Emitter<vscode.NotebookDocument>();
private readonly _onDidSaveNotebookDocument = new Emitter<vscode.NotebookDocument>();
readonly onDidSaveNotebookDocument = this._onDidSaveNotebookDocument.event;
private readonly _onDidChangeNotebookDocument = new Emitter<vscode.NotebookDocumentChangeEvent>();
readonly onDidChangeNotebookDocument = this._onDidChangeNotebookDocument.event;
constructor(
@ILogService private readonly _logService: ILogService,
private readonly _notebooksAndEditors: ExtHostNotebookController,
) { }
$acceptModelChanged(uri: UriComponents, event: SerializableObjectWithBuffers<extHostProtocol.NotebookCellsChangedEventDto>, isDirty: boolean): void {
$acceptModelChanged(uri: UriComponents, event: SerializableObjectWithBuffers<extHostProtocol.NotebookCellsChangedEventDto>, isDirty: boolean, newMetadata?: NotebookDocumentMetadata): void {
const document = this._notebooksAndEditors.getNotebookDocument(URI.revive(uri));
document.acceptModelChanged(event.value, isDirty);
const e = document.acceptModelChanged(event.value, isDirty, newMetadata);
this._onDidChangeNotebookDocument.fire(deepFreeze(e));
}
$acceptDirtyStateChanged(uri: UriComponents, isDirty: boolean): void {