widget.ts 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { IClientSession } from '@jupyterlab/apputils';
  4. import {
  5. Cell,
  6. CellDragUtils,
  7. CellModel,
  8. CodeCell,
  9. CodeCellModel,
  10. ICodeCellModel,
  11. isCodeCellModel,
  12. IRawCellModel,
  13. RawCell,
  14. RawCellModel
  15. } from '@jupyterlab/cells';
  16. import { IEditorMimeTypeService, CodeEditor } from '@jupyterlab/codeeditor';
  17. import { nbformat } from '@jupyterlab/coreutils';
  18. import { IObservableList, ObservableList } from '@jupyterlab/observables';
  19. import { RenderMimeRegistry } from '@jupyterlab/rendermime';
  20. import { KernelMessage } from '@jupyterlab/services';
  21. import { each } from '@phosphor/algorithm';
  22. import { MimeData, JSONObject } from '@phosphor/coreutils';
  23. import { Drag } from '@phosphor/dragdrop';
  24. import { Message } from '@phosphor/messaging';
  25. import { ISignal, Signal } from '@phosphor/signaling';
  26. import { Panel, PanelLayout, Widget } from '@phosphor/widgets';
  27. import { ConsoleHistory, IConsoleHistory } from './history';
  28. /**
  29. * The data attribute added to a widget that has an active kernel.
  30. */
  31. const KERNEL_USER = 'jpKernelUser';
  32. /**
  33. * The data attribute added to a widget can run code.
  34. */
  35. const CODE_RUNNER = 'jpCodeRunner';
  36. /**
  37. * The class name added to console widgets.
  38. */
  39. const CONSOLE_CLASS = 'jp-CodeConsole';
  40. /**
  41. * The class added to console cells
  42. */
  43. const CONSOLE_CELL_CLASS = 'jp-Console-cell';
  44. /**
  45. * The class name added to the console banner.
  46. */
  47. const BANNER_CLASS = 'jp-CodeConsole-banner';
  48. /**
  49. * The class name of the active prompt cell.
  50. */
  51. const PROMPT_CLASS = 'jp-CodeConsole-promptCell';
  52. /**
  53. * The class name of the panel that holds cell content.
  54. */
  55. const CONTENT_CLASS = 'jp-CodeConsole-content';
  56. /**
  57. * The class name of the panel that holds prompts.
  58. */
  59. const INPUT_CLASS = 'jp-CodeConsole-input';
  60. /**
  61. * The timeout in ms for execution requests to the kernel.
  62. */
  63. const EXECUTION_TIMEOUT = 250;
  64. /**
  65. * The mimetype used for Jupyter cell data.
  66. */
  67. const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
  68. /**
  69. * A widget containing a Jupyter console.
  70. *
  71. * #### Notes
  72. * The CodeConsole class is intended to be used within a ConsolePanel
  73. * instance. Under most circumstances, it is not instantiated by user code.
  74. */
  75. export class CodeConsole extends Widget {
  76. /**
  77. * Construct a console widget.
  78. */
  79. constructor(options: CodeConsole.IOptions) {
  80. super();
  81. this.addClass(CONSOLE_CLASS);
  82. this.node.dataset[KERNEL_USER] = 'true';
  83. this.node.dataset[CODE_RUNNER] = 'true';
  84. this.node.tabIndex = -1; // Allow the widget to take focus.
  85. // Create the panels that hold the content and input.
  86. const layout = (this.layout = new PanelLayout());
  87. this._cells = new ObservableList<Cell>();
  88. this._content = new Panel();
  89. this._input = new Panel();
  90. this.contentFactory =
  91. options.contentFactory || CodeConsole.defaultContentFactory;
  92. this.modelFactory = options.modelFactory || CodeConsole.defaultModelFactory;
  93. this.rendermime = options.rendermime;
  94. this.session = options.session;
  95. this._mimeTypeService = options.mimeTypeService;
  96. // Add top-level CSS classes.
  97. this._content.addClass(CONTENT_CLASS);
  98. this._input.addClass(INPUT_CLASS);
  99. // Insert the content and input panes into the widget.
  100. layout.addWidget(this._content);
  101. layout.addWidget(this._input);
  102. this._history = new ConsoleHistory({
  103. session: this.session
  104. });
  105. this._onKernelChanged();
  106. this.session.kernelChanged.connect(
  107. this._onKernelChanged,
  108. this
  109. );
  110. this.session.statusChanged.connect(
  111. this._onKernelStatusChanged,
  112. this
  113. );
  114. }
  115. /**
  116. * A signal emitted when the console finished executing its prompt cell.
  117. */
  118. get executed(): ISignal<this, Date> {
  119. return this._executed;
  120. }
  121. /**
  122. * A signal emitted when a new prompt cell is created.
  123. */
  124. get promptCellCreated(): ISignal<this, CodeCell> {
  125. return this._promptCellCreated;
  126. }
  127. /**
  128. * The content factory used by the console.
  129. */
  130. readonly contentFactory: CodeConsole.IContentFactory;
  131. /**
  132. * The model factory for the console widget.
  133. */
  134. readonly modelFactory: CodeConsole.IModelFactory;
  135. /**
  136. * The rendermime instance used by the console.
  137. */
  138. readonly rendermime: RenderMimeRegistry;
  139. /**
  140. * The client session used by the console.
  141. */
  142. readonly session: IClientSession;
  143. /**
  144. * The list of content cells in the console.
  145. *
  146. * #### Notes
  147. * This list does not include the current banner or the prompt for a console.
  148. * It may include previous banners as raw cells.
  149. */
  150. get cells(): IObservableList<Cell> {
  151. return this._cells;
  152. }
  153. /*
  154. * The console input prompt cell.
  155. */
  156. get promptCell(): CodeCell | null {
  157. let inputLayout = this._input.layout as PanelLayout;
  158. return (inputLayout.widgets[0] as CodeCell) || null;
  159. }
  160. /**
  161. * Add a new cell to the content panel.
  162. *
  163. * @param cell - The code cell widget being added to the content panel.
  164. *
  165. * @param msgId - The optional execution message id for the cell.
  166. *
  167. * #### Notes
  168. * This method is meant for use by outside classes that want to add cells to a
  169. * console. It is distinct from the `inject` method in that it requires
  170. * rendered code cell widgets and does not execute them (though it can store
  171. * the execution message id).
  172. */
  173. addCell(cell: CodeCell, msgId?: string) {
  174. cell.addClass(CONSOLE_CELL_CLASS);
  175. this._content.addWidget(cell);
  176. this._cells.push(cell);
  177. if (msgId) {
  178. this._msgIds.set(msgId, cell);
  179. this._msgIdCells.set(cell, msgId);
  180. }
  181. cell.disposed.connect(
  182. this._onCellDisposed,
  183. this
  184. );
  185. this.update();
  186. }
  187. /**
  188. * Add a banner cell.
  189. */
  190. addBanner() {
  191. if (this._banner) {
  192. // An old banner just becomes a normal cell now.
  193. let cell = this._banner;
  194. this._cells.push(this._banner);
  195. cell.disposed.connect(
  196. this._onCellDisposed,
  197. this
  198. );
  199. }
  200. // Create the banner.
  201. let model = this.modelFactory.createRawCell({});
  202. model.value.text = '...';
  203. let banner = (this._banner = new RawCell({
  204. model,
  205. contentFactory: this.contentFactory
  206. })).initializeState();
  207. banner.addClass(BANNER_CLASS);
  208. banner.readOnly = true;
  209. this._content.addWidget(banner);
  210. }
  211. /**
  212. * Clear the code cells.
  213. */
  214. clear(): void {
  215. // Dispose all the content cells
  216. let cells = this._cells;
  217. while (cells.length > 0) {
  218. cells.get(0).dispose();
  219. }
  220. }
  221. /**
  222. * Create a new cell with the built-in factory.
  223. */
  224. createCodeCell(): CodeCell {
  225. let factory = this.contentFactory;
  226. let options = this._createCodeCellOptions();
  227. let cell = factory.createCodeCell(options);
  228. cell.readOnly = true;
  229. cell.model.mimeType = this._mimetype;
  230. return cell;
  231. }
  232. /**
  233. * Dispose of the resources held by the widget.
  234. */
  235. dispose() {
  236. // Do nothing if already disposed.
  237. if (this.isDisposed) {
  238. return;
  239. }
  240. this._cells.clear();
  241. this._msgIdCells = null;
  242. this._msgIds = null;
  243. this._history.dispose();
  244. super.dispose();
  245. }
  246. /**
  247. * Execute the current prompt.
  248. *
  249. * @param force - Whether to force execution without checking code
  250. * completeness.
  251. *
  252. * @param timeout - The length of time, in milliseconds, that the execution
  253. * should wait for the API to determine whether code being submitted is
  254. * incomplete before attempting submission anyway. The default value is `250`.
  255. */
  256. execute(force = false, timeout = EXECUTION_TIMEOUT): Promise<void> {
  257. if (this.session.status === 'dead') {
  258. return Promise.resolve(void 0);
  259. }
  260. const promptCell = this.promptCell;
  261. if (!promptCell) {
  262. return Promise.reject('Cannot execute without a prompt cell');
  263. }
  264. promptCell.model.trusted = true;
  265. if (force) {
  266. // Create a new prompt cell before kernel execution to allow typeahead.
  267. this.newPromptCell();
  268. return this._execute(promptCell);
  269. }
  270. // Check whether we should execute.
  271. return this._shouldExecute(timeout).then(should => {
  272. if (this.isDisposed) {
  273. return;
  274. }
  275. if (should) {
  276. // Create a new prompt cell before kernel execution to allow typeahead.
  277. this.newPromptCell();
  278. this.promptCell!.editor.focus();
  279. return this._execute(promptCell);
  280. } else {
  281. // add a newline if we shouldn't execute
  282. promptCell.editor.newIndentedLine();
  283. }
  284. });
  285. }
  286. /**
  287. * Get a cell given a message id.
  288. *
  289. * @param msgId - The message id.
  290. */
  291. getCell(msgId: string): CodeCell | undefined {
  292. return this._msgIds.get(msgId);
  293. }
  294. /**
  295. * Inject arbitrary code for the console to execute immediately.
  296. *
  297. * @param code - The code contents of the cell being injected.
  298. *
  299. * @returns A promise that indicates when the injected cell's execution ends.
  300. */
  301. inject(code: string, metadata: JSONObject = {}): Promise<void> {
  302. let cell = this.createCodeCell();
  303. cell.model.value.text = code;
  304. for (let key of Object.keys(metadata)) {
  305. cell.model.metadata.set(key, metadata[key]);
  306. }
  307. this.addCell(cell);
  308. return this._execute(cell);
  309. }
  310. /**
  311. * Insert a line break in the prompt cell.
  312. */
  313. insertLinebreak(): void {
  314. let promptCell = this.promptCell;
  315. if (!promptCell) {
  316. return;
  317. }
  318. promptCell.editor.newIndentedLine();
  319. }
  320. /**
  321. * Serialize the output.
  322. *
  323. * #### Notes
  324. * This only serializes the code cells and the prompt cell if it exists, and
  325. * skips any old banner cells.
  326. */
  327. serialize(): nbformat.ICodeCell[] {
  328. const cells: nbformat.ICodeCell[] = [];
  329. each(this._cells, cell => {
  330. let model = cell.model;
  331. if (isCodeCellModel(model)) {
  332. cells.push(model.toJSON());
  333. }
  334. });
  335. if (this.promptCell) {
  336. cells.push(this.promptCell.model.toJSON());
  337. }
  338. return cells;
  339. }
  340. /**
  341. * Handle `mousedown` events for the widget.
  342. */
  343. private _evtMouseDown(event: MouseEvent): void {
  344. const { button, shiftKey } = event;
  345. // We only handle main or secondary button actions.
  346. if (
  347. !(button === 0 || button === 2) ||
  348. // Shift right-click gives the browser default behavior.
  349. (shiftKey && button === 2)
  350. ) {
  351. return;
  352. }
  353. let target = event.target as HTMLElement;
  354. let cellFilter = (node: HTMLElement) =>
  355. node.classList.contains(CONSOLE_CELL_CLASS);
  356. let cellIndex = CellDragUtils.findCell(target, this._cells, cellFilter);
  357. if (cellIndex === -1) {
  358. // `event.target` sometimes gives an orphaned node in
  359. // Firefox 57, which can have `null` anywhere in its parent line. If we fail
  360. // to find a cell using `event.target`, try again using a target
  361. // reconstructed from the position of the click event.
  362. target = document.elementFromPoint(
  363. event.clientX,
  364. event.clientY
  365. ) as HTMLElement;
  366. cellIndex = CellDragUtils.findCell(target, this._cells, cellFilter);
  367. }
  368. if (cellIndex === -1) {
  369. return;
  370. }
  371. const cell = this._cells.get(cellIndex);
  372. let targetArea: CellDragUtils.ICellTargetArea = CellDragUtils.detectTargetArea(
  373. cell,
  374. event.target as HTMLElement
  375. );
  376. if (targetArea === 'prompt') {
  377. this._dragData = {
  378. pressX: event.clientX,
  379. pressY: event.clientY,
  380. index: cellIndex
  381. };
  382. this._focusedCell = cell;
  383. document.addEventListener('mouseup', this, true);
  384. document.addEventListener('mousemove', this, true);
  385. event.preventDefault();
  386. }
  387. }
  388. /**
  389. * Handle `mousemove` event of widget
  390. */
  391. private _evtMouseMove(event: MouseEvent) {
  392. const data = this._dragData;
  393. if (
  394. CellDragUtils.shouldStartDrag(
  395. data.pressX,
  396. data.pressY,
  397. event.clientX,
  398. event.clientY
  399. )
  400. ) {
  401. void this._startDrag(data.index, event.clientX, event.clientY);
  402. }
  403. }
  404. /**
  405. * Start a drag event
  406. */
  407. private _startDrag(
  408. index: number,
  409. clientX: number,
  410. clientY: number
  411. ): Promise<void> {
  412. const cellModel = this._focusedCell.model as ICodeCellModel;
  413. let selected: nbformat.ICell[] = [cellModel.toJSON()];
  414. const dragImage = CellDragUtils.createCellDragImage(
  415. this._focusedCell,
  416. selected
  417. );
  418. this._drag = new Drag({
  419. mimeData: new MimeData(),
  420. dragImage,
  421. proposedAction: 'copy',
  422. supportedActions: 'copy',
  423. source: this
  424. });
  425. this._drag.mimeData.setData(JUPYTER_CELL_MIME, selected);
  426. const textContent = cellModel.value.text;
  427. this._drag.mimeData.setData('text/plain', textContent);
  428. this._focusedCell = null;
  429. document.removeEventListener('mousemove', this, true);
  430. document.removeEventListener('mouseup', this, true);
  431. return this._drag.start(clientX, clientY).then(() => {
  432. if (this.isDisposed) {
  433. return;
  434. }
  435. this._drag = null;
  436. this._dragData = null;
  437. });
  438. }
  439. /**
  440. * Handle the DOM events for the widget.
  441. *
  442. * @param event -The DOM event sent to the widget.
  443. *
  444. * #### Notes
  445. * This method implements the DOM `EventListener` interface and is
  446. * called in response to events on the notebook panel's node. It should
  447. * not be called directly by user code.
  448. */
  449. handleEvent(event: Event): void {
  450. switch (event.type) {
  451. case 'keydown':
  452. this._evtKeyDown(event as KeyboardEvent);
  453. break;
  454. case 'mousedown':
  455. this._evtMouseDown(event as MouseEvent);
  456. break;
  457. case 'mousemove':
  458. this._evtMouseMove(event as MouseEvent);
  459. break;
  460. case 'mouseup':
  461. this._evtMouseUp(event as MouseEvent);
  462. break;
  463. default:
  464. break;
  465. }
  466. }
  467. /**
  468. * Handle `after_attach` messages for the widget.
  469. */
  470. protected onAfterAttach(msg: Message): void {
  471. let node = this.node;
  472. node.addEventListener('keydown', this, true);
  473. node.addEventListener('click', this);
  474. node.addEventListener('mousedown', this);
  475. // Create a prompt if necessary.
  476. if (!this.promptCell) {
  477. this.newPromptCell();
  478. } else {
  479. this.promptCell.editor.focus();
  480. this.update();
  481. }
  482. }
  483. /**
  484. * Handle `before-detach` messages for the widget.
  485. */
  486. protected onBeforeDetach(msg: Message): void {
  487. let node = this.node;
  488. node.removeEventListener('keydown', this, true);
  489. node.removeEventListener('click', this);
  490. }
  491. /**
  492. * Handle `'activate-request'` messages.
  493. */
  494. protected onActivateRequest(msg: Message): void {
  495. let editor = this.promptCell && this.promptCell.editor;
  496. if (editor) {
  497. editor.focus();
  498. }
  499. this.update();
  500. }
  501. /**
  502. * Make a new prompt cell.
  503. */
  504. protected newPromptCell(): void {
  505. let promptCell = this.promptCell;
  506. let input = this._input;
  507. // Make the last prompt read-only, clear its signals, and move to content.
  508. if (promptCell) {
  509. promptCell.readOnly = true;
  510. promptCell.removeClass(PROMPT_CLASS);
  511. Signal.clearData(promptCell.editor);
  512. let child = input.widgets[0];
  513. child.parent = null;
  514. this.addCell(promptCell);
  515. }
  516. // Create the new prompt cell.
  517. let factory = this.contentFactory;
  518. let options = this._createCodeCellOptions();
  519. promptCell = factory.createCodeCell(options);
  520. promptCell.model.mimeType = this._mimetype;
  521. promptCell.addClass(PROMPT_CLASS);
  522. this._input.addWidget(promptCell);
  523. // Suppress the default "Enter" key handling.
  524. let editor = promptCell.editor;
  525. editor.addKeydownHandler(this._onEditorKeydown);
  526. this._history.editor = editor;
  527. this._promptCellCreated.emit(promptCell);
  528. }
  529. /**
  530. * Handle `update-request` messages.
  531. */
  532. protected onUpdateRequest(msg: Message): void {
  533. Private.scrollToBottom(this._content.node);
  534. }
  535. /**
  536. * Handle the `'keydown'` event for the widget.
  537. */
  538. private _evtKeyDown(event: KeyboardEvent): void {
  539. let editor = this.promptCell && this.promptCell.editor;
  540. if (!editor) {
  541. return;
  542. }
  543. if (event.keyCode === 13 && !editor.hasFocus()) {
  544. event.preventDefault();
  545. editor.focus();
  546. }
  547. }
  548. /**
  549. * Handle the `'mouseup'` event for the widget.
  550. */
  551. private _evtMouseUp(event: MouseEvent): void {
  552. if (
  553. this.promptCell &&
  554. this.promptCell.node.contains(event.target as HTMLElement)
  555. ) {
  556. this.promptCell.editor.focus();
  557. }
  558. }
  559. /**
  560. * Execute the code in the current prompt cell.
  561. */
  562. private _execute(cell: CodeCell): Promise<void> {
  563. let source = cell.model.value.text;
  564. this._history.push(source);
  565. // If the source of the console is just "clear", clear the console as we
  566. // do in IPython or QtConsole.
  567. if (source === 'clear' || source === '%clear') {
  568. this.clear();
  569. return Promise.resolve(void 0);
  570. }
  571. cell.model.contentChanged.connect(
  572. this.update,
  573. this
  574. );
  575. let onSuccess = (value: KernelMessage.IExecuteReplyMsg) => {
  576. if (this.isDisposed) {
  577. return;
  578. }
  579. if (value && value.content.status === 'ok') {
  580. let content = value.content as KernelMessage.IExecuteOkReply;
  581. // Use deprecated payloads for backwards compatibility.
  582. if (content.payload && content.payload.length) {
  583. let setNextInput = content.payload.filter(i => {
  584. return (i as any).source === 'set_next_input';
  585. })[0];
  586. if (setNextInput) {
  587. let text = (setNextInput as any).text;
  588. // Ignore the `replace` value and always set the next cell.
  589. cell.model.value.text = text;
  590. }
  591. }
  592. } else if (value && value.content.status === 'error') {
  593. each(this._cells, (cell: CodeCell) => {
  594. if (cell.model.executionCount === null) {
  595. cell.setPrompt('');
  596. }
  597. });
  598. }
  599. cell.model.contentChanged.disconnect(this.update, this);
  600. this.update();
  601. this._executed.emit(new Date());
  602. };
  603. let onFailure = () => {
  604. if (this.isDisposed) {
  605. return;
  606. }
  607. cell.model.contentChanged.disconnect(this.update, this);
  608. this.update();
  609. };
  610. return CodeCell.execute(cell, this.session).then(onSuccess, onFailure);
  611. }
  612. /**
  613. * Update the console based on the kernel info.
  614. */
  615. private _handleInfo(info: KernelMessage.IInfoReply): void {
  616. this._banner.model.value.text = info.banner;
  617. let lang = info.language_info as nbformat.ILanguageInfoMetadata;
  618. this._mimetype = this._mimeTypeService.getMimeTypeByLanguage(lang);
  619. if (this.promptCell) {
  620. this.promptCell.model.mimeType = this._mimetype;
  621. }
  622. }
  623. /**
  624. * Create the options used to initialize a code cell widget.
  625. */
  626. private _createCodeCellOptions(): CodeCell.IOptions {
  627. let contentFactory = this.contentFactory;
  628. let modelFactory = this.modelFactory;
  629. let model = modelFactory.createCodeCell({});
  630. let rendermime = this.rendermime;
  631. return { model, rendermime, contentFactory };
  632. }
  633. /**
  634. * Handle cell disposed signals.
  635. */
  636. private _onCellDisposed(sender: Cell, args: void): void {
  637. if (!this.isDisposed) {
  638. this._cells.removeValue(sender);
  639. const msgId = this._msgIdCells.get(sender as CodeCell);
  640. if (msgId) {
  641. this._msgIdCells.delete(sender as CodeCell);
  642. this._msgIds.delete(msgId);
  643. }
  644. }
  645. }
  646. /**
  647. * Test whether we should execute the prompt cell.
  648. */
  649. private _shouldExecute(timeout: number): Promise<boolean> {
  650. const promptCell = this.promptCell;
  651. if (!promptCell) {
  652. return Promise.resolve(false);
  653. }
  654. let model = promptCell.model;
  655. let code = model.value.text;
  656. return new Promise<boolean>((resolve, reject) => {
  657. let timer = setTimeout(() => {
  658. resolve(true);
  659. }, timeout);
  660. let kernel = this.session.kernel;
  661. if (!kernel) {
  662. resolve(false);
  663. return;
  664. }
  665. kernel
  666. .requestIsComplete({ code })
  667. .then(isComplete => {
  668. clearTimeout(timer);
  669. if (this.isDisposed) {
  670. resolve(false);
  671. }
  672. if (isComplete.content.status !== 'incomplete') {
  673. resolve(true);
  674. return;
  675. }
  676. resolve(false);
  677. })
  678. .catch(() => {
  679. resolve(true);
  680. });
  681. });
  682. }
  683. /**
  684. * Handle a keydown event on an editor.
  685. */
  686. private _onEditorKeydown(editor: CodeEditor.IEditor, event: KeyboardEvent) {
  687. // Suppress "Enter" events.
  688. return event.keyCode === 13;
  689. }
  690. /**
  691. * Handle a change to the kernel.
  692. */
  693. private _onKernelChanged(): void {
  694. this.clear();
  695. if (this._banner) {
  696. this._banner.dispose();
  697. this._banner = null;
  698. }
  699. this.addBanner();
  700. }
  701. /**
  702. * Handle a change to the kernel status.
  703. */
  704. private _onKernelStatusChanged(): void {
  705. if (this.session.status === 'connected') {
  706. // we just had a kernel restart or reconnect - reset banner
  707. let kernel = this.session.kernel;
  708. if (!kernel) {
  709. return;
  710. }
  711. kernel
  712. .requestKernelInfo()
  713. .then(() => {
  714. if (this.isDisposed || !kernel || !kernel.info) {
  715. return;
  716. }
  717. this._handleInfo(this.session.kernel.info);
  718. })
  719. .catch(err => {
  720. console.error('could not get kernel info');
  721. });
  722. } else if (this.session.status === 'restarting') {
  723. this.addBanner();
  724. }
  725. }
  726. private _banner: RawCell = null;
  727. private _cells: IObservableList<Cell>;
  728. private _content: Panel;
  729. private _executed = new Signal<this, Date>(this);
  730. private _history: IConsoleHistory;
  731. private _input: Panel;
  732. private _mimetype = 'text/x-ipython';
  733. private _mimeTypeService: IEditorMimeTypeService;
  734. private _msgIds = new Map<string, CodeCell>();
  735. private _msgIdCells = new Map<CodeCell, string>();
  736. private _promptCellCreated = new Signal<this, CodeCell>(this);
  737. private _dragData: { pressX: number; pressY: number; index: number } = null;
  738. private _drag: Drag = null;
  739. private _focusedCell: Cell = null;
  740. }
  741. /**
  742. * A namespace for CodeConsole statics.
  743. */
  744. export namespace CodeConsole {
  745. /**
  746. * The initialization options for a console widget.
  747. */
  748. export interface IOptions {
  749. /**
  750. * The content factory for the console widget.
  751. */
  752. contentFactory: IContentFactory;
  753. /**
  754. * The model factory for the console widget.
  755. */
  756. modelFactory?: IModelFactory;
  757. /**
  758. * The mime renderer for the console widget.
  759. */
  760. rendermime: RenderMimeRegistry;
  761. /**
  762. * The client session for the console widget.
  763. */
  764. session: IClientSession;
  765. /**
  766. * The service used to look up mime types.
  767. */
  768. mimeTypeService: IEditorMimeTypeService;
  769. }
  770. /**
  771. * A content factory for console children.
  772. */
  773. export interface IContentFactory extends Cell.IContentFactory {
  774. /**
  775. * Create a new code cell widget.
  776. */
  777. createCodeCell(options: CodeCell.IOptions): CodeCell;
  778. /**
  779. * Create a new raw cell widget.
  780. */
  781. createRawCell(options: RawCell.IOptions): RawCell;
  782. }
  783. /**
  784. * Default implementation of `IContentFactory`.
  785. */
  786. export class ContentFactory extends Cell.ContentFactory
  787. implements IContentFactory {
  788. /**
  789. * Create a new code cell widget.
  790. *
  791. * #### Notes
  792. * If no cell content factory is passed in with the options, the one on the
  793. * notebook content factory is used.
  794. */
  795. createCodeCell(options: CodeCell.IOptions): CodeCell {
  796. if (!options.contentFactory) {
  797. options.contentFactory = this;
  798. }
  799. return new CodeCell(options).initializeState();
  800. }
  801. /**
  802. * Create a new raw cell widget.
  803. *
  804. * #### Notes
  805. * If no cell content factory is passed in with the options, the one on the
  806. * notebook content factory is used.
  807. */
  808. createRawCell(options: RawCell.IOptions): RawCell {
  809. if (!options.contentFactory) {
  810. options.contentFactory = this;
  811. }
  812. return new RawCell(options).initializeState();
  813. }
  814. }
  815. /**
  816. * A namespace for the code console content factory.
  817. */
  818. export namespace ContentFactory {
  819. /**
  820. * An initialize options for `ContentFactory`.
  821. */
  822. export interface IOptions extends Cell.IContentFactory {}
  823. }
  824. /**
  825. * A default content factory for the code console.
  826. */
  827. export const defaultContentFactory: IContentFactory = new ContentFactory();
  828. /**
  829. * A model factory for a console widget.
  830. */
  831. export interface IModelFactory {
  832. /**
  833. * The factory for code cell content.
  834. */
  835. readonly codeCellContentFactory: CodeCellModel.IContentFactory;
  836. /**
  837. * Create a new code cell.
  838. *
  839. * @param options - The options used to create the cell.
  840. *
  841. * @returns A new code cell. If a source cell is provided, the
  842. * new cell will be initialized with the data from the source.
  843. */
  844. createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel;
  845. /**
  846. * Create a new raw cell.
  847. *
  848. * @param options - The options used to create the cell.
  849. *
  850. * @returns A new raw cell. If a source cell is provided, the
  851. * new cell will be initialized with the data from the source.
  852. */
  853. createRawCell(options: CellModel.IOptions): IRawCellModel;
  854. }
  855. /**
  856. * The default implementation of an `IModelFactory`.
  857. */
  858. export class ModelFactory {
  859. /**
  860. * Create a new cell model factory.
  861. */
  862. constructor(options: IModelFactoryOptions = {}) {
  863. this.codeCellContentFactory =
  864. options.codeCellContentFactory || CodeCellModel.defaultContentFactory;
  865. }
  866. /**
  867. * The factory for output area models.
  868. */
  869. readonly codeCellContentFactory: CodeCellModel.IContentFactory;
  870. /**
  871. * Create a new code cell.
  872. *
  873. * @param source - The data to use for the original source data.
  874. *
  875. * @returns A new code cell. If a source cell is provided, the
  876. * new cell will be initialized with the data from the source.
  877. * If the contentFactory is not provided, the instance
  878. * `codeCellContentFactory` will be used.
  879. */
  880. createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel {
  881. if (!options.contentFactory) {
  882. options.contentFactory = this.codeCellContentFactory;
  883. }
  884. return new CodeCellModel(options);
  885. }
  886. /**
  887. * Create a new raw cell.
  888. *
  889. * @param source - The data to use for the original source data.
  890. *
  891. * @returns A new raw cell. If a source cell is provided, the
  892. * new cell will be initialized with the data from the source.
  893. */
  894. createRawCell(options: CellModel.IOptions): IRawCellModel {
  895. return new RawCellModel(options);
  896. }
  897. }
  898. /**
  899. * The options used to initialize a `ModelFactory`.
  900. */
  901. export interface IModelFactoryOptions {
  902. /**
  903. * The factory for output area models.
  904. */
  905. codeCellContentFactory?: CodeCellModel.IContentFactory;
  906. }
  907. /**
  908. * The default `ModelFactory` instance.
  909. */
  910. export const defaultModelFactory = new ModelFactory({});
  911. }
  912. /**
  913. * A namespace for console widget private data.
  914. */
  915. namespace Private {
  916. /**
  917. * Jump to the bottom of a node.
  918. *
  919. * @param node - The scrollable element.
  920. */
  921. export function scrollToBottom(node: HTMLElement): void {
  922. node.scrollTop = node.scrollHeight - node.clientHeight;
  923. }
  924. }