diff --git a/packages/notebook/src/browser/contributions/notebook-actions-contribution.ts b/packages/notebook/src/browser/contributions/notebook-actions-contribution.ts index abd9516f3b346..9d73b6a766323 100644 --- a/packages/notebook/src/browser/contributions/notebook-actions-contribution.ts +++ b/packages/notebook/src/browser/contributions/notebook-actions-contribution.ts @@ -20,7 +20,7 @@ import { ApplicationShell, codicon, CommonCommands } from '@theia/core/lib/brows import { NotebookModel } from '../view-model/notebook-model'; import { NotebookService } from '../service/notebook-service'; import { CellEditType, CellKind } from '../../common'; -import { KernelPickerMRUStrategy, NotebookKernelQuickPickService } from '../service/notebook-kernel-quick-pick-service'; +import { NotebookKernelQuickPickService } from '../service/notebook-kernel-quick-pick-service'; import { NotebookExecutionService } from '../service/notebook-execution-service'; import { NotebookEditorWidget } from '../notebook-editor-widget'; @@ -66,7 +66,7 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon protected notebookService: NotebookService; @inject(NotebookKernelQuickPickService) - protected notebookKernelQuickPickService: KernelPickerMRUStrategy; + protected notebookKernelQuickPickService: NotebookKernelQuickPickService; @inject(NotebookExecutionService) protected notebookExecutionService: NotebookExecutionService; diff --git a/packages/notebook/src/browser/index.ts b/packages/notebook/src/browser/index.ts index eccec73435328..1c213a4415eeb 100644 --- a/packages/notebook/src/browser/index.ts +++ b/packages/notebook/src/browser/index.ts @@ -24,3 +24,4 @@ export * from './service/notebook-execution-state-service'; export * from './service/notebook-model-resolver-service'; export * from './service/notebook-renderer-messaging-service'; export * from './renderers/cell-output-webview'; +export * from './notebook-types'; diff --git a/packages/notebook/src/browser/notebook-frontend-module.ts b/packages/notebook/src/browser/notebook-frontend-module.ts index c3602f522216d..7839f0aa1f631 100644 --- a/packages/notebook/src/browser/notebook-frontend-module.ts +++ b/packages/notebook/src/browser/notebook-frontend-module.ts @@ -37,7 +37,7 @@ import { NotebookActionsContribution } from './contributions/notebook-actions-co import { NotebookExecutionService } from './service/notebook-execution-service'; import { NotebookExecutionStateService } from './service/notebook-execution-state-service'; import { NotebookKernelService } from './service/notebook-kernel-service'; -import { KernelPickerMRUStrategy, NotebookKernelQuickPickService } from './service/notebook-kernel-quick-pick-service'; +import { NotebookKernelQuickPickService } from './service/notebook-kernel-quick-pick-service'; import { NotebookKernelHistoryService } from './service/notebook-kernel-history-service'; import { NotebookEditorWidgetService } from './service/notebook-editor-widget-service'; import { NotebookRendererMessagingService } from './service/notebook-renderer-messaging-service'; @@ -65,7 +65,7 @@ export default new ContainerModule(bind => { bind(NotebookKernelService).toSelf().inSingletonScope(); bind(NotebookRendererMessagingService).toSelf().inSingletonScope(); bind(NotebookKernelHistoryService).toSelf().inSingletonScope(); - bind(NotebookKernelQuickPickService).to(KernelPickerMRUStrategy).inSingletonScope(); + bind(NotebookKernelQuickPickService).toSelf().inSingletonScope(); bind(NotebookCellResourceResolver).toSelf().inSingletonScope(); bind(ResourceResolver).toService(NotebookCellResourceResolver); diff --git a/packages/notebook/src/browser/notebook-types.ts b/packages/notebook/src/browser/notebook-types.ts new file mode 100644 index 0000000000000..a1ac52ae52370 --- /dev/null +++ b/packages/notebook/src/browser/notebook-types.ts @@ -0,0 +1,172 @@ +// ***************************************************************************** +// Copyright (C) 2023 TypeFox and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { + CellData, CellEditType, CellMetadataEdit, CellOutput, CellOutputItem, CellRange, NotebookCellContentChangeEvent, + NotebookCellInternalMetadata, + NotebookCellsChangeInternalMetadataEvent, + NotebookCellsChangeLanguageEvent, + NotebookCellsChangeMetadataEvent, + NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookDocumentMetadata +} from '../common'; +import { NotebookCell } from './view-model/notebook-cell-model'; + +export interface NotebookTextModelChangedEvent { + readonly rawEvents: NotebookContentChangedEvent[]; + // readonly versionId: number; + readonly synchronous?: boolean; + readonly endSelectionState?: SelectionState; +}; + +export type NotebookContentChangedEvent = (NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | + NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookOutputItemChangedEvent | + NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | + NotebookCellsChangeInternalMetadataEvent | NotebookDocumentUnknownChangeEvent); // & { transient: boolean }; + +export interface NotebookCellsInitializeEvent { + readonly kind: NotebookCellsChangeType.Initialize; + readonly changes: NotebookCellTextModelSplice[]; +} + +export interface NotebookDocumentChangeMetadataEvent { + readonly kind: NotebookCellsChangeType.ChangeDocumentMetadata; + readonly metadata: NotebookDocumentMetadata; +} + +export interface NotebookCellsModelChangedEvent { + readonly kind: NotebookCellsChangeType.ModelChange; + readonly changes: NotebookCellTextModelSplice[]; +} + +export interface NotebookModelWillAddRemoveEvent { + readonly rawEvent: NotebookCellsModelChangedEvent; +}; + +export interface NotebookCellsModelMoveEvent { + readonly kind: NotebookCellsChangeType.Move; + readonly index: number; + readonly length: number; + readonly newIdx: number; + readonly cells: T[]; +} + +export interface NotebookOutputChangedEvent { + readonly kind: NotebookCellsChangeType.Output; + readonly index: number; + readonly outputs: CellOutput[]; + readonly append: boolean; +} + +export interface NotebookOutputItemChangedEvent { + readonly kind: NotebookCellsChangeType.OutputItem; + readonly index: number; + readonly outputId: string; + readonly outputItems: CellOutputItem[]; + readonly append: boolean; +} + +export interface NotebookDocumentUnknownChangeEvent { + readonly kind: NotebookCellsChangeType.Unknown; +} + +export enum SelectionStateType { + Handle = 0, + Index = 1 +} + +export interface SelectionHandleState { + kind: SelectionStateType.Handle; + primary: number | null; + selections: number[]; +} + +export interface SelectionIndexState { + kind: SelectionStateType.Index; + focus: CellRange; + selections: CellRange[]; +} + +export type SelectionState = SelectionHandleState | SelectionIndexState; + +export interface NotebookModelWillAddRemoveEvent { + readonly newCellIds?: number[]; + readonly rawEvent: NotebookCellsModelChangedEvent; +}; + +export interface CellOutputEdit { + editType: CellEditType.Output; + index: number; + outputs: CellOutput[]; + append?: boolean; +} + +export interface CellOutputEditByHandle { + editType: CellEditType.Output; + handle: number; + outputs: CellOutput[]; + append?: boolean; +} + +export interface CellOutputItemEdit { + editType: CellEditType.OutputItems; + items: CellOutputItem[]; + outputId: string; + append?: boolean; +} + +export interface CellLanguageEdit { + editType: CellEditType.CellLanguage; + index: number; + language: string; +} + +export interface DocumentMetadataEdit { + editType: CellEditType.DocumentMetadata; + metadata: NotebookDocumentMetadata; +} + +export interface CellMoveEdit { + editType: CellEditType.Move; + index: number; + length: number; + newIdx: number; +} + +export interface CellReplaceEdit { + editType: CellEditType.Replace; + index: number; + count: number; + cells: CellData[]; +} + +export type ImmediateCellEditOperation = CellOutputEditByHandle | CellOutputItemEdit | CellPartialInternalMetadataEditByHandle; // add more later on +export type CellEditOperation = ImmediateCellEditOperation | CellReplaceEdit | CellOutputEdit | + CellMetadataEdit | CellLanguageEdit | DocumentMetadataEdit | CellMoveEdit; // add more later on + +export type NullablePartialNotebookCellInternalMetadata = { + [Key in keyof Partial]: NotebookCellInternalMetadata[Key] | null +}; +export interface CellPartialInternalMetadataEditByHandle { + editType: CellEditType.PartialInternalMetadata; + handle: number; + internalMetadata: NullablePartialNotebookCellInternalMetadata; +} + +export interface NotebookCellOutputsSplice { + start: number; + deleteCount: number; + newOutputs: CellOutput[]; +}; 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 aefe1655a4ad6..200e3e2f4dff9 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 } from '@theia/core/lib/browser/context-key-service'; +import { ContextKeyChangeEvent, ContextKeyService, ScopedValueStore } 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'; @@ -23,7 +23,7 @@ import { CellKind } from '../../common'; import { NotebookExecutionStateService } from '../service/notebook-execution-state-service'; @injectable() -export class NotebookCellContextManager implements Disposable { +export class NotebookCellContextManager implements NotebookCellContextManager, Disposable { @inject(ContextKeyService) protected contextKeyService: ContextKeyService; @inject(NotebookExecutionStateService) @@ -31,9 +31,10 @@ export class NotebookCellContextManager implements Disposable { protected readonly toDispose = new DisposableCollection(); + protected currentStore: ScopedValueStore; protected currentContext: HTMLLIElement; - protected readonly onDidChangeContextEmitter = new Emitter(); + protected readonly onDidChangeContextEmitter = new Emitter(); readonly onDidChangeContext = this.onDidChangeContextEmitter.event; updateCellContext(cell: NotebookCellModel, newHtmlContext: HTMLLIElement): void { @@ -41,28 +42,32 @@ export class NotebookCellContextManager implements Disposable { this.toDispose.dispose(); this.currentContext = newHtmlContext; - const currentStore = this.contextKeyService.createScoped(newHtmlContext); - this.toDispose.push(currentStore); + this.currentStore = this.contextKeyService.createScoped(newHtmlContext); - currentStore.setContext(NOTEBOOK_CELL_TYPE, cell.cellKind === CellKind.Code ? 'code' : 'markdown'); + this.currentStore.setContext(NOTEBOOK_CELL_TYPE, cell.cellKind === CellKind.Code ? 'code' : 'markdown'); + + this.toDispose.push(this.contextKeyService.onDidChange(e => { + this.onDidChangeContextEmitter.fire(e); + })); this.toDispose.push(cell.onDidRequestCellEditChange(cellEdit => { - currentStore?.setContext(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, cellEdit); - this.onDidChangeContextEmitter.fire(); + this.currentStore?.setContext(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, cellEdit); + this.onDidChangeContextEmitter.fire({ affects: keys => keys.has(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE) }); })); this.toDispose.push(this.executionStateService.onDidChangeExecution(e => { if (e.affectsCell(cell.uri)) { - currentStore?.setContext(NOTEBOOK_CELL_EXECUTING, !!e.changed); - currentStore?.setContext(NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle'); - this.onDidChangeContextEmitter.fire(); + this.currentStore?.setContext(NOTEBOOK_CELL_EXECUTING, !!e.changed); + this.currentStore?.setContext(NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle'); + this.onDidChangeContextEmitter.fire({ affects: keys => keys.has(NOTEBOOK_CELL_EXECUTING) || keys.has(NOTEBOOK_CELL_EXECUTION_STATE) }); } })); - this.onDidChangeContextEmitter.fire(); + this.onDidChangeContextEmitter.fire({ affects: keys => true }); } } dispose(): void { this.toDispose.dispose(); + this.currentStore?.dispose(); this.onDidChangeContextEmitter.dispose(); } } diff --git a/packages/notebook/src/browser/service/notebook-editor-widget-service.ts b/packages/notebook/src/browser/service/notebook-editor-widget-service.ts index f8307cdb9bd6f..3416285f4adc7 100644 --- a/packages/notebook/src/browser/service/notebook-editor-widget-service.ts +++ b/packages/notebook/src/browser/service/notebook-editor-widget-service.ts @@ -86,8 +86,8 @@ export class NotebookEditorWidgetService implements Disposable { return this.notebookEditors.get(editorId); } - listNotebookEditors(): readonly NotebookEditorWidget[] { - return [...this.notebookEditors].map(e => e[1]); + getNotebookEditors(): readonly NotebookEditorWidget[] { + return Array.from(this.notebookEditors.values()); } } diff --git a/packages/notebook/src/browser/service/notebook-execution-service.ts b/packages/notebook/src/browser/service/notebook-execution-service.ts index abfbbaaf4795e..022607a5720ff 100644 --- a/packages/notebook/src/browser/service/notebook-execution-service.ts +++ b/packages/notebook/src/browser/service/notebook-execution-service.ts @@ -23,11 +23,10 @@ import { CellExecution, NotebookExecutionStateService } from '../service/noteboo import { CellKind, NotebookCellExecutionState } from '../../common'; import { NotebookCellModel } from '../view-model/notebook-cell-model'; import { NotebookModel } from '../view-model/notebook-model'; -import { NotebookKernelService, NotebookKernel } from './notebook-kernel-service'; +import { NotebookKernelService } from './notebook-kernel-service'; import { CommandService, Disposable } from '@theia/core'; -import { NotebookKernelQuickPickService, NotebookKernelQuickPickServiceImpl } from './notebook-kernel-quick-pick-service'; +import { NotebookKernelQuickPickService } from './notebook-kernel-quick-pick-service'; import { NotebookKernelHistoryService } from './notebook-kernel-history-service'; -import { NotebookCommands } from '../contributions/notebook-actions-contribution'; export interface CellExecutionParticipant { onWillExecuteCell(executions: CellExecution[]): Promise; @@ -49,7 +48,7 @@ export class NotebookExecutionService { protected commandService: CommandService; @inject(NotebookKernelQuickPickService) - protected notebookKernelQuickPickService: NotebookKernelQuickPickServiceImpl; + protected notebookKernelQuickPickService: NotebookKernelQuickPickService; private readonly cellExecutionParticipants = new Set(); @@ -69,7 +68,7 @@ export class NotebookExecutionService { } } - const kernel = await this.resolveKernel(notebook); + const kernel = await this.notebookKernelHistoryService.resolveSelectedKernel(notebook); if (!kernel) { // clear all pending cell executions @@ -125,15 +124,4 @@ export class NotebookExecutionService { this.cancelNotebookCellHandles(notebook, Array.from(cells, cell => cell.handle)); } - async resolveKernel(notebook: NotebookModel): Promise { - const alreadySelected = this.notebookKernelHistoryService.getKernels(notebook); - - if (alreadySelected.selected) { - return alreadySelected.selected; - } - - await this.commandService.executeCommand(NotebookCommands.SELECT_KERNEL_COMMAND.id, notebook); - const { selected } = this.notebookKernelHistoryService.getKernels(notebook); - return selected; - } } 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 51ed8dc2059c5..3512293d78b4b 100644 --- a/packages/notebook/src/browser/service/notebook-execution-state-service.ts +++ b/packages/notebook/src/browser/service/notebook-execution-state-service.ts @@ -23,8 +23,9 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { NotebookService } from './notebook-service'; import { CellEditType, CellExecuteOutputEdit, CellExecuteOutputItemEdit, CellExecutionUpdateType, - CellUri, CellPartialInternalMetadataEditByHandle, NotebookCellExecutionState, CellEditOperation, NotebookCellInternalMetadata + CellUri, NotebookCellExecutionState, NotebookCellInternalMetadata } from '../../common'; +import { CellPartialInternalMetadataEditByHandle, CellEditOperation } from '../notebook-types'; import { NotebookModel } from '../view-model/notebook-model'; import { v4 } from 'uuid'; @@ -43,10 +44,6 @@ export interface CellExecutionStateUpdate { isPaused?: boolean; } -export interface ICellExecutionComplete { - runEndTime?: number; - lastRunSuccess?: boolean; -} export enum NotebookExecutionType { cell, notebook diff --git a/packages/notebook/src/browser/service/notebook-kernel-history-service.ts b/packages/notebook/src/browser/service/notebook-kernel-history-service.ts index a28cba3bbccc4..5115820134209 100644 --- a/packages/notebook/src/browser/service/notebook-kernel-history-service.ts +++ b/packages/notebook/src/browser/service/notebook-kernel-history-service.ts @@ -21,7 +21,9 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { StorageService } from '@theia/core/lib/browser'; import { NotebookKernel, NotebookTextModelLike, NotebookKernelService } from './notebook-kernel-service'; -import { Disposable } from '@theia/core'; +import { CommandService, Disposable } from '@theia/core'; +import { NotebookModel } from '../view-model/notebook-model'; +import { NotebookCommands } from '../contributions/notebook-actions-contribution'; interface KernelsList { [viewType: string]: string[]; @@ -43,6 +45,9 @@ export class NotebookKernelHistoryService implements Disposable { @inject(NotebookKernelService) protected notebookKernelService: NotebookKernelService; + @inject(CommandService) + protected commandService: CommandService; + declare serviceBrand: undefined; private static STORAGE_KEY = 'notebook.kernelHistory'; @@ -68,6 +73,18 @@ export class NotebookKernelHistoryService implements Disposable { }; } + async resolveSelectedKernel(notebook: NotebookModel): Promise { + const alreadySelected = this.getKernels(notebook); + + if (alreadySelected.selected) { + return alreadySelected.selected; + } + + await this.commandService.executeCommand(NotebookCommands.SELECT_KERNEL_COMMAND.id, notebook); + const { selected } = this.getKernels(notebook); + return selected; + } + addMostRecentKernel(kernel: NotebookKernel): void { const viewType = kernel.viewType; const recentKernels = this.mostRecentKernelsMap[viewType] ?? [kernel.id]; 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 83a123082f884..874672cee0ab7 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 @@ -29,8 +29,6 @@ import debounce = require('@theia/core/shared/lodash.debounce'); export const JUPYTER_EXTENSION_ID = 'ms-toolsai.jupyter'; -export const NotebookKernelQuickPickService = Symbol('NotebookKernelQuickPickService'); - type KernelPick = QuickPickItem & { kernel: NotebookKernel }; function isKernelPick(item: QuickPickInput): item is KernelPick { return 'kernel' in item; @@ -82,7 +80,7 @@ function toKernelQuickPick(kernel: NotebookKernel, selected: NotebookKernel | un } @injectable() -export abstract class NotebookKernelQuickPickServiceImpl { +export class NotebookKernelQuickPickService { @inject(NotebookKernelService) protected readonly notebookKernelService: NotebookKernelService; @@ -91,6 +89,12 @@ export abstract class NotebookKernelQuickPickServiceImpl { @inject(CommandService) protected readonly commandService: CommandService; + @inject(OpenerService) + protected openerService: OpenerService; + + @inject(NotebookKernelHistoryService) + protected notebookKernelHistoryService: NotebookKernelHistoryService; + async showQuickPick(editor: NotebookModel, wantedId?: string, skipAutoRun?: boolean): Promise { const notebook = editor; const matchResult = this.getMatchingResult(notebook); @@ -200,40 +204,6 @@ export abstract class NotebookKernelQuickPickServiceImpl { return false; } - protected getMatchingResult(notebook: NotebookModel): NotebookKernelMatchResult { - return this.notebookKernelService.getMatchingKernel(notebook); - } - - protected abstract getKernelPickerQuickPickItems(matchResult: NotebookKernelMatchResult): QuickPickInput[]; - - protected async handleQuickPick(editor: NotebookModel, pick: KernelQuickPickItem, quickPickItems: KernelQuickPickItem[]): Promise { - if (isKernelPick(pick)) { - const newKernel = pick.kernel; - this.selectKernel(editor, newKernel); - return true; - } - - if (isSourcePick(pick)) { - // selected explicitly, it should trigger the execution? - pick.action.run(this.commandService); - } - - return true; - } - - protected selectKernel(notebook: NotebookModel, kernel: NotebookKernel): void { - this.notebookKernelService.selectKernelForNotebook(kernel, notebook); - } -} -@injectable() -export class KernelPickerMRUStrategy extends NotebookKernelQuickPickServiceImpl { - - @inject(OpenerService) - protected openerService: OpenerService; - - @inject(NotebookKernelHistoryService) - protected notebookKernelHistoryService: NotebookKernelHistoryService; - protected getKernelPickerQuickPickItems(matchResult: NotebookKernelMatchResult): QuickPickInput[] { const quickPickItems: QuickPickInput[] = []; @@ -266,17 +236,17 @@ export class KernelPickerMRUStrategy extends NotebookKernelQuickPickServiceImpl return quickPickItems; } - protected override selectKernel(notebook: NotebookModel, kernel: NotebookKernel): void { + protected selectKernel(notebook: NotebookModel, kernel: NotebookKernel): void { const currentInfo = this.notebookKernelService.getMatchingKernel(notebook); if (currentInfo.selected) { // there is already a selected kernel this.notebookKernelHistoryService.addMostRecentKernel(currentInfo.selected); } - super.selectKernel(notebook, kernel); + this.notebookKernelService.selectKernelForNotebook(kernel, notebook); this.notebookKernelHistoryService.addMostRecentKernel(kernel); } - protected override getMatchingResult(notebook: NotebookModel): NotebookKernelMatchResult { + protected getMatchingResult(notebook: NotebookModel): NotebookKernelMatchResult { const { selected, all } = this.notebookKernelHistoryService.getKernels(notebook); const matchingResult = this.notebookKernelService.getMatchingKernel(notebook); return { @@ -287,12 +257,23 @@ export class KernelPickerMRUStrategy extends NotebookKernelQuickPickServiceImpl }; } - protected override async handleQuickPick(editor: NotebookModel, pick: KernelQuickPickItem, items: KernelQuickPickItem[]): Promise { + protected async handleQuickPick(editor: NotebookModel, pick: KernelQuickPickItem, items: KernelQuickPickItem[]): Promise { if (pick.id === 'selectAnother') { return this.displaySelectAnotherQuickPick(editor, items.length === 1 && items[0] === pick); } - return super.handleQuickPick(editor, pick, items); + if (isKernelPick(pick)) { + const newKernel = pick.kernel; + this.selectKernel(editor, newKernel); + return true; + } + + if (isSourcePick(pick)) { + // selected explicitly, it should trigger the execution? + pick.action.run(this.commandService); + } + + return true; } private async displaySelectAnotherQuickPick(editor: NotebookModel, kernelListEmpty: boolean): Promise { 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 bd804be68afbd..1389d59c0dd7b 100644 --- a/packages/notebook/src/browser/view-model/notebook-cell-model.ts +++ b/packages/notebook/src/browser/view-model/notebook-cell-model.ts @@ -20,12 +20,14 @@ import { Disposable, DisposableCollection, Emitter, Event, URI } from '@theia/core'; import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; +import { ContextKeyChangeEvent } from '@theia/core/lib/browser/context-key-service'; import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model'; import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { - CellInternalMetadataChangedEvent, CellKind, NotebookCellCollapseState, NotebookCellInternalMetadata, - NotebookCellMetadata, NotebookCellOutputsSplice, CellOutput, CellData, NotebookCell, CellOutputItem + CellKind, NotebookCellCollapseState, NotebookCellInternalMetadata, + NotebookCellMetadata, CellOutput, CellData, CellOutputItem } from '../../common'; +import { NotebookCellOutputsSplice } from '../notebook-types'; import { NotebookCellOutputModel } from './notebook-cell-output-model'; export const NotebookCellModelFactory = Symbol('NotebookModelFactory'); @@ -47,7 +49,28 @@ const NotebookCellContextManager = Symbol('NotebookCellContextManager'); interface NotebookCellContextManager { updateCellContext(cell: NotebookCellModel, context: HTMLElement): void; dispose(): void; - onDidChangeContext: Event; + onDidChangeContext: Event; +} + +export interface CellInternalMetadataChangedEvent { + readonly lastRunSuccessChanged?: boolean; +} + +export interface NotebookCell { + readonly uri: URI; + handle: number; + language: string; + cellKind: CellKind; + outputs: CellOutput[]; + metadata: NotebookCellMetadata; + internalMetadata: NotebookCellInternalMetadata; + text: string; + onDidChangeOutputs?: Event; + onDidChangeOutputItems?: Event; + onDidChangeLanguage: Event; + onDidChangeMetadata: Event; + onDidChangeInternalMetadata: Event; + } const NotebookCellModelProps = Symbol('NotebookModelProps'); @@ -144,6 +167,7 @@ export class NotebookCellModel implements NotebookCell, Disposable { } set source(source: string) { this.props.source = source; + this.textModel?.textEditorModel.setValue(source); } get language(): string { return this.props.language; @@ -274,6 +298,9 @@ export class NotebookCellModel implements NotebookCell, Disposable { const ref = await this.textModelService.createModelReference(this.uri); this.textModel = ref.object; + this.textModel.onDidChangeContent(e => { + this.props.source = e.model.getText(); + }); return ref.object; } } diff --git a/packages/notebook/src/browser/view-model/notebook-model.ts b/packages/notebook/src/browser/view-model/notebook-model.ts index e8f1b897e3add..9ec960c104787 100644 --- a/packages/notebook/src/browser/view-model/notebook-model.ts +++ b/packages/notebook/src/browser/view-model/notebook-model.ts @@ -17,18 +17,16 @@ import { Disposable, Emitter, URI } from '@theia/core'; import { Saveable, SaveOptions } from '@theia/core/lib/browser'; import { - CellData, - CellEditOperation, CellEditType, CellUri, NotebookCellInternalMetadata, + CellData, CellEditType, CellUri, NotebookCellInternalMetadata, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, - NotebookDocumentMetadata, NotebookModelWillAddRemoveEvent, - NotebookTextModelChangedEvent, NullablePartialNotebookCellInternalMetadata + NotebookDocumentMetadata, } from '../../common'; +import { NotebookContentChangedEvent, NotebookModelWillAddRemoveEvent, CellEditOperation, NullablePartialNotebookCellInternalMetadata } from '../notebook-types'; import { NotebookSerializer } from '../service/notebook-service'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { NotebookCellModel, NotebookCellModelFactory } from './notebook-cell-model'; import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; 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'; export const NotebookModelFactory = Symbol('NotebookModelFactory'); @@ -62,7 +60,7 @@ export class NotebookModel implements Saveable, Disposable { protected readonly onDidAddOrRemoveCellEmitter = new Emitter(); readonly onDidAddOrRemoveCell = this.onDidAddOrRemoveCellEmitter.event; - protected readonly onDidChangeContentEmitter = new Emitter(); + protected readonly onDidChangeContentEmitter = new Emitter(); readonly onDidChangeContent = this.onDidChangeContentEmitter.event; @inject(FileService) @@ -81,11 +79,19 @@ export class NotebookModel implements Saveable, Disposable { protected cellModelFactory: NotebookCellModelFactory; readonly autoSave: 'off' | 'afterDelay' | 'onFocusChange' | 'onWindowChange'; - nextHandle: number = 0; + protected nextHandle: number = 0; - kernel?: NotebookKernel; + protected _dirty: boolean = false; + + set dirty(dirty: boolean) { + this._dirty = dirty; + this.onDirtyChangedEmitter.fire(); + } + + get dirty(): boolean { + return this._dirty; + } - dirty: boolean; selectedCell?: NotebookCellModel; protected dirtyCells: NotebookCellModel[] = []; @@ -121,18 +127,6 @@ export class NotebookModel implements Saveable, Disposable { this.metadata = this.metadata; - this.modelService.onDidCreate(editorModel => { - const modelUri = new URI(editorModel.uri); - if (modelUri.scheme === CellUri.scheme) { - const cellUri = CellUri.parse(modelUri); - if (cellUri && cellUri.notebook.isEqual(this.uri)) { - const cell = this.cells.find(c => c.handle === cellUri.handle); - if (cell) { - cell.textModel = editorModel; - } - } - } - }); this.nextHandle = this.cells.length; } @@ -147,7 +141,6 @@ export class NotebookModel implements Saveable, Disposable { async save(options: SaveOptions): Promise { this.dirtyCells = []; this.dirty = false; - this.onDirtyChangedEmitter.fire(); const serializedNotebook = await this.props.serializer.fromNotebook({ cells: this.cells.map(cell => cell.getData()), @@ -170,9 +163,34 @@ export class NotebookModel implements Saveable, Disposable { }; } + async applySnapshot(snapshot: Saveable.Snapshot): Promise { + const rawData = 'read' in snapshot ? snapshot.read() : snapshot.value; + if (!rawData) { + throw new Error('could not read notebook snapshot'); + } + const data = JSON.parse(rawData); + const cells = data.cells.map((cell: CellData, index: number) => { + const handle = this.nextHandle++; + return this.cellModelFactory({ + uri: CellUri.generate(this.uri, handle), + handle: handle, + source: cell.source, + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata, + internalMetadata: cell.internalMetadata, + collapseState: cell.collapseState + }); + }); + this.addCellOutputListeners(cells); + + this.metadata = data.metadata; + + } + async revert(options?: Saveable.RevertOptions): Promise { this.dirty = false; - this.onDirtyChangedEmitter.fire(); } isDirty(): boolean { @@ -186,8 +204,8 @@ export class NotebookModel implements Saveable, Disposable { this.dirtyCells.splice(this.dirtyCells.indexOf(cell), 1); } - const oldDirtyState = this.dirty; - this.dirty = this.dirtyCells.length > 0; + const oldDirtyState = this._dirty; + this._dirty = this.dirtyCells.length > 0; if (this.dirty !== oldDirtyState) { this.onDirtyChangedEmitter.fire(); } @@ -211,7 +229,6 @@ export class NotebookModel implements Saveable, Disposable { for (const cell of cells) { cell.onDidChangeOutputs(() => { this.dirty = true; - this.onDirtyChangedEmitter.fire(); }); } } @@ -294,7 +311,7 @@ export class NotebookModel implements Saveable, Disposable { }); this.addCellOutputListeners(cells); - const changes: NotebookCellTextModelSplice[] = [[start, deleteCount, cells]]; + const changes: NotebookCellTextModelSplice[] = [{ start, deleteCount, newItems: cells }]; const deletedCells = this.cells.splice(start, deleteCount, ...cells); @@ -308,8 +325,8 @@ export class NotebookModel implements Saveable, Disposable { async () => this.replaceCells(start, deleteCount, newCells, false)); } - this.onDidAddOrRemoveCellEmitter.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes } }); - this.onDidChangeContentEmitter.fire({ rawEvents: [{ kind: NotebookCellsChangeType.ModelChange, changes }] }); + this.onDidAddOrRemoveCellEmitter.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes }, newCellIds: cells.map(cell => cell.handle) }); + this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ModelChange, changes }]); } private changeCellInternalMetadataPartial(cell: NotebookCellModel, internalMetadata: NullablePartialNotebookCellInternalMetadata): void { @@ -323,11 +340,9 @@ export class NotebookModel implements Saveable, Disposable { } cell.internalMetadata = newInternalMetadata; - this.onDidChangeContentEmitter.fire({ - rawEvents: [ - { kind: NotebookCellsChangeType.ChangeCellInternalMetadata, index: this.cells.indexOf(cell), internalMetadata: newInternalMetadata } - ] - }); + this.onDidChangeContentEmitter.fire([ + { kind: NotebookCellsChangeType.ChangeCellInternalMetadata, index: this.cells.indexOf(cell), internalMetadata: newInternalMetadata } + ]); } private updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean): void { @@ -340,10 +355,7 @@ export class NotebookModel implements Saveable, Disposable { } this.metadata = metadata; - this.onDidChangeContentEmitter.fire({ - rawEvents: [{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata }], - synchronous: true, - }); + this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata }]); } private changeCellLanguage(cell: NotebookCellModel, languageId: string, computeUndoRedo: boolean): void { @@ -353,10 +365,7 @@ export class NotebookModel implements Saveable, Disposable { cell.language = languageId; - this.onDidChangeContentEmitter.fire({ - rawEvents: [{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this.cells.indexOf(cell), language: languageId }], - synchronous: true, - }); + this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this.cells.indexOf(cell), language: languageId }]); } private moveCellToIndex(fromIndex: number, length: number, toIndex: number, computeUndoRedo: boolean): boolean { @@ -369,9 +378,7 @@ export class NotebookModel implements Saveable, Disposable { const cells = this.cells.splice(fromIndex, length); this.cells.splice(toIndex, 0, ...cells); - this.onDidChangeContentEmitter.fire({ - rawEvents: [{ kind: NotebookCellsChangeType.Move, index: fromIndex, length, newIdx: toIndex, cells }], - }); + this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.Move, index: fromIndex, length, newIdx: toIndex, cells }]); return true; } diff --git a/packages/notebook/src/browser/view/notebook-cell-editor.tsx b/packages/notebook/src/browser/view/notebook-cell-editor.tsx index a029e9b4ced1e..723e2ef3b5b10 100644 --- a/packages/notebook/src/browser/view/notebook-cell-editor.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-editor.tsx @@ -77,13 +77,12 @@ export class CellEditor extends React.Component { })); this.toDispose.push(this.editor.onDocumentContentChanged(e => { notebookModel.cellDirtyChanged(cell, true); - cell.source = e.document.getText(); })); } } - protected assignRef = (component: HTMLDivElement) => { - this.container = component; + protected setContainer(component: HTMLDivElement | null): void { + this.container = component ?? undefined; }; protected handleResize = () => { @@ -91,7 +90,10 @@ export class CellEditor extends React.Component { }; override render(): React.ReactNode { - return
; - } + return
this.setContainer(container)}> + +
; + } } diff --git a/packages/notebook/src/browser/view/notebook-cell-list-view.tsx b/packages/notebook/src/browser/view/notebook-cell-list-view.tsx index ada36f51fc247..f1ea9aab4945a 100644 --- a/packages/notebook/src/browser/view/notebook-cell-list-view.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-list-view.tsx @@ -47,7 +47,11 @@ export class NotebookCellListView extends React.Component { - this.setState({ selectedCell: undefined }); + if (e.newCellIds && e.newCellIds.length > 0) { + this.setState({ ...this.state, selectedCell: this.props.notebookModel.cells.find(model => model.handle === e.newCellIds![e.newCellIds!.length - 1]) }); + } else { + this.setState({ ...this.state, selectedCell: this.props.notebookModel.cells.find(cell => cell === this.state.selectedCell)}); + } })); } @@ -101,7 +105,8 @@ export class NotebookCellListView extends React.Component, index: number): void { event.stopPropagation(); - event.dataTransfer.setData('text/notebook-cell-index', index.toString()); + event.dataTransfer.setData('text/theia-notebook-cell-index', index.toString()); + event.dataTransfer.setData('text/plain', this.props.notebookModel.cells[index].source); } protected onDragOver(event: React.DragEvent, cell: NotebookCellModel, position?: 'top' | 'bottom'): void { @@ -112,7 +117,7 @@ export class NotebookCellListView extends React.Component, dropElementIndex: number): void { - const index = parseInt(event.dataTransfer.getData('text/notebook-cell-index')); + const index = parseInt(event.dataTransfer.getData('text/theia-notebook-cell-index')); const isTargetBelow = index < dropElementIndex; let newIdx = this.state.dragOverIndicator?.position === 'top' ? dropElementIndex : dropElementIndex + 1; newIdx = isTargetBelow ? newIdx - 1 : newIdx; diff --git a/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx b/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx index 31fb3a15d2e5c..ac7038a893a29 100644 --- a/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx @@ -29,6 +29,7 @@ export interface NotebookCellToolbarItem { icon?: string; label?: string; onClick: (e: React.MouseEvent) => void; + contextKeys?: Set } @injectable() @@ -85,7 +86,8 @@ export class NotebookCellToolbarFactory { includeAnchorArg: false, args: [notebookModel, cell, output] }) : - () => this.commandRegistry.executeCommand(menuNode.command!, notebookModel, cell, output) + () => this.commandRegistry.executeCommand(menuNode.command!, notebookModel, cell, output), + contextKeys: menuNode.when ? this.contextKeyService.parseKeys(menuNode.when) : undefined }; } } diff --git a/packages/notebook/src/browser/view/notebook-cell-toolbar.tsx b/packages/notebook/src/browser/view/notebook-cell-toolbar.tsx index d1e366eb43b1b..adcdc2d3a83fa 100644 --- a/packages/notebook/src/browser/view/notebook-cell-toolbar.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-toolbar.tsx @@ -17,24 +17,27 @@ import * as React from '@theia/core/shared/react'; import { ACTION_ITEM } from '@theia/core/lib/browser'; import { NotebookCellToolbarItem } from './notebook-cell-toolbar-factory'; import { DisposableCollection, Event } from '@theia/core'; +import { ContextKeyChangeEvent } from '@theia/core/lib/browser/context-key-service'; export interface NotebookCellToolbarProps { getMenuItems: () => NotebookCellToolbarItem[]; - onContextKeysChanged: Event; + onContextKeysChanged: Event; } interface NotebookCellToolbarState { inlineItems: NotebookCellToolbarItem[]; } -abstract class NotebookCellActionItems extends React.Component { +abstract class NotebookCellActionBar extends React.Component { protected toDispose = new DisposableCollection(); constructor(props: NotebookCellToolbarProps) { super(props); this.toDispose.push(props.onContextKeysChanged(e => { - this.setState({ inlineItems: this.props.getMenuItems() }); + if (this.props.getMenuItems().some(item => item.contextKeys ? e.affects(item.contextKeys) : false)) { + this.setState({ inlineItems: this.props.getMenuItems() }); + } })); this.state = { inlineItems: this.props.getMenuItems() }; } @@ -49,7 +52,7 @@ abstract class NotebookCellActionItems extends React.Component @@ -59,7 +62,7 @@ export class NotebookCellToolbar extends NotebookCellActionItems { } -export class NotebookCellSidebar extends NotebookCellActionItems { +export class NotebookCellSidebar extends NotebookCellActionBar { override render(): React.ReactNode { return
diff --git a/packages/notebook/src/browser/view/notebook-main-toolbar.tsx b/packages/notebook/src/browser/view/notebook-main-toolbar.tsx index bc12319b10ca5..80e21e1974d34 100644 --- a/packages/notebook/src/browser/view/notebook-main-toolbar.tsx +++ b/packages/notebook/src/browser/view/notebook-main-toolbar.tsx @@ -66,6 +66,15 @@ export class NotebookMainToolbar extends React.Component(); + this.getMenuItems().filter(item => item.when).forEach(item => props.contextKeyService.parseKeys(item.when!)?.forEach(key => contextKeys.add(key))); + props.contextKeyService.onDidChange(e => { + if (e.affects(contextKeys)) { + this.forceUpdate(); + } + }); } override componentWillUnmount(): void { diff --git a/packages/notebook/src/common/notebook-common.ts b/packages/notebook/src/common/notebook-common.ts index 7fb5a4dbc26cc..e5e9591261398 100644 --- a/packages/notebook/src/common/notebook-common.ts +++ b/packages/notebook/src/common/notebook-common.ts @@ -14,11 +14,10 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { CancellationToken, Command, Event, URI } from '@theia/core'; +import { Command, URI } from '@theia/core'; import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; import { UriComponents } from '@theia/core/lib/common/uri'; -import { CellRange } from './notebook-range'; export enum CellKind { Markup = 1, @@ -88,23 +87,6 @@ export interface NotebookCellCollapseState { outputCollapsed?: boolean; } -export interface NotebookCell { - readonly uri: URI; - handle: number; - language: string; - cellKind: CellKind; - outputs: CellOutput[]; - metadata: NotebookCellMetadata; - internalMetadata: NotebookCellInternalMetadata; - text: string; - onDidChangeOutputs?: Event; - onDidChangeOutputItems?: Event; - onDidChangeLanguage: Event; - onDidChangeMetadata: Event; - onDidChangeInternalMetadata: Event; - -} - export interface CellData { source: string; language: string; @@ -115,13 +97,6 @@ export interface CellData { collapseState?: NotebookCellCollapseState; } -export interface CellReplaceEdit { - editType: CellEditType.Replace; - index: number; - count: number; - cells: CellData[]; -} - export interface NotebookDocumentMetadataEdit { editType: CellEditType.DocumentMetadata; metadata: NotebookDocumentMetadata; @@ -140,32 +115,11 @@ export interface NotebookContributionData { exclusive: boolean; } -export interface NotebookCellStatusBarItemList { - items: NotebookCellStatusBarItem[]; - dispose?(): void; -} - -export interface NotebookCellStatusBarItemProvider { - viewType: string; - onDidChangeStatusBarItems?: Event; - provideCellStatusBarItems(uri: UriComponents, index: number, token: CancellationToken): Promise; -} - -export interface NotebookCellOutputsSplice { - start: number /* start */; - deleteCount: number /* delete count */; - newOutputs: CellOutput[]; -}; - -export interface CellInternalMetadataChangedEvent { - readonly lastRunSuccessChanged?: boolean; -} - -export type NotebookCellTextModelSplice = [ +export interface NotebookCellTextModelSplice { start: number, deleteCount: number, newItems: T[] -]; +}; export enum NotebookCellsChangeType { ModelChange = 1, @@ -182,69 +136,12 @@ export enum NotebookCellsChangeType { Unknown = 100 } -export enum SelectionStateType { - Handle = 0, - Index = 1 -} -export interface SelectionHandleState { - kind: SelectionStateType.Handle; - primary: number | null; - selections: number[]; -} - -export interface SelectionIndexState { - kind: SelectionStateType.Index; - focus: CellRange; - selections: CellRange[]; -} - -export type SelectionState = SelectionHandleState | SelectionIndexState; - -export interface NotebookTextModelChangedEvent { - readonly rawEvents: NotebookRawContentEvent[]; - // readonly versionId: number; - readonly synchronous?: boolean; - readonly endSelectionState?: SelectionState; -}; - -export interface NotebookCellsInitializeEvent { - readonly kind: NotebookCellsChangeType.Initialize; - readonly changes: NotebookCellTextModelSplice[]; -} - export interface NotebookCellsChangeLanguageEvent { readonly kind: NotebookCellsChangeType.ChangeCellLanguage; readonly index: number; readonly language: string; } -export interface NotebookCellsModelChangedEvent { - readonly kind: NotebookCellsChangeType.ModelChange; - readonly changes: NotebookCellTextModelSplice[]; -} - -export interface NotebookCellsModelMoveEvent { - readonly kind: NotebookCellsChangeType.Move; - readonly index: number; - readonly length: number; - readonly newIdx: number; - readonly cells: T[]; -} - -export interface NotebookOutputChangedEvent { - readonly kind: NotebookCellsChangeType.Output; - readonly index: number; - readonly outputs: CellOutput[]; - readonly append: boolean; -} - -export interface NotebookOutputItemChangedEvent { - readonly kind: NotebookCellsChangeType.OutputItem; - readonly index: number; - readonly outputId: string; - readonly outputItems: CellOutputItem[]; - readonly append: boolean; -} export interface NotebookCellsChangeMetadataEvent { readonly kind: NotebookCellsChangeType.ChangeCellMetadata; readonly index: number; @@ -257,36 +154,11 @@ export interface NotebookCellsChangeInternalMetadataEvent { readonly internalMetadata: NotebookCellInternalMetadata; } -export interface NotebookDocumentChangeMetadataEvent { - readonly kind: NotebookCellsChangeType.ChangeDocumentMetadata; - readonly metadata: NotebookDocumentMetadata; -} - -export interface NotebookDocumentUnknownChangeEvent { - readonly kind: NotebookCellsChangeType.Unknown; -} - export interface NotebookCellContentChangeEvent { readonly kind: NotebookCellsChangeType.ChangeCellContent; readonly index: number; } -export type NotebookRawContentEvent = (NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | - NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookOutputItemChangedEvent | - NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | - NotebookCellsChangeInternalMetadataEvent | NotebookDocumentUnknownChangeEvent); // & { transient: boolean }; - -export interface NotebookModelChangedEvent { - readonly rawEvents: NotebookRawContentEvent[]; - readonly versionId: number; - // readonly synchronous: boolean | undefined; - // readonly endSelectionState: ISelectionState | undefined; -}; - -export interface NotebookModelWillAddRemoveEvent { - readonly rawEvent: NotebookCellsModelChangedEvent; -}; - export enum NotebookCellExecutionState { Unconfirmed = 1, Pending = 2, @@ -321,51 +193,12 @@ export interface CellExecutionStateUpdateDto { isPaused?: boolean; } -export interface CellOutputEdit { - editType: CellEditType.Output; - index: number; - outputs: CellOutput[]; - append?: boolean; -} - -export interface CellOutputEditByHandle { - editType: CellEditType.Output; - handle: number; - outputs: CellOutput[]; - append?: boolean; -} - -export interface CellOutputItemEdit { - editType: CellEditType.OutputItems; - items: CellOutputItem[]; - outputId: string; - append?: boolean; -} - export interface CellMetadataEdit { editType: CellEditType.Metadata; index: number; metadata: NotebookCellMetadata; } -export interface CellLanguageEdit { - editType: CellEditType.CellLanguage; - index: number; - language: string; -} - -export interface DocumentMetadataEdit { - editType: CellEditType.DocumentMetadata; - metadata: NotebookDocumentMetadata; -} - -export interface CellMoveEdit { - editType: CellEditType.Move; - index: number; - length: number; - newIdx: number; -} - export const enum CellEditType { Replace = 1, Output = 2, @@ -378,19 +211,6 @@ export const enum CellEditType { PartialInternalMetadata = 9, } -export type ImmediateCellEditOperation = CellOutputEditByHandle | CellOutputItemEdit | CellPartialInternalMetadataEditByHandle; // add more later on -export type CellEditOperation = ImmediateCellEditOperation | CellReplaceEdit | CellOutputEdit | - CellMetadataEdit | CellLanguageEdit | DocumentMetadataEdit | CellMoveEdit; // add more later on - -export type NullablePartialNotebookCellInternalMetadata = { - [Key in keyof Partial]: NotebookCellInternalMetadata[Key] | null -}; -export interface CellPartialInternalMetadataEditByHandle { - editType: CellEditType.PartialInternalMetadata; - handle: number; - internalMetadata: NullablePartialNotebookCellInternalMetadata; -} - export interface NotebookKernelSourceAction { readonly label: string; readonly description?: string; diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 71b67d64b82a7..c44cdbf396c54 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -2342,7 +2342,7 @@ export interface NotebookOutputItemDto { export interface NotebookOutputDto { outputId: string; items: NotebookOutputItemDto[]; - metadata?: Record; + metadata?: Record; } export interface NotebookCellDataDto { 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 166869ef0d79c..a421ac4ae8f63 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 @@ -42,7 +42,7 @@ interface NotebookAndEditorDelta { } class NotebookAndEditorState { - static delta(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): NotebookAndEditorDelta { + static computeDelta(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): NotebookAndEditorDelta { if (!before) { return { addedDocuments: [...after.documents], @@ -148,7 +148,7 @@ export class NotebooksAndEditorsMain implements NotebookDocumentsAndEditorsMain const editors = new Map(); const visibleEditorsMap = new Map(); - for (const editor of this.notebookEditorService.listNotebookEditors()) { + for (const editor of this.notebookEditorService.getNotebookEditors()) { if (editor.model) { editors.set(editor.id, editor); } @@ -176,12 +176,12 @@ export class NotebooksAndEditorsMain implements NotebookDocumentsAndEditorsMain new Set(this.notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap); - await this.onDelta(NotebookAndEditorState.delta(this.currentState, newState)); + await this.onDelta(NotebookAndEditorState.computeDelta(this.currentState, newState)); this.currentState = newState; } private async onDelta(delta: NotebookAndEditorDelta): Promise { - if (NotebooksAndEditorsMain._isDeltaEmpty(delta)) { + if (NotebooksAndEditorsMain.isDeltaEmpty(delta)) { return; } @@ -204,23 +204,23 @@ export class NotebooksAndEditorsMain implements NotebookDocumentsAndEditorsMain this.notebookEditorsMain.handleEditorsAdded(delta.addedEditors); } - private static _isDeltaEmpty(delta: NotebookAndEditorDelta): boolean { - if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) { + private static isDeltaEmpty(delta: NotebookAndEditorDelta): boolean { + if (delta.addedDocuments?.length) { return false; } - if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) { + if (delta.removedDocuments?.length) { return false; } - if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) { + if (delta.addedEditors?.length) { return false; } - if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) { + if (delta.removedEditors?.length) { return false; } - if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) { + if (delta.visibleEditors?.length) { return false; } - if (delta.newActiveEditor !== undefined) { + if (delta.newActiveEditor) { return false; } return true; 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 d989535659219..04fe85dbcaacc 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 @@ -20,7 +20,7 @@ import { interfaces } from '@theia/core/shared/inversify'; import { NotebookModelResolverService } from '@theia/notebook/lib/browser'; import { NotebookModel } from '@theia/notebook/lib/browser/view-model/notebook-model'; import { NotebookCellsChangeType } from '@theia/notebook/lib/common'; -import { MAIN_RPC_CONTEXT, NotebookCellDto, NotebookCellsChangedEventDto, NotebookDataDto, NotebookDocumentsExt, NotebookDocumentsMain } from '../../../common'; +import { MAIN_RPC_CONTEXT, NotebookCellsChangedEventDto, NotebookDataDto, NotebookDocumentsExt, NotebookDocumentsMain } from '../../../common'; import { RPCProtocol } from '../../../common/rpc-protocol'; import { NotebookDto } from './notebook-dto'; @@ -54,23 +54,22 @@ export class NotebookDocumentsMainImpl implements NotebookDocumentsMain { handleNotebooksAdded(notebooks: readonly NotebookModel[]): void { - for (const textModel of notebooks) { - const disposableStore = new DisposableCollection(); - disposableStore.push(textModel.onDidChangeContent(event => { + for (const notebook of notebooks) { + const listener = notebook.onDidChangeContent(events => { const eventDto: NotebookCellsChangedEventDto = { versionId: 1, // TODO implement version ID support rawEvents: [] }; - for (const e of event.rawEvents) { + for (const e of events) { switch (e.kind) { case NotebookCellsChangeType.ModelChange: eventDto.rawEvents.push({ kind: e.kind, changes: e.changes.map(diff => - [diff[0], diff[1], diff[2].map(NotebookDto.toNotebookCellDto)] as [number, number, NotebookCellDto[]]) + ({ ...diff, newItems: diff.newItems.map(NotebookDto.toNotebookCellDto) })) }); break; case NotebookCellsChangeType.Move: @@ -106,20 +105,20 @@ export class NotebookDocumentsMainImpl implements NotebookDocumentsMain { } } - const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); + const hasDocumentMetadataChangeEvent = events.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); // using the model resolver service to know if the model is dirty or not. // assuming this is the first listener it can mean that at first the model // is marked as dirty and that another event is fired this.proxy.$acceptModelChanged( - textModel.uri.toComponents(), + notebook.uri.toComponents(), eventDto, - textModel.isDirty(), - hasDocumentMetadataChangeEvent ? textModel.metadata : undefined + notebook.isDirty(), + hasDocumentMetadataChangeEvent ? notebook.metadata : undefined ); - })); + }); - this.documentEventListenersMapping.set(textModel.uri.toString(), disposableStore); + this.documentEventListenersMapping.set(notebook.uri.toString(), new DisposableCollection(listener)); } } 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 e6afbb8d79ed6..491613ff55267 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-dto.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-dto.ts @@ -18,6 +18,8 @@ import { OS } from '@theia/core'; import * as notebookCommon from '@theia/notebook/lib/common'; import { NotebookCellModel } from '@theia/notebook/lib/browser/view-model/notebook-cell-model'; import * as rpc from '../../../common'; +import { CellExecutionUpdateType } from '@theia/notebook/lib/common'; +import { CellExecuteUpdate, CellExecutionComplete } from '@theia/notebook/lib/browser'; export namespace NotebookDto { @@ -102,40 +104,28 @@ export namespace NotebookDto { }; } - // export function fromCellExecuteUpdateDto(data: extHostProtocol.ICellExecuteUpdateDto): ICellExecuteUpdate { - // if (data.editType === CellExecutionUpdateType.Output) { - // return { - // editType: data.editType, - // cellHandle: data.cellHandle, - // append: data.append, - // outputs: data.outputs.map(fromNotebookOutputDto) - // }; - // } else if (data.editType === CellExecutionUpdateType.OutputItems) { - // return { - // editType: data.editType, - // append: data.append, - // outputId: data.outputId, - // items: data.items.map(fromNotebookOutputItemDto) - // }; - // } else { - // return data; - // } - // } + export function fromCellExecuteUpdateDto(data: rpc.CellExecuteUpdateDto): CellExecuteUpdate { + if (data.editType === CellExecutionUpdateType.Output) { + return { + editType: data.editType, + cellHandle: data.cellHandle, + append: data.append, + outputs: data.outputs.map(fromNotebookOutputDto) + }; + } else if (data.editType === CellExecutionUpdateType.OutputItems) { + return { + editType: data.editType, + outputId: data.outputId, + append: data.append, + items: data.items.map(fromNotebookOutputItemDto) + }; + } else { + return data; + } + } - // export function fromCellExecuteCompleteDto(data: extHostProtocol.ICellExecutionCompleteDto): ICellExecutionComplete { - // return data; - // } + export function fromCellExecuteCompleteDto(data: rpc.CellExecutionCompleteDto): CellExecutionComplete { + return data; + } - // export function fromCellEditOperationDto(edit: extHostProtocol.ICellEditOperationDto): notebookCommon.ICellEditOperation { - // if (edit.editType === notebookCommon.CellEditType.Replace) { - // return { - // editType: edit.editType, - // index: edit.index, - // count: edit.count, - // cells: edit.cells.map(fromNotebookCellDataDto) - // }; - // } else { - // return edit; - // } - // } } 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 6f9e735f1f112..d3ea21f53c753 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 @@ -27,7 +27,7 @@ import { CellExecution, NotebookExecutionStateService, NotebookKernelChangeEvent import { combinedDisposable } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle'; import { interfaces } from '@theia/core/shared/inversify'; import { NotebookKernelSourceAction } from '@theia/notebook/lib/common'; -import { NotebookDto } from '../../../plugin/type-converters'; +import { NotebookDto } from './notebook-dto'; abstract class NotebookKernel { private readonly onDidChangeEmitter = new Emitter(); 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 19e3b39d62164..9764b6242ccde 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts @@ -14,9 +14,9 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { CancellationToken, DisposableCollection, Emitter } from '@theia/core'; +import { CancellationToken, DisposableCollection, Emitter, Event } from '@theia/core'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { NotebookCellStatusBarItemList, NotebookCellStatusBarItemProvider, NotebookData, TransientOptions } from '@theia/notebook/lib/common'; +import { NotebookCellStatusBarItem, 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'; @@ -25,6 +25,17 @@ import { NotebookDto } from './notebook-dto'; import { UriComponents } from '@theia/core/lib/common/uri'; import { HostedPluginSupport } from '../../../hosted/browser/hosted-plugin'; +export interface NotebookCellStatusBarItemList { + items: NotebookCellStatusBarItem[]; + dispose?(): void; +} + +export interface NotebookCellStatusBarItemProvider { + viewType: string; + onDidChangeStatusBarItems?: Event; + provideCellStatusBarItems(uri: UriComponents, index: number, token: CancellationToken): Promise; +} + export class NotebooksMainImpl implements NotebooksMain { private readonly disposables = new DisposableCollection(); diff --git a/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx b/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx index cde345671d7f2..fbd832304adcb 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx +++ b/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx @@ -20,7 +20,7 @@ import * as React from '@theia/core/shared/react'; import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; -import { NotebookRendererMessagingService, CellOutputWebview, NotebookRendererRegistry, NotebookEditorWidgetService } from '@theia/notebook/lib/browser'; +import { NotebookRendererMessagingService, CellOutputWebview, NotebookRendererRegistry, NotebookEditorWidgetService, NotebookCellOutputsSplice } from '@theia/notebook/lib/browser'; import { v4 } from 'uuid'; import { NotebookCellModel } from '@theia/notebook/lib/browser/view-model/notebook-cell-model'; import { WebviewWidget } from '../../webview/webview'; @@ -28,7 +28,7 @@ import { Message, WidgetManager } from '@theia/core/lib/browser'; import { outputWebviewPreload, PreloadContext } from './output-webview-internal'; import { WorkspaceTrustService } from '@theia/workspace/lib/browser'; import { ChangePreferredMimetypeMessage, FromWebviewMessage, OutputChangedMessage } from './webview-communication'; -import { CellUri, NotebookCellOutputsSplice } from '@theia/notebook/lib/common'; +import { CellUri } from '@theia/notebook/lib/common'; import { Disposable, DisposableCollection, nls, QuickPickService } from '@theia/core'; import { NotebookCellOutputModel } from '@theia/notebook/lib/browser/view-model/notebook-cell-output-model'; diff --git a/packages/plugin-ext/src/plugin/notebook/notebook-document.ts b/packages/plugin-ext/src/plugin/notebook/notebook-document.ts index a49954a6fe125..66b63479be6fb 100644 --- a/packages/plugin-ext/src/plugin/notebook/notebook-document.ts +++ b/packages/plugin-ext/src/plugin/notebook/notebook-document.ts @@ -288,17 +288,17 @@ export class NotebookDocument implements Disposable { this.setCellOutputs(rawEvent.index, rawEvent.outputs); relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, outputs: this.cells[rawEvent.index].apiCell.outputs }); - // } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.OutputItem) { - // this._setCellOutputItems(rawEvent.index, rawEvent.outputId, rawEvent.append, rawEvent.outputItems); - // relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, outputs: this.cells[rawEvent.index].apiCell.outputs }); + // } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.OutputItem) { + // this._setCellOutputItems(rawEvent.index, rawEvent.outputId, rawEvent.append, rawEvent.outputItems); + // relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, outputs: this.cells[rawEvent.index].apiCell.outputs }); } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellLanguage) { this.changeCellLanguage(rawEvent.index, rawEvent.language); relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, document: this.cells[rawEvent.index].apiCell.document }); } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellContent) { relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, document: this.cells[rawEvent.index].apiCell.document }); - // } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellMime) { - // this._changeCellMime(rawEvent.index, rawEvent.mime); + // } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellMime) { + // this._changeCellMime(rawEvent.index, rawEvent.mime); } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeCellMetadata) { this.changeCellMetadata(rawEvent.index, rawEvent.metadata); relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, metadata: this.cells[rawEvent.index].apiCell.metadata }); @@ -351,7 +351,7 @@ export class NotebookDocument implements Disposable { const removedCellDocuments: UriComponents[] = []; splices.reverse().forEach(splice => { - const cellDtos = splice[2]; + const cellDtos = splice.newItems; const newCells = cellDtos.map((cell: NotebookCellDto) => { const extCell = new Cell(this, this.editorsAndDocuments, cell); @@ -361,8 +361,8 @@ export class NotebookDocument implements Disposable { return extCell; }); - const changeEvent = new RawContentChangeEvent(splice[0], splice[1], [], newCells); - const deletedItems = this.cells.splice(splice[0], splice[1], ...newCells); + const changeEvent = new RawContentChangeEvent(splice.start, splice.deleteCount, [], newCells); + const deletedItems = this.cells.splice(splice.start, splice.deleteCount, ...newCells); for (const cell of deletedItems) { removedCellDocuments.push(cell.uri.toComponents()); changeEvent.deletedItems.push(cell.apiCell); diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index 512c81b359e75..405d6873b7050 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -31,8 +31,7 @@ import { DisposableCollection, Mutable, isEmptyObject, isObject } from '@theia/c import * as notebooks from '@theia/notebook/lib/common'; import { CommandsConverter } from './command-registry'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { CellData, CellExecutionUpdateType, CellOutput, CellOutputItem, CellRange, isTextStreamMime } from '@theia/notebook/lib/common'; -import { CellExecuteUpdate, CellExecutionComplete } from '@theia/notebook/lib/browser'; +import { CellRange, isTextStreamMime } from '@theia/notebook/lib/common'; import { MarkdownString as MarkdownStringDTO } from '@theia/core/lib/common/markdown-rendering'; import { TestItemDTO, TestMessageDTO } from '../common/test-types'; @@ -1636,126 +1635,6 @@ export namespace NotebookKernelSourceAction { } } -export namespace NotebookDto { - - export function toNotebookOutputItemDto(item: CellOutputItem): rpc.NotebookOutputItemDto { - return { - mime: item.mime, - valueBytes: item.data - }; - } - - export function toNotebookOutputDto(output: CellOutput): rpc.NotebookOutputDto { - return { - outputId: output.outputId, - metadata: output.metadata, - items: output.outputs.map(toNotebookOutputItemDto) - }; - } - - export function toNotebookCellDataDto(cell: CellData): rpc.NotebookCellDataDto { - return { - cellKind: cell.cellKind, - language: cell.language, - source: cell.source, - internalMetadata: cell.internalMetadata, - metadata: cell.metadata, - outputs: cell.outputs.map(toNotebookOutputDto) - }; - } - - // export function toNotebookDataDto(data: NotebookData): rpc.NotebookDataDto { - // return { - // metadata: data.metadata, - // cells: data.cells.map(toNotebookCellDataDto) - // }; - // } - - export function fromNotebookOutputItemDto(item: rpc.NotebookOutputItemDto): CellOutputItem { - return { - mime: item.mime, - data: item.valueBytes - }; - } - - export function fromNotebookOutputDto(output: rpc.NotebookOutputDto): CellOutput { - return { - outputId: output.outputId, - metadata: output.metadata, - outputs: output.items.map(fromNotebookOutputItemDto) - }; - } - - export function fromNotebookCellDataDto(cell: rpc.NotebookCellDataDto): CellData { - return { - cellKind: cell.cellKind, - language: cell.language, - source: cell.source, - outputs: cell.outputs.map(fromNotebookOutputDto), - metadata: cell.metadata, - internalMetadata: cell.internalMetadata - }; - } - - // export function fromNotebookDataDto(data: rpc.NotebookDataDto): NotebookData { - // return { - // metadata: data.metadata, - // cells: data.cells.map(fromNotebookCellDataDto) - // }; - // } - - // export function toNotebookCellDto(cell: Cell): rpc.NotebookCellDto { - // return { - // handle: cell.handle, - // uri: cell.uri, - // source: cell.textBuffer.getLinesContent(), - // eol: cell.textBuffer.getEOL(), - // language: cell.language, - // cellKind: cell.cellKind, - // outputs: cell.outputs.map(toNotebookOutputDto), - // metadata: cell.metadata, - // internalMetadata: cell.internalMetadata, - // }; - // } - - export function fromCellExecuteUpdateDto(data: rpc.CellExecuteUpdateDto): CellExecuteUpdate { - if (data.editType === CellExecutionUpdateType.Output) { - return { - editType: data.editType, - cellHandle: data.cellHandle, - append: data.append, - outputs: data.outputs.map(fromNotebookOutputDto) - }; - } else if (data.editType === CellExecutionUpdateType.OutputItems) { - return { - editType: data.editType, - append: data.append, - outputId: data.outputId, - items: data.items.map(fromNotebookOutputItemDto) - }; - } else { - return data; - } - } - - export function fromCellExecuteCompleteDto(data: rpc.CellExecutionCompleteDto): CellExecutionComplete { - return data; - } - - // export function fromCellEditOperationDto(edit: rpc.CellEditOperationDto): CellEditOperation { - // if (edit.editType === CellEditType.Replace) { - // return { - // editType: edit.editType, - // index: edit.index, - // count: edit.count, - // cells: edit.cells.map(fromNotebookCellDataDto) - // }; - // } else { - // return edit; - // } - // } -} - export namespace TestMessage { export function from(message: theia.TestMessage | readonly theia.TestMessage[]): TestMessageDTO[] { if (isReadonlyArray(message)) {