Skip to content

Commit a79e28a

Browse files
committed
basic api for cell execution and jupyter extension support
Signed-off-by: Jonah Iden <[email protected]>
1 parent eb95328 commit a79e28a

22 files changed

+1024
-110
lines changed

packages/notebook/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"dependencies": {
66
"@theia/core": "1.38.0",
77
"@theia/filesystem": "1.38.0",
8-
"@theia/monaco": "1.38.0"
8+
"@theia/monaco": "1.38.0",
9+
"uuid": "^8.3.2"
910
},
1011
"publishConfig": {
1112
"access": "public"

packages/notebook/src/browser/contributions/notebook-actions-contribution.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ import { codicon } from '@theia/core/lib/browser';
2020
import { NotebookModel } from '../view-model/notebook-model';
2121
import { NotebookService } from '../service/notebook-service';
2222
import { CellKind } from '../../common';
23+
import { NotebookKernelQuickPickService } from '../service/notebook-kernel-quick-pick-service';
2324

2425
export namespace NotebookCommands {
25-
export const Add_NEW_CELL_COMMAND = Command.toDefaultLocalizedCommand({
26+
export const ADD_NEW_CELL_COMMAND = Command.toDefaultLocalizedCommand({
2627
id: 'notebook.add-new-cell',
2728
iconClass: codicon('add')
2829
});
2930

31+
export const SELECT_KERNEL_COMMAND = Command.toDefaultLocalizedCommand({
32+
id: 'notebook.selectKernel',
33+
category: 'Notebook',
34+
iconClass: codicon('server-environment')
35+
});
3036
}
3137

3238
@injectable()
@@ -35,13 +41,22 @@ export class NotebookActionsContribution implements CommandContribution {
3541
@inject(NotebookService)
3642
protected notebookService: NotebookService;
3743

44+
@inject(NotebookKernelQuickPickService)
45+
protected notebookKernelQuickPickService: NotebookKernelQuickPickService;
46+
3847
registerCommands(commands: CommandRegistry): void {
39-
commands.registerCommand(NotebookCommands.Add_NEW_CELL_COMMAND, {
48+
commands.registerCommand(NotebookCommands.ADD_NEW_CELL_COMMAND, {
4049
execute: (notebookModel: NotebookModel, cellKind: CellKind, index?: number) => {
4150
notebookModel.insertNewCell(index ?? notebookModel.cells.length,
4251
[this.notebookService.createEmptyCellModel(notebookModel, cellKind)]);
4352
}
4453
});
54+
55+
commands.registerCommand(NotebookCommands.SELECT_KERNEL_COMMAND, {
56+
execute: (notebookModel: NotebookModel) => {
57+
this.notebookKernelQuickPickService.showQuickPick(notebookModel);
58+
}
59+
});
4560
}
4661

4762
}

packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { NotebookModel } from '../view-model/notebook-model';
2121
import { NotebookCellModel } from '../view-model/notebook-cell-model';
2222
import { NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NotebookContextKeys } from './notebook-context-keys';
2323
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
24+
import { NotebookExecutionService } from '../service/notebook-execution-service';
2425

2526
export namespace NotebookCellCommands {
2627
export const EDIT_COMMAND = Command.toDefaultLocalizedCommand({
@@ -39,6 +40,10 @@ export namespace NotebookCellCommands {
3940
id: 'notebook.cell.split-cell',
4041
iconClass: codicon('split-vertical'),
4142
});
43+
export const EXECUTE_SINGLE_CELL_COMMAND = Command.toDefaultLocalizedCommand({
44+
id: 'notebook.cell.execute-cell',
45+
iconClass: codicon('play'),
46+
});
4247
}
4348

4449
@injectable()
@@ -47,13 +52,8 @@ export class NotebookCellActionContribution implements MenuContribution, Command
4752
@inject(ContextKeyService)
4853
protected contextKeyService: ContextKeyService;
4954

50-
protected runDeleteAction(notebookModel: NotebookModel, cell: NotebookCellModel): void {
51-
notebookModel.removeCell(notebookModel.cells.indexOf(cell), 1);
52-
}
53-
54-
protected requestCellEdit(notebookModel: NotebookModel, cell: NotebookCellModel): void {
55-
cell.requestEdit();
56-
}
55+
@inject(NotebookExecutionService)
56+
protected notebookExecutionService: NotebookExecutionService;
5757

5858
@postConstruct()
5959
protected init(): void {
@@ -74,6 +74,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command
7474
when: `${NOTEBOOK_CELL_TYPE} == 'markdown' && ${NOTEBOOK_CELL_MARKDOWN_EDIT_MODE}`,
7575
order: '10'
7676
});
77+
menus.registerMenuAction([menuId], {
78+
commandId: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.id,
79+
icon: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.iconClass,
80+
when: `${NOTEBOOK_CELL_TYPE} == 'code'`,
81+
order: '10'
82+
});
7783
menus.registerMenuAction([menuId], {
7884
commandId: NotebookCellCommands.SPLIT_CELL_COMMAND.id,
7985
icon: NotebookCellCommands.SPLIT_CELL_COMMAND.iconClass,
@@ -86,18 +92,23 @@ export class NotebookCellActionContribution implements MenuContribution, Command
8692
});
8793

8894
const moreMenuPath = [menuId, 'more'];
89-
menus.registerSubmenu(moreMenuPath, 'more', { icon: codicon('ellipsis'), role: CompoundMenuNodeRole.Submenu, order: '100' });
95+
menus.registerSubmenu(moreMenuPath, 'more', { icon: codicon('ellipsis'), role: CompoundMenuNodeRole.Submenu, order: '999' });
9096
menus.registerMenuAction(moreMenuPath, {
9197
commandId: NotebookCellCommands.EDIT_COMMAND.id,
9298
label: 'test submenu item',
9399
});
94100
}
95101

96102
registerCommands(commands: CommandRegistry): void {
97-
commands.registerCommand(NotebookCellCommands.EDIT_COMMAND, { execute: this.requestCellEdit });
103+
commands.registerCommand(NotebookCellCommands.EDIT_COMMAND, { execute: (_, cell: NotebookCellModel) => cell.requestEdit() });
98104
commands.registerCommand(NotebookCellCommands.STOP_EDIT_COMMAND, { execute: (_, cell: NotebookCellModel) => cell.requestStopEdit() });
99-
commands.registerCommand(NotebookCellCommands.DELETE_COMMAND, { execute: this.runDeleteAction });
105+
commands.registerCommand(NotebookCellCommands.DELETE_COMMAND, {
106+
execute: (notebookModel: NotebookModel, cell: NotebookCellModel) => notebookModel.removeCell(notebookModel.cells.indexOf(cell), 1)
107+
});
100108
commands.registerCommand(NotebookCellCommands.SPLIT_CELL_COMMAND);
101-
}
102109

110+
commands.registerCommand(NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND, {
111+
execute: (notebookModel: NotebookModel, cell: NotebookCellModel) => this.notebookExecutionService.executeNotebookCells(notebookModel, [cell])
112+
});
113+
}
103114
}

packages/notebook/src/browser/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ export * from './notebook-type-registry';
1818
export * from './notebook-editor-widget';
1919
export * from './service/notebook-service';
2020
export * from './service/notebook-kernel-service';
21+
export * from './service/notebook-execution-state-service';
2122
export * from './service/notebook-model-resolver-service';

packages/notebook/src/browser/notebook-frontend-module.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ import { createNotebookEditorWidgetContainer, NotebookEditorContainerFactory, No
3232
import { NotebookCodeCellRenderer } from './view/notebook-code-cell-view';
3333
import { NotebookMarkdownCellRenderer } from './view/notebook-markdown-cell-view';
3434
import { NotebookActionsContribution } from './contributions/notebook-actions-contribution';
35+
import { NotebookExecutionService } from './service/notebook-execution-service';
36+
import { NotebookExecutionStateService } from './service/notebook-execution-state-service';
37+
import { NotebookKernelService } from './service/notebook-kernel-service';
38+
import { NotebookKernelQuickPickService } from './service/notebook-kernel-quick-pick-service';
3539

3640
export default new ContainerModule(bind => {
3741
bindContributionProvider(bind, Symbol('notebooks'));
@@ -45,6 +49,10 @@ export default new ContainerModule(bind => {
4549
bind(NotebookCellToolbarFactory).toSelf().inSingletonScope();
4650

4751
bind(NotebookService).toSelf().inSingletonScope();
52+
bind(NotebookExecutionService).toSelf().inSingletonScope();
53+
bind(NotebookExecutionStateService).toSelf().inSingletonScope();
54+
bind(NotebookKernelService).toSelf().inSingletonScope();
55+
bind(NotebookKernelQuickPickService).toSelf().inSingletonScope();
4856

4957
bind(NotebookCellResourceResolver).toSelf().inSingletonScope();
5058
bind(ResourceResolver).toService(NotebookCellResourceResolver);
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 TypeFox and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
/*---------------------------------------------------------------------------------------------
17+
* Copyright (c) Microsoft Corporation. All rights reserved.
18+
* Licensed under the MIT License. See License.txt in the project root for license information.
19+
*--------------------------------------------------------------------------------------------*/
20+
21+
import { inject, injectable } from '@theia/core/shared/inversify';
22+
import { CellExecution, NotebookExecutionStateService } from '../service/notebook-execution-state-service';
23+
import { CellKind, NotebookCellExecutionState } from '../../common';
24+
import { NotebookCellModel } from '../view-model/notebook-cell-model';
25+
import { NotebookModel } from '../view-model/notebook-model';
26+
import { NotebookKernelService } from './notebook-kernel-service';
27+
import { Disposable } from '@theia/core';
28+
29+
export interface CellExecutionParticipant {
30+
onWillExecuteCell(executions: CellExecution[]): Promise<void>;
31+
}
32+
33+
@injectable()
34+
export class NotebookExecutionService {
35+
36+
@inject(NotebookExecutionStateService)
37+
protected notebookExecutionStateService: NotebookExecutionStateService;
38+
39+
@inject(NotebookKernelService)
40+
protected notebookKernelService: NotebookKernelService;
41+
42+
private readonly cellExecutionParticipants = new Set<CellExecutionParticipant>();
43+
44+
async executeNotebookCells(notebook: NotebookModel, cells: Iterable<NotebookCellModel>): Promise<void> {
45+
const cellsArr = Array.from(cells)
46+
.filter(c => c.cellKind === CellKind.Code);
47+
if (!cellsArr.length) {
48+
return;
49+
}
50+
51+
console.debug(`NotebookExecutionService#executeNotebookCells ${JSON.stringify(cellsArr.map(c => c.handle))}`);
52+
53+
// create cell executions
54+
const cellExecutions: [NotebookCellModel, CellExecution][] = [];
55+
for (const cell of cellsArr) {
56+
const cellExe = this.notebookExecutionStateService.getCellExecution(cell.uri);
57+
if (!cellExe) {
58+
cellExecutions.push([cell, this.notebookExecutionStateService.createCellExecution(notebook.uri, cell.handle)]);
59+
}
60+
}
61+
62+
const kernel = this.notebookKernelService.getSelectedOrSuggestedKernel(notebook);
63+
64+
if (!kernel) {
65+
// clear all pending cell executions
66+
cellExecutions.forEach(cellExe => cellExe[1].complete({}));
67+
return;
68+
}
69+
70+
// filter cell executions based on selected kernel
71+
const validCellExecutions: CellExecution[] = [];
72+
for (const [cell, cellExecution] of cellExecutions) {
73+
if (!kernel.supportedLanguages.includes(cell.language)) {
74+
cellExecution.complete({});
75+
} else {
76+
validCellExecutions.push(cellExecution);
77+
}
78+
}
79+
80+
// request execution
81+
if (validCellExecutions.length > 0) {
82+
await this.runExecutionParticipants(validCellExecutions);
83+
84+
this.notebookKernelService.selectKernelForNotebook(kernel, notebook);
85+
await kernel.executeNotebookCellsRequest(notebook.uri, validCellExecutions.map(c => c.cellHandle));
86+
// the connecting state can change before the kernel resolves executeNotebookCellsRequest
87+
const unconfirmed = validCellExecutions.filter(exe => exe.state === NotebookCellExecutionState.Unconfirmed);
88+
if (unconfirmed.length) {
89+
console.debug(`NotebookExecutionService#executeNotebookCells completing unconfirmed executions ${JSON.stringify(unconfirmed.map(exe => exe.cellHandle))}`);
90+
unconfirmed.forEach(exe => exe.complete({}));
91+
}
92+
}
93+
}
94+
95+
registerExecutionParticipant(participant: CellExecutionParticipant): Disposable {
96+
this.cellExecutionParticipants.add(participant);
97+
return Disposable.create(() => this.cellExecutionParticipants.delete(participant));
98+
}
99+
100+
private async runExecutionParticipants(executions: CellExecution[]): Promise<void> {
101+
for (const participant of this.cellExecutionParticipants) {
102+
await participant.onWillExecuteCell(executions);
103+
}
104+
return;
105+
}
106+
107+
async cancelNotebookCellHandles(notebook: NotebookModel, cells: Iterable<number>): Promise<void> {
108+
const cellsArr = Array.from(cells);
109+
console.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`);
110+
const kernel = this.notebookKernelService.getSelectedOrSuggestedKernel(notebook);
111+
if (kernel) {
112+
await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr);
113+
114+
}
115+
}
116+
117+
async cancelNotebookCells(notebook: NotebookModel, cells: Iterable<NotebookCellModel>): Promise<void> {
118+
this.cancelNotebookCellHandles(notebook, Array.from(cells, cell => cell.handle));
119+
}
120+
}

0 commit comments

Comments
 (0)