Merge pull request #117069 from microsoft/roblou/refactorKernel

Move kernel/execution code out of NotebookEditorWidget for testability
This commit is contained in:
Rob Lourens 2021-02-22 17:00:10 -08:00 committed by GitHub
commit c159b34535
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 851 additions and 677 deletions

View file

@ -48,12 +48,6 @@ async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) {
await documentClosed;
}
async function updateCellMetadata(uri: vscode.Uri, cell: vscode.NotebookCell, newMetadata: vscode.NotebookCellMetadata) {
const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCellMetadata(uri, cell.index, newMetadata);
await vscode.workspace.applyEdit(edit);
}
async function updateNotebookMetadata(uri: vscode.Uri, newMetadata: vscode.NotebookDocumentMetadata) {
const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookMetadata(uri, newMetadata);
@ -489,7 +483,7 @@ suite('Notebook API tests', function () {
const version = vscode.window.activeNotebookEditor!.document.version;
await vscode.window.activeNotebookEditor!.edit(editBuilder => {
editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]);
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ runnable: false }));
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ breakpointMargin: false }));
});
await cellsChangeEvent;
@ -507,18 +501,18 @@ suite('Notebook API tests', function () {
const version = vscode.window.activeNotebookEditor!.document.version;
await vscode.window.activeNotebookEditor!.edit(editBuilder => {
editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]);
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ runnable: false }));
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ breakpointMargin: false }));
});
await cellsChangeEvent;
await cellMetadataChangeEvent;
assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3);
assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, false);
assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.breakpointMargin, false);
assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version);
await vscode.commands.executeCommand('undo');
assert.strictEqual(version + 2, vscode.window.activeNotebookEditor!.document.version);
assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, undefined);
assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0]?.metadata?.breakpointMargin, undefined);
assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2);
await saveAllFilesAndCloseAll(resource);
@ -686,35 +680,6 @@ suite('Notebook API tests', function () {
await saveFileAndCloseAll(resource);
});
test('cell runnable metadata is respected', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.window.activeNotebookEditor!;
await vscode.commands.executeCommand('notebook.focusTop');
const cell = editor.document.cells[0];
assert.strictEqual(cell.outputs.length, 0);
let metadataChangeEvent = asPromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata);
await updateCellMetadata(resource, cell, cell.metadata.with({ runnable: false }));
await metadataChangeEvent;
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work
metadataChangeEvent = asPromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata);
await updateCellMetadata(resource, cell, cell.metadata.with({ runnable: true }));
await metadataChangeEvent;
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
});
test('document runnable metadata is respected', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');

View file

@ -28,3 +28,14 @@ export function testRepeat(n: number, description: string, callback: (this: any)
test(`${description} (iteration ${i})`, callback);
}
}
export async function assertThrowsAsync(block: () => any, message: string | Error = 'Missing expected exception'): Promise<void> {
try {
await block();
} catch {
return;
}
const err = message instanceof Error ? message : new Error(message);
throw err;
}

View file

@ -142,6 +142,7 @@ export class MenuId {
static readonly NotebookCellInsert = new MenuId('NotebookCellInsert');
static readonly NotebookCellBetween = new MenuId('NotebookCellBetween');
static readonly NotebookCellListTop = new MenuId('NotebookCellTop');
static readonly NotebookCellExecute = new MenuId('NotebookCellExecute');
static readonly NotebookDiffCellInputTitle = new MenuId('NotebookDiffCellInputTitle');
static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle');
static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle');

View file

@ -0,0 +1,65 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import * as Types from 'vs/base/common/types';
import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, Omit, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
export class TestQuickInputService implements IQuickInputService {
declare readonly _serviceBrand: undefined;
readonly onShow = Event.None;
readonly onHide = Event.None;
readonly quickAccess = undefined!;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T | undefined> {
if (Types.isArray(picks)) {
return Promise.resolve(<any>{ label: 'selectedPick', description: 'pick description', value: 'selectedPick' });
} else {
return Promise.resolve(undefined);
}
}
public input(options?: IInputOptions, token?: CancellationToken): Promise<string> {
return Promise.resolve(options ? 'resolved' + options.prompt : 'resolved');
}
backButton!: IQuickInputButton;
createQuickPick<T extends IQuickPickItem>(): IQuickPick<T> {
throw new Error('not implemented.');
}
createInputBox(): IInputBox {
throw new Error('not implemented.');
}
focus(): void {
throw new Error('not implemented.');
}
toggle(): void {
throw new Error('not implemented.');
}
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void {
throw new Error('not implemented.');
}
accept(): Promise<void> {
throw new Error('not implemented.');
}
back(): Promise<void> {
throw new Error('not implemented.');
}
cancel(): Promise<void> {
throw new Error('not implemented.');
}
}

View file

@ -1118,16 +1118,15 @@ declare module 'vscode' {
readonly statusMessage?: string;
// run related API, will be removed
readonly runnable?: boolean;
readonly hasExecutionOrder?: boolean;
readonly executionOrder?: number;
readonly runState?: NotebookCellRunState;
readonly runStartTime?: number;
readonly lastRunDuration?: number;
constructor(editable?: boolean, breakpointMargin?: boolean, runnable?: boolean, hasExecutionOrder?: boolean, executionOrder?: number, runState?: NotebookCellRunState, runStartTime?: number, statusMessage?: string, lastRunDuration?: number, inputCollapsed?: boolean, outputCollapsed?: boolean, custom?: Record<string, any>)
constructor(editable?: boolean, breakpointMargin?: boolean, hasExecutionOrder?: boolean, executionOrder?: number, runState?: NotebookCellRunState, runStartTime?: number, statusMessage?: string, lastRunDuration?: number, inputCollapsed?: boolean, outputCollapsed?: boolean, custom?: Record<string, any>)
with(change: { editable?: boolean | null, breakpointMargin?: boolean | null, runnable?: boolean | null, hasExecutionOrder?: boolean | null, executionOrder?: number | null, runState?: NotebookCellRunState | null, runStartTime?: number | null, statusMessage?: string | null, lastRunDuration?: number | null, inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, custom?: Record<string, any> | null, }): NotebookCellMetadata;
with(change: { editable?: boolean | null, breakpointMargin?: boolean | null, hasExecutionOrder?: boolean | null, executionOrder?: number | null, runState?: NotebookCellRunState | null, runStartTime?: number | null, statusMessage?: string | null, lastRunDuration?: number | null, inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, custom?: Record<string, any> | null, }): NotebookCellMetadata;
}
// todo@API support ids https://github.com/jupyter/enhancement-proposals/blob/master/62-cell-id/cell-id.md
@ -1174,10 +1173,9 @@ declare module 'vscode' {
// todo@API infer from kernel
// todo@API remove
readonly runnable: boolean;
readonly cellRunnable: boolean;
readonly runState: NotebookRunState;
constructor(editable?: boolean, runnable?: boolean, cellEditable?: boolean, cellRunnable?: boolean, cellHasExecutionOrder?: boolean, displayOrder?: GlobPattern[], custom?: { [key: string]: any; }, runState?: NotebookRunState, trusted?: boolean);
constructor(editable?: boolean, runnable?: boolean, cellEditable?: boolean, cellHasExecutionOrder?: boolean, displayOrder?: GlobPattern[], custom?: { [key: string]: any; }, runState?: NotebookRunState, trusted?: boolean);
with(change: { editable?: boolean | null, runnable?: boolean | null, cellEditable?: boolean | null, cellRunnable?: boolean | null, cellHasExecutionOrder?: boolean | null, displayOrder?: GlobPattern[] | null, custom?: { [key: string]: any; } | null, runState?: NotebookRunState | null, trusted?: boolean | null, }): NotebookDocumentMetadata
}

View file

@ -1419,7 +1419,7 @@ export namespace NotebookCellRange {
export namespace NotebookCellMetadata {
export function to(data: notebooks.NotebookCellMetadata): types.NotebookCellMetadata {
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.runnable, data.hasExecutionOrder, data.executionOrder, data.runState, data.runStartTime, data.statusMessage, data.lastRunDuration, data.inputCollapsed, data.outputCollapsed, data.custom);
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.hasExecutionOrder, data.executionOrder, data.runState, data.runStartTime, data.statusMessage, data.lastRunDuration, data.inputCollapsed, data.outputCollapsed, data.custom);
}
}
@ -1430,7 +1430,7 @@ export namespace NotebookDocumentMetadata {
}
export function to(data: notebooks.NotebookDocumentMetadata): types.NotebookDocumentMetadata {
return new types.NotebookDocumentMetadata(data.editable, data.runnable, data.cellEditable, data.cellRunnable, data.cellHasExecutionOrder, data.displayOrder, data.custom, data.runState, data.trusted);
return new types.NotebookDocumentMetadata(data.editable, data.runnable, data.cellEditable, data.cellHasExecutionOrder, data.displayOrder, data.custom, data.runState, data.trusted);
}
}

View file

@ -2924,7 +2924,6 @@ export class NotebookCellMetadata {
constructor(
readonly editable?: boolean,
readonly breakpointMargin?: boolean,
readonly runnable?: boolean,
readonly hasExecutionOrder?: boolean,
readonly executionOrder?: number,
readonly runState?: NotebookCellRunState,
@ -2939,7 +2938,6 @@ export class NotebookCellMetadata {
with(change: {
editable?: boolean | null,
breakpointMargin?: boolean | null,
runnable?: boolean | null,
hasExecutionOrder?: boolean | null,
executionOrder?: number | null,
runState?: NotebookCellRunState | null,
@ -2951,7 +2949,7 @@ export class NotebookCellMetadata {
custom?: Record<string, any> | null,
}): NotebookCellMetadata {
let { editable, breakpointMargin, runnable, hasExecutionOrder, executionOrder, runState, runStartTime, statusMessage, lastRunDuration, inputCollapsed, outputCollapsed, custom } = change;
let { editable, breakpointMargin, hasExecutionOrder, executionOrder, runState, runStartTime, statusMessage, lastRunDuration, inputCollapsed, outputCollapsed, custom } = change;
if (editable === undefined) {
editable = this.editable;
@ -2963,11 +2961,6 @@ export class NotebookCellMetadata {
} else if (breakpointMargin === null) {
breakpointMargin = undefined;
}
if (runnable === undefined) {
runnable = this.runnable;
} else if (runnable === null) {
runnable = undefined;
}
if (hasExecutionOrder === undefined) {
hasExecutionOrder = this.hasExecutionOrder;
} else if (hasExecutionOrder === null) {
@ -3016,7 +3009,6 @@ export class NotebookCellMetadata {
if (editable === this.editable &&
breakpointMargin === this.breakpointMargin &&
runnable === this.runnable &&
hasExecutionOrder === this.hasExecutionOrder &&
executionOrder === this.executionOrder &&
runState === this.runState &&
@ -3033,7 +3025,6 @@ export class NotebookCellMetadata {
return new NotebookCellMetadata(
editable,
breakpointMargin,
runnable,
hasExecutionOrder,
executionOrder,
runState,
@ -3053,7 +3044,6 @@ export class NotebookDocumentMetadata {
readonly editable: boolean = true,
readonly runnable: boolean = true,
readonly cellEditable: boolean = true,
readonly cellRunnable: boolean = true,
readonly cellHasExecutionOrder: boolean = true,
readonly displayOrder: vscode.GlobPattern[] = NOTEBOOK_DISPLAY_ORDER,
readonly custom: { [key: string]: any; } = {},
@ -3065,7 +3055,6 @@ export class NotebookDocumentMetadata {
editable?: boolean | null,
runnable?: boolean | null,
cellEditable?: boolean | null,
cellRunnable?: boolean | null,
cellHasExecutionOrder?: boolean | null,
displayOrder?: vscode.GlobPattern[] | null,
custom?: { [key: string]: any; } | null,
@ -3073,7 +3062,7 @@ export class NotebookDocumentMetadata {
trusted?: boolean | null,
}): NotebookDocumentMetadata {
let { editable, runnable, cellEditable, cellRunnable, cellHasExecutionOrder, displayOrder, custom, runState, trusted } = change;
let { editable, runnable, cellEditable, cellHasExecutionOrder, displayOrder, custom, runState, trusted } = change;
if (editable === undefined) {
editable = this.editable;
@ -3090,11 +3079,6 @@ export class NotebookDocumentMetadata {
} else if (cellEditable === null) {
cellEditable = undefined;
}
if (cellRunnable === undefined) {
cellRunnable = this.cellRunnable;
} else if (cellRunnable === null) {
cellRunnable = undefined;
}
if (cellHasExecutionOrder === undefined) {
cellHasExecutionOrder = this.cellHasExecutionOrder;
} else if (cellHasExecutionOrder === null) {
@ -3124,7 +3108,6 @@ export class NotebookDocumentMetadata {
if (editable === this.editable &&
runnable === this.runnable &&
cellEditable === this.cellEditable &&
cellRunnable === this.cellRunnable &&
cellHasExecutionOrder === this.cellHasExecutionOrder &&
displayOrder === this.displayOrder &&
custom === this.custom &&
@ -3139,7 +3122,6 @@ export class NotebookDocumentMetadata {
editable,
runnable,
cellEditable,
cellRunnable,
cellHasExecutionOrder,
displayOrder,
custom,

View file

@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import * as glob from 'vs/base/common/glob';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
@ -20,20 +22,18 @@ import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/context
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { EditorsOrder } from 'vs/workbench/common/editor';
import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { EditorsOrder } from 'vs/workbench/common/editor';
import { INotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
// Notebook Commands
const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute';
@ -228,11 +228,17 @@ export function getWidgetFromUri(accessor: ServicesAccessor, uri: URI) {
return undefined;
}
const executeCellCondition = ContextKeyExpr.or(
ContextKeyExpr.and(
ContextKeyExpr.equals(NOTEBOOK_CELL_RUN_STATE.key, NotebookCellRunState[NotebookCellRunState.Idle]),
ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0)),
NOTEBOOK_CELL_TYPE.isEqualTo('markdown'));
registerAction2(class ExecuteCell extends NotebookCellAction<ICellRange> {
constructor() {
super({
id: EXECUTE_CELL_COMMAND_ID,
precondition: ContextKeyExpr.or(ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0), NOTEBOOK_CELL_TYPE.isEqualTo('markdown')),
precondition: executeCellCondition,
title: localize('notebookActions.execute', "Execute Cell"),
keybinding: {
when: NOTEBOOK_CELL_LIST_FOCUSED,
@ -242,6 +248,11 @@ registerAction2(class ExecuteCell extends NotebookCellAction<ICellRange> {
},
weight: EDITOR_WIDGET_ACTION_WEIGHT
},
menu: {
id: MenuId.NotebookCellExecute,
when: executeCellCondition,
group: 'inline'
},
description: {
description: localize('notebookActions.execute', "Execute Cell"),
args: [
@ -325,8 +336,14 @@ registerAction2(class StopExecuteCell extends NotebookCellAction<ICellRange> {
constructor() {
super({
id: CANCEL_CELL_COMMAND_ID,
precondition: ContextKeyExpr.equals(NOTEBOOK_CELL_RUN_STATE.key, NotebookCellRunState[NotebookCellRunState.Running]),
title: localize('notebookActions.cancel', "Stop Cell Execution"),
icon: icons.stopIcon,
menu: {
id: MenuId.NotebookCellExecute,
when: ContextKeyExpr.equals(NOTEBOOK_CELL_RUN_STATE.key, NotebookCellRunState[NotebookCellRunState.Running]),
group: 'inline'
},
description: {
description: localize('notebookActions.cancel', "Stop Cell Execution"),
args: [
@ -397,42 +414,6 @@ registerAction2(class StopExecuteCell extends NotebookCellAction<ICellRange> {
}
});
export class ExecuteCellAction extends MenuItemAction {
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService commandService: ICommandService
) {
super(
{
id: EXECUTE_CELL_COMMAND_ID,
title: localize('notebookActions.executeCell', "Execute Cell"),
icon: icons.executeIcon
},
undefined,
{ shouldForwardArgs: true },
contextKeyService,
commandService);
}
}
export class CancelCellAction extends MenuItemAction {
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService commandService: ICommandService
) {
super(
{
id: CANCEL_CELL_COMMAND_ID,
title: localize('notebookActions.CancelCell', "Cancel Execution"),
icon: icons.stopIcon
},
undefined,
{ shouldForwardArgs: true },
contextKeyService,
commandService);
}
}
export class DeleteCellAction extends MenuItemAction {
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@ -455,7 +436,7 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction {
constructor() {
super({
id: EXECUTE_CELL_SELECT_BELOW,
precondition: ContextKeyExpr.or(ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0), NOTEBOOK_CELL_TYPE.isEqualTo('markdown')),
precondition: executeCellCondition,
title: localize('notebookActions.executeAndSelectBelow', "Execute Notebook Cell and Select Below"),
keybinding: {
when: NOTEBOOK_CELL_LIST_FOCUSED,
@ -492,7 +473,7 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction {
constructor() {
super({
id: EXECUTE_CELL_INSERT_BELOW,
precondition: ContextKeyExpr.or(ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0), NOTEBOOK_CELL_TYPE.isEqualTo('markdown')),
precondition: executeCellCondition,
title: localize('notebookActions.executeAndInsertBelow', "Execute Notebook Cell and Insert Below"),
keybinding: {
when: NOTEBOOK_CELL_LIST_FOCUSED,
@ -1261,7 +1242,6 @@ registerAction2(class extends NotebookAction {
outputs: cell.outputs,
metadata: {
editable: cell.metadata?.editable,
runnable: cell.metadata?.runnable,
breakpointMargin: cell.metadata?.breakpointMargin,
hasExecutionOrder: cell.metadata?.hasExecutionOrder,
inputCollapsed: cell.metadata?.inputCollapsed,
@ -1319,7 +1299,6 @@ registerAction2(class extends NotebookCellAction {
outputs: cell.outputs,
metadata: {
editable: cell.metadata?.editable,
runnable: cell.metadata?.runnable,
breakpointMargin: cell.metadata?.breakpointMargin,
hasExecutionOrder: cell.metadata?.hasExecutionOrder,
inputCollapsed: cell.metadata?.inputCollapsed,

View file

@ -21,8 +21,8 @@ suite('Notebook Folding', () => {
const undoRedoService = instantiationService.stub(IUndoRedoService, () => { });
instantiationService.spy(IUndoRedoService, 'pushElement');
test('Folding based on markdown cells', function () {
withTestNotebook(
test('Folding based on markdown cells', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -50,8 +50,8 @@ suite('Notebook Folding', () => {
);
});
test('Top level header in a cell wins', function () {
withTestNotebook(
test('Top level header in a cell wins', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -84,8 +84,8 @@ suite('Notebook Folding', () => {
);
});
test('Folding', function () {
withTestNotebook(
test('Folding', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -109,7 +109,7 @@ suite('Notebook Folding', () => {
}
);
withTestNotebook(
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -134,7 +134,7 @@ suite('Notebook Folding', () => {
}
);
withTestNotebook(
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -160,8 +160,8 @@ suite('Notebook Folding', () => {
);
});
test('Nested Folding', function () {
withTestNotebook(
test('Nested Folding', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -217,8 +217,8 @@ suite('Notebook Folding', () => {
);
});
test('Folding Memento', function () {
withTestNotebook(
test('Folding Memento', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -249,7 +249,7 @@ suite('Notebook Folding', () => {
}
);
withTestNotebook(
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -284,7 +284,7 @@ suite('Notebook Folding', () => {
}
);
withTestNotebook(
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -320,8 +320,8 @@ suite('Notebook Folding', () => {
);
});
test('View Index', function () {
withTestNotebook(
test('View Index', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -360,7 +360,7 @@ suite('Notebook Folding', () => {
}
);
withTestNotebook(
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,

View file

@ -394,7 +394,6 @@ abstract class AbstractElementRenderer extends Disposable {
case 'hasExecutionOrder':
case 'inputCollapsed':
case 'outputCollapsed':
case 'runnable':
// boolean
if (typeof newMetadataObj[key] === 'boolean') {
result[key] = newMetadataObj[key];

View file

@ -488,9 +488,9 @@
justify-content: center;
}
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .runnable .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .runnable .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover .runnable .run-button-container .monaco-toolbar {
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .run-button-container .monaco-toolbar,
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover .run-button-container .monaco-toolbar {
visibility: visible;
}

View file

@ -658,10 +658,6 @@ class RegisterSchemasContribution extends Disposable implements IWorkbenchContri
type: 'boolean',
description: `Controls whether a cell's editor is editable/readonly`
},
['runnable']: {
type: 'boolean',
description: 'Controls if the cell is executable'
},
['breakpointMargin']: {
type: 'boolean',
description: 'Controls if the cell has a margin to support the breakpoint UI'

View file

@ -56,9 +56,8 @@ export const NOTEBOOK_CELL_TYPE = new RawContextKey<string>('notebookCellType',
export const NOTEBOOK_CELL_EDITABLE = new RawContextKey<boolean>('notebookCellEditable', false); // bool
export const NOTEBOOK_CELL_FOCUSED = new RawContextKey<boolean>('notebookCellFocused', false); // bool
export const NOTEBOOK_CELL_EDITOR_FOCUSED = new RawContextKey<boolean>('notebookCellEditorFocused', false); // bool
export const NOTEBOOK_CELL_RUNNABLE = new RawContextKey<boolean>('notebookCellRunnable', false); // bool
export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey<boolean>('notebookCellMarkdownEditMode', false); // bool
export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey<string>('notebookCellRunState', undefined); // idle, running
export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey<string>('notebookCellRunState', undefined); // Idle, Running
export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey<boolean>('notebookCellHasOutputs', false); // bool
export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellInputIsCollapsed', false); // bool
export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false); // bool

View file

@ -0,0 +1,445 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Memento } from 'vs/workbench/common/memento';
import { ICellViewModel, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { configureKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellKind, INotebookKernel, NotebookCellRunState, NotebookRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel';
export interface IKernelManagerDelegate {
viewModel: NotebookViewModel | undefined;
getId(): string;
getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[];
getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined;
getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise<INotebookKernel[]>;
loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel): Promise<void>;
}
export class NotebookEditorKernelManager extends Disposable {
private _isDisposed: boolean = false;
private _activeKernelExecuted: boolean = false;
private _activeKernel: INotebookKernel | undefined = undefined;
private readonly _onDidChangeKernel = this._register(new Emitter<void>());
readonly onDidChangeKernel: Event<void> = this._onDidChangeKernel.event;
private readonly _onDidChangeAvailableKernels = this._register(new Emitter<void>());
readonly onDidChangeAvailableKernels: Event<void> = this._onDidChangeAvailableKernels.event;
private _contributedKernelsComputePromise: CancelablePromise<INotebookKernel[]> | null = null;
private _initialKernelComputationDone: boolean = false;
private readonly _editorRunnable: IContextKey<boolean>;
private readonly _notebookExecuting: IContextKey<boolean>;
private readonly _notebookHasMultipleKernels: IContextKey<boolean>;
private readonly _notebookKernelCount: IContextKey<number>;
get activeKernel() {
return this._activeKernel;
}
set activeKernel(kernel: INotebookKernel | undefined) {
if (this._isDisposed) {
return;
}
if (!this._delegate.viewModel) {
return;
}
if (this._activeKernel === kernel) {
return;
}
this._activeKernel = kernel;
this._activeKernelResolvePromise = undefined;
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
memento[this._delegate.viewModel.viewType] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
this._onDidChangeKernel.fire();
if (this._activeKernel) {
this._delegate.loadKernelPreloads(this._activeKernel.extensionLocation, this._activeKernel);
}
}
private _activeKernelResolvePromise: Promise<void> | undefined = undefined;
private _multipleKernelsAvailable: boolean = false;
get multipleKernelsAvailable() {
return this._multipleKernelsAvailable;
}
set multipleKernelsAvailable(state: boolean) {
this._multipleKernelsAvailable = state;
this._onDidChangeAvailableKernels.fire();
}
private readonly _activeKernelMemento: Memento;
constructor(
private readonly _delegate: IKernelManagerDelegate,
@IStorageService storageService: IStorageService,
@IContextKeyService contextKeyService: IContextKeyService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IConfigurationService private readonly _configurationService: IConfigurationService,) {
super();
this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService);
this._editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(contextKeyService);
this._notebookExecuting = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(contextKeyService);
this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(contextKeyService);
this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService);
}
public async setKernels(tokenSource: CancellationTokenSource) {
if (!this._delegate.viewModel) {
return;
}
if (this._activeKernel !== undefined && this._activeKernelExecuted) {
// kernel already executed, we should not change it automatically
return;
}
const provider = this._delegate.getContributedNotebookProvider(this._delegate.viewModel.viewType) || this._delegate.getContributedNotebookProviders(this._delegate.viewModel.uri)[0];
const availableKernels = await this.beginComputeContributedKernels();
if (tokenSource.token.isCancellationRequested) {
return;
}
this._notebookKernelCount.set(availableKernels.length);
if (availableKernels.length > 1) {
this._notebookHasMultipleKernels.set(true);
this.multipleKernelsAvailable = true;
} else {
this._notebookHasMultipleKernels.set(false);
this.multipleKernelsAvailable = false;
}
const activeKernelStillExist = [...availableKernels].find(kernel => kernel.friendlyId === this.activeKernel?.friendlyId && this.activeKernel?.friendlyId !== undefined);
if (activeKernelStillExist) {
// the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost
return;
}
if (availableKernels.length) {
return this._setKernelsFromProviders(provider, availableKernels, tokenSource);
}
this._initialKernelComputationDone = true;
tokenSource.dispose();
}
async beginComputeContributedKernels() {
if (this._contributedKernelsComputePromise) {
return this._contributedKernelsComputePromise;
}
this._contributedKernelsComputePromise = createCancelablePromise(token => {
return this._delegate.getNotebookKernels(this._delegate.viewModel!.viewType, this._delegate.viewModel!.uri, token);
});
const result = await this._contributedKernelsComputePromise;
this._initialKernelComputationDone = true;
this._contributedKernelsComputePromise = null;
return result;
}
updateForMetadata(): void {
if (!this._delegate.viewModel) {
return;
}
const notebookMetadata = this._delegate.viewModel.metadata;
this._editorRunnable.set(this._delegate.viewModel.runnable);
this._notebookExecuting.set(notebookMetadata.runState === NotebookRunState.Running);
}
private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernel[], tokenSource: CancellationTokenSource) {
const rawAssociations = this._configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId) || [];
const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this._delegate.viewModel?.viewType)[0]?.kernelProvider;
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
if (userSetKernelProvider) {
const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider);
if (filteredKernels.length) {
const cachedKernelId = memento[provider.id];
this.activeKernel =
filteredKernels.find(kernel => kernel.isPreferred)
|| filteredKernels.find(kernel => kernel.friendlyId === cachedKernelId)
|| filteredKernels[0];
} else {
this.activeKernel = undefined;
}
if (this.activeKernel) {
await this._delegate.loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
if (tokenSource.token.isCancellationRequested) {
return;
}
this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
await this._activeKernelResolvePromise;
if (tokenSource.token.isCancellationRequested) {
return;
}
}
memento[provider.id] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
tokenSource.dispose();
return;
}
// choose a preferred kernel
const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId);
if (kernelsFromSameExtension.length) {
const cachedKernelId = memento[provider.id];
const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred)
|| kernelsFromSameExtension.find(kernel => kernel.friendlyId === cachedKernelId)
|| kernelsFromSameExtension[0];
this.activeKernel = preferedKernel;
if (this.activeKernel) {
await this._delegate.loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
}
if (tokenSource.token.isCancellationRequested) {
return;
}
await preferedKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return;
}
memento[provider.id] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
tokenSource.dispose();
return;
}
// the provider doesn't have a builtin kernel, choose a kernel
this.activeKernel = kernels[0];
if (this.activeKernel) {
await this._delegate.loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
if (tokenSource.token.isCancellationRequested) {
return;
}
await this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return;
}
}
tokenSource.dispose();
}
private async _ensureActiveKernel() {
if (this._activeKernel) {
return;
}
if (this._activeKernelResolvePromise) {
await this._activeKernelResolvePromise;
if (this._activeKernel) {
return;
}
}
if (!this._initialKernelComputationDone) {
await this.setKernels(new CancellationTokenSource());
if (this._activeKernel) {
return;
}
}
// pick active kernel
const picker = this._quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>();
picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook");
picker.matchOnDetail = true;
const tokenSource = new CancellationTokenSource();
const availableKernels = await this.beginComputeContributedKernels();
const picks: QuickPickInput<IQuickPickItem & { run(): void; kernelProviderId?: string; }>[] = availableKernels.map((a) => {
return {
id: a.friendlyId,
label: a.label,
picked: false,
description:
a.description
? a.description
: a.extension.value,
detail: a.detail,
kernelProviderId: a.extension.value,
run: async () => {
this.activeKernel = a;
this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
},
buttons: [{
iconClass: ThemeIcon.asClassName(configureKernelIcon),
tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", this._delegate.viewModel!.viewType)
}]
};
});
picker.items = picks;
picker.busy = false;
const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => {
picker.onDidAccept(() => {
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined);
picker.dispose();
});
picker.onDidTriggerItemButton(e => {
const pick = e.item;
const id = pick.id;
resolve(pick); // open the view
picker.dispose();
// And persist the setting
if (pick && id && pick.kernelProviderId) {
const newAssociation: NotebookKernelProviderAssociation = { viewType: this._delegate.viewModel!.viewType, kernelProvider: pick.kernelProviderId };
const currentAssociations = [...this._configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId)];
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.viewType === newAssociation.viewType) {
currentAssociations.splice(i, 1, newAssociation);
this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations);
return;
}
}
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations);
}
});
});
tokenSource.dispose();
if (pickedItem) {
await pickedItem.run();
}
return;
}
async cancelNotebookExecution(): Promise<void> {
if (!this._delegate.viewModel) {
return;
}
if (this._delegate.viewModel.metadata.runState !== NotebookRunState.Running) {
return;
}
await this._ensureActiveKernel();
await this._activeKernel?.cancelNotebookCell!(this._delegate.viewModel.uri, undefined);
}
async executeNotebook(): Promise<void> {
if (!this._delegate.viewModel) {
return;
}
if (!this._delegate.viewModel.runnable) {
return;
}
await this._ensureActiveKernel();
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCell!(this._delegate.viewModel.uri, undefined);
}
async cancelNotebookCellExecution(cell: ICellViewModel): Promise<void> {
if (!this._delegate.viewModel) {
return;
}
if (cell.cellKind !== CellKind.Code) {
return;
}
const metadata = cell.getEvaluatedMetadata(this._delegate.viewModel.metadata);
if (metadata.runState !== NotebookCellRunState.Running) {
return;
}
await this._ensureActiveKernel();
await this._activeKernel?.cancelNotebookCell!(this._delegate.viewModel.uri, cell.handle);
}
async executeNotebookCell(cell: ICellViewModel): Promise<void> {
if (!this._delegate.viewModel) {
return;
}
if (!this.canExecuteCell(cell)) {
throw new Error('Cell is not executable: ' + cell.uri);
}
await this._ensureActiveKernel();
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCell!(this._delegate.viewModel.uri, cell.handle);
}
private canExecuteCell(cell: ICellViewModel): boolean {
if (!this.activeKernel) {
return false;
}
if (cell.cellKind !== CellKind.Code) {
return false;
}
if (!this.activeKernel.supportedLanguages) {
return true;
}
if (this.activeKernel.supportedLanguages.includes(cell.language)) {
return true;
}
return false;
}
}

View file

@ -5,17 +5,18 @@
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import * as strings from 'vs/base/common/strings';
import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { IAction, Separator } from 'vs/base/common/actions';
import { CancelablePromise, createCancelablePromise, SequencerByKey } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { SequencerByKey } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Color, RGBA } from 'vs/base/common/color';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { extname } from 'vs/base/common/resources';
import { ScrollEvent } from 'vs/base/common/scrollable';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import 'vs/css!./media/notebook';
@ -24,6 +25,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { Range } from 'vs/editor/common/core/range';
import { IContentDecorationRenderOptions, IEditor, isThemeColor } from 'vs/editor/common/editorCommon';
import { IModeService } from 'vs/editor/common/services/modeService';
import * as nls from 'vs/nls';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -32,45 +34,40 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService, registerThemingParticipant, ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorMemento } from 'vs/workbench/common/editor';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { IKernelManagerDelegate, NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
import { errorStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
import { CodeCellRenderer, ListTopCellToolbar, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer';
import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd';
import { CodeCellRenderer, ListTopCellToolbar, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, CellToolbarLocKey, ICellRange, INotebookDecorationRenderOptions, INotebookKernel, NotebookCellRunState, NotebookRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, CellToolbarLocKey, ICellRange, INotebookDecorationRenderOptions, INotebookKernel, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { configureKernelIcon, errorStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { extname } from 'vs/base/common/resources';
import { IModeService } from 'vs/editor/common/services/modeService';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
const $ = DOM.$;
const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel';
export class NotebookEditorWidget extends Disposable implements INotebookEditor {
static readonly ID: string = 'workbench.editor.notebook';
private static readonly EDITOR_MEMENTOS = new Map<string, EditorMemento<unknown>>();
@ -95,16 +92,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private readonly _editorFocus: IContextKey<boolean>;
private readonly _outputFocus: IContextKey<boolean>;
private readonly _editorEditable: IContextKey<boolean>;
private readonly _editorRunnable: IContextKey<boolean>;
private readonly _notebookExecuting: IContextKey<boolean>;
private readonly _notebookHasMultipleKernels: IContextKey<boolean>;
private readonly _notebookKernelCount: IContextKey<number>;
private _outputRenderer: OutputRenderer;
protected readonly _contributions = new Map<string, INotebookEditorContribution>();
private _scrollBeyondLastLine: boolean;
private readonly _memento: Memento;
private readonly _activeKernelMemento: Memento;
private readonly _onDidFocusEmitter = this._register(new Emitter<void>());
public readonly onDidFocus = this._onDidFocusEmitter.event;
private readonly _onWillScroll = this._register(new Emitter<ScrollEvent>());
@ -118,6 +110,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._list.scrollTop = top;
}
private _kernelManger: NotebookEditorKernelManager;
private _cellContextKeyManager: CellContextKeyManager | null = null;
private _isVisible = false;
private readonly _uuid = generateUuid();
@ -152,57 +145,29 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return this._notebookViewModel?.notebookDocument;
}
private _activeKernelExecuted: boolean = false;
private _activeKernel: INotebookKernel | undefined = undefined;
private readonly _onDidChangeKernel = this._register(new Emitter<void>());
readonly onDidChangeKernel: Event<void> = this._onDidChangeKernel.event;
private readonly _onDidChangeAvailableKernels = this._register(new Emitter<void>());
readonly onDidChangeAvailableKernels: Event<void> = this._onDidChangeAvailableKernels.event;
private _contributedKernelsComputePromise: CancelablePromise<INotebookKernel[]> | null = null;
private _initialKernelComputationDone: boolean = false;
get onDidChangeKernel(): Event<void> {
return this._kernelManger.onDidChangeKernel;
}
get onDidChangeAvailableKernels(): Event<void> {
return this._kernelManger.onDidChangeAvailableKernels;
}
get activeKernel() {
return this._activeKernel;
return this._kernelManger.activeKernel;
}
set activeKernel(kernel: INotebookKernel | undefined) {
if (this._isDisposed) {
return;
}
if (!this.viewModel) {
return;
}
if (this._activeKernel === kernel) {
return;
}
this._activeKernel = kernel;
this._activeKernelResolvePromise = undefined;
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
memento[this.viewModel.viewType] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
this._onDidChangeKernel.fire();
if (this._activeKernel) {
this._loadKernelPreloads(this._activeKernel.extensionLocation, this._activeKernel);
}
this._kernelManger.activeKernel = kernel;
}
private _activeKernelResolvePromise: Promise<void> | undefined = undefined;
private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined;
private _multipleKernelsAvailable: boolean = false;
get multipleKernelsAvailable() {
return this._multipleKernelsAvailable;
return this._kernelManger.multipleKernelsAvailable;
}
set multipleKernelsAvailable(state: boolean) {
this._multipleKernelsAvailable = state;
this._onDidChangeAvailableKernels.fire();
this._kernelManger.multipleKernelsAvailable = state;
}
private readonly _onDidChangeActiveEditor = this._register(new Emitter<this>());
@ -258,7 +223,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
@ILayoutService private readonly layoutService: ILayoutService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IMenuService private readonly menuService: IMenuService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IThemeService private readonly themeService: IThemeService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IModeService private readonly modeService: IModeService,
@ -270,8 +234,23 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer);
this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
const that = this;
this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager, <IKernelManagerDelegate>{
getId() { return that.getId(); },
loadKernelPreloads: that._loadKernelPreloads.bind(that),
get viewModel() { return that.viewModel; },
getContributedNotebookProviders(resource?: URI) {
return that.notebookService.getContributedNotebookProviders(resource);
},
getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined {
return that.notebookService.getContributedNotebookProvider(viewType);
},
getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise<INotebookKernel[]> {
return that.notebookService.getNotebookKernels(viewType, resource, token);
}
});
this._memento = new Memento(NotebookEditorWidget.ID, storageService);
this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService);
this._outputRenderer = new OutputRenderer(this, this.instantiationService);
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
@ -304,10 +283,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.scopedContextKeyService);
this._outputFocus = NOTEBOOK_OUTPUT_FOCUSED.bindTo(this.scopedContextKeyService);
this._editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.scopedContextKeyService);
this._editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(this.scopedContextKeyService);
this._notebookExecuting = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.scopedContextKeyService);
this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(this.scopedContextKeyService);
this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(this.scopedContextKeyService);
let contributions: INotebookEditorContributionDescription[];
if (Array.isArray(this.creationOptions.contributions)) {
@ -777,24 +752,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._webview?.element.remove();
this._webview = null;
this._list.clear();
this._activeKernel = undefined;
this._activeKernelExecuted = false;
}
async beginComputeContributedKernels() {
if (this._contributedKernelsComputePromise) {
return this._contributedKernelsComputePromise;
}
this._contributedKernelsComputePromise = createCancelablePromise(token => {
return this.notebookService.getNotebookKernels(this.viewModel!.viewType, this.viewModel!.uri, token);
});
const result = await this._contributedKernelsComputePromise;
this._initialKernelComputationDone = true;
this._contributedKernelsComputePromise = null;
return result;
return this._kernelManger.beginComputeContributedKernels();
}
private async _setKernels(tokenSource: CancellationTokenSource) {
@ -802,127 +763,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return;
}
if (this._activeKernel !== undefined && this._activeKernelExecuted) {
// kernel already executed, we should not change it automatically
return;
}
const provider = this.notebookService.getContributedNotebookProvider(this.viewModel.viewType) || this.notebookService.getContributedNotebookProviders(this.viewModel.uri)[0];
const availableKernels = await this.beginComputeContributedKernels();
if (tokenSource.token.isCancellationRequested) {
return;
}
this._notebookKernelCount.set(availableKernels.length);
if (availableKernels.length > 1) {
this._notebookHasMultipleKernels.set(true);
this.multipleKernelsAvailable = true;
} else {
this._notebookHasMultipleKernels.set(false);
this.multipleKernelsAvailable = false;
}
const activeKernelStillExist = [...availableKernels].find(kernel => kernel.friendlyId === this.activeKernel?.friendlyId && this.activeKernel?.friendlyId !== undefined);
if (activeKernelStillExist) {
// the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost
return;
}
if (availableKernels.length) {
return this._setKernelsFromProviders(provider, availableKernels, tokenSource);
}
this._initialKernelComputationDone = true;
tokenSource.dispose();
}
private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernel[], tokenSource: CancellationTokenSource) {
const rawAssociations = this.configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId) || [];
const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this.viewModel?.viewType)[0]?.kernelProvider;
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
if (userSetKernelProvider) {
const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider);
if (filteredKernels.length) {
const cachedKernelId = memento[provider.id];
this.activeKernel =
filteredKernels.find(kernel => kernel.isPreferred)
|| filteredKernels.find(kernel => kernel.friendlyId === cachedKernelId)
|| filteredKernels[0];
} else {
this.activeKernel = undefined;
}
if (this.activeKernel) {
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
if (tokenSource.token.isCancellationRequested) {
return;
}
this._activeKernelResolvePromise = this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
await this._activeKernelResolvePromise;
if (tokenSource.token.isCancellationRequested) {
return;
}
}
memento[provider.id] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
tokenSource.dispose();
return;
}
// choose a preferred kernel
const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId);
if (kernelsFromSameExtension.length) {
const cachedKernelId = memento[provider.id];
const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred)
|| kernelsFromSameExtension.find(kernel => kernel.friendlyId === cachedKernelId)
|| kernelsFromSameExtension[0];
this.activeKernel = preferedKernel;
if (this.activeKernel) {
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
}
if (tokenSource.token.isCancellationRequested) {
return;
}
await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return;
}
memento[provider.id] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
tokenSource.dispose();
return;
}
// the provider doesn't have a builtin kernel, choose a kernel
this.activeKernel = kernels[0];
if (this.activeKernel) {
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
if (tokenSource.token.isCancellationRequested) {
return;
}
await this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return;
}
}
tokenSource.dispose();
this._kernelManger.setKernels(tokenSource);
}
private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel) {
@ -939,11 +780,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const notebookMetadata = this.viewModel.metadata;
this._editorEditable.set(!!notebookMetadata?.editable);
this._editorRunnable.set(this.viewModel.runnable);
this._overflowContainer.classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable);
this.getDomNode().classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable);
this._notebookExecuting.set(notebookMetadata.runState === NotebookRunState.Running);
this._kernelManger.updateForMetadata();
}
private async _resolveWebview(): Promise<BackLayerWebView<ICommonCellInfo> | null> {
@ -1498,7 +1338,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1;
let language;
if (type === CellKind.Code) {
const supportedLanguages = this._activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes();
const supportedLanguages = this._kernelManger.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes();
const defaultLanguage = supportedLanguages[0] || 'plaintext';
if (cell?.cellKind === CellKind.Code) {
language = cell.language;
@ -1726,152 +1566,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return undefined;
}
private async _ensureActiveKernel() {
if (this._activeKernel) {
return;
}
if (this._activeKernelResolvePromise) {
await this._activeKernelResolvePromise;
if (this._activeKernel) {
return;
}
}
if (!this._initialKernelComputationDone) {
await this._setKernels(new CancellationTokenSource());
if (this._activeKernel) {
return;
}
}
// pick active kernel
const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>();
picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook");
picker.matchOnDetail = true;
const tokenSource = new CancellationTokenSource();
const availableKernels = await this.beginComputeContributedKernels();
const picks: QuickPickInput<IQuickPickItem & { run(): void; kernelProviderId?: string; }>[] = availableKernels.map((a) => {
return {
id: a.friendlyId,
label: a.label,
picked: false,
description:
a.description
? a.description
: a.extension.value,
detail: a.detail,
kernelProviderId: a.extension.value,
run: async () => {
this.activeKernel = a;
this._activeKernelResolvePromise = this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
},
buttons: [{
iconClass: ThemeIcon.asClassName(configureKernelIcon),
tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", this.viewModel!.viewType)
}]
};
});
picker.items = picks;
picker.busy = false;
const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => {
picker.onDidAccept(() => {
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined);
picker.dispose();
});
picker.onDidTriggerItemButton(e => {
const pick = e.item;
const id = pick.id;
resolve(pick); // open the view
picker.dispose();
// And persist the setting
if (pick && id && pick.kernelProviderId) {
const newAssociation: NotebookKernelProviderAssociation = { viewType: this.viewModel!.viewType, kernelProvider: pick.kernelProviderId };
const currentAssociations = [...this.configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId)];
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.viewType === newAssociation.viewType) {
currentAssociations.splice(i, 1, newAssociation);
this.configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations);
return;
}
}
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
this.configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations);
}
});
});
tokenSource.dispose();
if (pickedItem) {
await pickedItem.run();
}
return;
}
async cancelNotebookExecution(): Promise<void> {
if (!this.viewModel) {
return;
}
if (this.viewModel.metadata.runState !== NotebookRunState.Running) {
return;
}
await this._ensureActiveKernel();
await this._activeKernel?.cancelNotebookCell!(this.viewModel.uri, undefined);
return this._kernelManger.cancelNotebookExecution();
}
async executeNotebook(): Promise<void> {
if (!this.viewModel) {
return;
}
if (!this.viewModel.runnable) {
return;
}
await this._ensureActiveKernel();
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCell!(this.viewModel.uri, undefined);
return this._kernelManger.executeNotebook();
}
async cancelNotebookCellExecution(cell: ICellViewModel): Promise<void> {
if (!this.viewModel) {
return;
}
if (cell.cellKind !== CellKind.Code) {
return;
}
const metadata = cell.getEvaluatedMetadata(this.viewModel.metadata);
if (!metadata.runnable) {
return;
}
if (metadata.runState !== NotebookCellRunState.Running) {
return;
}
await this._ensureActiveKernel();
await this._activeKernel?.cancelNotebookCell!(this.viewModel.uri, cell.handle);
return this._kernelManger.cancelNotebookCellExecution(cell);
}
async executeNotebookCell(cell: ICellViewModel): Promise<void> {
@ -1879,18 +1583,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return;
}
// TODO@roblourens, don't use the "execute" command for this
if (cell.cellKind === CellKind.Markdown) {
this.focusNotebookCell(cell, 'container');
return;
}
if (!cell.getEvaluatedMetadata(this.viewModel.metadata).runnable) {
return;
}
await this._ensureActiveKernel();
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCell!(this.viewModel.uri, cell.handle);
return this._kernelManger.executeNotebookCell(cell);
}
focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions) {

View file

@ -520,7 +520,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd
const cloneMetadata = (cell: NotebookCellTextModel) => {
return {
editable: cell.metadata?.editable,
runnable: cell.metadata?.runnable,
breakpointMargin: cell.metadata?.breakpointMargin,
hasExecutionOrder: cell.metadata?.hasExecutionOrder,
inputCollapsed: cell.metadata?.inputCollapsed,

View file

@ -5,7 +5,7 @@
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { INotebookTextModel, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_FOCUSED, INotebookEditor, NOTEBOOK_CELL_EDITOR_FOCUSED, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_FOCUSED, INotebookEditor, NOTEBOOK_CELL_EDITOR_FOCUSED, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
@ -15,7 +15,6 @@ export class CellContextKeyManager extends Disposable {
private cellType!: IContextKey<string>;
private viewType!: IContextKey<string>;
private cellEditable!: IContextKey<boolean>;
private cellRunnable!: IContextKey<boolean>;
private cellFocused!: IContextKey<boolean>;
private cellEditorFocused!: IContextKey<boolean>;
private cellRunState!: IContextKey<string>;
@ -41,7 +40,6 @@ export class CellContextKeyManager extends Disposable {
this.cellEditable = NOTEBOOK_CELL_EDITABLE.bindTo(this.contextKeyService);
this.cellFocused = NOTEBOOK_CELL_FOCUSED.bindTo(this.contextKeyService);
this.cellEditorFocused = NOTEBOOK_CELL_EDITOR_FOCUSED.bindTo(this.contextKeyService);
this.cellRunnable = NOTEBOOK_CELL_RUNNABLE.bindTo(this.contextKeyService);
this.markdownEditMode = NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.bindTo(this.contextKeyService);
this.cellRunState = NOTEBOOK_CELL_RUN_STATE.bindTo(this.contextKeyService);
this.cellHasOutputs = NOTEBOOK_CELL_HAS_OUTPUTS.bindTo(this.contextKeyService);
@ -116,7 +114,6 @@ export class CellContextKeyManager extends Disposable {
private updateForMetadata() {
const metadata = this.element.getEvaluatedMetadata(this.notebookTextModel.metadata);
this.cellEditable.set(!!metadata.editable);
this.cellRunnable.set(!!metadata.runnable);
const runState = metadata.runState ?? NotebookCellRunState.Idle;
this.cellRunState.set(NotebookCellRunState[runState]);

View file

@ -6,6 +6,7 @@
import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
// TODO@roblourens Is this class overkill now?
export class CellMenus {
constructor(
@IMenuService private readonly menuService: IMenuService,
@ -23,10 +24,12 @@ export class CellMenus {
return this.getMenu(MenuId.NotebookCellListTop, contextKeyService);
}
getCellExecuteMenu(contextKeyService: IContextKeyService): IMenu {
return this.getMenu(MenuId.NotebookCellExecute, contextKeyService);
}
private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu {
const menu = this.menuService.createMenu(menuId, contextKeyService);
return menu;
}
}

View file

@ -6,11 +6,11 @@
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IAction } from 'vs/base/common/actions';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@ -36,13 +36,13 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { BOTTOM_CELL_TOOLBAR_GAP, CELL_BOTTOM_MARGIN, CELL_TOP_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR, EDITOR_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants';
import { CancelCellAction, DeleteCellAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { DeleteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, EditorTopPaddingChangeEvent, EXPAND_CELL_CONTENT_COMMAND_ID, getEditorTopPadding, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd';
import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus';
import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets';
import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell';
import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd';
import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
@ -244,7 +244,7 @@ abstract class AbstractCellRenderer {
return toolbar;
}
private getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[] } {
protected getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[] } {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
@ -742,8 +742,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
const cellContainer = DOM.append(container, $('.cell.code'));
const runButtonContainer = DOM.append(cellContainer, $('.run-button-container'));
const runToolbar = disposables.add(this.createToolbar(runButtonContainer));
const runToolbar = disposables.add(this.setupRunToolbar(runButtonContainer, contextKeyService, disposables));
const executionOrderLabel = DOM.append(cellContainer, $('div.execution-count-label'));
const editorPart = DOM.append(cellContainer, $('.cell-editor-part'));
@ -772,7 +772,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart));
const timer = new TimerRenderer(statusBar.durationContainer);
const cellRunState = new RunStateRenderer(statusBar.cellRunStatusContainer, runToolbar, this.instantiationService);
const cellRunState = new RunStateRenderer(statusBar.cellRunStatusContainer);
const outputContainer = DOM.append(container, $('.output'));
const outputShowMoreContainer = DOM.append(container, $('.output-show-more-container'));
@ -834,6 +834,20 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
return templateData;
}
private setupRunToolbar(runButtonContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar {
const runToolbar = this.createToolbar(runButtonContainer);
const runMenu = this.cellMenus.getCellExecuteMenu(contextKeyService);
const update = () => {
const actions = this.getCellToolbarActions(runMenu, false);
runToolbar.setActions(actions.primary, actions.secondary);
};
disposables.add(runMenu.onDidChange(() => {
update();
}));
update();
return runToolbar;
}
private updateForOutputs(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void {
if (element.outputsViewModels.length) {
DOM.show(templateData.focusSinkElement);
@ -848,7 +862,6 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
}
const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel.notebookDocument.metadata);
templateData.container.classList.toggle('runnable', !!metadata.runnable);
this.updateExecutionOrder(metadata, templateData);
templateData.statusBar.cellStatusMessageContainer.textContent = metadata?.statusMessage || '';
@ -1062,7 +1075,7 @@ export class RunStateRenderer {
private spinnerTimer: any | undefined;
private pendingNewState: NotebookCellRunState | undefined;
constructor(private readonly element: HTMLElement, private readonly runToolbar: ToolBar, private readonly instantiationService: IInstantiationService) {
constructor(private readonly element: HTMLElement) {
DOM.hide(element);
}
@ -1079,12 +1092,6 @@ export class RunStateRenderer {
return;
}
if (runState === NotebookCellRunState.Running) {
this.runToolbar.setActions([this.instantiationService.createInstance(CancelCellAction)]);
} else {
this.runToolbar.setActions([this.instantiationService.createInstance(ExecuteCellAction)]);
}
if (runState === NotebookCellRunState.Success) {
DOM.reset(this.element, renderIcon(successStateIcon));
} else if (runState === NotebookCellRunState.Error) {

View file

@ -229,7 +229,6 @@ export class CodeCell extends Disposable {
const updatePlaceholder = () => {
if (this.notebookEditor.viewModel
&& this.notebookEditor.getActiveCell() === this.viewCell
&& viewCell.getEvaluatedMetadata(this.notebookEditor.viewModel.metadata).runnable
&& viewCell.metadata.runState === undefined
&& viewCell.metadata.lastRunDuration === undefined
) {

View file

@ -453,9 +453,6 @@ export abstract class BaseCellViewModel extends Disposable {
const editable = this.metadata?.editable ??
documentMetadata.cellEditable;
const runnable = (this.metadata?.runnable ??
documentMetadata.cellRunnable) && !!documentMetadata.trusted;
const hasExecutionOrder = this.metadata?.hasExecutionOrder ??
documentMetadata.cellHasExecutionOrder;
@ -463,7 +460,6 @@ export abstract class BaseCellViewModel extends Disposable {
...(this.metadata || {}),
...{
editable,
runnable,
hasExecutionOrder
}
};

View file

@ -153,9 +153,6 @@ export class NotebookCellTextModel extends Disposable implements ICell {
const editable = this.metadata?.editable ??
documentMetadata.cellEditable;
const runnable = this.metadata?.runnable ??
documentMetadata.cellRunnable;
const hasExecutionOrder = this.metadata?.hasExecutionOrder ??
documentMetadata.cellHasExecutionOrder;
@ -163,7 +160,6 @@ export class NotebookCellTextModel extends Disposable implements ICell {
...(this.metadata || {}),
...{
editable,
runnable,
hasExecutionOrder
}
};

View file

@ -62,7 +62,6 @@ export const notebookDocumentMetadataDefaults: Required<NotebookDocumentMetadata
editable: true,
runnable: true,
cellEditable: true,
cellRunnable: true,
cellHasExecutionOrder: true,
displayOrder: NOTEBOOK_DISPLAY_ORDER,
custom: {},
@ -74,7 +73,6 @@ export interface NotebookDocumentMetadata {
editable: boolean;
runnable: boolean;
cellEditable: boolean;
cellRunnable: boolean;
cellHasExecutionOrder: boolean;
displayOrder?: (string | glob.IRelativePattern)[];
custom?: { [key: string]: unknown };
@ -91,7 +89,6 @@ export enum NotebookCellRunState {
export interface NotebookCellMetadata {
editable?: boolean;
runnable?: boolean;
breakpointMargin?: boolean;
hasExecutionOrder?: boolean;
executionOrder?: number;

View file

@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as sinon from 'sinon';
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
import { assertThrowsAsync } from 'vs/base/test/common/utils';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { TestQuickInputService } from 'vs/platform/quickinput/test/testQuickInputService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, INotebookKernel, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
suite('NotebookEditorKernelManager', () => {
const instantiationService = setupInstantiationService();
instantiationService.stub(IStorageService, new TestStorageService());
instantiationService.stub(IContextKeyService, new MockContextKeyService());
instantiationService.stub(IQuickInputService, new TestQuickInputService());
const bulkEditService = instantiationService.get(IBulkEditService);
const undoRedoService = instantiationService.get(IUndoRedoService);
const loadKernelPreloads = async () => { };
async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise<void>) {
return _withTestNotebook(instantiationService, bulkEditService, undoRedoService, cells, (_editor, viewModel, textModel) => callback(viewModel, textModel));
}
test('ctor', () => {
instantiationService.createInstance(NotebookEditorKernelManager, {});
const contextKeyService = instantiationService.get(IContextKeyService);
assert.strictEqual(contextKeyService.getContextKeyValue(NOTEBOOK_KERNEL_COUNT.key), 0);
});
test('cell is not runnable when no kernel is selected', async () => {
await withTestNotebook(
[],
async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { viewModel, loadKernelPreloads });
const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell));
});
});
test('cell is not runnable when kernel does not support the language', async () => {
await withTestNotebook(
[],
async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { viewModel, loadKernelPreloads });
kernelManager.activeKernel = new TestNotebookKernel({ languages: ['testlang'] });
const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell));
});
});
test('cell is runnable when kernel does support the language', async () => {
await withTestNotebook(
[],
async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { viewModel, loadKernelPreloads });
const kernel = new TestNotebookKernel({ languages: ['javascript'] });
const executeSpy = sinon.spy();
kernel.executeNotebookCell = executeSpy;
kernelManager.activeKernel = kernel;
const cell = viewModel.createCell(0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
await kernelManager.executeNotebookCell(cell);
assert.strictEqual(executeSpy.calledOnce, true);
});
});
});
class TestNotebookKernel implements INotebookKernel {
id?: string | undefined;
friendlyId: string = '';
label: string = '';
extension: ExtensionIdentifier = new ExtensionIdentifier('test');
extensionLocation: URI = URI.file('/test');
providerHandle?: number | undefined;
description?: string | undefined;
detail?: string | undefined;
isPreferred?: boolean | undefined;
preloads?: URI[] | undefined;
supportedLanguages?: string[] | undefined;
async resolve(uri: URI, editorId: string, token: CancellationToken): Promise<void> { }
executeNotebookCell(uri: URI, handle: number | undefined): Promise<void> {
throw new Error('Method not implemented.');
}
cancelNotebookCell(uri: URI, handle: number | undefined): Promise<void> {
throw new Error('Method not implemented.');
}
constructor(opts?: { languages?: string[] }) {
this.supportedLanguages = opts?.languages;
}
}

View file

@ -17,8 +17,8 @@ suite('NotebookTextModel', () => {
const undoRedoService = instantiationService.stub(IUndoRedoService, () => { });
instantiationService.spy(IUndoRedoService, 'pushElement');
test('insert', function () {
withTestNotebook(
test('insert', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -42,8 +42,8 @@ suite('NotebookTextModel', () => {
);
});
test('multiple inserts at same position', function () {
withTestNotebook(
test('multiple inserts at same position', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -67,8 +67,8 @@ suite('NotebookTextModel', () => {
);
});
test('delete', function () {
withTestNotebook(
test('delete', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -90,8 +90,8 @@ suite('NotebookTextModel', () => {
);
});
test('delete + insert', function () {
withTestNotebook(
test('delete + insert', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -115,8 +115,8 @@ suite('NotebookTextModel', () => {
);
});
test('delete + insert at same position', function () {
withTestNotebook(
test('delete + insert at same position', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -140,8 +140,8 @@ suite('NotebookTextModel', () => {
);
});
test('(replace) delete + insert at same position', function () {
withTestNotebook(
test('(replace) delete + insert at same position', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -164,8 +164,8 @@ suite('NotebookTextModel', () => {
);
});
test('output', function () {
withTestNotebook(
test('output', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -239,8 +239,8 @@ suite('NotebookTextModel', () => {
);
});
test('metadata', function () {
withTestNotebook(
test('metadata', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -279,8 +279,8 @@ suite('NotebookTextModel', () => {
);
});
test('multiple inserts in one edit', function () {
withTestNotebook(
test('multiple inserts in one edit', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
@ -316,8 +316,8 @@ suite('NotebookTextModel', () => {
);
});
test('insert and metadata change in one edit', function () {
withTestNotebook(
test('insert and metadata change in one edit', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,

View file

@ -19,21 +19,21 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
suite('NotebookViewModel', () => {
const instantiationService = setupInstantiationService();
const textModelService = instantiationService.get(ITextModelService);
const blukEditService = instantiationService.get(IBulkEditService);
const bulkEditService = instantiationService.get(IBulkEditService);
const undoRedoService = instantiationService.get(IUndoRedoService);
test('ctor', function () {
const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false }, undoRedoService, textModelService);
const model = new NotebookEditorTestModel(notebook);
const eventDispatcher = new NotebookEventDispatcher();
const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService);
const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, bulkEditService, undoRedoService);
assert.equal(viewModel.viewType, 'notebook');
});
test('insert/delete', function () {
withTestNotebook(
test('insert/delete', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
bulkEditService,
undoRedoService,
[
['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }],
@ -56,10 +56,10 @@ suite('NotebookViewModel', () => {
);
});
test('move cells down', function () {
withTestNotebook(
test('move cells down', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
bulkEditService,
undoRedoService,
[
['//a', 'javascript', CellKind.Code, [], { editable: true }],
@ -87,10 +87,10 @@ suite('NotebookViewModel', () => {
);
});
test('move cells up', function () {
withTestNotebook(
test('move cells up', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
bulkEditService,
undoRedoService,
[
['//a', 'javascript', CellKind.Code, [], { editable: true }],
@ -112,10 +112,10 @@ suite('NotebookViewModel', () => {
);
});
test('index', function () {
withTestNotebook(
test('index', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
bulkEditService,
undoRedoService,
[
['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }],
@ -141,90 +141,79 @@ suite('NotebookViewModel', () => {
);
});
test('metadata', function () {
withTestNotebook(
test('metadata', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
bulkEditService,
undoRedoService,
[
['var a = 1;', 'javascript', CellKind.Code, [], {}],
['var b = 2;', 'javascript', CellKind.Code, [], { editable: true, runnable: true }],
['var c = 3;', 'javascript', CellKind.Code, [], { editable: true, runnable: false }],
['var d = 4;', 'javascript', CellKind.Code, [], { editable: false, runnable: true }],
['var e = 5;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }],
['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }],
['var c = 3;', 'javascript', CellKind.Code, [], { editable: true }],
['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }],
['var e = 5;', 'javascript', CellKind.Code, [], { editable: false }],
],
(editor, viewModel) => {
viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: true, cellEditable: true, cellHasExecutionOrder: true, trusted: true };
viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellEditable: true, cellHasExecutionOrder: true, trusted: true };
const defaults = { hasExecutionOrder: true };
assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: true,
runnable: true,
...defaults
});
assert.deepEqual(viewModel.viewCells[1].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: true,
runnable: true,
...defaults
});
assert.deepEqual(viewModel.viewCells[2].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: true,
runnable: false,
...defaults
});
assert.deepEqual(viewModel.viewCells[3].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: false,
runnable: true,
...defaults
});
assert.deepEqual(viewModel.viewCells[4].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: false,
runnable: false,
...defaults
});
viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: true, cellHasExecutionOrder: true, trusted: true };
viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellEditable: true, cellHasExecutionOrder: true, trusted: true };
assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: true,
runnable: false,
...defaults
});
assert.deepEqual(viewModel.viewCells[1].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: true,
runnable: true,
...defaults
});
assert.deepEqual(viewModel.viewCells[2].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: true,
runnable: false,
...defaults
});
assert.deepEqual(viewModel.viewCells[3].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: false,
runnable: true,
...defaults
});
assert.deepEqual(viewModel.viewCells[4].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: false,
runnable: false,
...defaults
});
viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: false, cellHasExecutionOrder: true, trusted: true };
viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellEditable: false, cellHasExecutionOrder: true, trusted: true };
assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), <NotebookCellMetadata>{
editable: false,
runnable: false,
...defaults
});
}
@ -262,17 +251,17 @@ suite('NotebookViewModel Decorations', () => {
const blukEditService = instantiationService.get(IBulkEditService);
const undoRedoService = instantiationService.get(IUndoRedoService);
test('tracking range', function () {
withTestNotebook(
test('tracking range', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
[
['var a = 1;', 'javascript', CellKind.Code, [], {}],
['var b = 2;', 'javascript', CellKind.Code, [], { editable: true, runnable: true }],
['var c = 3;', 'javascript', CellKind.Code, [], { editable: true, runnable: false }],
['var d = 4;', 'javascript', CellKind.Code, [], { editable: false, runnable: true }],
['var e = 5;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }],
['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }],
['var c = 3;', 'javascript', CellKind.Code, [], { editable: true }],
['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }],
['var e = 5;', 'javascript', CellKind.Code, [], { editable: false }],
],
(editor, viewModel) => {
const trackedId = viewModel.setTrackedRange('test', { start: 1, end: 2 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter);
@ -320,19 +309,19 @@ suite('NotebookViewModel Decorations', () => {
);
});
test('tracking range 2', function () {
withTestNotebook(
test('tracking range 2', async function () {
await withTestNotebook(
instantiationService,
blukEditService,
undoRedoService,
[
['var a = 1;', 'javascript', CellKind.Code, [], {}],
['var b = 2;', 'javascript', CellKind.Code, [], { editable: true, runnable: true }],
['var c = 3;', 'javascript', CellKind.Code, [], { editable: true, runnable: false }],
['var d = 4;', 'javascript', CellKind.Code, [], { editable: false, runnable: true }],
['var e = 5;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }],
['var e = 6;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }],
['var e = 7;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }],
['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }],
['var c = 3;', 'javascript', CellKind.Code, [], { editable: true }],
['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }],
['var e = 5;', 'javascript', CellKind.Code, [], { editable: false }],
['var e = 6;', 'javascript', CellKind.Code, [], { editable: false }],
['var e = 7;', 'javascript', CellKind.Code, [], { editable: false }],
],
(editor, viewModel) => {
const trackedId = viewModel.setTrackedRange('test', { start: 1, end: 3 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter);
@ -359,7 +348,7 @@ suite('NotebookViewModel Decorations', () => {
);
});
test('reduce range', function () {
test('reduce range', async function () {
assert.deepEqual(reduceCellRanges([
{ start: 0, end: 1 },
{ start: 1, end: 2 },
@ -378,7 +367,7 @@ suite('NotebookViewModel Decorations', () => {
]);
});
test('diff hidden ranges', function () {
test('diff hidden ranges', async function () {
assert.deepEqual(getVisibleCells<number>([1, 2, 3, 4, 5], []), [1, 2, 3, 4, 5]);
assert.deepEqual(
@ -420,7 +409,7 @@ suite('NotebookViewModel Decorations', () => {
}), [{ start: 1, deleteCount: 1, toInsert: [2, 6] }]);
});
test('hidden ranges', function () {
test('hidden ranges', async function () {
});
});

View file

@ -456,7 +456,8 @@ export function setupInstantiationService() {
return instantiationService;
}
export function withTestNotebook(instantiationService: TestInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) {
// TODO await all usages
export async function withTestNotebook(instantiationService: TestInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise<void>) {
const textModelService = instantiationService.get(ITextModelService);
const viewType = 'notebook';
@ -474,7 +475,7 @@ export function withTestNotebook(instantiationService: TestInstantiationService,
const eventDispatcher = new NotebookEventDispatcher();
const viewModel = new NotebookViewModel(viewType, model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService);
callback(editor, viewModel, notebook);
await callback(editor, viewModel, notebook);
viewModel.dispose();
return;

View file

@ -4,29 +4,27 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { normalize } from 'vs/base/common/path';
import { Emitter, Event } from 'vs/base/common/event';
import { URI as uri } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { Workspace, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { TestEditorService, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IQuickInputService, IQuickPickItem, QuickPickInput, IPickOptions, Omit, IInputOptions, IQuickInputButton, IQuickPick, IInputBox, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import * as Types from 'vs/base/common/types';
import { EditorType } from 'vs/editor/common/editorCommon';
import { normalize } from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { URI as uri } from 'vs/base/common/uri';
import { Selection } from 'vs/editor/common/core/selection';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { EditorType } from 'vs/editor/common/editorCommon';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IFormatterChangeEvent, ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label';
import { TestQuickInputService } from 'vs/platform/quickinput/test/testQuickInputService';
import { IWorkspace, IWorkspaceFolder, Workspace } from 'vs/platform/workspace/common/workspace';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { TestEditorService, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
const mockLineNumber = 10;
class TestEditorServiceWithActiveEditor extends TestEditorService {
@ -66,13 +64,13 @@ suite('Configuration Resolver Service', () => {
let editorService: TestEditorServiceWithActiveEditor;
let containingWorkspace: Workspace;
let workspace: IWorkspaceFolder;
let quickInputService: MockQuickInputService;
let quickInputService: TestQuickInputService;
let labelService: MockLabelService;
setup(() => {
mockCommandService = new MockCommandService();
editorService = new TestEditorServiceWithActiveEditor();
quickInputService = new MockQuickInputService();
quickInputService = new TestQuickInputService();
environmentService = new MockWorkbenchEnvironmentService(envVariables);
labelService = new MockLabelService();
containingWorkspace = testWorkspace(uri.parse('file:///VSCode/workspaceLocation'));
@ -648,63 +646,6 @@ class MockCommandService implements ICommandService {
}
}
class MockQuickInputService implements IQuickInputService {
declare readonly _serviceBrand: undefined;
readonly onShow = Event.None;
readonly onHide = Event.None;
readonly quickAccess = undefined!;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T | undefined> {
if (Types.isArray(picks)) {
return Promise.resolve(<any>{ label: 'selectedPick', description: 'pick description', value: 'selectedPick' });
} else {
return Promise.resolve(undefined);
}
}
public input(options?: IInputOptions, token?: CancellationToken): Promise<string> {
return Promise.resolve(options ? 'resolved' + options.prompt : 'resolved');
}
backButton!: IQuickInputButton;
createQuickPick<T extends IQuickPickItem>(): IQuickPick<T> {
throw new Error('not implemented.');
}
createInputBox(): IInputBox {
throw new Error('not implemented.');
}
focus(): void {
throw new Error('not implemented.');
}
toggle(): void {
throw new Error('not implemented.');
}
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void {
throw new Error('not implemented.');
}
accept(): Promise<void> {
throw new Error('not implemented.');
}
back(): Promise<void> {
throw new Error('not implemented.');
}
cancel(): Promise<void> {
throw new Error('not implemented.');
}
}
class MockLabelService implements ILabelService {
_serviceBrand: undefined;
getUriLabel(resource: uri, options?: { relative?: boolean | undefined; noPrefix?: boolean | undefined; endWithSeparator?: boolean | undefined; }): string {

View file

@ -653,7 +653,6 @@ suite('ExtHostTypes', function () {
const obj = new types.NotebookDocumentMetadata();
assert.strictEqual(obj.cellEditable, notebookDocumentMetadataDefaults.cellEditable);
assert.strictEqual(obj.cellHasExecutionOrder, notebookDocumentMetadataDefaults.cellHasExecutionOrder);
assert.strictEqual(obj.cellRunnable, notebookDocumentMetadataDefaults.cellRunnable);
assert.deepStrictEqual(obj.custom, notebookDocumentMetadataDefaults.custom);
assert.deepStrictEqual(obj.displayOrder, notebookDocumentMetadataDefaults.displayOrder);
assert.strictEqual(obj.editable, notebookDocumentMetadataDefaults.editable);