未验证 提交 d39ba974 编写于 作者: J Johannes Rieken 提交者: GitHub

Merge pull request #102760 from microsoft/joh/celldocs

Clean up and simplify cell text document world
...@@ -131,6 +131,46 @@ suite('Notebook API tests', () => { ...@@ -131,6 +131,46 @@ suite('Notebook API tests', () => {
await firstDocumentClose; await firstDocumentClose;
}); });
test('notebook open/close, all cell-documents are ready', async function () {
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
const p = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument).then(notebook => {
for (let cell of notebook.cells) {
const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString());
assert.ok(doc);
assert.strictEqual(doc === cell.document, true);
assert.strictEqual(doc?.languageId, cell.language);
assert.strictEqual(doc?.isDirty, false);
assert.strictEqual(doc?.isClosed, false);
}
});
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await p;
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('notebook open/close, notebook ready when cell-document open event is fired', async function () {
const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');
let didHappen = false;
const p = getEventOncePromise(vscode.workspace.onDidOpenTextDocument).then(doc => {
if (doc.uri.scheme !== 'vscode-notebook-cell') {
return;
}
const notebook = vscode.notebook.notebookDocuments.find(notebook => {
const cell = notebook.cells.find(cell => cell.document === doc);
return Boolean(cell);
});
assert.ok(notebook, `notebook for cell ${doc.uri} NOT found`);
didHappen = true;
});
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await p;
assert.strictEqual(didHappen, true);
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('shared document in notebook editors', async function () { test('shared document in notebook editors', async function () {
assertInitalState(); assertInitalState();
......
...@@ -58,6 +58,8 @@ export namespace Schemas { ...@@ -58,6 +58,8 @@ export namespace Schemas {
export const vscodeNotebook = 'vscode-notebook'; export const vscodeNotebook = 'vscode-notebook';
export const vscodeNotebookCell = 'vscode-notebook-cell';
export const vscodeSettings = 'vscode-settings'; export const vscodeSettings = 'vscode-settings';
export const webviewPanel = 'webview-panel'; export const webviewPanel = 'webview-panel';
......
...@@ -203,7 +203,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo ...@@ -203,7 +203,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
async removeNotebookTextModel(uri: URI): Promise<void> { async removeNotebookTextModel(uri: URI): Promise<void> {
// TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together // TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] }); this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] });
let textModelDisposableStore = this._editorEventListenersMapping.get(uri.toString()); let textModelDisposableStore = this._editorEventListenersMapping.get(uri.toString());
textModelDisposableStore?.dispose(); textModelDisposableStore?.dispose();
this._editorEventListenersMapping.delete(URI.from(uri).toString()); this._editorEventListenersMapping.delete(URI.from(uri).toString());
......
...@@ -1633,7 +1633,7 @@ export interface ExtHostNotebookShape { ...@@ -1633,7 +1633,7 @@ export interface ExtHostNotebookShape {
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
$acceptModelSaved(uriComponents: UriComponents): void; $acceptModelSaved(uriComponents: UriComponents): void;
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void; $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>; $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
$undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>; $undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
$redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>; $redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
......
...@@ -53,7 +53,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { ...@@ -53,7 +53,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
} }
public getAllDocumentData(): ExtHostDocumentData[] { public getAllDocumentData(): ExtHostDocumentData[] {
return this._documentsAndEditors.allDocuments(); return [...this._documentsAndEditors.allDocuments()];
} }
public getDocumentData(resource: vscode.Uri): ExtHostDocumentData | undefined { public getDocumentData(resource: vscode.Uri): ExtHostDocumentData | undefined {
...@@ -69,8 +69,8 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { ...@@ -69,8 +69,8 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
public getDocument(resource: vscode.Uri): vscode.TextDocument { public getDocument(resource: vscode.Uri): vscode.TextDocument {
const data = this.getDocumentData(resource); const data = this.getDocumentData(resource);
if (!data || !data.document) { if (!data?.document) {
throw new Error('Unable to retrieve document from URI'); throw new Error(`Unable to retrieve document from URI '${resource}'`);
} }
return data.document; return data.document;
} }
......
...@@ -15,6 +15,19 @@ import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; ...@@ -15,6 +15,19 @@ import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { ResourceMap } from 'vs/base/common/map'; import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { Iterable } from 'vs/base/common/iterator';
class Reference<T> {
private _count = 0;
constructor(readonly value: T) { }
ref() {
this._count++;
}
unref() {
return --this._count === 0;
}
}
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
...@@ -23,7 +36,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -23,7 +36,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
private _activeEditorId: string | null = null; private _activeEditorId: string | null = null;
private readonly _editors = new Map<string, ExtHostTextEditor>(); private readonly _editors = new Map<string, ExtHostTextEditor>();
private readonly _documents = new ResourceMap<ExtHostDocumentData>(); private readonly _documents = new ResourceMap<Reference<ExtHostDocumentData>>();
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>(); private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>(); private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
...@@ -50,9 +63,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -50,9 +63,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
for (const uriComponent of delta.removedDocuments) { for (const uriComponent of delta.removedDocuments) {
const uri = URI.revive(uriComponent); const uri = URI.revive(uriComponent);
const data = this._documents.get(uri); const data = this._documents.get(uri);
this._documents.delete(uri); if (data?.unref()) {
if (data) { this._documents.delete(uri);
removedDocuments.push(data); removedDocuments.push(data.value);
} }
} }
} }
...@@ -60,19 +73,30 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -60,19 +73,30 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
if (delta.addedDocuments) { if (delta.addedDocuments) {
for (const data of delta.addedDocuments) { for (const data of delta.addedDocuments) {
const resource = URI.revive(data.uri); const resource = URI.revive(data.uri);
assert.ok(!this._documents.has(resource), `document '${resource} already exists!'`); let ref = this._documents.get(resource);
const documentData = new ExtHostDocumentData( // double check -> only notebook cell documents should be
this._extHostRpc.getProxy(MainContext.MainThreadDocuments), // referenced/opened more than once...
resource, if (ref) {
data.lines, if (resource.scheme !== Schemas.vscodeNotebookCell) {
data.EOL, throw new Error(`document '${resource} already exists!'`);
data.modeId, }
data.versionId, }
data.isDirty if (!ref) {
); ref = new Reference(new ExtHostDocumentData(
this._documents.set(resource, documentData); this._extHostRpc.getProxy(MainContext.MainThreadDocuments),
addedDocuments.push(documentData); resource,
data.lines,
data.EOL,
data.modeId,
data.versionId,
data.isDirty
));
this._documents.set(resource, ref);
addedDocuments.push(ref.value);
}
ref.ref();
} }
} }
...@@ -92,7 +116,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -92,7 +116,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
assert.ok(this._documents.has(resource), `document '${resource}' does not exist`); assert.ok(this._documents.has(resource), `document '${resource}' does not exist`);
assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`); assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
const documentData = this._documents.get(resource)!; const documentData = this._documents.get(resource)!.value;
const editor = new ExtHostTextEditor( const editor = new ExtHostTextEditor(
data.id, data.id,
this._extHostRpc.getProxy(MainContext.MainThreadTextEditors), this._extHostRpc.getProxy(MainContext.MainThreadTextEditors),
...@@ -132,11 +156,11 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -132,11 +156,11 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
} }
getDocument(uri: URI): ExtHostDocumentData | undefined { getDocument(uri: URI): ExtHostDocumentData | undefined {
return this._documents.get(uri); return this._documents.get(uri)?.value;
} }
allDocuments(): ExtHostDocumentData[] { allDocuments(): Iterable<ExtHostDocumentData> {
return [...this._documents.values()]; return Iterable.map(this._documents.values(), ref => ref.value);
} }
getEditor(id: string): ExtHostTextEditor | undefined { getEditor(id: string): ExtHostTextEditor | undefined {
......
...@@ -7,18 +7,16 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance ...@@ -7,18 +7,16 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
import { readonly } from 'vs/base/common/errors'; import { readonly } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { hash } from 'vs/base/common/hash'; import { hash } from 'vs/base/common/hash';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { joinPath } from 'vs/base/common/resources'; import { joinPath } from 'vs/base/common/resources';
import { ISplice } from 'vs/base/common/sequence'; import { ISplice } from 'vs/base/common/sequence';
import { NotImplementedProxy } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid'; import * as UUID from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { CellKind, ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { CellKind, ExtHostNotebookShape, IMainContext, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadDocumentsShape, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
...@@ -28,6 +26,7 @@ import { CellEditType, CellOutputKind, diff, ICellDeleteEdit, ICellEditOperation ...@@ -28,6 +26,7 @@ import { CellEditType, CellOutputKind, diff, ICellDeleteEdit, ICellEditOperation
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { Cache } from './cache'; import { Cache } from './cache';
interface IObservable<T> { interface IObservable<T> {
proxy: T; proxy: T;
onDidChange: Event<void>; onDidChange: Event<void>;
...@@ -59,27 +58,18 @@ interface INotebookEventEmitter { ...@@ -59,27 +58,18 @@ interface INotebookEventEmitter {
const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich
? ({ ...output, outputId: id }) : output; ? ({ ...output, outputId: id }) : output;
class DettachedCellDocumentData extends ExtHostDocumentData { export class ExtHostCell extends Disposable implements vscode.NotebookCell {
private static readonly _fakeProxy = new class extends NotImplementedProxy<MainThreadDocumentsShape>('document') {
$trySaveDocument() {
return Promise.reject('Cell-document cannot be saved');
}
};
constructor(cell: IMainCellDto) { public static asModelAddData(cell: IMainCellDto): IModelAddedData {
super(DettachedCellDocumentData._fakeProxy, return {
URI.revive(cell.uri), EOL: cell.eol,
cell.source, lines: cell.source,
cell.eol, modeId: cell.language,
cell.language, uri: cell.uri,
0, isDirty: false,
false versionId: 1
); };
} }
}
export class ExtHostCell extends Disposable implements vscode.NotebookCell {
private _onDidChangeOutputs = new Emitter<ISplice<IProcessedOutput>[]>(); private _onDidChangeOutputs = new Emitter<ISplice<IProcessedOutput>[]>();
readonly onDidChangeOutputs: Event<ISplice<IProcessedOutput>[]> = this._onDidChangeOutputs.event; readonly onDidChangeOutputs: Event<ISplice<IProcessedOutput>[]> = this._onDidChangeOutputs.event;
...@@ -94,13 +84,6 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { ...@@ -94,13 +84,6 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
readonly uri: URI; readonly uri: URI;
readonly cellKind: CellKind; readonly cellKind: CellKind;
// todo@jrieken this is a little fish because we have
// vscode.TextDocument for which we never fired an onDidOpen
// event and which doesn't appear in the list of documents.
// this will change once the "real" document comes along. We
// should come up with a better approach here...
readonly defaultDocument: DettachedCellDocumentData;
constructor( constructor(
private _proxy: MainThreadNotebookShape, private _proxy: MainThreadNotebookShape,
readonly notebook: ExtHostNotebookDocument, readonly notebook: ExtHostNotebookDocument,
...@@ -112,7 +95,6 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { ...@@ -112,7 +95,6 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
this.handle = cell.handle; this.handle = cell.handle;
this.uri = URI.revive(cell.uri); this.uri = URI.revive(cell.uri);
this.cellKind = cell.cellKind; this.cellKind = cell.cellKind;
this.defaultDocument = new DettachedCellDocumentData(cell);
this._outputs = cell.outputs; this._outputs = cell.outputs;
for (const output of this._outputs) { for (const output of this._outputs) {
...@@ -128,7 +110,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { ...@@ -128,7 +110,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
} }
get document(): vscode.TextDocument { get document(): vscode.TextDocument {
return this._extHostDocument.getDocument(this.uri)?.document ?? this.defaultDocument.document; return this._extHostDocument.getDocument(this.uri)!.document;
} }
get language(): string { get language(): string {
...@@ -348,7 +330,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -348,7 +330,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
dispose() { dispose() {
this._disposed = true; this._disposed = true;
super.dispose(); super.dispose();
this._cellDisposableMapping.forEach(cell => cell.dispose()); dispose(this._cellDisposableMapping.values());
} }
get fileName() { return this.uri.fsPath; } get fileName() { return this.uri.fsPath; }
...@@ -380,6 +362,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -380,6 +362,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
} }
const contentChangeEvents: vscode.NotebookCellsChangeData[] = []; const contentChangeEvents: vscode.NotebookCellsChangeData[] = [];
const addedCellDocuments: IModelAddedData[] = [];
splices.reverse().forEach(splice => { splices.reverse().forEach(splice => {
const cellDtos = splice[2]; const cellDtos = splice[2];
...@@ -387,6 +370,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -387,6 +370,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell); const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell);
if (!initialization) {
addedCellDocuments.push(ExtHostCell.asModelAddData(cell));
}
if (!this._cellDisposableMapping.has(extCell.handle)) { if (!this._cellDisposableMapping.has(extCell.handle)) {
this._cellDisposableMapping.set(extCell.handle, new DisposableStore()); this._cellDisposableMapping.set(extCell.handle, new DisposableStore());
} }
...@@ -403,21 +390,22 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -403,21 +390,22 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
for (let j = splice[0]; j < splice[0] + splice[1]; j++) { for (let j = splice[0]; j < splice[0] + splice[1]; j++) {
this._cellDisposableMapping.get(this.cells[j].handle)?.dispose(); this._cellDisposableMapping.get(this.cells[j].handle)?.dispose();
this._cellDisposableMapping.delete(this.cells[j].handle); this._cellDisposableMapping.delete(this.cells[j].handle);
} }
const deletedItems = this.cells.splice(splice[0], splice[1], ...newCells); const deletedItems = this.cells.splice(splice[0], splice[1], ...newCells);
const event: vscode.NotebookCellsChangeData = { contentChangeEvents.push({
start: splice[0], start: splice[0],
deletedCount: splice[1], deletedCount: splice[1],
deletedItems, deletedItems,
items: newCells items: newCells
}; });
contentChangeEvents.push(event);
}); });
if (addedCellDocuments) {
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments });
}
if (!initialization) { if (!initialization) {
this._emitter.emitModelChange({ this._emitter.emitModelChange({
document: this, document: this,
...@@ -467,7 +455,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -467,7 +455,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
private $changeCellLanguage(index: number, language: string): void { private $changeCellLanguage(index: number, language: string): void {
const cell = this.cells[index]; const cell = this.cells[index];
cell.defaultDocument._acceptLanguageId(language);
const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language }; const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language };
this._emitter.emitCellLanguageChange(event); this._emitter.emitCellLanguageChange(event);
} }
...@@ -1526,7 +1513,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1526,7 +1513,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
this._editors.set(editorId, { editor }); this._editors.set(editorId, { editor });
} }
async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) { $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void {
let editorChanged = false; let editorChanged = false;
if (delta.removedDocuments) { if (delta.removedDocuments) {
...@@ -1538,6 +1525,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1538,6 +1525,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
if (document) { if (document) {
document.dispose(); document.dispose();
this._documents.delete(revivedUriStr); this._documents.delete(revivedUriStr);
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: document.cells.map(cell => cell.uri) });
this._onDidCloseNotebookDocument.fire(document); this._onDidCloseNotebookDocument.fire(document);
} }
...@@ -1552,6 +1540,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1552,6 +1540,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
} }
if (delta.addedDocuments) { if (delta.addedDocuments) {
const addedCellDocuments: IModelAddedData[] = [];
delta.addedDocuments.forEach(modelData => { delta.addedDocuments.forEach(modelData => {
const revivedUri = URI.revive(modelData.uri); const revivedUri = URI.revive(modelData.uri);
const revivedUriStr = revivedUri.toString(); const revivedUriStr = revivedUri.toString();
...@@ -1598,6 +1589,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1598,6 +1589,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
]] ]]
}); });
// add cell document as vscode.TextDocument
addedCellDocuments.push(...modelData.cells.map(ExtHostCell.asModelAddData));
this._documents.get(revivedUriStr)?.dispose(); this._documents.get(revivedUriStr)?.dispose();
this._documents.set(revivedUriStr, document); this._documents.set(revivedUriStr, document);
...@@ -1608,6 +1602,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1608,6 +1602,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
} }
} }
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({
addedDocuments: addedCellDocuments
});
const document = this._documents.get(revivedUriStr)!; const document = this._documents.get(revivedUriStr)!;
this._onDidOpenNotebookDocument.fire(document); this._onDidOpenNotebookDocument.fire(document);
}); });
......
...@@ -425,7 +425,7 @@ export function getCellUndoRedoComparisonKey(uri: URI) { ...@@ -425,7 +425,7 @@ export function getCellUndoRedoComparisonKey(uri: URI) {
export namespace CellUri { export namespace CellUri {
export const scheme = 'vscode-notebook-cell'; export const scheme = Schemas.vscodeNotebookCell;
const _regex = /^\d{7,}/; const _regex = /^\d{7,}/;
export function generate(notebook: URI, handle: number): URI { export function generate(notebook: URI, handle: number): URI {
......
...@@ -4,148 +4,215 @@ ...@@ -4,148 +4,215 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import * as vscode from 'vscode';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { DisposableStore } from 'vs/base/common/lifecycle'; import { DisposableStore } from 'vs/base/common/lifecycle';
import { NullLogService } from 'vs/platform/log/common/log'; import { NullLogService } from 'vs/platform/log/common/log';
import { mock } from 'vs/base/test/common/mock'; import { mock } from 'vs/base/test/common/mock';
import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { IModelAddedData, MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostNotebookDocument, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { isEqual } from 'vs/base/common/resources';
suite('NotebookCell#Document', function () {
suite('NotebookCell', function () {
let rpcProtocol: TestRPCProtocol; let rpcProtocol: TestRPCProtocol;
let notebook: ExtHostNotebookDocument;
let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors; let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
let extHostDocuments: ExtHostDocuments;
let extHostNotebooks: ExtHostNotebookController;
const notebookUri = URI.parse('test:///notebook.file');
const disposables = new DisposableStore(); const disposables = new DisposableStore();
const fakeNotebookProxy = new class extends mock<MainThreadNotebookShape>() { };
const fakeNotebook = new class extends mock<ExtHostNotebookDocument>() { };
setup(async function () { setup(async function () {
disposables.clear(); disposables.clear();
rpcProtocol = new TestRPCProtocol(); rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {
$registerCommand() { }
});
rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock<MainThreadNotebookShape>() {
async $registerNotebookProvider() { }
async $unregisterNotebookProvider() { }
});
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
}); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors);
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService());
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { }
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({
addedDocuments: [{
handle: 0,
uri: notebookUri,
viewType: 'test',
versionId: 0,
cells: [{
handle: 0,
uri: CellUri.generate(notebookUri, 0),
source: ['### Heading'],
eol: '\n',
language: 'markdown',
cellKind: CellKind.Markdown,
outputs: [],
}, {
handle: 1,
uri: CellUri.generate(notebookUri, 1),
source: ['console.log("aaa")', 'console.log("bbb")'],
eol: '\n',
language: 'javascript',
cellKind: CellKind.Code,
outputs: [],
}],
}],
addedEditors: [{
documentUri: notebookUri,
id: '_notebook_editor_0',
selections: [0]
}]
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' });
test('Document is real', function () { notebook = extHostNotebooks.notebookDocuments[0]!;
const dto = { disposables.add(reg);
cellKind: CellKind.Code, disposables.add(notebook);
eol: '\n', disposables.add(extHostDocuments);
source: ['aaaa', 'bbbb', 'cccc'],
handle: 0,
language: 'fooLang',
outputs: [],
uri: URI.parse('test:/path')
};
const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto);
assert.ok(cell.document);
assert.strictEqual(cell.document.version, 0);
assert.strictEqual(cell.document.languageId, dto.language);
assert.strictEqual(cell.document.uri.toString(), dto.uri.toString());
assert.strictEqual(cell.uri.toString(), dto.uri.toString());
}); });
test('Document is uses actual document when possible', function () { test('cell document is vscode.TextDocument', async function () {
const dto = { assert.strictEqual(notebook.cells.length, 2);
cellKind: CellKind.Code,
eol: '\n',
source: ['aaaa', 'bbbb', 'cccc'],
handle: 0,
language: 'fooLang',
outputs: [],
uri: URI.parse('test:/path')
};
const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto);
// this is the "default document" which is used when the real const [c1, c2] = notebook.cells;
// document isn't open const d1 = extHostDocuments.getDocument(c1.uri);
const documentNow = cell.document;
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ assert.ok(d1);
addedDocuments: [{ assert.equal(d1.languageId, c1.language);
isDirty: false, assert.equal(d1.version, 1);
versionId: 12,
modeId: dto.language,
uri: dto.uri,
lines: dto.source,
EOL: dto.eol
}]
});
// the real document
assert.ok(documentNow !== cell.document);
assert.strictEqual(cell.document.languageId, dto.language);
assert.strictEqual(cell.document.uri.toString(), dto.uri.toString());
assert.strictEqual(cell.uri.toString(), dto.uri.toString());
// back to "default document" const d2 = extHostDocuments.getDocument(c2.uri);
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: [dto.uri] }); assert.ok(d2);
assert.ok(documentNow === cell.document); assert.equal(d2.languageId, c2.language);
assert.equal(d2.version, 1);
}); });
test('Document can change language (1/2)', function () { test('cell document goes when notebook closes', async function () {
const cellUris: string[] = [];
const dto = { for (let cell of notebook.cells) {
cellKind: CellKind.Code, assert.ok(extHostDocuments.getDocument(cell.uri));
eol: '\n', cellUris.push(cell.uri.toString());
source: ['aaaa', 'bbbb', 'cccc'], }
handle: 0,
language: 'fooLang', const removedCellUris: string[] = [];
outputs: [], const reg = extHostDocuments.onDidRemoveDocument(doc => {
uri: URI.parse('test:/path') removedCellUris.push(doc.uri.toString());
}; });
const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto);
extHostNotebooks.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
assert.strictEqual(cell.document.languageId, dto.language); reg.dispose();
cell.defaultDocument._acceptLanguageId('barLang');
assert.strictEqual(cell.document.languageId, 'barLang'); assert.strictEqual(removedCellUris.length, 2);
assert.deepStrictEqual(removedCellUris.sort(), cellUris.sort());
}); });
test('cell document is vscode.TextDocument after changing it', async function () {
test('Document can change language (1/2)', function () { const p = new Promise((resolve, reject) => {
extHostNotebooks.onDidChangeNotebookCells(e => {
try {
assert.strictEqual(e.changes.length, 1);
assert.strictEqual(e.changes[0].items.length, 2);
const [first, second] = e.changes[0].items;
const dto = { const doc1 = extHostDocuments.getAllDocumentData().find(data => isEqual(data.document.uri, first.uri));
cellKind: CellKind.Code, assert.ok(doc1);
eol: '\n', assert.strictEqual(doc1?.document === first.document, true);
source: ['aaaa', 'bbbb', 'cccc'],
handle: 0,
language: 'fooLang',
outputs: [],
uri: URI.parse('test:/path')
};
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ const doc2 = extHostDocuments.getAllDocumentData().find(data => isEqual(data.document.uri, second.uri));
addedDocuments: [{ assert.ok(doc2);
isDirty: false, assert.strictEqual(doc2?.document === second.document, true);
versionId: 12,
modeId: dto.language,
uri: dto.uri,
lines: dto.source,
EOL: dto.eol
}]
});
const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); resolve();
const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); } catch (err) {
reject(err);
}
});
});
// a real document already exists and therefore extHostNotebooks.$acceptModelChanged(notebookUri, {
// the "default document" doesn't count kind: NotebookCellsChangeType.ModelChange,
versionId: notebook.versionId + 1,
changes: [[0, 0, [{
handle: 2,
uri: CellUri.generate(notebookUri, 2),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 3,
uri: CellUri.generate(notebookUri, 3),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
});
assert.strictEqual(cell.document.languageId, dto.language); await p;
cell.defaultDocument._acceptLanguageId('barLang');
assert.strictEqual(cell.document.languageId, dto.language);
extHostDocuments.$acceptModelModeChanged(dto.uri, dto.language, 'barLang');
assert.strictEqual(cell.document.languageId, 'barLang');
}); });
test('cell document stays open when notebook is still open', async function () {
const docs: vscode.TextDocument[] = [];
const addData: IModelAddedData[] = [];
for (let cell of notebook.cells) {
const doc = extHostDocuments.getDocument(cell.uri);
assert.ok(doc);
assert.equal(extHostDocuments.getDocument(cell.uri).isClosed, false);
docs.push(doc);
addData.push({
EOL: '\n',
isDirty: doc.isDirty,
lines: doc.getText().split('\n'),
modeId: doc.languageId,
uri: doc.uri,
versionId: doc.version
});
}
// this call happens when opening a document on the main side
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addData });
// this call happens when closing a document from the main side
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: docs.map(d => d.uri) });
// notebook is still open -> cell documents stay open
for (let cell of notebook.cells) {
assert.ok(extHostDocuments.getDocument(cell.uri));
assert.equal(extHostDocuments.getDocument(cell.uri).isClosed, false);
}
// close notebook -> docs are closed
extHostNotebooks.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
for (let cell of notebook.cells) {
assert.throws(() => extHostDocuments.getDocument(cell.uri));
}
for (let doc of docs) {
assert.equal(doc.isClosed, true);
}
});
}); });
...@@ -48,7 +48,7 @@ suite('NotebookConcatDocument', function () { ...@@ -48,7 +48,7 @@ suite('NotebookConcatDocument', function () {
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() { let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { } // async openNotebook() { }
}); });
await extHostNotebooks.$acceptDocumentAndEditorsDelta({ extHostNotebooks.$acceptDocumentAndEditorsDelta({
addedDocuments: [{ addedDocuments: [{
handle: 0, handle: 0,
uri: notebookUri, uri: notebookUri,
...@@ -72,7 +72,7 @@ suite('NotebookConcatDocument', function () { ...@@ -72,7 +72,7 @@ suite('NotebookConcatDocument', function () {
} }
] ]
}); });
await extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' }); extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' });
notebook = extHostNotebooks.notebookDocuments[0]!; notebook = extHostNotebooks.notebookDocuments[0]!;
...@@ -292,17 +292,6 @@ suite('NotebookConcatDocument', function () { ...@@ -292,17 +292,6 @@ suite('NotebookConcatDocument', function () {
let cell1End = doc.offsetAt(new Position(2, 12)); let cell1End = doc.offsetAt(new Position(2, 12));
assert.equal(doc.positionAt(cell1End).isEqual(new Position(2, 12)), true); assert.equal(doc.positionAt(cell1End).isEqual(new Position(2, 12)), true);
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({
addedDocuments: [{
uri: notebook.cells[0].uri,
versionId: 1,
lines: ['Hello', 'World', 'Hello World!'],
EOL: '\n',
modeId: '',
isDirty: false
}]
});
extHostDocuments.$acceptModelChanged(notebook.cells[0].uri, { extHostDocuments.$acceptModelChanged(notebook.cells[0].uri, {
versionId: 0, versionId: 0,
eol: '\n', eol: '\n',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册