actions.ts 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. KernelMessage
  5. } from '@jupyterlab/services';
  6. import {
  7. IClientSession, Clipboard, Dialog, showDialog
  8. } from '@jupyterlab/apputils';
  9. import {
  10. nbformat
  11. } from '@jupyterlab/coreutils';
  12. import {
  13. ICellModel, ICodeCellModel,
  14. CodeCell, Cell, MarkdownCell
  15. } from '@jupyterlab/cells';
  16. import {
  17. ArrayExt, each, toArray
  18. } from '@phosphor/algorithm';
  19. import {
  20. ElementExt
  21. } from '@phosphor/domutils';
  22. import {
  23. h
  24. } from '@phosphor/virtualdom';
  25. import {
  26. INotebookModel
  27. } from './model';
  28. import {
  29. Notebook
  30. } from './widget';
  31. // The message to display to the user when prompting to trust the notebook.
  32. const TRUST_MESSAGE = h.p(
  33. 'A trusted Jupyter notebook may execute hidden malicious code when you ',
  34. 'open it.',
  35. h.br(),
  36. 'Selecting trust will re-render this notebook in a trusted state.',
  37. h.br(),
  38. 'For more information, see the',
  39. h.a({ href: 'https://jupyter-notebook.readthedocs.io/en/stable/security.html' },
  40. 'Jupyter security documentation'),
  41. );
  42. /**
  43. * The mimetype used for Jupyter cell data.
  44. */
  45. const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
  46. /**
  47. * A namespace for handling actions on a notebook.
  48. *
  49. * #### Notes
  50. * All of the actions are a no-op if there is no model on the notebook.
  51. * The actions set the widget `mode` to `'command'` unless otherwise specified.
  52. * The actions will preserve the selection on the notebook widget unless
  53. * otherwise specified.
  54. */
  55. export
  56. namespace NotebookActions {
  57. /**
  58. * Split the active cell into two cells.
  59. *
  60. * @param widget - The target notebook widget.
  61. *
  62. * #### Notes
  63. * It will preserve the existing mode.
  64. * The second cell will be activated.
  65. * The existing selection will be cleared.
  66. * The leading whitespace in the second cell will be removed.
  67. * If there is no content, two empty cells will be created.
  68. * Both cells will have the same type as the original cell.
  69. * This action can be undone.
  70. */
  71. export
  72. function splitCell(widget: Notebook): void {
  73. if (!widget.model || !widget.activeCell) {
  74. return;
  75. }
  76. let state = Private.getState(widget);
  77. widget.deselectAll();
  78. let nbModel = widget.model;
  79. let index = widget.activeCellIndex;
  80. let child = widget.widgets[index];
  81. let editor = child.editor;
  82. let position = editor.getCursorPosition();
  83. let offset = editor.getOffsetAt(position);
  84. let orig = child.model.value.text;
  85. // Create new models to preserve history.
  86. let clone0 = Private.cloneCell(nbModel, child.model);
  87. let clone1 = Private.cloneCell(nbModel, child.model);
  88. if (clone0.type === 'code') {
  89. (clone0 as ICodeCellModel).outputs.clear();
  90. }
  91. clone0.value.text = orig.slice(0, offset).replace(/^\n+/, '').replace(/\n+$/, '');
  92. clone1.value.text = orig.slice(offset).replace(/^\n+/, '').replace(/\n+$/, '');
  93. // Make the changes while preserving history.
  94. let cells = nbModel.cells;
  95. cells.beginCompoundOperation();
  96. cells.set(index, clone0);
  97. cells.insert(index + 1, clone1);
  98. cells.endCompoundOperation();
  99. widget.activeCellIndex++;
  100. Private.handleState(widget, state);
  101. }
  102. /**
  103. * Merge the selected cells.
  104. *
  105. * @param widget - The target notebook widget.
  106. *
  107. * #### Notes
  108. * The widget mode will be preserved.
  109. * If only one cell is selected, the next cell will be selected.
  110. * If the active cell is a code cell, its outputs will be cleared.
  111. * This action can be undone.
  112. * The final cell will have the same type as the active cell.
  113. * If the active cell is a markdown cell, it will be unrendered.
  114. */
  115. export
  116. function mergeCells(widget: Notebook): void {
  117. if (!widget.model || !widget.activeCell) {
  118. return;
  119. }
  120. let state = Private.getState(widget);
  121. let toMerge: string[] = [];
  122. let toDelete: ICellModel[] = [];
  123. let model = widget.model;
  124. let cells = model.cells;
  125. let primary = widget.activeCell;
  126. let index = widget.activeCellIndex;
  127. // Get the cells to merge.
  128. each(widget.widgets, (child, i) => {
  129. if (widget.isSelectedOrActive(child)) {
  130. toMerge.push(child.model.value.text);
  131. if (i !== index) {
  132. toDelete.push(child.model);
  133. }
  134. }
  135. });
  136. // Check for only a single cell selected.
  137. if (toMerge.length === 1) {
  138. // Bail if it is the last cell.
  139. if (index === cells.length - 1) {
  140. return;
  141. }
  142. // Otherwise merge with the next cell.
  143. let cellModel = cells.get(index + 1);
  144. toMerge.push(cellModel.value.text);
  145. toDelete.push(cellModel);
  146. }
  147. widget.deselectAll();
  148. // Create a new cell for the source to preserve history.
  149. let newModel = Private.cloneCell(model, primary.model);
  150. newModel.value.text = toMerge.join('\n\n');
  151. if (newModel.type === 'code') {
  152. (newModel as ICodeCellModel).outputs.clear();
  153. }
  154. // Make the changes while preserving history.
  155. cells.beginCompoundOperation();
  156. cells.set(index, newModel);
  157. each(toDelete, cell => {
  158. cells.removeValue(cell);
  159. });
  160. cells.endCompoundOperation();
  161. // If the original cell is a markdown cell, make sure
  162. // the new cell is unrendered.
  163. if (primary instanceof MarkdownCell) {
  164. let cell = widget.activeCell as MarkdownCell;
  165. cell.rendered = false;
  166. }
  167. Private.handleState(widget, state);
  168. }
  169. /**
  170. * Delete the selected cells.
  171. *
  172. * @param widget - The target notebook widget.
  173. *
  174. * #### Notes
  175. * The cell after the last selected cell will be activated.
  176. * It will add a code cell if all cells are deleted.
  177. * This action can be undone.
  178. */
  179. export
  180. function deleteCells(widget: Notebook): void {
  181. if (!widget.model || !widget.activeCell) {
  182. return;
  183. }
  184. let state = Private.getState(widget);
  185. Private.deleteCells(widget);
  186. Private.handleState(widget, state);
  187. }
  188. /**
  189. * Insert a new code cell above the active cell.
  190. *
  191. * @param widget - The target notebook widget.
  192. *
  193. * #### Notes
  194. * The widget mode will be preserved.
  195. * This action can be undone.
  196. * The existing selection will be cleared.
  197. * The new cell will the active cell.
  198. */
  199. export
  200. function insertAbove(widget: Notebook): void {
  201. if (!widget.model || !widget.activeCell) {
  202. return;
  203. }
  204. let state = Private.getState(widget);
  205. let model = widget.model;
  206. let cell = model.contentFactory.createCodeCell({ });
  207. let index = widget.activeCellIndex;
  208. model.cells.insert(index, cell);
  209. // Make the newly inserted cell active.
  210. widget.activeCellIndex = index;
  211. widget.deselectAll();
  212. Private.handleState(widget, state, true);
  213. }
  214. /**
  215. * Insert a new code cell below the active cell.
  216. *
  217. * @param widget - The target notebook widget.
  218. *
  219. * #### Notes
  220. * The widget mode will be preserved.
  221. * This action can be undone.
  222. * The existing selection will be cleared.
  223. * The new cell will be the active cell.
  224. */
  225. export
  226. function insertBelow(widget: Notebook): void {
  227. if (!widget.model || !widget.activeCell) {
  228. return;
  229. }
  230. let state = Private.getState(widget);
  231. let model = widget.model;
  232. let cell = model.contentFactory.createCodeCell({});
  233. model.cells.insert(widget.activeCellIndex + 1, cell);
  234. // Make the newly inserted cell active.
  235. widget.activeCellIndex++;
  236. widget.deselectAll();
  237. Private.handleState(widget, state, true);
  238. }
  239. /**
  240. * Move the selected cell(s) down.
  241. *
  242. * @param widget = The target notebook widget.
  243. */
  244. export
  245. function moveDown(widget: Notebook): void {
  246. if (!widget.model || !widget.activeCell) {
  247. return;
  248. }
  249. let state = Private.getState(widget);
  250. let cells = widget.model.cells;
  251. let widgets = widget.widgets;
  252. cells.beginCompoundOperation();
  253. for (let i = cells.length - 2; i > -1; i--) {
  254. if (widget.isSelectedOrActive(widgets[i])) {
  255. if (!widget.isSelectedOrActive(widgets[i + 1])) {
  256. cells.move(i, i + 1);
  257. if (widget.activeCellIndex === i) {
  258. widget.activeCellIndex++;
  259. }
  260. widget.select(widgets[i + 1]);
  261. widget.deselect(widgets[i]);
  262. }
  263. }
  264. }
  265. cells.endCompoundOperation();
  266. Private.handleState(widget, state, true);
  267. }
  268. /**
  269. * Move the selected cell(s) up.
  270. *
  271. * @param widget - The target notebook widget.
  272. */
  273. export
  274. function moveUp(widget: Notebook): void {
  275. if (!widget.model || !widget.activeCell) {
  276. return;
  277. }
  278. let state = Private.getState(widget);
  279. let cells = widget.model.cells;
  280. let widgets = widget.widgets;
  281. cells.beginCompoundOperation();
  282. for (let i = 1; i < cells.length; i++) {
  283. if (widget.isSelectedOrActive(widgets[i])) {
  284. if (!widget.isSelectedOrActive(widgets[i - 1])) {
  285. cells.move(i, i - 1);
  286. if (widget.activeCellIndex === i) {
  287. widget.activeCellIndex--;
  288. }
  289. widget.select(widgets[i - 1]);
  290. widget.deselect(widgets[i]);
  291. }
  292. }
  293. }
  294. cells.endCompoundOperation();
  295. Private.handleState(widget, state, true);
  296. }
  297. /**
  298. * Change the selected cell type(s).
  299. *
  300. * @param widget - The target notebook widget.
  301. *
  302. * @param value - The target cell type.
  303. *
  304. * #### Notes
  305. * It should preserve the widget mode.
  306. * This action can be undone.
  307. * The existing selection will be cleared.
  308. * Any cells converted to markdown will be unrendered.
  309. */
  310. export
  311. function changeCellType(widget: Notebook, value: nbformat.CellType): void {
  312. if (!widget.model || !widget.activeCell) {
  313. return;
  314. }
  315. let state = Private.getState(widget);
  316. Private.changeCellType(widget, value);
  317. Private.handleState(widget, state);
  318. }
  319. /**
  320. * Run the selected cell(s).
  321. *
  322. * @param widget - The target notebook widget.
  323. *
  324. * @param session - The optional client session object.
  325. *
  326. * #### Notes
  327. * The last selected cell will be activated, but not scrolled into view.
  328. * The existing selection will be cleared.
  329. * An execution error will prevent the remaining code cells from executing.
  330. * All markdown cells will be rendered.
  331. */
  332. export
  333. function run(widget: Notebook, session?: IClientSession): Promise<boolean> {
  334. if (!widget.model || !widget.activeCell) {
  335. return Promise.resolve(false);
  336. }
  337. let state = Private.getState(widget);
  338. let promise = Private.runSelected(widget, session);
  339. Private.handleRunState(widget, state, false);
  340. return promise;
  341. }
  342. /**
  343. * Run the selected cell(s) and advance to the next cell.
  344. *
  345. * @param widget - The target notebook widget.
  346. *
  347. * @param session - The optional client session object.
  348. *
  349. * #### Notes
  350. * The existing selection will be cleared.
  351. * The cell after the last selected cell will be activated and scrolled into view.
  352. * An execution error will prevent the remaining code cells from executing.
  353. * All markdown cells will be rendered.
  354. * If the last selected cell is the last cell, a new code cell
  355. * will be created in `'edit'` mode. The new cell creation can be undone.
  356. */
  357. export
  358. function runAndAdvance(widget: Notebook, session?: IClientSession): Promise<boolean> {
  359. if (!widget.model || !widget.activeCell) {
  360. return Promise.resolve(false);
  361. }
  362. let state = Private.getState(widget);
  363. let promise = Private.runSelected(widget, session);
  364. let model = widget.model;
  365. if (widget.activeCellIndex === widget.widgets.length - 1) {
  366. let cell = model.contentFactory.createCodeCell({});
  367. model.cells.push(cell);
  368. widget.activeCellIndex++;
  369. widget.mode = 'edit';
  370. } else {
  371. widget.activeCellIndex++;
  372. }
  373. Private.handleRunState(widget, state, true);
  374. return promise;
  375. }
  376. /**
  377. * Run the selected cell(s) and insert a new code cell.
  378. *
  379. * @param widget - The target notebook widget.
  380. *
  381. * @param session - The optional client session object.
  382. *
  383. * #### Notes
  384. * An execution error will prevent the remaining code cells from executing.
  385. * All markdown cells will be rendered.
  386. * The widget mode will be set to `'edit'` after running.
  387. * The existing selection will be cleared.
  388. * The cell insert can be undone.
  389. * The new cell will be scrolled into view.
  390. */
  391. export
  392. function runAndInsert(widget: Notebook, session?: IClientSession): Promise<boolean> {
  393. if (!widget.model || !widget.activeCell) {
  394. return Promise.resolve(false);
  395. }
  396. let state = Private.getState(widget);
  397. let promise = Private.runSelected(widget, session);
  398. let model = widget.model;
  399. let cell = model.contentFactory.createCodeCell({});
  400. model.cells.insert(widget.activeCellIndex + 1, cell);
  401. widget.activeCellIndex++;
  402. widget.mode = 'edit';
  403. Private.handleRunState(widget, state, true);
  404. return promise;
  405. }
  406. /**
  407. * Run all of the cells in the notebook.
  408. *
  409. * @param widget - The target notebook widget.
  410. *
  411. * @param session - The optional client session object.
  412. *
  413. * #### Notes
  414. * The existing selection will be cleared.
  415. * An execution error will prevent the remaining code cells from executing.
  416. * All markdown cells will be rendered.
  417. * The last cell in the notebook will be activated and scrolled into view.
  418. */
  419. export
  420. function runAll(widget: Notebook, session?: IClientSession): Promise<boolean> {
  421. if (!widget.model || !widget.activeCell) {
  422. return Promise.resolve(false);
  423. }
  424. let state = Private.getState(widget);
  425. each(widget.widgets, child => {
  426. widget.select(child);
  427. });
  428. let promise = Private.runSelected(widget, session);
  429. Private.handleRunState(widget, state, true);
  430. return promise;
  431. }
  432. /**
  433. * Run all of the cells before the currently active cell (exclusive).
  434. *
  435. * @param widget - The target notebook widget.
  436. *
  437. * @param session - The optional client session object.
  438. *
  439. * #### Notes
  440. * The existing selection will be cleared.
  441. * An execution error will prevent the remaining code cells from executing.
  442. * All markdown cells will be rendered.
  443. * The currently active cell will remain selected.
  444. */
  445. export
  446. function runAllAbove(widget: Notebook, session?: IClientSession): Promise<boolean> {
  447. if (!widget.model || !widget.activeCell || widget.activeCellIndex === 0) {
  448. return Promise.resolve(false);
  449. }
  450. let state = Private.getState(widget);
  451. widget.activeCellIndex--;
  452. widget.deselectAll();
  453. for (let i = 0; i < widget.activeCellIndex; ++i) {
  454. widget.select(widget.widgets[i]);
  455. }
  456. let promise = Private.runSelected(widget, session);
  457. widget.activeCellIndex++;
  458. Private.handleRunState(widget, state, true);
  459. return promise;
  460. }
  461. /**
  462. * Run all of the cells after the currently active cell (inclusive).
  463. *
  464. * @param widget - The target notebook widget.
  465. *
  466. * @param session - The optional client session object.
  467. *
  468. * #### Notes
  469. * The existing selection will be cleared.
  470. * An execution error will prevent the remaining code cells from executing.
  471. * All markdown cells will be rendered.
  472. * The last cell in the notebook will be activated and scrolled into view.
  473. */
  474. export
  475. function runAllBelow(widget: Notebook, session?: IClientSession): Promise<boolean> {
  476. if (!widget.model || !widget.activeCell) {
  477. return Promise.resolve(false);
  478. }
  479. let state = Private.getState(widget);
  480. widget.deselectAll();
  481. for (let i = widget.activeCellIndex; i < widget.widgets.length; ++i) {
  482. widget.select(widget.widgets[i]);
  483. }
  484. let promise = Private.runSelected(widget, session);
  485. Private.handleRunState(widget, state, true);
  486. return promise;
  487. }
  488. /**
  489. * Select the above the active cell.
  490. *
  491. * @param widget - The target notebook widget.
  492. *
  493. * #### Notes
  494. * The widget mode will be preserved.
  495. * This is a no-op if the first cell is the active cell.
  496. * The existing selection will be cleared.
  497. */
  498. export
  499. function selectAbove(widget: Notebook): void {
  500. if (!widget.model || !widget.activeCell) {
  501. return;
  502. }
  503. if (widget.activeCellIndex === 0) {
  504. return;
  505. }
  506. let state = Private.getState(widget);
  507. widget.activeCellIndex -= 1;
  508. widget.deselectAll();
  509. Private.handleState(widget, state, true);
  510. }
  511. /**
  512. * Select the cell below the active cell.
  513. *
  514. * @param widget - The target notebook widget.
  515. *
  516. * #### Notes
  517. * The widget mode will be preserved.
  518. * This is a no-op if the last cell is the active cell.
  519. * The existing selection will be cleared.
  520. */
  521. export
  522. function selectBelow(widget: Notebook): void {
  523. if (!widget.model || !widget.activeCell) {
  524. return;
  525. }
  526. if (widget.activeCellIndex === widget.widgets.length - 1) {
  527. return;
  528. }
  529. let state = Private.getState(widget);
  530. widget.activeCellIndex += 1;
  531. widget.deselectAll();
  532. Private.handleState(widget, state, true);
  533. }
  534. /**
  535. * Extend the selection to the cell above.
  536. *
  537. * @param widget - The target notebook widget.
  538. *
  539. * #### Notes
  540. * This is a no-op if the first cell is the active cell.
  541. * The new cell will be activated.
  542. */
  543. export
  544. function extendSelectionAbove(widget: Notebook): void {
  545. if (!widget.model || !widget.activeCell) {
  546. return;
  547. }
  548. // Do not wrap around.
  549. if (widget.activeCellIndex === 0) {
  550. return;
  551. }
  552. let state = Private.getState(widget);
  553. widget.mode = 'command';
  554. widget.extendContiguousSelectionTo(widget.activeCellIndex - 1);
  555. Private.handleState(widget, state, true);
  556. }
  557. /**
  558. * Extend the selection to the cell below.
  559. *
  560. * @param widget - The target notebook widget.
  561. *
  562. * #### Notes
  563. * This is a no-op if the last cell is the active cell.
  564. * The new cell will be activated.
  565. */
  566. export
  567. function extendSelectionBelow(widget: Notebook): void {
  568. if (!widget.model || !widget.activeCell) {
  569. return;
  570. }
  571. // Do not wrap around.
  572. if (widget.activeCellIndex === widget.widgets.length - 1) {
  573. return;
  574. }
  575. let state = Private.getState(widget);
  576. widget.mode = 'command';
  577. widget.extendContiguousSelectionTo(widget.activeCellIndex + 1);
  578. Private.handleState(widget, state, true);
  579. }
  580. /**
  581. * Select all of the cells of the notebook.
  582. *
  583. * @param widget - the targe notebook widget.
  584. */
  585. export
  586. function selectAll(widget: Notebook): void {
  587. if (!widget.model || !widget.activeCell) {
  588. return;
  589. }
  590. each(widget.widgets, child => {
  591. widget.select(child);
  592. });
  593. }
  594. /**
  595. * Deselect all of the cells of the notebook.
  596. *
  597. * @param widget - the targe notebook widget.
  598. */
  599. export
  600. function deselectAll(widget: Notebook): void {
  601. if (!widget.model || !widget.activeCell) {
  602. return;
  603. }
  604. widget.deselectAll();
  605. }
  606. /**
  607. * Copy the selected cell data to a clipboard.
  608. *
  609. * @param widget - The target notebook widget.
  610. */
  611. export
  612. function copy(widget: Notebook): void {
  613. Private.copyOrCut(widget, false);
  614. }
  615. /**
  616. * Cut the selected cell data to a clipboard.
  617. *
  618. * @param widget - The target notebook widget.
  619. *
  620. * #### Notes
  621. * This action can be undone.
  622. * A new code cell is added if all cells are cut.
  623. */
  624. export
  625. function cut(widget: Notebook): void {
  626. Private.copyOrCut(widget, true);
  627. }
  628. /**
  629. * Paste cells from the application clipboard.
  630. *
  631. * @param widget - The target notebook widget.
  632. *
  633. * @param mode - the mode of the paste operation: 'below' pastes cells
  634. * below the active cell, 'above' pastes cells above the active cell,
  635. * and 'replace' removes the currently selected cells and pastes cells
  636. * in their place.
  637. *
  638. * #### Notes
  639. * The last pasted cell becomes the active cell.
  640. * This is a no-op if there is no cell data on the clipboard.
  641. * This action can be undone.
  642. */
  643. export
  644. function paste(widget: Notebook, mode: 'below' | 'above' | 'replace' = 'below'): void {
  645. if (!widget.model || !widget.activeCell) {
  646. return;
  647. }
  648. let clipboard = Clipboard.getInstance();
  649. if (!clipboard.hasData(JUPYTER_CELL_MIME)) {
  650. return;
  651. }
  652. let state = Private.getState(widget);
  653. let values = clipboard.getData(JUPYTER_CELL_MIME) as nbformat.IBaseCell[];
  654. let model = widget.model;
  655. let newCells: ICellModel[] = [];
  656. widget.mode = 'command';
  657. each(values, cell => {
  658. switch (cell.cell_type) {
  659. case 'code':
  660. newCells.push(model.contentFactory.createCodeCell({ cell }));
  661. break;
  662. case 'markdown':
  663. newCells.push(model.contentFactory.createMarkdownCell({ cell }));
  664. break;
  665. default:
  666. newCells.push(model.contentFactory.createRawCell({ cell }));
  667. break;
  668. }
  669. });
  670. let cells = widget.model.cells;
  671. let index: number;
  672. cells.beginCompoundOperation();
  673. // Set the starting index of the paste
  674. // operation depending upon the mode.
  675. switch (mode) {
  676. case 'below':
  677. index = widget.activeCellIndex;
  678. break;
  679. case 'above':
  680. index = widget.activeCellIndex - 1;
  681. break;
  682. case 'replace':
  683. // Find the cells to delete.
  684. const toDelete: number[] = [];
  685. each(widget.widgets, (child, i) => {
  686. let deletable = child.model.metadata.get('deletable');
  687. if (widget.isSelectedOrActive(child) && deletable !== false) {
  688. toDelete.push(i);
  689. }
  690. });
  691. // If cells are not deletable, we may not have anything to delete.
  692. if (toDelete.length > 0) {
  693. // Delete the cells as one undo event.
  694. each(toDelete.reverse(), i => {
  695. cells.remove(i);
  696. });
  697. }
  698. index = toDelete[0];
  699. break;
  700. default:
  701. break;
  702. }
  703. each(newCells, cell => {
  704. cells.insert(++index, cell);
  705. });
  706. cells.endCompoundOperation();
  707. widget.activeCellIndex += newCells.length;
  708. widget.deselectAll();
  709. Private.handleState(widget, state);
  710. }
  711. /**
  712. * Undo a cell action.
  713. *
  714. * @param widget - The target notebook widget.
  715. *
  716. * #### Notes
  717. * This is a no-op if if there are no cell actions to undo.
  718. */
  719. export
  720. function undo(widget: Notebook): void {
  721. if (!widget.model || !widget.activeCell) {
  722. return;
  723. }
  724. let state = Private.getState(widget);
  725. widget.mode = 'command';
  726. widget.model.cells.undo();
  727. widget.deselectAll();
  728. Private.handleState(widget, state);
  729. }
  730. /**
  731. * Redo a cell action.
  732. *
  733. * @param widget - The target notebook widget.
  734. *
  735. * #### Notes
  736. * This is a no-op if there are no cell actions to redo.
  737. */
  738. export
  739. function redo(widget: Notebook): void {
  740. if (!widget.model || !widget.activeCell) {
  741. return;
  742. }
  743. let state = Private.getState(widget);
  744. widget.mode = 'command';
  745. widget.model.cells.redo();
  746. widget.deselectAll();
  747. Private.handleState(widget, state);
  748. }
  749. /**
  750. * Toggle the line number of all cells.
  751. *
  752. * @param widget - The target notebook widget.
  753. *
  754. * #### Notes
  755. * The original state is based on the state of the active cell.
  756. * The `mode` of the widget will be preserved.
  757. */
  758. export
  759. function toggleAllLineNumbers(widget: Notebook): void {
  760. if (!widget.model || !widget.activeCell) {
  761. return;
  762. }
  763. let state = Private.getState(widget);
  764. const config = widget.editorConfig;
  765. const lineNumbers = !(config.code.lineNumbers &&
  766. config.markdown.lineNumbers && config.raw.lineNumbers);
  767. const newConfig = {
  768. code: { ...config.code, lineNumbers },
  769. markdown: { ...config.markdown, lineNumbers },
  770. raw: { ...config.raw, lineNumbers }
  771. };
  772. widget.editorConfig = newConfig;
  773. Private.handleState(widget, state);
  774. }
  775. /**
  776. * Clear the code outputs of the selected cells.
  777. *
  778. * @param widget - The target notebook widget.
  779. *
  780. * #### Notes
  781. * The widget `mode` will be preserved.
  782. */
  783. export
  784. function clearOutputs(widget: Notebook): void {
  785. if (!widget.model || !widget.activeCell) {
  786. return;
  787. }
  788. let state = Private.getState(widget);
  789. let cells = widget.model.cells;
  790. let i = 0;
  791. each(cells, (cell: ICodeCellModel) => {
  792. let child = widget.widgets[i];
  793. if (widget.isSelectedOrActive(child) && cell.type === 'code') {
  794. cell.outputs.clear();
  795. (child as CodeCell).outputHidden = false;
  796. cell.executionCount = null;
  797. }
  798. i++;
  799. });
  800. Private.handleState(widget, state);
  801. }
  802. /**
  803. * Clear all the code outputs on the widget.
  804. *
  805. * @param widget - The target notebook widget.
  806. *
  807. * #### Notes
  808. * The widget `mode` will be preserved.
  809. */
  810. export
  811. function clearAllOutputs(widget: Notebook): void {
  812. if (!widget.model || !widget.activeCell) {
  813. return;
  814. }
  815. let state = Private.getState(widget);
  816. let i = 0;
  817. each(widget.model.cells, (cell: ICodeCellModel) => {
  818. let child = widget.widgets[i];
  819. if (cell.type === 'code') {
  820. cell.outputs.clear();
  821. cell.executionCount = null;
  822. (child as CodeCell).outputHidden = false;
  823. }
  824. i++;
  825. });
  826. Private.handleState(widget, state);
  827. }
  828. /**
  829. * Hide the code on selected code cells.
  830. *
  831. * @param widget - The target notebook widget.
  832. */
  833. export
  834. function hideCode(widget: Notebook): void {
  835. if (!widget.model || !widget.activeCell) {
  836. return;
  837. }
  838. let state = Private.getState(widget);
  839. let cells = widget.widgets;
  840. each(cells, (cell: Cell) => {
  841. if (widget.isSelectedOrActive(cell) && cell.model.type === 'code') {
  842. cell.inputHidden = true;
  843. }
  844. });
  845. Private.handleState(widget, state);
  846. }
  847. /**
  848. * Show the code on selected code cells.
  849. *
  850. * @param widget - The target notebook widget.
  851. */
  852. export
  853. function showCode(widget: Notebook): void {
  854. if (!widget.model || !widget.activeCell) {
  855. return;
  856. }
  857. let state = Private.getState(widget);
  858. let cells = widget.widgets;
  859. each(cells, (cell: Cell) => {
  860. if (widget.isSelectedOrActive(cell) && cell.model.type === 'code') {
  861. cell.inputHidden = false;
  862. }
  863. });
  864. Private.handleState(widget, state);
  865. }
  866. /**
  867. * Hide the code on all code cells.
  868. *
  869. * @param widget - The target notebook widget.
  870. */
  871. export
  872. function hideAllCode(widget: Notebook): void {
  873. if (!widget.model || !widget.activeCell) {
  874. return;
  875. }
  876. let state = Private.getState(widget);
  877. let cells = widget.widgets;
  878. each(cells, (cell: Cell) => {
  879. if (cell.model.type === 'code') {
  880. cell.inputHidden = true;
  881. }
  882. });
  883. Private.handleState(widget, state);
  884. }
  885. /**
  886. * Show the code on all code cells.
  887. *
  888. * @param widget - The target notebook widget.
  889. */
  890. export
  891. function showAllCode(widget: Notebook): void {
  892. if (!widget.model || !widget.activeCell) {
  893. return;
  894. }
  895. let state = Private.getState(widget);
  896. let cells = widget.widgets;
  897. each(cells, (cell: Cell) => {
  898. if (cell.model.type === 'code') {
  899. cell.inputHidden = false;
  900. }
  901. });
  902. Private.handleState(widget, state);
  903. }
  904. /**
  905. * Hide the output on selected code cells.
  906. *
  907. * @param widget - The target notebook widget.
  908. */
  909. export
  910. function hideOutput(widget: Notebook): void {
  911. if (!widget.model || !widget.activeCell) {
  912. return;
  913. }
  914. let state = Private.getState(widget);
  915. let cells = widget.widgets;
  916. each(cells, (cell: Cell) => {
  917. if (widget.isSelectedOrActive(cell) && cell.model.type === 'code') {
  918. (cell as CodeCell).inputHidden = true;
  919. }
  920. });
  921. Private.handleState(widget, state);
  922. }
  923. /**
  924. * Show the output on selected code cells.
  925. *
  926. * @param widget - The target notebook widget.
  927. */
  928. export
  929. function showOutput(widget: Notebook): void {
  930. if (!widget.model || !widget.activeCell) {
  931. return;
  932. }
  933. let state = Private.getState(widget);
  934. let cells = widget.widgets;
  935. each(cells, (cell: Cell) => {
  936. if (widget.isSelectedOrActive(cell) && cell.model.type === 'code') {
  937. (cell as CodeCell).inputHidden = false;
  938. }
  939. });
  940. Private.handleState(widget, state);
  941. }
  942. /**
  943. * Hide the output on all code cells.
  944. *
  945. * @param widget - The target notebook widget.
  946. */
  947. export
  948. function hideAllOutputs(widget: Notebook): void {
  949. if (!widget.model || !widget.activeCell) {
  950. return;
  951. }
  952. let state = Private.getState(widget);
  953. let cells = widget.widgets;
  954. each(cells, (cell: Cell) => {
  955. if (cell.model.type === 'code') {
  956. (cell as CodeCell).outputHidden = true;
  957. }
  958. });
  959. Private.handleState(widget, state);
  960. }
  961. /**
  962. * Show the output on all code cells.
  963. *
  964. * @param widget - The target notebook widget.
  965. */
  966. export
  967. function showAllOutputs(widget: Notebook): void {
  968. if (!widget.model || !widget.activeCell) {
  969. return;
  970. }
  971. let state = Private.getState(widget);
  972. let cells = widget.widgets;
  973. each(cells, (cell: Cell) => {
  974. if (cell.model.type === 'code') {
  975. (cell as CodeCell).outputHidden = false;
  976. }
  977. });
  978. Private.handleState(widget, state);
  979. }
  980. /**
  981. * Set the markdown header level.
  982. *
  983. * @param widget - The target notebook widget.
  984. *
  985. * @param level - The header level.
  986. *
  987. * #### Notes
  988. * All selected cells will be switched to markdown.
  989. * The level will be clamped between 1 and 6.
  990. * If there is an existing header, it will be replaced.
  991. * There will always be one blank space after the header.
  992. * The cells will be unrendered.
  993. */
  994. export
  995. function setMarkdownHeader(widget: Notebook, level: number) {
  996. if (!widget.model || !widget.activeCell) {
  997. return;
  998. }
  999. let state = Private.getState(widget);
  1000. level = Math.min(Math.max(level, 1), 6);
  1001. let cells = widget.model.cells;
  1002. let i = 0;
  1003. each(widget.widgets, (child: MarkdownCell) => {
  1004. if (widget.isSelectedOrActive(child)) {
  1005. Private.setMarkdownHeader(cells.get(i), level);
  1006. }
  1007. i++;
  1008. });
  1009. Private.changeCellType(widget, 'markdown');
  1010. Private.handleState(widget, state);
  1011. }
  1012. /**
  1013. * Trust the notebook after prompting the user.
  1014. *
  1015. * @param widget - The target notebook widget.
  1016. *
  1017. * @returns a promise that resolves when the transaction is finished.
  1018. *
  1019. * #### Notes
  1020. * No dialog will be presented if the notebook is already trusted.
  1021. */
  1022. export
  1023. function trust(widget: Notebook): Promise<void> {
  1024. if (!widget.model) {
  1025. return Promise.resolve(void 0);
  1026. }
  1027. // Do nothing if already trusted.
  1028. let cells = widget.model.cells;
  1029. let trusted = true;
  1030. for (let i = 0; i < cells.length; i++) {
  1031. let cell = cells.get(i);
  1032. if (!cell.trusted) {
  1033. trusted = false;
  1034. }
  1035. }
  1036. if (trusted) {
  1037. return showDialog({
  1038. body: 'Notebook is already trusted',
  1039. buttons: [Dialog.okButton()]
  1040. }).then(() => void 0);
  1041. }
  1042. return showDialog({
  1043. body: TRUST_MESSAGE,
  1044. title: 'Trust this notebook?',
  1045. buttons: [Dialog.cancelButton(), Dialog.warnButton()]
  1046. }).then(result => {
  1047. if (result.button.accept) {
  1048. for (let i = 0; i < cells.length; i++) {
  1049. let cell = cells.get(i);
  1050. cell.trusted = true;
  1051. }
  1052. }
  1053. });
  1054. }
  1055. }
  1056. /**
  1057. * A namespace for private data.
  1058. */
  1059. namespace Private {
  1060. /**
  1061. * The interface for a widget state.
  1062. */
  1063. export
  1064. interface IState {
  1065. /**
  1066. * Whether the widget had focus.
  1067. */
  1068. wasFocused: boolean;
  1069. /**
  1070. * The active cell before the action.
  1071. */
  1072. activeCell: Cell;
  1073. }
  1074. /**
  1075. * Get the state of a widget before running an action.
  1076. */
  1077. export
  1078. function getState(widget: Notebook): IState {
  1079. return {
  1080. wasFocused: widget.node.contains(document.activeElement),
  1081. activeCell: widget.activeCell
  1082. };
  1083. }
  1084. /**
  1085. * Handle the state of a widget after running an action.
  1086. */
  1087. export
  1088. function handleState(widget: Notebook, state: IState, scrollIfNeeded=false): void {
  1089. if (state.wasFocused || widget.mode === 'edit') {
  1090. widget.activate();
  1091. }
  1092. if (scrollIfNeeded) {
  1093. ElementExt.scrollIntoViewIfNeeded(widget.node, widget.activeCell.node);
  1094. }
  1095. }
  1096. /**
  1097. * Handle the state of a widget after running a run action.
  1098. */
  1099. export
  1100. function handleRunState(widget: Notebook, state: IState, scroll = false): void {
  1101. if (state.wasFocused || widget.mode === 'edit') {
  1102. widget.activate();
  1103. }
  1104. if (scroll) {
  1105. // Scroll to the top of the previous active cell output.
  1106. let er = state.activeCell.inputArea.node.getBoundingClientRect();
  1107. widget.scrollToPosition(er.bottom);
  1108. }
  1109. }
  1110. /**
  1111. * Clone a cell model.
  1112. */
  1113. export
  1114. function cloneCell(model: INotebookModel, cell: ICellModel): ICellModel {
  1115. switch (cell.type) {
  1116. case 'code':
  1117. // TODO why isnt modeldb or id passed here?
  1118. return model.contentFactory.createCodeCell({ cell: cell.toJSON() });
  1119. case 'markdown':
  1120. // TODO why isnt modeldb or id passed here?
  1121. return model.contentFactory.createMarkdownCell({ cell: cell.toJSON() });
  1122. default:
  1123. // TODO why isnt modeldb or id passed here?
  1124. return model.contentFactory.createRawCell({ cell: cell.toJSON() });
  1125. }
  1126. }
  1127. /**
  1128. * Run the selected cells.
  1129. */
  1130. export
  1131. function runSelected(widget: Notebook, session?: IClientSession): Promise<boolean> {
  1132. widget.mode = 'command';
  1133. let selected: Cell[] = [];
  1134. let lastIndex = widget.activeCellIndex;
  1135. let i = 0;
  1136. each(widget.widgets, child => {
  1137. if (widget.isSelectedOrActive(child)) {
  1138. selected.push(child);
  1139. lastIndex = i;
  1140. }
  1141. i++;
  1142. });
  1143. widget.activeCellIndex = lastIndex;
  1144. widget.deselectAll();
  1145. let promises: Promise<boolean>[] = [];
  1146. each(selected, child => {
  1147. promises.push(runCell(widget, child, session));
  1148. });
  1149. return Promise.all(promises).then(results => {
  1150. if (widget.isDisposed) {
  1151. return false;
  1152. }
  1153. // Post an update request.
  1154. widget.update();
  1155. for (let result of results) {
  1156. if (!result) {
  1157. return false;
  1158. }
  1159. }
  1160. return true;
  1161. });
  1162. }
  1163. /**
  1164. * Run a cell.
  1165. */
  1166. function runCell(parent: Notebook, child: Cell, session?: IClientSession): Promise<boolean> {
  1167. switch (child.model.type) {
  1168. case 'markdown':
  1169. (child as MarkdownCell).rendered = true;
  1170. child.inputHidden = false;
  1171. break;
  1172. case 'code':
  1173. if (session) {
  1174. return CodeCell.execute(child as CodeCell, session).then(reply => {
  1175. if (child.isDisposed) {
  1176. return false;
  1177. }
  1178. if (reply && reply.content.status === 'ok') {
  1179. let content = reply.content as KernelMessage.IExecuteOkReply;
  1180. if (content.payload && content.payload.length) {
  1181. handlePayload(content, parent, child);
  1182. }
  1183. }
  1184. return reply ? reply.content.status === 'ok' : true;
  1185. }).catch(e => {
  1186. if (e.message !== 'Canceled') {
  1187. throw e;
  1188. }
  1189. return false;
  1190. });
  1191. }
  1192. (child.model as ICodeCellModel).executionCount = null;
  1193. break;
  1194. default:
  1195. break;
  1196. }
  1197. return Promise.resolve(true);
  1198. }
  1199. /**
  1200. * Handle payloads from an execute reply.
  1201. *
  1202. * #### Notes
  1203. * Payloads are deprecated and there are no official interfaces for them in
  1204. * the kernel type definitions.
  1205. * See [Payloads (DEPRECATED)](https://jupyter-client.readthedocs.io/en/latest/messaging.html#payloads-deprecated).
  1206. */
  1207. function handlePayload(content: KernelMessage.IExecuteOkReply, parent: Notebook, child: Cell) {
  1208. let setNextInput = content.payload.filter(i => {
  1209. return (i as any).source === 'set_next_input';
  1210. })[0];
  1211. if (!setNextInput) {
  1212. return;
  1213. }
  1214. let text = (setNextInput as any).text;
  1215. let replace = (setNextInput as any).replace;
  1216. if (replace) {
  1217. child.model.value.text = text;
  1218. return;
  1219. }
  1220. // Create a new code cell and add as the next cell.
  1221. let cell = parent.model.contentFactory.createCodeCell({});
  1222. cell.value.text = text;
  1223. let cells = parent.model.cells;
  1224. let i = ArrayExt.firstIndexOf(toArray(cells), child.model);
  1225. if (i === -1) {
  1226. cells.push(cell);
  1227. } else {
  1228. cells.insert(i + 1, cell);
  1229. }
  1230. }
  1231. /**
  1232. * Copy or cut the selected cell data to the application clipboard.
  1233. *
  1234. * @param widget - The target notebook widget.
  1235. *
  1236. * @param cut - Whether to copy or cut.
  1237. */
  1238. export
  1239. function copyOrCut(widget: Notebook, cut: boolean): void {
  1240. if (!widget.model || !widget.activeCell) {
  1241. return;
  1242. }
  1243. let state = getState(widget);
  1244. widget.mode = 'command';
  1245. let clipboard = Clipboard.getInstance();
  1246. clipboard.clear();
  1247. let data: nbformat.IBaseCell[] = [];
  1248. each(widget.widgets, child => {
  1249. if (widget.isSelectedOrActive(child)) {
  1250. data.push(child.model.toJSON());
  1251. }
  1252. });
  1253. clipboard.setData(JUPYTER_CELL_MIME, data);
  1254. if (cut) {
  1255. deleteCells(widget);
  1256. } else {
  1257. widget.deselectAll();
  1258. }
  1259. handleState(widget, state);
  1260. }
  1261. /**
  1262. * Change the selected cell type(s).
  1263. *
  1264. * @param widget - The target notebook widget.
  1265. *
  1266. * @param value - The target cell type.
  1267. *
  1268. * #### Notes
  1269. * It should preserve the widget mode.
  1270. * This action can be undone.
  1271. * The existing selection will be cleared.
  1272. * Any cells converted to markdown will be unrendered.
  1273. */
  1274. export
  1275. function changeCellType(widget: Notebook, value: nbformat.CellType): void {
  1276. let model = widget.model;
  1277. let cells = model.cells;
  1278. cells.beginCompoundOperation();
  1279. each(widget.widgets, (child, i) => {
  1280. if (!widget.isSelectedOrActive(child)) {
  1281. return;
  1282. }
  1283. if (child.model.type !== value) {
  1284. let cell: nbformat.IBaseCell = child.model.toJSON();
  1285. let newCell: ICellModel;
  1286. switch (value) {
  1287. case 'code':
  1288. newCell = model.contentFactory.createCodeCell({ cell });
  1289. break;
  1290. case 'markdown':
  1291. newCell = model.contentFactory.createMarkdownCell({ cell });
  1292. if (child.model.type === 'code') {
  1293. newCell.trusted = false;
  1294. }
  1295. break;
  1296. default:
  1297. newCell = model.contentFactory.createRawCell({ cell });
  1298. if (child.model.type === 'code') {
  1299. newCell.trusted = false;
  1300. }
  1301. }
  1302. cells.set(i, newCell);
  1303. }
  1304. if (value === 'markdown') {
  1305. // Fetch the new widget and unrender it.
  1306. child = widget.widgets[i];
  1307. (child as MarkdownCell).rendered = false;
  1308. }
  1309. });
  1310. cells.endCompoundOperation();
  1311. widget.deselectAll();
  1312. }
  1313. /**
  1314. * Delete the selected cells.
  1315. *
  1316. * @param widget - The target notebook widget.
  1317. *
  1318. * #### Notes
  1319. * The cell after the last selected cell will be activated.
  1320. * It will add a code cell if all cells are deleted.
  1321. * This action can be undone.
  1322. */
  1323. export
  1324. function deleteCells(widget: Notebook): void {
  1325. let model = widget.model;
  1326. let cells = model.cells;
  1327. let toDelete: number[] = [];
  1328. widget.mode = 'command';
  1329. // Find the cells to delete.
  1330. each(widget.widgets, (child, i) => {
  1331. let deletable = child.model.metadata.get('deletable');
  1332. if (widget.isSelectedOrActive(child) && deletable !== false) {
  1333. toDelete.push(i);
  1334. }
  1335. });
  1336. // If cells are not deletable, we may not have anything to delete.
  1337. if (toDelete.length > 0) {
  1338. // Delete the cells as one undo event.
  1339. cells.beginCompoundOperation();
  1340. each(toDelete.reverse(), i => {
  1341. cells.remove(i);
  1342. });
  1343. // Add a new cell if the notebook is empty. This is done
  1344. // within the compound operation to make the deletion of
  1345. // a notebook's last cell undoable.
  1346. if (!cells.length) {
  1347. cells.push(model.contentFactory.createCodeCell({}));
  1348. }
  1349. cells.endCompoundOperation();
  1350. // Select the *first* interior cell not deleted or the cell
  1351. // *after* the last selected cell.
  1352. // Note: The activeCellIndex is clamped to the available cells,
  1353. // so if the last cell is deleted the previous cell will be activated.
  1354. widget.activeCellIndex = toDelete[0];
  1355. }
  1356. // Deselect any remaining, undeletable cells. Do this even if we don't
  1357. // delete anything so that users are aware *something* happened.
  1358. widget.deselectAll();
  1359. }
  1360. /**
  1361. * Set the markdown header level of a cell.
  1362. */
  1363. export
  1364. function setMarkdownHeader(cell: ICellModel, level: number) {
  1365. let source = cell.value.text;
  1366. let newHeader = Array(level + 1).join('#') + ' ';
  1367. // Remove existing header or leading white space.
  1368. let regex = /^(#+\s*)|^(\s*)/;
  1369. let matches = regex.exec(source);
  1370. if (matches) {
  1371. source = source.slice(matches[0].length);
  1372. }
  1373. cell.value.text = newHeader + source;
  1374. }
  1375. }