diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5ec78c64b0f..66f959b8854 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1879,7 +1879,8 @@ declare module 'vscode' { export const onDidOpenNotebookDocument: Event; export const onDidCloseNotebookDocument: Event; - // export const onDidChangeVisibleNotebookEditors: Event; + export let visibleNotebookEditors: NotebookEditor[]; + export const onDidChangeVisibleNotebookEditors: Event; // remove activeNotebookDocument, now that there is activeNotebookEditor.document export let activeNotebookDocument: NotebookDocument | undefined; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 12a4a876caf..f7e6505cce4 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta } from '../common/extHost.protocol'; +import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -58,11 +58,77 @@ export class MainThreadNotebookDocument extends Disposable { } } +class DocumentAndEditorState { + static ofMaps(before: Map, after: Map): { removed: V[], added: V[] } { + const removed: V[] = []; + const added: V[] = []; + before.forEach((value, index) => { + if (!after.has(index)) { + removed.push(value); + } + }); + after.forEach((value, index) => { + if (!before.has(index)) { + added.push(value); + } + }); + return { removed, added }; + } + + static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta { + if (!before) { + const apiEditors = []; + for (let id in after.textEditors) { + const editor = after.textEditors.get(id)!; + apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.textModel!.selections }); + } + + return { + addedDocuments: [], + addedEditors: apiEditors + }; + } + // const documentDelta = delta.ofSets(before.documents, after.documents); + const editorDelta = DocumentAndEditorState.ofMaps(before.textEditors, after.textEditors); + const addedAPIEditors = editorDelta.added.map(add => ({ + id: add.getId(), + documentUri: add.uri!, + selections: add.textModel!.selections + })); + + const removedAPIEditors = editorDelta.removed.map(removed => removed.getId()); + + // const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined; + const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; + + // return new DocumentAndEditorStateDelta( + // documentDelta.removed, documentDelta.added, + // editorDelta.removed, editorDelta.added, + // oldActiveEditor, newActiveEditor + // ); + return { + addedEditors: addedAPIEditors, + removedEditors: removedAPIEditors, + newActiveEditor: newActiveEditor + }; + } + + constructor( + readonly documents: Set, + readonly textEditors: Map, + readonly activeEditor: string | null | undefined, + ) { + // + } +} + @extHostNamedCustomer(MainContext.MainThreadNotebook) export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { private readonly _notebookProviders = new Map(); private readonly _notebookKernels = new Map(); private readonly _proxy: ExtHostNotebookShape; + private _toDisposeOnEditorRemove = new Map(); + private _currentState?: DocumentAndEditorState; constructor( extHostContext: IExtHostContext, @@ -90,10 +156,18 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo registerListeners() { this._register(this._notebookService.onDidChangeActiveEditor(e => { this._proxy.$acceptDocumentAndEditorsDelta({ - newActiveEditor: e.uri + newActiveEditor: e }); })); + this._register(this._notebookService.onNotebookEditorAdd(editor => { + this._addNotebookEditor(editor); + })); + + this._register(this._notebookService.onNotebookEditorRemove(editor => { + this._removeNotebookEditor(editor); + })); + const updateOrder = () => { let userOrder = this.configurationService.getValue('notebook.displayOrder'); this._proxy.$acceptDisplayOrder({ @@ -115,6 +189,57 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo })); } + private _addNotebookEditor(e: IEditor) { + this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable( + e.onDidChangeModel(() => this._updateState()), + e.onDidFocusEditorWidget(() => this._updateState(e)), + )); + + this._updateState(); + } + + private _removeNotebookEditor(e: IEditor) { + const sub = this._toDisposeOnEditorRemove.get(e.getId()); + if (sub) { + this._toDisposeOnEditorRemove.delete(e.getId()); + sub.dispose(); + this._updateState(); + } + } + + private async _updateState(focusedNotebookEditor?: IEditor) { + const documents = new Set(); + this._notebookService.listNotebookDocuments().forEach(document => { + documents.add(document.uri); + }); + + const editors = new Map(); + let activeEditor: string | null = null; + + for (const editor of this._notebookService.listNotebookEditors()) { + if (editor.hasModel()) { + editors.set(editor.getId(), editor); + if (editor.hasFocus()) { + activeEditor = editor.getId(); + } + } + } + + // editors always have view model attached, which means there is already a document in exthost. + const newState = new DocumentAndEditorState(documents, editors, activeEditor); + const delta = DocumentAndEditorState.compute(this._currentState, newState); + // const isEmptyChange = (!delta.addedDocuments || delta.addedDocuments.length === 0) + // && (!delta.removedDocuments || delta.removedDocuments.length === 0) + // && (!delta.addedEditors || delta.addedEditors.length === 0) + // && (!delta.removedEditors || delta.removedEditors.length === 0) + // && (delta.newActiveEditor === undefined) + + // if (!isEmptyChange) { + this._currentState = newState; + await this._proxy.$acceptDocumentAndEditorsDelta(delta); + // } + } + async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise { this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri))); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e5e4797bd54..51656a8ce75 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -918,6 +918,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidCloseNotebookDocument; }, + get visibleNotebookEditors() { + return extHostNotebook.visibleNotebookEditors; + }, + get onDidChangeVisibleNotebookEditors() { + return extHostNotebook.onDidChangeVisibleNotebookEditors; + }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0621306fda3..5742c3a445c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1553,12 +1553,18 @@ export interface INotebookModelAddedData { metadata?: NotebookDocumentMetadata; } +export interface INotebookEditorAddData { + id: string; + documentUri: UriComponents; + selections: number[]; +} + export interface INotebookDocumentsAndEditorsDelta { removedDocuments?: UriComponents[]; addedDocuments?: INotebookModelAddedData[]; - // removedEditors?: string[]; - // addedEditors?: ITextEditorAddData[]; - newActiveEditor?: UriComponents | null; + removedEditors?: string[]; + addedEditors?: INotebookEditorAddData[]; + newActiveEditor?: string | null; } export interface ExtHostNotebookShape { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 22f82abf365..04301f43156 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -219,6 +219,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return this._versionId; } + webviewId: string = ''; + constructor( private readonly _proxy: MainThreadNotebookShape, private _documentsAndEditors: ExtHostDocumentsAndEditors, @@ -499,7 +501,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook public uri: URI, private _proxy: MainThreadNotebookShape, private _onDidReceiveMessage: Emitter, - private _webviewId: string, + public _webviewId: string, private _webviewInitData: WebviewInitData, public document: ExtHostNotebookDocument, private _documentsAndEditors: ExtHostDocumentsAndEditors @@ -627,8 +629,6 @@ export interface ExtHostNotebookOutputRenderingHandler { } export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler { - private static _handlePool: number = 0; - private readonly _proxy: MainThreadNotebookShape; private readonly _notebookContentProviders = new Map(); private readonly _notebookKernels = new Map(); @@ -662,6 +662,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN onDidOpenNotebookDocument: Event = this._onDidOpenNotebookDocument.event; private _onDidCloseNotebookDocument = new Emitter(); onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; + visibleNotebookEditors: ExtHostNotebookEditor[] = []; + private _onDidChangeVisibleNotebookEditors = new Emitter(); + onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); @@ -934,8 +937,21 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._outputDisplayOrder = displayOrder; } + // TODO: remove document - editor one on one mapping + private _getEditorFromURI(uriComponents: UriComponents) { + const uriStr = URI.revive(uriComponents).toString(); + let editor: { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter; } | undefined; + this._editors.forEach(e => { + if (e.editor.uri.toString() === uriStr) { + editor = e; + } + }); + + return editor; + } + $onDidReceiveMessage(uri: UriComponents, message: any): void { - let editor = this._editors.get(URI.revive(uri).toString()); + let editor = this._getEditorFromURI(uri); if (editor) { editor.onDidReceiveMessage.fire(message); @@ -943,7 +959,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void { - let editor = this._editors.get(URI.revive(uriComponents).toString()); + let editor = this._getEditorFromURI(uriComponents); if (editor) { editor.editor.document.accpetModelChanged(event); @@ -956,7 +972,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void { - let editor = this._editors.get(URI.revive(uriComponents).toString()); + let editor = this._getEditorFromURI(uriComponents); if (!editor) { return; @@ -1029,34 +1045,79 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ] }); + document.webviewId = modelData.webviewId; this._documents.set(revivedUriStr, document); } - const onDidReceiveMessage = new Emitter(); const document = this._documents.get(revivedUriStr)!; - - let editor = new ExtHostNotebookEditor( - viewType, - `${ExtHostNotebookController._handlePool++}`, - revivedUri, - this._proxy, - onDidReceiveMessage, - modelData.webviewId, - this._webviewInitData, - document, - this._documentsAndEditors - ); - this._onDidOpenNotebookDocument.fire(document); - - // TODO, does it already exist? - this._editors.set(revivedUriStr, { editor, onDidReceiveMessage }); }); } - if (delta.newActiveEditor) { - this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString()); - this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor; + let editorChanged = false; + + if (delta.addedEditors) { + delta.addedEditors.forEach(editorModelData => { + const revivedUri = URI.revive(editorModelData.documentUri); + const document = this._documents.get(revivedUri.toString()); + + if (document) { + const onDidReceiveMessage = new Emitter(); + + let editor = new ExtHostNotebookEditor( + document.viewType, + editorModelData.id, + revivedUri, + this._proxy, + onDidReceiveMessage, + document.webviewId, + this._webviewInitData, + document, + this._documentsAndEditors + ); + + const cells = editor.document.cells; + + if (editorModelData.selections.length) { + const firstCell = editorModelData.selections[0]; + editor.selection = cells.find(cell => cell.handle === firstCell); + } else { + editor.selection = undefined; + } + + editorChanged = true; + + this._editors.set(editorModelData.id, { editor, onDidReceiveMessage }); + } + }); + } + + if (delta.removedEditors) { + delta.removedEditors.forEach(editorid => { + const editor = this._editors.get(editorid); + + if (editor) { + editorChanged = true; + this._editors.delete(editorid); + + // TODO, dispose the editor + } + }); + } + + if (editorChanged) { + this.visibleNotebookEditors = [...this._editors.values()].map(e => e.editor); + this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors); + } + + if (delta.newActiveEditor !== undefined) { + if (delta.newActiveEditor) { + this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor; + this._activeNotebookDocument = this._documents.get(this._activeNotebookEditor!.uri.toString()); + } else { + this._activeNotebookEditor = undefined; + this._activeNotebookDocument = undefined; + } } } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index bfba4378b08..6655a3a969e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -144,9 +144,10 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri })); this._register(this.editorService.onDidActiveEditorChange(() => { - if (this.editorService.activeEditor && this.editorService.activeEditor! instanceof NotebookEditorInput) { - let editorInput = this.editorService.activeEditor! as NotebookEditorInput; - this.notebookService.updateActiveNotebookDocument(editorInput.viewType!, editorInput.resource!); + const activeEditorPane = editorService.activeEditorPane as any | undefined; + const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined; + if (notebookEditor) { + this.notebookService.updateActiveNotebookEditor(notebookEditor); } })); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index ca2f573a4f7..0bf3e0694af 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -23,9 +23,9 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { CellLanguageStatusBarItem, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -import { ICompositeCodeEditor } from 'vs/editor/common/editorCommon'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); @@ -143,7 +143,7 @@ export interface INotebookEditorContribution { restoreViewState?(state: any): void; } -export interface INotebookEditor extends ICompositeCodeEditor { +export interface INotebookEditor extends IEditor { /** * Notebook view model attached to the current editor @@ -154,11 +154,13 @@ export interface INotebookEditor extends ICompositeCodeEditor { * An event emitted when the model of this editor has changed. * @event */ - readonly onDidChangeModel: Event; + readonly onDidChangeModel: Event; + readonly onDidFocusEditorWidget: Event; isNotebookEditor: boolean; activeKernel: INotebookKernelInfo | undefined; readonly onDidChangeKernel: Event; + getId(): string; getDomNode(): HTMLElement; getInnerWebview(): Webview | undefined; @@ -167,6 +169,8 @@ export interface INotebookEditor extends ICompositeCodeEditor { */ focus(): void; + hasFocus(): boolean; + /** * Select & focus cell */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 10688b8e124..5d38d13826c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -68,6 +68,7 @@ export class NotebookEditorOptions extends EditorOptions { } +let EDITOR_ID = 0; export class NotebookEditorWidget extends Disposable implements INotebookEditor { static readonly ID: string = 'workbench.editor.notebook'; @@ -94,8 +95,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private scrollBeyondLastLine: boolean; private readonly memento: Memento; private _isDisposed: boolean = false; - - + private readonly _id: number; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @@ -105,6 +105,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor @ILayoutService private readonly _layoutService: ILayoutService ) { super(); + this._id = (++EDITOR_ID); this.memento = new Memento(NotebookEditorWidget.ID, storageService); this.outputRenderer = new OutputRenderer(this, this.instantiationService); @@ -119,21 +120,41 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } }); + + this.notebookService.addNotebookEditor(this); } - private readonly _onDidChangeModel = new Emitter(); - readonly onDidChangeModel: Event = this._onDidChangeModel.event; + public getId(): string { + return 'vs.editor.INotebookEditor:' + this._id; + } + private readonly _onDidChangeModel = new Emitter(); + readonly onDidChangeModel: Event = this._onDidChangeModel.event; + + private readonly _onDidFocusEditorWidget = new Emitter(); + readonly onDidFocusEditorWidget = this._onDidFocusEditorWidget.event; set viewModel(newModel: NotebookViewModel | undefined) { this.notebookViewModel = newModel; - this._onDidChangeModel.fire(); + this._onDidChangeModel.fire(newModel?.notebookDocument); } get viewModel() { return this.notebookViewModel; } + get uri() { + return this.notebookViewModel?.uri; + } + + get textModel() { + return this.notebookViewModel?.notebookDocument; + } + + hasModel() { + return !!this.notebookViewModel; + } + private _activeKernel: INotebookKernelInfo | undefined = undefined; private readonly _onDidChangeKernel = new Emitter(); readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; @@ -158,11 +179,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#region Editor Core protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { - const mementoKey = `${this.getId()}${key}`; + const mementoKey = `${NotebookEditorWidget.ID}${key}`; let editorMemento = NotebookEditorWidget.EDITOR_MEMENTOS.get(mementoKey); if (!editorMemento) { - editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService); + editorMemento = new EditorMemento(NotebookEditorWidget.ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService); NotebookEditorWidget.EDITOR_MEMENTOS.set(mementoKey, editorMemento); } @@ -173,12 +194,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.memento.getMemento(scope); } - - getId(): string { - return NotebookEditorWidget.ID; - } - - public get isNotebookEditor() { return true; } @@ -187,6 +202,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // Note - focus going to the webview will fire 'blur', but the webview element will be // a descendent of the notebook editor root. this.editorFocus?.set(DOM.isAncestor(document.activeElement, this.overlayContainer)); + this._onDidFocusEditorWidget.fire(); + } + + hasFocus() { + return this.editorFocus?.get() || false; } createEditor(): void { @@ -1256,6 +1276,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor dispose() { this._isDisposed = true; + this.notebookService.removeNotebookEditor(this); const keys = Object.keys(this._contributions); for (let i = 0, len = keys.length; i < len; i++) { const contributionId = keys[i]; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index d6a9fa1b142..fdafadc5704 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -23,6 +23,7 @@ import { NotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; import * as glob from 'vs/base/common/glob'; import { basename } from 'vs/base/common/resources'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -101,8 +102,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu notebookProviderInfoStore: NotebookProviderInfoStore = new NotebookProviderInfoStore(); notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); private readonly _models: { [modelId: string]: ModelData; }; - private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>(); - onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event; + private _onDidChangeActiveEditor = new Emitter(); + onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; + private readonly _onNotebookEditorAdd: Emitter = this._register(new Emitter()); + public readonly onNotebookEditorAdd: Event = this._onNotebookEditorAdd.event; + private readonly _onNotebookEditorRemove: Emitter = this._register(new Emitter()); + public readonly onNotebookEditorRemove: Event = this._onNotebookEditorRemove.event; + private readonly _notebookEditors: { [editorId: string]: INotebookEditor; }; private readonly _onDidChangeViewTypes = new Emitter(); onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; @@ -121,6 +127,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu super(); this._models = {}; + this._notebookEditors = Object.create(null); this.modelManager = this.instantiationService.createInstance(NotebookEditorModelManager); notebookProviderExtensionPoint.setHandler((extensions) => { @@ -352,6 +359,25 @@ export class NotebookService extends Disposable implements INotebookService, ICu return ret; } + removeNotebookEditor(editor: INotebookEditor) { + if (delete this._notebookEditors[editor.getId()]) { + this._onNotebookEditorRemove.fire(editor); + } + } + + addNotebookEditor(editor: INotebookEditor) { + this._notebookEditors[editor.getId()] = editor; + this._onNotebookEditorAdd.fire(editor); + } + + listNotebookEditors(): INotebookEditor[] { + return Object.keys(this._notebookEditors).map(id => this._notebookEditors[id]); + } + + listNotebookDocuments(): NotebookTextModel[] { + return Object.keys(this._models).map(id => this._models[id].model); + } + destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void { let provider = this._notebookProviders.get(viewType); @@ -360,8 +386,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu } } - updateActiveNotebookDocument(viewType: string, resource: URI): void { - this._onDidChangeActiveEditor.fire({ viewType, uri: resource }); + updateActiveNotebookEditor(editor: INotebookEditor) { + this._onDidChangeActiveEditor.fire(editor.getId()); } setToCopy(items: NotebookCellTextModel[]) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 05e45dc04ed..1200e221ee5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -507,3 +507,14 @@ export interface INotebookTextModelBackup { languages: string[]; cells: ICellDto2[] } + +export interface IEditor extends editorCommon.ICompositeCodeEditor { + readonly onDidChangeModel: Event; + readonly onDidFocusEditorWidget: Event; + isNotebookEditor: boolean; + uri?: URI; + textModel?: NotebookTextModel; + getId(): string; + hasFocus(): boolean; + hasModel(): boolean; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index a91ea5ec312..6aa772b825b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -31,7 +31,9 @@ export interface INotebookService { _serviceBrand: undefined; modelManager: INotebookEditorModelManager; canResolve(viewType: string): Promise; - onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>; + onDidChangeActiveEditor: Event; + onNotebookEditorAdd: Event; + onNotebookEditorRemove: Event; onDidChangeKernels: Event; registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; unregisterNotebookProvider(viewType: string): void; @@ -51,10 +53,17 @@ export interface INotebookService { getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; getNotebookProviderResourceRoots(): URI[]; destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; - updateActiveNotebookDocument(viewType: string, resource: URI): void; + updateActiveNotebookEditor(editor: IEditor): void; save(viewType: string, resource: URI, token: CancellationToken): Promise; saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; onDidReceiveMessage(viewType: string, uri: URI, message: any): void; setToCopy(items: NotebookCellTextModel[]): void; getToCopy(): NotebookCellTextModel[] | undefined; + + // editor events + addNotebookEditor(editor: IEditor): void; + removeNotebookEditor(editor: IEditor): void; + listNotebookEditors(): readonly IEditor[]; + listNotebookDocuments(): readonly NotebookTextModel[]; + } diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 1020fd08268..b932b80e94e 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -45,6 +45,19 @@ export class TestNotebookEditor implements INotebookEditor { constructor( ) { } + + hasModel(): boolean { + return true; + } + + onDidFocusEditorWidget: Event = new Emitter().event; + hasFocus(): boolean { + return true; + } + getId(): string { + return 'notebook.testEditor'; + } + activeKernel: INotebookKernelInfo | undefined; onDidChangeKernel: Event = new Emitter().event; onDidChangeActiveEditor: Event = new Emitter().event; @@ -53,8 +66,8 @@ export class TestNotebookEditor implements INotebookEditor { throw new Error('Method not implemented.'); } - private _onDidChangeModel = new Emitter(); - onDidChangeModel: Event = this._onDidChangeModel.event; + private _onDidChangeModel = new Emitter(); + onDidChangeModel: Event = this._onDidChangeModel.event; getContribution(id: string): T { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 333b8e44cfe..cc45bd7f4ef 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -63,9 +63,16 @@ suite('NotebookConcatDocument', function () { outputs: [], }], versionId: 0 - }] + }], + addedEditors: [ + { + documentUri: notebookUri, + id: '_notebook_editor_0', + selections: [0] + } + ] }); - await extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: notebookUri }); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' }); notebook = extHostNotebooks.activeNotebookDocument!;