UNPKG

thebe-core

Version:

Typescript based core functionality for Thebe

205 lines (183 loc) 5.73 kB
import type { CellKind, IThebeCell, IThebeCellExecuteReturn, JsonObject } from './types'; import type ThebeSession from './session'; import PassiveCellRenderer from './passive'; import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import type { Config } from './config'; import { CellStatusEvent, ErrorStatusEvent, errorToMessage, EventSubject } from './events'; import { EventEmitter } from './emitter'; import type { ICodeCell, IError, IOutput } from '@jupyterlab/nbformat'; import { ensureString, shortId } from './utils'; class ThebeCodeCell extends PassiveCellRenderer implements IThebeCell { kind: CellKind; source: string; metadata: JsonObject; session?: ThebeSession; executionCount: number | null; protected initialOutputs: IOutput[]; readonly notebookId: string; protected busy: boolean; protected events: EventEmitter; constructor( id: string, notebookId: string, source: string, config: Config, metadata: JsonObject, rendermime: IRenderMimeRegistry, ) { super(id, rendermime); this.kind = 'code'; this.events = new EventEmitter(id, config, EventSubject.cell, this); this.notebookId = notebookId; this.source = source; this.metadata = metadata; this.busy = false; this.executionCount = null; this.initialOutputs = []; console.debug('thebe:cell constructor', this); } static fromICodeCell( icc: ICodeCell, notebookId: string, config: Config, rendermime: IRenderMimeRegistry, ) { const cell = new ThebeCodeCell( icc.id ?? shortId(), notebookId, ensureString(icc.source), config, icc.metadata, rendermime, ); Object.assign(cell.metadata, icc.metadata); return cell; } get isBusy() { return this.busy; } get isAttached() { return this.session !== undefined; } get tags(): string[] { return this.metadata.tags ?? []; } /** * Attaches to the session and adds the widgets factory to the rendermine registry * call this version if using ThebeCell in isolation, otherwise call ThebeNotebook::attachSession * * @param session */ attachSession(session: ThebeSession) { this.session = session; this.events.triggerStatus({ status: CellStatusEvent.attached, message: 'Attached to session', }); } /** * Detaches from the session and removes the widgets factory from the rendermine registry * call this version if using ThebeCell in isolation, otherwise call ThebeNotebook::detachSession * */ detachSession() { this.session = undefined; this.events.triggerStatus({ status: CellStatusEvent.detached, message: 'Detached from session', }); } setAsBusy() { console.debug(`thebe:renderer:message:busy ${this.id}`); this.busy = true; this.events.triggerStatus({ status: CellStatusEvent.executing, message: 'Executing...', }); } setAsIdle() { console.debug(`thebe:renderer:message:completed ${this.id}`); this.busy = false; this.events.triggerStatus({ status: CellStatusEvent.idle, message: 'Completed', }); } /** * reset the DOM representation of the cell to the initial state * along with the execution count * * @param hideWidgets boolean - if true, hide widgets */ initOutputs(initialOutputs: IOutput[]) { this.initialOutputs = initialOutputs; this.render(initialOutputs); this.executionCount = null; } /** * reset the DOM representation of the cell to the initial state * along with the execution count * * @param hideWidgets boolean - if true, hide widgets */ reset() { this.render(this.initialOutputs); this.executionCount = null; } /** * TODO * - pass execute_count or timestamp or something back to redux on success/failure? * * @param source? * @returns */ async execute(source?: string): Promise<IThebeCellExecuteReturn | null> { if (!this.session || !this.session.kernel) { console.warn('Attempting to execute on a cell without an attached kernel'); return null; } const code = source ?? this.source; try { console.debug(`thebe:renderer:execute ${this.id}`); if (!this.isBusy) this.setAsBusy(); this.area.future = this.session.kernel.requestExecute({ code }); // TODO consider how to enable execution without the await here const reply = await this.area.future.done; this.executionCount = reply.content.execution_count; let executeErrors: IError[] | undefined; for (let i = 0; i < this.model.length; i++) { const out = this.model.get(i); console.debug('thebecell:execute:output', { out: out.toJSON() }); if (out.type === 'error') { const json = out.toJSON() as IError; if (json.ename === 'stderr') { this.events.triggerError({ status: ErrorStatusEvent.warning, message: errorToMessage(json), }); } else { if (!executeErrors) executeErrors = [json]; else executeErrors?.push(json); this.events.triggerError({ status: ErrorStatusEvent.executeError, message: errorToMessage(json), }); } } } this.setAsIdle(); return { id: this.id, height: this.area.node.offsetHeight, width: this.area.node.offsetWidth, error: executeErrors, }; } catch (err: any) { console.error('thebe:renderer:execute Error:', err); this.clearOnError(err); this.events.triggerError(err.message); return null; } } } export default ThebeCodeCell;