diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3273d4f7dd4a7..298a30258921d 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -26,10 +26,10 @@ jobs: node-version: 18.x registry-url: 'https://registry.npmjs.org' - - name: Use Python 3.x + - name: Use Python 3.11 uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: '3.11' - name: Install and Build shell: bash @@ -67,10 +67,10 @@ jobs: node-version: ${{ matrix.node }} registry-url: 'https://registry.npmjs.org' - - name: Use Python 3.x + - name: Use Python 3.11 uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: '3.11' - name: Install shell: bash diff --git a/packages/core/src/browser/saveable.ts b/packages/core/src/browser/saveable.ts index a0d0810b447be..61a8e39aaca1a 100644 --- a/packages/core/src/browser/saveable.ts +++ b/packages/core/src/browser/saveable.ts @@ -50,7 +50,7 @@ export interface SaveableSource { readonly saveable: Saveable; } -export class SaveableDelegate implements Saveable { +export class DelegatingSaveable implements Saveable { dirty = false; protected readonly onDirtyChangedEmitter = new Emitter(); @@ -60,20 +60,20 @@ export class SaveableDelegate implements Saveable { autoSave: 'off' | 'afterDelay' | 'onFocusChange' | 'onWindowChange' = 'off'; async save(options?: SaveOptions): Promise { - await this.delegate?.save(options); + await this._delegate?.save(options); } revert?(options?: Saveable.RevertOptions): Promise; createSnapshot?(): Saveable.Snapshot; applySnapshot?(snapshot: object): void; - protected delegate?: Saveable; + protected _delegate?: Saveable; protected toDispose?: Disposable; - set(delegate: Saveable): void { + set delegate(delegate: Saveable) { this.toDispose?.dispose(); - this.delegate = delegate; - this.toDispose = this.delegate.onDirtyChanged(() => { + this._delegate = delegate; + this.toDispose = delegate.onDirtyChanged(() => { this.dirty = delegate.dirty; this.onDirtyChangedEmitter.fire(); }); diff --git a/packages/core/src/common/uri.ts b/packages/core/src/common/uri.ts index 2076519c7d6f6..fe24bc2e54339 100644 --- a/packages/core/src/common/uri.ts +++ b/packages/core/src/common/uri.ts @@ -27,10 +27,6 @@ export class URI { return new URI(Uri.file(path)); } - public static isUri(uri: unknown): boolean { - return Uri.isUri(uri); - } - private readonly codeUri: Uri; private _path: Path | undefined; diff --git a/packages/monaco/src/browser/monaco-languages.ts b/packages/monaco/src/browser/monaco-languages.ts index 6bfb920e8bf74..d533475d804e0 100644 --- a/packages/monaco/src/browser/monaco-languages.ts +++ b/packages/monaco/src/browser/monaco-languages.ts @@ -85,10 +85,6 @@ export class MonacoLanguages implements LanguageService { return this.getLanguage(languageId)?.extensions.values().next().value; } - getLanguageIdByLanguageName(languageName: string): string | undefined { - return monaco.languages.getLanguages().find(language => language.aliases?.includes(languageName))?.id; - } - protected mergeLanguages(registered: monaco.languages.ILanguageExtensionPoint[]): Map> { const languages = new Map>(); for (const { id, aliases, extensions, filenames } of registered) { diff --git a/packages/monaco/src/browser/monaco-code-editor.ts b/packages/monaco/src/browser/simple-monaco-editor.ts similarity index 99% rename from packages/monaco/src/browser/monaco-code-editor.ts rename to packages/monaco/src/browser/simple-monaco-editor.ts index 62a57e8a56fd8..61096e5437796 100644 --- a/packages/monaco/src/browser/monaco-code-editor.ts +++ b/packages/monaco/src/browser/simple-monaco-editor.ts @@ -26,7 +26,7 @@ import { Dimension, EditorMouseEvent, MouseTarget, Position, TextDocumentChangeE import * as monaco from '@theia/monaco-editor-core'; import { ElementExt } from '@theia/core/shared/@phosphor/domutils'; -export class MonacoCodeEditor extends MonacoEditorServices implements Disposable { +export class SimpleMonacoEditor extends MonacoEditorServices implements Disposable { protected editor: CodeEditorWidget; protected readonly toDispose = new DisposableCollection(); diff --git a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts index d06d0d94c0dfc..39071c329623d 100644 --- a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts +++ b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts @@ -26,35 +26,42 @@ import { NotebookCellOutputModel } from '../view-model/notebook-cell-output-mode import { CellEditType } from '../../common'; export namespace NotebookCellCommands { + /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */ export const EDIT_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.edit', iconClass: codicon('edit') }); + /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */ export const STOP_EDIT_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.stop-edit', iconClass: codicon('check') }); + /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */ export const DELETE_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.delete', iconClass: codicon('trash') }); + /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */ export const SPLIT_CELL_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.split-cell', iconClass: codicon('split-vertical'), }); + /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */ export const EXECUTE_SINGLE_CELL_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.execute-cell', iconClass: codicon('play'), }); + /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */ export const STOP_CELL_EXECUTION_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.stop-cell-execution', iconClass: codicon('stop'), }); - + /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */ export const CLEAR_OUTPUTS_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.clear-outputs', label: 'Clear Cell Outputs', }); + /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel | undefined, output: NotebookCellOutputModel */ export const CHANGE_OUTPUT_PRESENTATION_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.change-presentation', label: 'Change Presentation', @@ -184,7 +191,7 @@ export class NotebookCellActionContribution implements MenuContribution, Command execute: (notebookModel: NotebookModel, cell: NotebookCellModel) => this.notebookExecutionService.cancelNotebookCells(notebookModel, [cell]) }); commands.registerCommand(NotebookCellCommands.CLEAR_OUTPUTS_COMMAND, { - execute: (notebookModel: NotebookModel, cell: NotebookCellModel) => cell.spliceNotebookCellOutputs({ start: 0, deleteCount: cell.outputs.length, newOutputs: [] }) + execute: (_, cell: NotebookCellModel) => cell.spliceNotebookCellOutputs({ start: 0, deleteCount: cell.outputs.length, newOutputs: [] }) }); commands.registerCommand(NotebookCellCommands.CHANGE_OUTPUT_PRESENTATION_COMMAND, { execute: (_, __, output: NotebookCellOutputModel) => output.requestOutputPresentationUpdate() diff --git a/packages/notebook/src/browser/contributions/notebook-context-keys.ts b/packages/notebook/src/browser/contributions/notebook-context-keys.ts index aff494546161f..5bc987d154897 100644 --- a/packages/notebook/src/browser/contributions/notebook-context-keys.ts +++ b/packages/notebook/src/browser/contributions/notebook-context-keys.ts @@ -13,11 +13,18 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; export type NotebookCellExecutionStateContext = 'idle' | 'pending' | 'executing' | 'succeeded' | 'failed'; +/** + * Context Keys for the Notebook Editor as defined by vscode in https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts + */ export const HAS_OPENED_NOTEBOOK = 'userHasOpenedNotebook'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = 'notebookFindWidgetFocused'; export const NOTEBOOK_EDITOR_FOCUSED = 'notebookEditorFocused'; diff --git a/packages/notebook/src/browser/index.ts b/packages/notebook/src/browser/index.ts index 74299913c414f..eccec73435328 100644 --- a/packages/notebook/src/browser/index.ts +++ b/packages/notebook/src/browser/index.ts @@ -18,7 +18,7 @@ export * from './notebook-type-registry'; export * from './notebook-renderer-registry'; export * from './notebook-editor-widget'; export * from './service/notebook-service'; -export * from './service/notebook-editor-service'; +export * from './service/notebook-editor-widget-service'; export * from './service/notebook-kernel-service'; export * from './service/notebook-execution-state-service'; export * from './service/notebook-model-resolver-service'; diff --git a/packages/notebook/src/browser/notebook-cell-resource-resolver.ts b/packages/notebook/src/browser/notebook-cell-resource-resolver.ts index c09d85f3a30b2..9e2db9b92b1c4 100644 --- a/packages/notebook/src/browser/notebook-cell-resource-resolver.ts +++ b/packages/notebook/src/browser/notebook-cell-resource-resolver.ts @@ -14,7 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { Emitter, Resource, ResourceReadOptions, ResourceResolver, ResourceVersion, URI } from '@theia/core'; +import { Emitter, Resource, ResourceReadOptions, ResourceResolver, URI } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { CellUri } from '../common'; import { NotebookService } from './service/notebook-service'; @@ -25,10 +25,6 @@ export class NotebookCellResource implements Resource { protected readonly didChangeContentsEmitter = new Emitter(); readonly onDidChangeContents = this.didChangeContentsEmitter.event; - version?: ResourceVersion | undefined; - encoding?: string | undefined; - isReadonly?: boolean | undefined; - private cell: NotebookCellModel; constructor(public uri: URI, cell: NotebookCellModel) { diff --git a/packages/notebook/src/browser/notebook-editor-widget-factory.ts b/packages/notebook/src/browser/notebook-editor-widget-factory.ts index d17832b56fd8a..fc9d90d0ac109 100644 --- a/packages/notebook/src/browser/notebook-editor-widget-factory.ts +++ b/packages/notebook/src/browser/notebook-editor-widget-factory.ts @@ -17,7 +17,7 @@ import { URI } from '@theia/core'; import { WidgetFactory, NavigatableWidgetOptions, LabelProvider } from '@theia/core/lib/browser'; import { inject, injectable } from '@theia/core/shared/inversify'; -import { NotebookEditorWidget, NotebookEditorContainerFactory, NotebookEditorProps } from './notebook-editor-widget'; +import { NotebookEditorWidget, NotebookEditorWidgetContainerFactory, NotebookEditorProps } from './notebook-editor-widget'; import { NotebookService } from './service/notebook-service'; import { NotebookModelResolverService } from './service/notebook-model-resolver-service'; @@ -38,7 +38,7 @@ export class NotebookEditorWidgetFactory implements WidgetFactory { @inject(LabelProvider) protected readonly labelProvider: LabelProvider; - @inject(NotebookEditorContainerFactory) + @inject(NotebookEditorWidgetContainerFactory) protected readonly createNotebookEditorWidget: (props: NotebookEditorProps) => NotebookEditorWidget; async createWidget(options?: NotebookEditorWidgetOptions): Promise { diff --git a/packages/notebook/src/browser/notebook-editor-widget.tsx b/packages/notebook/src/browser/notebook-editor-widget.tsx index 7d600ce5afa08..6ca2be986332c 100644 --- a/packages/notebook/src/browser/notebook-editor-widget.tsx +++ b/packages/notebook/src/browser/notebook-editor-widget.tsx @@ -16,7 +16,7 @@ import * as React from '@theia/core/shared/react'; import { CommandRegistry, MenuModelRegistry, URI } from '@theia/core'; -import { ReactWidget, Navigatable, SaveableSource, Message, SaveableDelegate } from '@theia/core/lib/browser'; +import { ReactWidget, Navigatable, SaveableSource, Message, DelegatingSaveable } from '@theia/core/lib/browser'; import { ReactNode } from '@theia/core/shared/react'; import { CellKind } from '../common'; import { CellRenderer as CellRenderer, NotebookCellListView } from './view/notebook-cell-list-view'; @@ -24,12 +24,12 @@ import { NotebookCodeCellRenderer } from './view/notebook-code-cell-view'; import { NotebookMarkdownCellRenderer } from './view/notebook-markdown-cell-view'; import { NotebookModel } from './view-model/notebook-model'; import { NotebookCellToolbarFactory } from './view/notebook-cell-toolbar-factory'; -import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; +import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/shared/vscode-languageserver-protocol'; -import { NotebookEditorWidgetService } from './service/notebook-editor-service'; +import { NotebookEditorWidgetService } from './service/notebook-editor-widget-service'; import { NotebookMainToolbarRenderer } from './view/notebook-main-toolbar'; -export const NotebookEditorContainerFactory = Symbol('NotebookModelFactory'); +export const NotebookEditorWidgetContainerFactory = Symbol('NotebookEditorWidgetContainerFactory'); export function createNotebookEditorWidgetContainer(parent: interfaces.Container, props: NotebookEditorProps): interfaces.Container { const child = parent.createChild(); @@ -53,7 +53,7 @@ export const NOTEBOOK_EDITOR_ID_PREFIX = 'notebook:'; export class NotebookEditorWidget extends ReactWidget implements Navigatable, SaveableSource { static readonly ID = 'notebook'; - readonly saveable = new SaveableDelegate(); + readonly saveable = new DelegatingSaveable(); @inject(NotebookCellToolbarFactory) protected readonly cellToolbarFactory: NotebookCellToolbarFactory; @@ -70,6 +70,13 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa @inject(NotebookMainToolbarRenderer) protected notebookMainToolbarRenderer: NotebookMainToolbarRenderer; + @inject(NotebookCodeCellRenderer) + protected codeCellRenderer: NotebookCodeCellRenderer; + @inject(NotebookMarkdownCellRenderer) + protected markdownCellRenderer: NotebookMarkdownCellRenderer; + @inject(NotebookEditorProps) + protected readonly props: NotebookEditorProps; + protected readonly onDidChangeModelEmitter = new Emitter(); readonly onDidChangeModel = this.onDidChangeModelEmitter.event; @@ -84,11 +91,8 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa return this._model; } - constructor( - @inject(NotebookCodeCellRenderer) codeCellRenderer: NotebookCodeCellRenderer, - @inject(NotebookMarkdownCellRenderer) markdownCellRenderer: NotebookMarkdownCellRenderer, - @inject(NotebookEditorProps) private readonly props: NotebookEditorProps) { - super(); + @postConstruct() + protected init(): void { this.id = NOTEBOOK_EDITOR_ID_PREFIX + this.props.uri.toString(); this.node.tabIndex = -1; @@ -97,14 +101,15 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa this.toDispose.push(this.onDidChangeModelEmitter); - this.renderers.set(CellKind.Markup, markdownCellRenderer); - this.renderers.set(CellKind.Code, codeCellRenderer); + this.renderers.set(CellKind.Markup, this.markdownCellRenderer); + this.renderers.set(CellKind.Code, this.codeCellRenderer); this.waitForData(); + } protected async waitForData(): Promise { this._model = await this.props.notebookData; - this.saveable.set(this._model); + this.saveable.delegate = this._model; this.toDispose.push(this._model); // Ensure that the model is loaded before adding the editor this.notebookEditorService.addNotebookEditor(this); diff --git a/packages/notebook/src/browser/notebook-frontend-module.ts b/packages/notebook/src/browser/notebook-frontend-module.ts index 7746ad0f10ec8..c3602f522216d 100644 --- a/packages/notebook/src/browser/notebook-frontend-module.ts +++ b/packages/notebook/src/browser/notebook-frontend-module.ts @@ -30,7 +30,7 @@ import { NotebookCellActionContribution } from './contributions/notebook-cell-ac import { NotebookCellToolbarFactory } from './view/notebook-cell-toolbar-factory'; import { createNotebookModelContainer, NotebookModel, NotebookModelFactory, NotebookModelProps } from './view-model/notebook-model'; import { createNotebookCellModelContainer, NotebookCellModel, NotebookCellModelFactory, NotebookCellModelProps } from './view-model/notebook-cell-model'; -import { createNotebookEditorWidgetContainer, NotebookEditorContainerFactory, NotebookEditorProps, NotebookEditorWidget } from './notebook-editor-widget'; +import { createNotebookEditorWidgetContainer, NotebookEditorWidgetContainerFactory, NotebookEditorProps, NotebookEditorWidget } from './notebook-editor-widget'; import { NotebookCodeCellRenderer } from './view/notebook-code-cell-view'; import { NotebookMarkdownCellRenderer } from './view/notebook-markdown-cell-view'; import { NotebookActionsContribution } from './contributions/notebook-actions-contribution'; @@ -39,7 +39,7 @@ import { NotebookExecutionStateService } from './service/notebook-execution-stat import { NotebookKernelService } from './service/notebook-kernel-service'; import { KernelPickerMRUStrategy, NotebookKernelQuickPickService } from './service/notebook-kernel-quick-pick-service'; import { NotebookKernelHistoryService } from './service/notebook-kernel-history-service'; -import { NotebookEditorWidgetService } from './service/notebook-editor-service'; +import { NotebookEditorWidgetService } from './service/notebook-editor-widget-service'; import { NotebookRendererMessagingService } from './service/notebook-renderer-messaging-service'; import { NotebookColorContribution } from './contributions/notebook-color-contribution'; import { NotebookCellContextManager } from './service/notebook-cell-context-manager'; @@ -83,7 +83,7 @@ export default new ContainerModule(bind => { bind(NotebookMarkdownCellRenderer).toSelf().inSingletonScope(); bind(NotebookMainToolbarRenderer).toSelf().inSingletonScope(); - bind(NotebookEditorContainerFactory).toFactory(ctx => (props: NotebookEditorProps) => + bind(NotebookEditorWidgetContainerFactory).toFactory(ctx => (props: NotebookEditorProps) => createNotebookEditorWidgetContainer(ctx.container, props).get(NotebookEditorWidget) ); bind(NotebookModelFactory).toFactory(ctx => (props: NotebookModelProps) => diff --git a/packages/notebook/src/browser/notebook-open-handler.ts b/packages/notebook/src/browser/notebook-open-handler.ts index 7edb01ca97db5..b260e24c13ac3 100644 --- a/packages/notebook/src/browser/notebook-open-handler.ts +++ b/packages/notebook/src/browser/notebook-open-handler.ts @@ -28,9 +28,8 @@ export class NotebookOpenHandler extends NavigatableWidgetOpenHandler { const priorities = this.notebookTypeRegistry.notebookTypes @@ -59,7 +58,7 @@ export class NotebookOpenHandler extends NavigatableWidgetOpenHandler { - this.notebookRenderers.splice(this.notebookRenderers.findIndex(renderer => renderer.id === type.id), 1); + this._notebookRenderers.splice(this._notebookRenderers.findIndex(renderer => renderer.id === type.id), 1); }); } } diff --git a/packages/notebook/src/browser/notebook-type-registry.ts b/packages/notebook/src/browser/notebook-type-registry.ts index 973652e2304b6..923d982c22cb9 100644 --- a/packages/notebook/src/browser/notebook-type-registry.ts +++ b/packages/notebook/src/browser/notebook-type-registry.ts @@ -19,12 +19,16 @@ import { NotebookTypeDescriptor } from '../common/notebook-protocol'; @injectable() export class NotebookTypeRegistry { - readonly notebookTypes: NotebookTypeDescriptor[] = []; + private readonly _notebookTypes: NotebookTypeDescriptor[] = []; + + get notebookTypes(): readonly NotebookTypeDescriptor[] { + return this._notebookTypes; + } registerNotebookType(type: NotebookTypeDescriptor): Disposable { - this.notebookTypes.push(type); + this._notebookTypes.push(type); return Disposable.create(() => { - this.notebookTypes.splice(this.notebookTypes.indexOf(type), 1); + this._notebookTypes.splice(this._notebookTypes.indexOf(type), 1); }); } } diff --git a/packages/notebook/src/browser/renderers/cell-output-webview.ts b/packages/notebook/src/browser/renderers/cell-output-webview.ts index 3e84347f854fb..0466ef6b6c9e2 100644 --- a/packages/notebook/src/browser/renderers/cell-output-webview.ts +++ b/packages/notebook/src/browser/renderers/cell-output-webview.ts @@ -25,7 +25,7 @@ export interface CellOutputWebview extends Disposable { readonly id: string; - render(): React.JSX.Element; + render(): React.ReactNode; attachWebview(): void; isAttached(): boolean diff --git a/packages/notebook/src/browser/service/notebook-cell-context-manager.ts b/packages/notebook/src/browser/service/notebook-cell-context-manager.ts index a0d4386f0128a..aefe1655a4ad6 100644 --- a/packages/notebook/src/browser/service/notebook-cell-context-manager.ts +++ b/packages/notebook/src/browser/service/notebook-cell-context-manager.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { inject, injectable } from '@theia/core/shared/inversify'; -import { ContextKeyService, ScopedValueStore } from '@theia/core/lib/browser/context-key-service'; +import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { NotebookCellModel } from '../view-model/notebook-cell-model'; import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE } from '../contributions/notebook-context-keys'; import { Disposable, DisposableCollection, Emitter } from '@theia/core'; @@ -31,7 +31,6 @@ export class NotebookCellContextManager implements Disposable { protected readonly toDispose = new DisposableCollection(); - protected currentStore: ScopedValueStore; protected currentContext: HTMLLIElement; protected readonly onDidChangeContextEmitter = new Emitter(); @@ -40,21 +39,21 @@ export class NotebookCellContextManager implements Disposable { updateCellContext(cell: NotebookCellModel, newHtmlContext: HTMLLIElement): void { if (newHtmlContext !== this.currentContext) { this.toDispose.dispose(); - this.currentStore?.dispose(); this.currentContext = newHtmlContext; - this.currentStore = this.contextKeyService.createScoped(newHtmlContext); + const currentStore = this.contextKeyService.createScoped(newHtmlContext); + this.toDispose.push(currentStore); - this.currentStore.setContext(NOTEBOOK_CELL_TYPE, cell.cellKind === CellKind.Code ? 'code' : 'markdown'); + currentStore.setContext(NOTEBOOK_CELL_TYPE, cell.cellKind === CellKind.Code ? 'code' : 'markdown'); this.toDispose.push(cell.onDidRequestCellEditChange(cellEdit => { - this.currentStore?.setContext(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, cellEdit); + currentStore?.setContext(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, cellEdit); this.onDidChangeContextEmitter.fire(); })); this.toDispose.push(this.executionStateService.onDidChangeExecution(e => { if (e.affectsCell(cell.uri)) { - this.currentStore?.setContext(NOTEBOOK_CELL_EXECUTING, !!e.changed); - this.currentStore?.setContext(NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle'); + currentStore?.setContext(NOTEBOOK_CELL_EXECUTING, !!e.changed); + currentStore?.setContext(NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle'); this.onDidChangeContextEmitter.fire(); } })); @@ -64,7 +63,6 @@ export class NotebookCellContextManager implements Disposable { dispose(): void { this.toDispose.dispose(); - this.currentStore?.dispose(); this.onDidChangeContextEmitter.dispose(); } } diff --git a/packages/notebook/src/browser/service/notebook-editor-service.ts b/packages/notebook/src/browser/service/notebook-editor-widget-service.ts similarity index 70% rename from packages/notebook/src/browser/service/notebook-editor-service.ts rename to packages/notebook/src/browser/service/notebook-editor-widget-service.ts index 2cce03f68e1ae..f8307cdb9bd6f 100644 --- a/packages/notebook/src/browser/service/notebook-editor-service.ts +++ b/packages/notebook/src/browser/service/notebook-editor-widget-service.ts @@ -22,7 +22,7 @@ import { Disposable, DisposableCollection, Emitter } from '@theia/core'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { ApplicationShell } from '@theia/core/lib/browser'; -import { NotebookEditorWidget, NOTEBOOK_EDITOR_ID_PREFIX } from '../notebook-editor-widget'; +import { NotebookEditorWidget } from '../notebook-editor-widget'; @injectable() export class NotebookEditorWidgetService implements Disposable { @@ -33,37 +33,42 @@ export class NotebookEditorWidgetService implements Disposable { private readonly notebookEditors = new Map(); private readonly onNotebookEditorAddEmitter = new Emitter(); - private readonly onNotebookEditorsRemoveEmitter = new Emitter(); + private readonly onNotebookEditorRemoveEmitter = new Emitter(); readonly onDidAddNotebookEditor = this.onNotebookEditorAddEmitter.event; - readonly onDidRemoveNotebookEditor = this.onNotebookEditorsRemoveEmitter.event; + readonly onDidRemoveNotebookEditor = this.onNotebookEditorRemoveEmitter.event; - private readonly onFocusedEditorChangedEmitter = new Emitter(); - readonly onFocusedEditorChanged = this.onFocusedEditorChangedEmitter.event; + private readonly onDidChangeFocusedEditorEmitter = new Emitter(); + readonly onDidChangeFocusedEditor = this.onDidChangeFocusedEditorEmitter.event; private readonly toDispose = new DisposableCollection(); - currentFocusedEditor?: NotebookEditorWidget = undefined; + focusedEditor?: NotebookEditorWidget = undefined; @postConstruct() protected init(): void { this.toDispose.push(this.applicationShell.onDidChangeActiveWidget(event => { - if (event.newValue?.id.startsWith(NOTEBOOK_EDITOR_ID_PREFIX) && event.newValue !== this.currentFocusedEditor) { - this.currentFocusedEditor = event.newValue as NotebookEditorWidget; - this.onFocusedEditorChangedEmitter.fire(this.currentFocusedEditor); + if (event.newValue instanceof NotebookEditorWidget && event.newValue !== this.focusedEditor) { + this.focusedEditor = event.newValue; + this.onDidChangeFocusedEditorEmitter.fire(this.focusedEditor); + } else { + this.onDidChangeFocusedEditorEmitter.fire(undefined); } })); } dispose(): void { this.onNotebookEditorAddEmitter.dispose(); - this.onNotebookEditorsRemoveEmitter.dispose(); - this.onFocusedEditorChangedEmitter.dispose(); + this.onNotebookEditorRemoveEmitter.dispose(); + this.onDidChangeFocusedEditorEmitter.dispose(); this.toDispose.dispose(); } // --- editor management addNotebookEditor(editor: NotebookEditorWidget): void { + if (this.notebookEditors.has(editor.id)) { + console.warn('Attempting to add duplicated notebook editor: ' + editor.id); + } this.notebookEditors.set(editor.id, editor); this.onNotebookEditorAddEmitter.fire(editor); } @@ -71,7 +76,9 @@ export class NotebookEditorWidgetService implements Disposable { removeNotebookEditor(editor: NotebookEditorWidget): void { if (this.notebookEditors.has(editor.id)) { this.notebookEditors.delete(editor.id); - this.onNotebookEditorsRemoveEmitter.fire(editor); + this.onNotebookEditorRemoveEmitter.fire(editor); + } else { + console.warn('Attempting to remove not registered editor: ' + editor.id); } } diff --git a/packages/notebook/src/browser/service/notebook-execution-service.ts b/packages/notebook/src/browser/service/notebook-execution-service.ts index d0e3525194f8a..abfbbaaf4795e 100644 --- a/packages/notebook/src/browser/service/notebook-execution-service.ts +++ b/packages/notebook/src/browser/service/notebook-execution-service.ts @@ -65,7 +65,7 @@ export class NotebookExecutionService { for (const cell of cellsArr) { const cellExe = this.notebookExecutionStateService.getCellExecution(cell.uri); if (!cellExe) { - cellExecutions.push([cell, this.notebookExecutionStateService.createCellExecution(notebook.uri, cell.handle)]); + cellExecutions.push([cell, this.notebookExecutionStateService.getOrCreateCellExecution(notebook.uri, cell.handle)]); } } diff --git a/packages/notebook/src/browser/service/notebook-execution-state-service.ts b/packages/notebook/src/browser/service/notebook-execution-state-service.ts index 63227fd5b6545..4eee51691f278 100644 --- a/packages/notebook/src/browser/service/notebook-execution-state-service.ts +++ b/packages/notebook/src/browser/service/notebook-execution-state-service.ts @@ -18,7 +18,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, Emitter, URI } from '@theia/core'; +import { Disposable, DisposableCollection, Emitter, URI } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { NotebookService } from './notebook-service'; import { @@ -43,22 +43,6 @@ export interface CellExecutionStateUpdate { isPaused?: boolean; } -export interface ICellExecutionStateUpdate { - editType: CellExecutionUpdateType.ExecutionState; - executionOrder?: number; - runStartTime?: number; - didPause?: boolean; - isPaused?: boolean; -} - -export interface ICellExecutionStateUpdate { - editType: CellExecutionUpdateType.ExecutionState; - executionOrder?: number; - runStartTime?: number; - didPause?: boolean; - isPaused?: boolean; -} - export interface ICellExecutionComplete { runEndTime?: number; lastRunSuccess?: boolean; @@ -85,7 +69,9 @@ export class NotebookExecutionStateService implements Disposable { @inject(NotebookService) protected notebookService: NotebookService; - protected readonly executions = new Map(); + protected toDispose: DisposableCollection = new DisposableCollection(); + + protected readonly executions = new Map>(); private readonly onDidChangeExecutionEmitter = new Emitter(); onDidChangeExecution = this.onDidChangeExecutionEmitter.event; @@ -93,18 +79,21 @@ export class NotebookExecutionStateService implements Disposable { private readonly onDidChangeLastRunFailStateEmitter = new Emitter(); onDidChangeLastRunFailState = this.onDidChangeLastRunFailStateEmitter.event; - createCellExecution(notebookUri: URI, cellHandle: number): CellExecution { + getOrCreateCellExecution(notebookUri: URI, cellHandle: number): CellExecution { const notebook = this.notebookService.getNotebookEditorModel(notebookUri); if (!notebook) { throw new Error(`Notebook not found: ${notebookUri.toString()}`); } - let execution = this.executions.get(`${notebookUri}/${cellHandle}`); + let execution = this.executions.get(notebookUri.toString())?.get(cellHandle); if (!execution) { execution = this.createNotebookCellExecution(notebook, cellHandle); - this.executions.set(`${notebookUri}/${cellHandle}`, execution); + if (!this.executions.has(notebookUri.toString())) { + this.executions.set(notebookUri.toString(), new Map()); + } + this.executions.get(notebookUri.toString())?.set(cellHandle, execution); execution.initialize(); this.onDidChangeExecutionEmitter.fire(new CellExecutionStateChangedEvent(notebookUri, cellHandle, execution)); } @@ -116,20 +105,20 @@ export class NotebookExecutionStateService implements Disposable { private createNotebookCellExecution(notebook: NotebookModel, cellHandle: number): CellExecution { const notebookUri = notebook.uri; const execution = new CellExecution(cellHandle, notebook); - execution.onDidUpdate(() => this.onDidChangeExecutionEmitter.fire(new CellExecutionStateChangedEvent(notebookUri, cellHandle, execution))); - execution.onDidComplete(lastRunSuccess => this.onCellExecutionDidComplete(notebookUri, cellHandle, execution, lastRunSuccess)); + execution.toDispose.push(execution.onDidUpdate(() => this.onDidChangeExecutionEmitter.fire(new CellExecutionStateChangedEvent(notebookUri, cellHandle, execution)))); + execution.toDispose.push(execution.onDidComplete(lastRunSuccess => this.onCellExecutionDidComplete(notebookUri, cellHandle, execution, lastRunSuccess))); return execution; } private onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution, lastRunSuccess?: boolean): void { - const notebookExecutions = this.executions.get(`${notebookUri}/${cellHandle}`); + const notebookExecutions = this.executions.get(notebookUri.toString())?.get(cellHandle); if (!notebookExecutions) { - return; + throw new Error('Notebook Cell Execution not found while trying to complete it'); } exe.dispose(); - this.executions.delete(`${notebookUri}/${cellHandle}`); + this.executions.get(notebookUri.toString())?.delete(cellHandle); this.onDidChangeExecutionEmitter.fire(new CellExecutionStateChangedEvent(notebookUri, cellHandle)); } @@ -140,14 +129,14 @@ export class NotebookExecutionStateService implements Disposable { throw new Error(`Not a cell URI: ${cellUri}`); } - return this.executions.get(`${parsed.notebook.toString()}/${parsed.handle}`); + return this.executions.get(parsed.notebook.toString())?.get(parsed.handle); } dispose(): void { this.onDidChangeExecutionEmitter.dispose(); this.onDidChangeLastRunFailStateEmitter.dispose(); - this.executions.forEach(cellExecution => cellExecution.dispose()); + this.executions.forEach(notebookExecutions => notebookExecutions.forEach(execution => execution.dispose())); } } @@ -159,6 +148,8 @@ export class CellExecution implements Disposable { private readonly onDidCompleteEmitter = new Emitter(); readonly onDidComplete = this.onDidCompleteEmitter.event; + toDispose = new DisposableCollection(); + private _state: NotebookCellExecutionState = NotebookCellExecutionState.Unconfirmed; get state(): NotebookCellExecutionState { return this._state; @@ -198,7 +189,7 @@ export class CellExecution implements Disposable { renderDuration: undefined, } }; - this.applyExecutionEdits([startExecuteEdit]); + this.applyCellExecutionEditsToNotebook([startExecuteEdit]); } private getCellLog(): string { @@ -221,7 +212,7 @@ export class CellExecution implements Disposable { const lastIsPausedUpdate = [...updates].reverse().find(u => u.editType === CellExecutionUpdateType.ExecutionState && typeof u.isPaused === 'boolean'); if (lastIsPausedUpdate) { - this._isPaused = (lastIsPausedUpdate as ICellExecutionStateUpdate).isPaused!; + this._isPaused = (lastIsPausedUpdate as CellExecutionStateUpdate).isPaused!; } const cellModel = this.notebook.cells.find(c => c.handle === this.cellHandle); @@ -229,7 +220,7 @@ export class CellExecution implements Disposable { console.debug(`CellExecution#update, updating cell not in notebook: ${this.notebook.uri.toString()}, ${this.cellHandle}`); } else { const edits = updates.map(update => updateToEdit(update, this.cellHandle)); - this.applyExecutionEdits(edits); + this.applyCellExecutionEditsToNotebook(edits); } if (updates.some(u => u.editType === CellExecutionUpdateType.ExecutionState)) { @@ -254,7 +245,7 @@ export class CellExecution implements Disposable { runEndTime: this._didPause ? null : completionData.runEndTime, } }; - this.applyExecutionEdits([edit]); + this.applyCellExecutionEditsToNotebook([edit]); } this.onDidCompleteEmitter.fire(completionData.lastRunSuccess); @@ -264,9 +255,10 @@ export class CellExecution implements Disposable { dispose(): void { this.onDidUpdateEmitter.dispose(); this.onDidCompleteEmitter.dispose(); + this.toDispose.dispose(); } - private applyExecutionEdits(edits: CellEditOperation[]): void { + private applyCellExecutionEditsToNotebook(edits: CellEditOperation[]): void { this.notebook.applyEdits(edits, false); } } diff --git a/packages/notebook/src/browser/service/notebook-kernel-quick-pick-service.ts b/packages/notebook/src/browser/service/notebook-kernel-quick-pick-service.ts index 5461d99bb47e1..83a123082f884 100644 --- a/packages/notebook/src/browser/service/notebook-kernel-quick-pick-service.ts +++ b/packages/notebook/src/browser/service/notebook-kernel-quick-pick-service.ts @@ -18,7 +18,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ArrayUtils, Command, CommandService, DisposableCollection, Event, nls, QuickInputButton, QuickInputService, QuickPickInput, QuickPickItem, URI, } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { NotebookKernelService, NotebookKernel, NotebookKernelMatchResult, SourceCommand } from './notebook-kernel-service'; @@ -216,7 +215,7 @@ export abstract class NotebookKernelQuickPickServiceImpl { if (isSourcePick(pick)) { // selected explicitly, it should trigger the execution? - pick.action.run(); + pick.action.run(this.commandService); } return true; @@ -316,7 +315,7 @@ export class KernelPickerMRUStrategy extends NotebookKernelQuickPickServiceImpl quickPick.onDidTriggerItemButton(async e => { if (isKernelSourceQuickPickItem(e.item) && e.item.documentation !== undefined) { - const uri: URI | undefined = URI.isUri(e.item.documentation) ? new URI(e.item.documentation) : await this.commandService.executeCommand(e.item.documentation); + const uri: URI | undefined = this.isUri(e.item.documentation) ? new URI(e.item.documentation) : await this.commandService.executeCommand(e.item.documentation); if (uri) { (await this.openerService.getOpener(uri, { openExternal: true })).open(uri, { openExternal: true }); } @@ -386,7 +385,7 @@ export class KernelPickerMRUStrategy extends NotebookKernelQuickPickServiceImpl } else if (isSourcePick(selectedKernelPickItem)) { // selected explicitly, it should trigger the execution? try { - await selectedKernelPickItem.action.run(); + await selectedKernelPickItem.action.run(this.commandService); return true; } catch (ex) { return false; @@ -416,18 +415,22 @@ export class KernelPickerMRUStrategy extends NotebookKernelQuickPickServiceImpl return false; } + private isUri(value: string): boolean { + return /^(?\w[\w\d+.-]*):/.test(value); + } + private async calculateKernelSources(editor: NotebookModel): Promise[]> { const notebook: NotebookModel = editor; const actions = await this.notebookKernelService.getKernelSourceActionsFromProviders(notebook); const matchResult = this.getMatchingResult(notebook); - const others = matchResult.all.filter(item => item.extension !== JUPYTER_EXTENSION_ID); + const others = matchResult.all.filter(item => item.extensionId !== JUPYTER_EXTENSION_ID); const quickPickItems: QuickPickInput[] = []; // group controllers by extension - for (const group of ArrayUtils.groupBy(others, (a, b) => a.extension === b.extension ? 0 : 1)) { - const source = group[0].extension; + for (const group of ArrayUtils.groupBy(others, (a, b) => a.extensionId === b.extensionId ? 0 : 1)) { + const source = group[0].extensionId; if (group.length > 1) { quickPickItems.push({ label: source, diff --git a/packages/notebook/src/browser/service/notebook-kernel-service.ts b/packages/notebook/src/browser/service/notebook-kernel-service.ts index 756dc86237848..eea71c10f69ef 100644 --- a/packages/notebook/src/browser/service/notebook-kernel-service.ts +++ b/packages/notebook/src/browser/service/notebook-kernel-service.ts @@ -18,7 +18,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Command, CommandRegistry, Disposable, Emitter, Event, URI } from '@theia/core'; +import { Command, CommandService, Disposable, Emitter, Event, URI } from '@theia/core'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { StorageService } from '@theia/core/lib/browser'; import { NotebookKernelSourceAction } from '../../common'; @@ -51,11 +51,8 @@ export interface NotebookKernel { readonly id: string; readonly viewType: string; readonly onDidChange: Event>; - readonly extension: string; - - readonly localResourceRoot: URI; - readonly preloadUris: URI[]; - readonly preloadProvides: string[]; + // ID of the extension providing this kernel + readonly extensionId: string; label: string; description?: string; @@ -78,24 +75,20 @@ export interface INotebookProxyKernelChangeEvent extends NotebookKernelChangeEve connectionState?: true; } -export interface NotebookKernelDetectionTask { - readonly notebookType: string; -} - export interface NotebookTextModelLike { uri: URI; viewType: string } class KernelInfo { - private static logicClock = 0; + private static instanceCounter = 0; + score: number; readonly kernel: NotebookKernel; - public score: number; - readonly time: number; + readonly handle: number; constructor(kernel: NotebookKernel) { this.kernel = kernel; this.score = -1; - this.time = KernelInfo.logicClock++; + this.handle = KernelInfo.instanceCounter++; } } @@ -116,27 +109,25 @@ export class SourceCommand implements Disposable { readonly onDidChangeState = this.onDidChangeStateEmitter.event; constructor( - readonly commandRegistry: CommandRegistry, readonly command: Command, readonly model: NotebookTextModelLike, - readonly isPrimary: boolean ) { } - async run(): Promise { + async run(commandService: CommandService): Promise { if (this.execution) { return this.execution; } - this.execution = this.runCommand(); + this.execution = this.runCommand(commandService); this.onDidChangeStateEmitter.fire(); await this.execution; this.execution = undefined; this.onDidChangeStateEmitter.fire(); } - private async runCommand(): Promise { + private async runCommand(commandService: CommandService): Promise { try { - await this.commandRegistry.executeCommand(this.command.id, { + await commandService.executeCommand(this.command.id, { uri: this.model.uri, }); @@ -165,7 +156,7 @@ export class NotebookKernelService implements Disposable { private notebookBindings: { [key: string]: string } = {}; - private readonly kernelDetectionTasks = new Map(); + private readonly kernelDetectionTasks = new Map(); private readonly onDidChangeKernelDetectionTasksEmitter = new Emitter(); readonly onDidChangeKernelDetectionTasks = this.onDidChangeKernelDetectionTasksEmitter.event; @@ -196,7 +187,7 @@ export class NotebookKernelService implements Disposable { registerKernel(kernel: NotebookKernel): Disposable { if (this.kernels.has(kernel.id)) { - throw new Error(`NOTEBOOK CONTROLLER with id '${kernel.id}' already exists`); + throw new Error(`Notebook Controller with id '${kernel.id}' already exists`); } this.kernels.set(kernel.id, new KernelInfo(kernel)); @@ -215,6 +206,15 @@ export class NotebookKernelService implements Disposable { }); } + /** + * Helps to find the best matching kernel for a notebook. + * @param notebook notebook to get the matching kernel for + * @returns and object containing: + * all kernels sorted to match the notebook best first (affinity ascending, score descending, label)) + * the selected kernel (if any) + * specificly suggested kernels (if any) + * hidden kernels (if any) + */ getMatchingKernel(notebook: NotebookTextModelLike): NotebookKernelMatchResult { const kernels: { kernel: NotebookKernel; instanceAffinity: number; score: number }[] = []; for (const info of this.kernels.values()) { @@ -269,10 +269,10 @@ export class NotebookKernelService implements Disposable { } private static score(kernel: NotebookKernel, notebook: NotebookTextModelLike): number { - if (kernel.viewType === '*') { - return 5; - } else if (kernel.viewType === notebook.viewType) { + if (kernel.viewType === notebook.viewType) { return 10; + } else if (kernel.viewType === '*') { + return 5; } else { return 0; } @@ -295,15 +295,14 @@ export class NotebookKernelService implements Disposable { } } - registerNotebookKernelDetectionTask(task: NotebookKernelDetectionTask): Disposable { - const notebookType = task.notebookType; + registerNotebookKernelDetectionTask(notebookType: string): Disposable { const all = this.kernelDetectionTasks.get(notebookType) ?? []; - all.push(task); + all.push(notebookType); this.kernelDetectionTasks.set(notebookType, all); this.onDidChangeKernelDetectionTasksEmitter.fire(notebookType); return Disposable.create(() => { const allTasks = this.kernelDetectionTasks.get(notebookType) ?? []; - const taskIndex = allTasks.indexOf(task); + const taskIndex = allTasks.indexOf(notebookType); if (taskIndex >= 0) { allTasks.splice(taskIndex, 1); this.kernelDetectionTasks.set(notebookType, allTasks); @@ -312,7 +311,7 @@ export class NotebookKernelService implements Disposable { }); } - getKernelDetectionTasks(notebook: NotebookTextModelLike): NotebookKernelDetectionTask[] { + getKernelDetectionTasks(notebook: NotebookTextModelLike): string[] { return this.kernelDetectionTasks.get(notebook.viewType) ?? []; } diff --git a/packages/notebook/src/browser/service/notebook-model-resolver-service.ts b/packages/notebook/src/browser/service/notebook-model-resolver-service.ts index 07386ab739c0d..03f42c4997701 100644 --- a/packages/notebook/src/browser/service/notebook-model-resolver-service.ts +++ b/packages/notebook/src/browser/service/notebook-model-resolver-service.ts @@ -23,6 +23,7 @@ import { NotebookModel } from '../view-model/notebook-model'; import { NotebookService } from './notebook-service'; import { NotebookTypeRegistry } from '../notebook-type-registry'; import { NotebookFileSelector } from '../../common/notebook-protocol'; +import { match } from '@theia/core/lib/common/glob'; export interface UntitledResource { untitledResource: URI | undefined @@ -44,40 +45,21 @@ export class NotebookModelResolverService { protected onDidSaveNotebookEmitter = new Emitter(); readonly onDidSaveNotebook = this.onDidSaveNotebookEmitter.event; - async resolve(resource: URI, viewType?: string): Promise; - async resolve(resource: UntitledResource, viewType: string): Promise; - async resolve(arg: URI | UntitledResource, viewType: string): Promise { - let resource: URI; - // let hasAssociatedFilePath = false; - if (arg instanceof URI) { - resource = arg; - } else { - arg = arg as UntitledResource; - if (!arg.untitledResource) { - const notebookTypeInfo = this.notebookTypeRegistry.notebookTypes.find(info => info.type === viewType); - if (!notebookTypeInfo) { - throw new Error('UNKNOWN view type: ' + viewType); - } + async resolve(resource: URI, viewType?: string): Promise { - const suffix = this.getPossibleFileEndings(notebookTypeInfo.selector ?? []) ?? ''; - for (let counter = 1; ; counter++) { - const candidate = new URI() - .withScheme('untitled') - .withPath(`Untitled-notebook-${counter}${suffix}`) - .withQuery(viewType); - if (!this.notebookService.getNotebookEditorModel(candidate)) { - resource = candidate; - break; - } - } - } else if (arg.untitledResource.scheme === 'untitled') { - resource = arg.untitledResource; + if (!viewType) { + const existingViewType = this.notebookService.getNotebookEditorModel(resource)?.viewType; + if (existingViewType) { + viewType = existingViewType; } else { - resource = arg.untitledResource.withScheme('untitled'); - // hasAssociatedFilePath = true; + viewType = this.findViewTypeForResource(resource); } } + if (!viewType) { + throw new Error(`Missing viewType for '${resource}'`); + } + const notebookData = await this.resolveExistingNotebookData(resource, viewType!); const notebookModel = await this.notebookService.createNotebookModel(notebookData, viewType, resource); @@ -88,6 +70,39 @@ export class NotebookModelResolverService { return notebookModel; } + async resolveUntitledResource(arg: UntitledResource, viewType: string): Promise { + let resource: URI; + // let hasAssociatedFilePath = false; + arg = arg as UntitledResource; + if (!arg.untitledResource) { + const notebookTypeInfo = this.notebookTypeRegistry.notebookTypes.find(info => info.type === viewType); + if (!notebookTypeInfo) { + throw new Error('UNKNOWN view type: ' + viewType); + } + + const suffix = this.getPossibleFileEnding(notebookTypeInfo.selector ?? []) ?? ''; + for (let counter = 1; ; counter++) { + const candidate = new URI() + .withScheme('untitled') + .withPath(`Untitled-notebook-${counter}${suffix}`) + .withQuery(viewType); + if (!this.notebookService.getNotebookEditorModel(candidate)) { + resource = candidate; + break; + } + } + } else if (arg.untitledResource.scheme === 'untitled') { + resource = arg.untitledResource; + } else { + throw new Error('Invalid untitled resource: ' + arg.untitledResource.toString() + ' untitled resources with associated file path are not supported yet'); + // TODO implement associated file path support + // resource = arg.untitledResource.withScheme('untitled'); + // hasAssociatedFilePath = true; + } + + return this.resolve(resource, viewType); + } + protected async resolveExistingNotebookData(resource: URI, viewType: string): Promise { if (resource.scheme === 'untitled') { @@ -106,13 +121,13 @@ export class NotebookModelResolverService { const file = await this.fileService.readFile(resource); const dataProvider = await this.notebookService.getNotebookDataProvider(viewType); - const notebook = await dataProvider.serializer.dataToNotebook(file.value); + const notebook = await dataProvider.serializer.toNotebook(file.value); return notebook; } } - protected getPossibleFileEndings(selectors: readonly NotebookFileSelector[]): string | undefined { + protected getPossibleFileEnding(selectors: readonly NotebookFileSelector[]): string | undefined { for (const selector of selectors) { const ending = this.possibleFileEnding(selector); if (ending) { @@ -126,16 +141,22 @@ export class NotebookModelResolverService { const pattern = /^.*(\.[a-zA-Z0-9_-]+)$/; - const candidate: string | undefined = typeof selector === 'string' ? selector : selector.filenamePattern; + const candidate = typeof selector === 'string' ? selector : selector.filenamePattern; if (candidate) { - const match = pattern.exec(candidate); - if (match) { - return match[1]; + const matches = pattern.exec(candidate); + if (matches) { + return matches[1]; } } return undefined; } + protected findViewTypeForResource(resource: URI): string | undefined { + return this.notebookTypeRegistry.notebookTypes.find(info => + info.selector?.some(selector => selector.filenamePattern && match(selector.filenamePattern, resource.path.name + resource.path.ext)) + )?.type; + } + } diff --git a/packages/notebook/src/browser/service/notebook-renderer-messaging-service.ts b/packages/notebook/src/browser/service/notebook-renderer-messaging-service.ts index 923dafbb1db93..db2072ba1f420 100644 --- a/packages/notebook/src/browser/service/notebook-renderer-messaging-service.ts +++ b/packages/notebook/src/browser/service/notebook-renderer-messaging-service.ts @@ -22,18 +22,18 @@ import { Emitter } from '@theia/core'; import { injectable } from '@theia/core/shared/inversify'; import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; -interface MessageToSend { +interface RendererMessage { editorId: string; rendererId: string; message: unknown }; -export interface ScopedRendererMessaging extends Disposable { +export interface RendererMessaging extends Disposable { /** * Method called when a message is received. Should return a boolean * indicating whether a renderer received it. */ - receiveMessageHandler?: (rendererId: string, message: unknown) => Promise; + receiveMessage?: (rendererId: string, message: unknown) => Promise; /** * Sends a message to an extension from a renderer. @@ -44,19 +44,22 @@ export interface ScopedRendererMessaging extends Disposable { @injectable() export class NotebookRendererMessagingService implements Disposable { - private readonly postMessageEmitter = new Emitter(); - readonly onShouldPostMessage = this.postMessageEmitter.event; + private readonly postMessageEmitter = new Emitter(); + readonly onPostMessage = this.postMessageEmitter.event; - private readonly activations = new Map(); - private readonly scopedMessaging = new Map(); + private readonly willActivateRendererEmitter = new Emitter(); + readonly onWillActivateRenderer = this.willActivateRendererEmitter.event; + + private readonly activations = new Map(); + private readonly scopedMessaging = new Map(); receiveMessage(editorId: string | undefined, rendererId: string, message: unknown): Promise { if (editorId === undefined) { - const sends = [...this.scopedMessaging.values()].map(e => e.receiveMessageHandler?.(rendererId, message)); + const sends = [...this.scopedMessaging.values()].map(e => e.receiveMessage?.(rendererId, message)); return Promise.all(sends).then(values => values.some(value => !!value)); } - return this.scopedMessaging.get(editorId)?.receiveMessageHandler?.(rendererId, message) ?? Promise.resolve(false); + return this.scopedMessaging.get(editorId)?.receiveMessage?.(rendererId, message) ?? Promise.resolve(false); } prepare(rendererId: string): void { @@ -64,25 +67,24 @@ export class NotebookRendererMessagingService implements Disposable { return; } - const queue: MessageToSend[] = []; + const queue: RendererMessage[] = []; this.activations.set(rendererId, queue); - // activate renderer - // this.extensionService.activateByEvent(`onRenderer:${rendererId}`).then(() => { - // for (const message of queue) { - // this.postMessageEmitter.fire(message); - // } - // this.activations.set(rendererId, undefined); - // }); + Promise.all(this.willActivateRendererEmitter.fire(rendererId)).then(() => { + for (const message of queue) { + this.postMessageEmitter.fire(message); + } + this.activations.set(rendererId, undefined); + }); } - public getScoped(editorId: string): ScopedRendererMessaging { + public getScoped(editorId: string): RendererMessaging { const existing = this.scopedMessaging.get(editorId); if (existing) { return existing; } - const messaging: ScopedRendererMessaging = { + const messaging: RendererMessaging = { postMessage: (rendererId, message) => this.postMessage(editorId, rendererId, message), dispose: () => this.scopedMessaging.delete(editorId), }; diff --git a/packages/notebook/src/browser/service/notebook-service.ts b/packages/notebook/src/browser/service/notebook-service.ts index 2bf7bc0c6c0da..f8a8d48c3b21d 100644 --- a/packages/notebook/src/browser/service/notebook-service.ts +++ b/packages/notebook/src/browser/service/notebook-service.ts @@ -17,7 +17,7 @@ import { Disposable, DisposableCollection, Emitter, URI } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { NotebookData, NotebookExtensionDescription, TransientOptions } from '../../common'; +import { NotebookData, TransientOptions } from '../../common'; import { NotebookModel, NotebookModelFactory, NotebookModelProps } from '../view-model/notebook-model'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; @@ -26,16 +26,15 @@ import { Deferred } from '@theia/core/lib/common/promise-util'; export const NotebookProvider = Symbol('notebook provider'); -export interface SimpleNotebookProviderInfo { +export interface NotebookProviderInfo { readonly notebookType: string, readonly serializer: NotebookSerializer, - readonly extensionData: NotebookExtensionDescription } export interface NotebookSerializer { options: TransientOptions; - dataToNotebook(data: BinaryBuffer): Promise; - notebookToData(data: NotebookData): Promise; + toNotebook(data: BinaryBuffer): Promise; + fromNotebook(data: NotebookData): Promise; } @injectable() @@ -53,16 +52,16 @@ export class NotebookService implements Disposable { @inject(NotebookCellModelFactory) protected notebookCellModelFactory: (props: NotebookCellModelProps) => NotebookCellModel; - protected notebookSerializerEmitter = new Emitter(); - readonly onNotebookSerializer = this.notebookSerializerEmitter.event; + protected willUseNotebookSerializerEmitter = new Emitter(); + readonly onWillUseNotebookSerializer = this.willUseNotebookSerializerEmitter.event; protected readonly disposables = new DisposableCollection(); - protected readonly notebookProviders = new Map(); + protected readonly notebookProviders = new Map(); protected readonly notebookModels = new Map(); - protected readonly didAddViewTypeEmitter = new Emitter(); - readonly onDidAddViewType = this.didAddViewTypeEmitter.event; + protected readonly didRegisterNotebookSerializerEmitter = new Emitter(); + readonly onDidRegisterNotebookSerializer = this.didRegisterNotebookSerializerEmitter.event; protected readonly didRemoveViewTypeEmitter = new Emitter(); readonly onDidRemoveViewType = this.didRemoveViewTypeEmitter.event; @@ -70,12 +69,8 @@ export class NotebookService implements Disposable { protected readonly willOpenNotebookTypeEmitter = new Emitter(); readonly onWillOpenNotebook = this.willOpenNotebookTypeEmitter.event; - protected readonly willAddNotebookDocumentEmitter = new Emitter(); - readonly onWillAddNotebookDocument = this.willAddNotebookDocumentEmitter.event; protected readonly didAddNotebookDocumentEmitter = new Emitter(); readonly onDidAddNotebookDocument = this.didAddNotebookDocumentEmitter.event; - protected readonly willRemoveNotebookDocumentEmitter = new Emitter(); - readonly onWillRemoveNotebookDocument = this.willRemoveNotebookDocumentEmitter.event; protected readonly didRemoveNotebookDocumentEmitter = new Emitter(); readonly onDidRemoveNotebookDocument = this.didRemoveNotebookDocumentEmitter.event; @@ -92,17 +87,17 @@ export class NotebookService implements Disposable { this.ready.resolve(); } - registerNotebookSerializer(notebookType: string, extensionData: NotebookExtensionDescription, serializer: NotebookSerializer): Disposable { - if (this.notebookProviders.has(notebookType)) { - throw new Error(`notebook provider for viewtype '${notebookType}' already exists`); + registerNotebookSerializer(viewType: string, serializer: NotebookSerializer): Disposable { + if (this.notebookProviders.has(viewType)) { + throw new Error(`notebook provider for viewtype '${viewType}' already exists`); } - this.notebookProviders.set(notebookType, { notebookType: notebookType, serializer, extensionData }); - this.didAddViewTypeEmitter.fire(notebookType); + this.notebookProviders.set(viewType, { notebookType: viewType, serializer }); + this.didRegisterNotebookSerializerEmitter.fire(viewType); return Disposable.create(() => { - this.notebookProviders.delete(notebookType); - this.didRemoveViewTypeEmitter.fire(notebookType); + this.notebookProviders.delete(viewType); + this.didRemoveViewTypeEmitter.fire(viewType); }); } @@ -112,7 +107,6 @@ export class NotebookService implements Disposable { throw new Error('no notebook serializer for ' + viewType); } - this.willAddNotebookDocumentEmitter.fire(uri); const model = this.notebookModelFactory({ data, uri, viewType, serializer }); this.notebookModels.set(uri.toString(), model); // Resolve cell text models right after creating the notebook model @@ -122,9 +116,8 @@ export class NotebookService implements Disposable { return model; } - async getNotebookDataProvider(viewType: string): Promise { + async getNotebookDataProvider(viewType: string): Promise { await this.ready.promise; - await this.notebookSerializerEmitter.sequence(async listener => listener(`onNotebookSerializer:${viewType}`)); const result = await this.waitForNotebookProvider(viewType); if (!result) { @@ -138,14 +131,15 @@ export class NotebookService implements Disposable { * It takes a few seconds for the plugin host to start so that notebook data providers can be registered. * This methods waits until the notebook provider is registered. */ - protected async waitForNotebookProvider(type: string): Promise { + protected async waitForNotebookProvider(type: string): Promise { if (this.notebookProviders.has(type)) { return this.notebookProviders.get(type); } - const deferred = new Deferred(); + await Promise.all(this.willUseNotebookSerializerEmitter.fire(type)); + const deferred = new Deferred(); // 20 seconds of timeout const timeoutDuration = 20_000; - const disposable = this.onDidAddViewType(viewType => { + const disposable = this.onDidRegisterNotebookSerializer(viewType => { if (viewType === type) { clearTimeout(timeout); disposable.dispose(); diff --git a/packages/notebook/src/browser/view-model/notebook-cell-model.ts b/packages/notebook/src/browser/view-model/notebook-cell-model.ts index 44551c8f19d40..6c5fe56ff6588 100644 --- a/packages/notebook/src/browser/view-model/notebook-cell-model.ts +++ b/packages/notebook/src/browser/view-model/notebook-cell-model.ts @@ -29,6 +29,7 @@ import { import { NotebookCellOutputModel } from './notebook-cell-output-model'; export const NotebookCellModelFactory = Symbol('NotebookModelFactory'); +export type NotebookCellModelFactory = (props: NotebookCellModelProps) => NotebookCellModel; export function createNotebookCellModelContainer(parent: interfaces.Container, props: NotebookCellModelProps, notebookCellContextManager: new (...args: never[]) => unknown): interfaces.Container { @@ -134,7 +135,7 @@ export class NotebookCellModel implements NotebookCell, Disposable { return this.htmlContext; } - get textBuffer(): string { + get text(): string { return this.textModel ? this.textModel.getText() : this.source; } @@ -243,7 +244,7 @@ export class NotebookCellModel implements NotebookCell, Disposable { cellKind: this.cellKind, language: this.language, outputs: this.outputs.map(output => output.getData()), - source: this.textBuffer, + source: this.text, collapseState: this.props.collapseState, internalMetadata: this.internalMetadata, metadata: this.metadata diff --git a/packages/notebook/src/browser/view-model/notebook-model.ts b/packages/notebook/src/browser/view-model/notebook-model.ts index 624ecedad58b5..cf1f2359d6b1f 100644 --- a/packages/notebook/src/browser/view-model/notebook-model.ts +++ b/packages/notebook/src/browser/view-model/notebook-model.ts @@ -25,9 +25,9 @@ import { } from '../../common'; import { NotebookSerializer } from '../service/notebook-service'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; -import { NotebookCellModel, NotebookCellModelFactory, NotebookCellModelProps } from './notebook-cell-model'; +import { NotebookCellModel, NotebookCellModelFactory } from './notebook-cell-model'; import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; -import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; +import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import { NotebookKernel } from '../service/notebook-kernel-service'; import { UndoRedoService } from '@theia/editor/lib/browser/undo-redo-service'; @@ -53,24 +53,32 @@ export interface NotebookModelProps { @injectable() export class NotebookModel implements Saveable, Disposable { - private readonly onDirtyChangedEmitter = new Emitter(); + protected readonly onDirtyChangedEmitter = new Emitter(); readonly onDirtyChanged = this.onDirtyChangedEmitter.event; - private readonly onDidSaveNotebookEmitter = new Emitter(); + protected readonly onDidSaveNotebookEmitter = new Emitter(); readonly onDidSaveNotebook = this.onDidSaveNotebookEmitter.event; - private readonly onDidAddOrRemoveCellEmitter = new Emitter(); + protected readonly onDidAddOrRemoveCellEmitter = new Emitter(); readonly onDidAddOrRemoveCell = this.onDidAddOrRemoveCellEmitter.event; - private readonly onDidChangeContentEmitter = new Emitter(); + protected readonly onDidChangeContentEmitter = new Emitter(); readonly onDidChangeContent = this.onDidChangeContentEmitter.event; @inject(FileService) - private readonly fileService: FileService; + protected readonly fileService: FileService; @inject(UndoRedoService) - private readonly undoRedoService: UndoRedoService; + protected readonly undoRedoService: UndoRedoService; + @inject(NotebookModelProps) + protected props: NotebookModelProps; + + @inject(MonacoTextModelService) + protected modelService: MonacoTextModelService; + + @inject(NotebookCellModelFactory) + protected cellModelFactory: NotebookCellModelFactory; readonly autoSave: 'off' | 'afterDelay' | 'onFocusChange' | 'onWindowChange'; nextHandle: number = 0; @@ -79,7 +87,7 @@ export class NotebookModel implements Saveable, Disposable { dirty: boolean; selectedCell?: NotebookCellModel; - private dirtyCells: NotebookCellModel[] = []; + protected dirtyCells: NotebookCellModel[] = []; cells: NotebookCellModel[]; @@ -93,13 +101,12 @@ export class NotebookModel implements Saveable, Disposable { metadata: NotebookDocumentMetadata = {}; - constructor(@inject(NotebookModelProps) private props: NotebookModelProps, - @inject(MonacoTextModelService) modelService: MonacoTextModelService, - @inject(NotebookCellModelFactory) private cellModelFactory: (props: NotebookCellModelProps) => NotebookCellModel) { + @postConstruct() + initialize(): void { this.dirty = false; - this.cells = props.data.cells.map((cell, index) => cellModelFactory({ - uri: CellUri.generate(props.uri, index), + this.cells = this.props.data.cells.map((cell, index) => this.cellModelFactory({ + uri: CellUri.generate(this.props.uri, index), handle: index, source: cell.source, language: cell.language, @@ -114,7 +121,7 @@ export class NotebookModel implements Saveable, Disposable { this.metadata = this.metadata; - modelService.onDidCreate(editorModel => { + this.modelService.onDidCreate(editorModel => { const modelUri = new URI(editorModel.uri); if (modelUri.scheme === CellUri.scheme) { const cellUri = CellUri.parse(modelUri); @@ -142,7 +149,7 @@ export class NotebookModel implements Saveable, Disposable { this.dirty = false; this.onDirtyChangedEmitter.fire(); - const serializedNotebook = await this.props.serializer.notebookToData({ + const serializedNotebook = await this.props.serializer.fromNotebook({ cells: this.cells.map(cell => cell.getData()), metadata: this.metadata }); diff --git a/packages/notebook/src/browser/view/notebook-cell-editor.tsx b/packages/notebook/src/browser/view/notebook-cell-editor.tsx index da6afb8e97ee4..a029e9b4ced1e 100644 --- a/packages/notebook/src/browser/view/notebook-cell-editor.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-editor.tsx @@ -17,7 +17,7 @@ import * as React from '@theia/core/shared/react'; import { NotebookModel } from '../view-model/notebook-model'; import { NotebookCellModel } from '../view-model/notebook-cell-model'; -import { MonacoCodeEditor } from '@theia/monaco/lib/browser/monaco-code-editor'; +import { SimpleMonacoEditor } from '@theia/monaco/lib/browser/simple-monaco-editor'; import { MonacoEditorServices } from '@theia/monaco/lib/browser/monaco-editor'; import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider'; import { DisposableCollection } from '@theia/core'; @@ -40,7 +40,7 @@ const DEFAULT_EDITOR_OPTIONS = { export class CellEditor extends React.Component { - protected editor?: MonacoCodeEditor; + protected editor?: SimpleMonacoEditor; protected toDispose = new DisposableCollection(); protected container?: HTMLDivElement; @@ -64,7 +64,7 @@ export class CellEditor extends React.Component { const editorNode = this.container; const editorModel = await cell.resolveTextModel(); const uri = cell.uri; - this.editor = new MonacoCodeEditor(uri, + this.editor = new SimpleMonacoEditor(uri, editorModel, editorNode, monacoServices, diff --git a/packages/notebook/src/common/notebook-common.ts b/packages/notebook/src/common/notebook-common.ts index 86b59ff7dd5b3..6d60da581f57f 100644 --- a/packages/notebook/src/common/notebook-common.ts +++ b/packages/notebook/src/common/notebook-common.ts @@ -72,11 +72,6 @@ export interface TransientOptions { readonly transientDocumentMetadata: TransientDocumentMetadata; } -export interface NotebookExtensionDescription { - readonly id: string; - readonly location: string | undefined; -} - export interface CellOutputItem { readonly mime: string; readonly data: BinaryBuffer; @@ -101,7 +96,7 @@ export interface NotebookCell { outputs: CellOutput[]; metadata: NotebookCellMetadata; internalMetadata: NotebookCellInternalMetadata; - textBuffer: string; + text: string; onDidChangeOutputs?: Event; onDidChangeOutputItems?: Event; onDidChangeLanguage: Event; diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index bce8f9dfde8c5..bba09a687b62c 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -2464,7 +2464,7 @@ export interface NotebooksExt extends NotebookDocumentsAndEditorsExt { } export interface NotebooksMain extends Disposable { - $registerNotebookSerializer(handle: number, extension: notebookCommon.NotebookExtensionDescription, viewType: string, options: notebookCommon.TransientOptions): void; + $registerNotebookSerializer(handle: number, viewType: string, options: notebookCommon.TransientOptions): void; $unregisterNotebookSerializer(handle: number): void; $registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, viewType: string): Promise; diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 1c3f281337c69..13679f53bd200 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -69,7 +69,7 @@ import { LanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/ import { Measurement, Stopwatch } from '@theia/core/lib/common'; import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '@theia/core/lib/common/message-rpc/uint8-array-message-buffer'; import { BasicChannel } from '@theia/core/lib/common/message-rpc/channel'; -import { NotebookTypeRegistry, NotebookService } from '@theia/notebook/lib/browser'; +import { NotebookTypeRegistry, NotebookService, NotebookRendererMessagingService } from '@theia/notebook/lib/browser'; export type PluginHost = 'frontend' | string; export type DebugActivationEvent = 'onDebugResolve' | 'onDebugInitialConfigurations' | 'onDebugAdapterProtocolTracker' | 'onDebugDynamicConfigurations'; @@ -118,6 +118,9 @@ export class HostedPluginSupport { @inject(NotebookService) protected readonly notebookService: NotebookService; + @inject(NotebookRendererMessagingService) + protected readonly notebookRendererMessagingService: NotebookRendererMessagingService; + @inject(CommandRegistry) protected readonly commands: CommandRegistry; @@ -224,6 +227,7 @@ export class HostedPluginSupport { this.fileService.onWillActivateFileSystemProvider(event => this.ensureFileSystemActivation(event)); this.customEditorRegistry.onWillOpenCustomEditor(event => this.activateByCustomEditor(event)); this.notebookService.onWillOpenNotebook(async event => this.activateByNotebook(event)); + this.notebookRendererMessagingService.onWillActivateRenderer(rendererId => this.activateByNotebookRenderer(rendererId)); this.widgets.onDidCreateWidget(({ factoryId, widget }) => { if ((factoryId === WebviewWidget.FACTORY_ID || factoryId === CustomEditorWidget.FACTORY_ID) && widget instanceof WebviewWidget) { @@ -633,6 +637,14 @@ export class HostedPluginSupport { await this.activateByEvent(`onNotebook:${viewType}`); } + async activateByNotebookSerializer(viewType: string): Promise { + await this.activateByEvent(`onNotebookSerializer:${viewType}`); + } + + async activateByNotebookRenderer(rendererId: string): Promise { + await this.activateByEvent(`onRenderer:${rendererId}`); + } + activateByFileSystem(event: FileSystemProviderActivationEvent): Promise { return this.activateByEvent(`onFileSystem:${event.scheme}`); } diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-and-editors-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-and-editors-main.ts index 3e20545e201aa..166869ef0d79c 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-and-editors-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-and-editors-main.ts @@ -111,7 +111,7 @@ export class NotebooksAndEditorsMain implements NotebookDocumentsAndEditorsMain // this.WidgetManager.onActiveEditorChanged(() => this.updateState(), this, this.disposables); this.notebookEditorService.onDidAddNotebookEditor(async editor => this.handleEditorAdd(editor), this, this.disposables); this.notebookEditorService.onDidRemoveNotebookEditor(async editor => this.handleEditorRemove(editor), this, this.disposables); - this.notebookEditorService.onFocusedEditorChanged(async editor => this.updateState(editor), this, this.disposables); + this.notebookEditorService.onDidChangeFocusedEditor(async editor => this.updateState(editor), this, this.disposables); } dispose(): void { @@ -154,7 +154,7 @@ export class NotebooksAndEditorsMain implements NotebookDocumentsAndEditorsMain } } - const activeNotebookEditor = this.notebookEditorService.currentFocusedEditor; + const activeNotebookEditor = this.notebookEditorService.focusedEditor; let activeEditor: string | null = null; if (activeNotebookEditor) { activeEditor = activeNotebookEditor.id; diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts index 16012ddc0798a..d989535659219 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts @@ -131,7 +131,7 @@ export class NotebookDocumentsMainImpl implements NotebookDocumentsMain { } async $tryCreateNotebook(options: { viewType: string; content?: NotebookDataDto }): Promise { - const ref = await this.notebookModelResolverService.resolve({ untitledResource: undefined }, options.viewType); + const ref = await this.notebookModelResolverService.resolveUntitledResource({ untitledResource: undefined }, options.viewType); // untitled notebooks are disposed when they get saved. we should not hold a reference // to such a disposed notebook and therefore dispose the reference as well diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-dto.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-dto.ts index 3b3f0c0014e61..e6afbb8d79ed6 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-dto.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-dto.ts @@ -88,11 +88,11 @@ export namespace NotebookDto { } export function toNotebookCellDto(cell: NotebookCellModel): rpc.NotebookCellDto { - const eol = OS.backend.isWindows ? '\r\n' : '\n'; + const eol = OS.backend.EOL; return { handle: cell.handle, uri: cell.uri.toComponents(), - source: cell.textBuffer.split(eol), + source: cell.text.split(eol), eol, language: cell.language, cellKind: cell.cellKind, diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts index 9f12589bb0237..6f9e735f1f112 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts @@ -36,7 +36,7 @@ abstract class NotebookKernel { readonly id: string; readonly viewType: string; - readonly extension: string; + readonly extensionId: string; implementsInterrupt: boolean; label: string; @@ -57,7 +57,7 @@ abstract class NotebookKernel { constructor(data: NotebookKernelDto, private languageService: LanguageService) { this.id = data.id; this.viewType = data.notebookType; - this.extension = data.extensionId; + this.extensionId = data.extensionId; this.implementsInterrupt = data.supportsInterrupt ?? false; this.label = data.label; @@ -104,10 +104,6 @@ abstract class NotebookKernel { abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; } -class KernelDetectionTask { - constructor(readonly notebookType: string) { } -} - export interface KernelSourceActionProvider { readonly viewType: string; onDidChangeSourceActions?: Event; @@ -120,7 +116,7 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain { private readonly kernels = new Map(); - private readonly kernelDetectionTasks = new Map(); + private readonly kernelDetectionTasks = new Map(); private readonly kernelSourceActionProviders = new Map(); private readonly kernelSourceActionProvidersEventRegistrations = new Map(); @@ -202,7 +198,7 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain { if (!kernel.selected || kernel.selected.id !== controllerId) { throw new Error(`Kernel is not selected: ${kernel.selected?.id} !== ${controllerId}`); } - const execution = this.notebookExecutionStateService.createCellExecution(uri, cellHandle); + const execution = this.notebookExecutionStateService.getOrCreateCellExecution(uri, cellHandle); execution.confirm(); this.executions.set(handle, execution); } @@ -234,9 +230,8 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain { } async $addKernelDetectionTask(handle: number, notebookType: string): Promise { - const kernelDetectionTask = new KernelDetectionTask(notebookType); - const registration = this.notebookKernelService.registerNotebookKernelDetectionTask(kernelDetectionTask); - this.kernelDetectionTasks.set(handle, [kernelDetectionTask, registration]); + const registration = this.notebookKernelService.registerNotebookKernelDetectionTask(notebookType); + this.kernelDetectionTasks.set(handle, [notebookType, registration]); } $removeKernelDetectionTask(handle: number): void { const tuple = this.kernelDetectionTasks.get(handle); diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-renderers-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-renderers-main.ts index 867f6fa14faa3..fd8ef07e4a7f7 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-renderers-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-renderers-main.ts @@ -32,7 +32,7 @@ export class NotebookRenderersMainImpl implements NotebookRenderersMain { ) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.NOTEBOOK_RENDERERS_EXT); this.rendererMessagingService = container.get(NotebookRendererMessagingService); - this.rendererMessagingService.onShouldPostMessage(e => { + this.rendererMessagingService.onPostMessage(e => { this.proxy.$postRendererMessage(e.editorId, e.rendererId, e.message); }); } diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts index f31816ea8656e..19e3b39d62164 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts @@ -16,7 +16,7 @@ import { CancellationToken, DisposableCollection, Emitter } from '@theia/core'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { NotebookCellStatusBarItemList, NotebookCellStatusBarItemProvider, NotebookData, NotebookExtensionDescription, TransientOptions } from '@theia/notebook/lib/common'; +import { NotebookCellStatusBarItemList, NotebookCellStatusBarItemProvider, NotebookData, TransientOptions } from '@theia/notebook/lib/common'; import { NotebookService } from '@theia/notebook/lib/browser'; import { Disposable } from '@theia/plugin'; import { MAIN_RPC_CONTEXT, NotebooksExt, NotebooksMain } from '../../../common'; @@ -39,7 +39,7 @@ export class NotebooksMainImpl implements NotebooksMain { plugins: HostedPluginSupport ) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.NOTEBOOKS_EXT); - notebookService.onNotebookSerializer(async event => plugins.activateByEvent(event)); + notebookService.onWillUseNotebookSerializer(async event => plugins.activateByNotebookSerializer(event)); notebookService.markReady(); } @@ -50,16 +50,16 @@ export class NotebooksMainImpl implements NotebooksMain { } } - $registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void { + $registerNotebookSerializer(handle: number, viewType: string, options: TransientOptions): void { const disposables = new DisposableCollection(); - disposables.push(this.notebookService.registerNotebookSerializer(viewType, extension, { + disposables.push(this.notebookService.registerNotebookSerializer(viewType, { options, - dataToNotebook: async (data: BinaryBuffer): Promise => { + toNotebook: async (data: BinaryBuffer): Promise => { const dto = await this.proxy.$dataToNotebook(handle, data, CancellationToken.None); return NotebookDto.fromNotebookDataDto(dto); }, - notebookToData: (data: NotebookData): Promise => + fromNotebook: (data: NotebookData): Promise => this.proxy.$notebookToData(handle, NotebookDto.toNotebookDataDto(data), CancellationToken.None) })); diff --git a/packages/plugin-ext/src/plugin/notebook/notebooks.ts b/packages/plugin-ext/src/plugin/notebook/notebooks.ts index d27b3df60b36b..773c9c3ba081b 100644 --- a/packages/plugin-ext/src/plugin/notebook/notebooks.ts +++ b/packages/plugin-ext/src/plugin/notebook/notebooks.ts @@ -137,7 +137,6 @@ export class NotebooksExtImpl implements NotebooksExt { this.notebookSerializer.set(handle, serializer); this.notebookProxy.$registerNotebookSerializer( handle, - { id: plugin.model.id, location: plugin.pluginUri }, viewType, typeConverters.NotebookDocumentContentOptions.from(options), ); @@ -347,8 +346,7 @@ export class NotebooksExtImpl implements NotebooksExt { } async showNotebookDocument(notebookOrUri: theia.NotebookDocument | TheiaURI, options?: theia.NotebookDocumentShowOptions): Promise { - - if (URI.isUri(notebookOrUri)) { + if (TheiaURI.isUri(notebookOrUri)) { notebookOrUri = await this.openNotebookDocument(notebookOrUri as TheiaURI); }