##// END OF EJS Templates
add ctrlKey.which to utils...
Matthias BUSSONNIER -
Show More
@@ -1,681 +1,681
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16 var key = IPython.utils.keycodes;
17 17
18 18 var CodeCell = function (notebook) {
19 19 this.code_mirror = null;
20 20 this.input_prompt_number = null;
21 21 this.completion_cursor = null;
22 22 this.outputs = [];
23 23 this.collapsed = false;
24 24 this.tooltip_timeout = null;
25 25 this.clear_out_timeout = null;
26 26 IPython.Cell.apply(this, arguments);
27 27 };
28 28
29 29
30 30 CodeCell.prototype = new IPython.Cell();
31 31
32 32
33 33 CodeCell.prototype.create_element = function () {
34 34 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
35 35 cell.attr('tabindex','2');
36 36 var input = $('<div></div>').addClass('input hbox');
37 37 input.append($('<div/>').addClass('prompt input_prompt'));
38 38 var input_area = $('<div/>').addClass('input_area box-flex1');
39 39 this.code_mirror = CodeMirror(input_area.get(0), {
40 40 indentUnit : 4,
41 41 mode: 'python',
42 42 theme: 'ipython',
43 43 readOnly: this.read_only,
44 44 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
45 45 });
46 46 input.append(input_area);
47 47 var output = $('<div></div>').addClass('output vbox');
48 48 cell.append(input).append(output);
49 49 this.element = cell;
50 50 this.collapse();
51 51
52 52 // construct a completer
53 53 // And give it the function to call to get the completion list
54 54 var that = this;
55 55 this.completer = new IPython.Completer(this.code_mirror,function(callback){that.requestCompletion(callback)});
56 56 };
57 57
58 58 //TODO, try to diminish the number of parameters.
59 59 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
60 60 var that = this;
61 61 if (pre_cursor === "" || pre_cursor === "(" ) {
62 62 // don't do anything if line beggin with '(' or is empty
63 63 } else {
64 64 // Will set a timer to request tooltip in `time`
65 65 that.tooltip_timeout = setTimeout(function(){
66 66 IPython.notebook.request_tool_tip(that, pre_cursor)
67 67 },time);
68 68 }
69 69 };
70 70
71 71 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
72 72 // This method gets called in CodeMirror's onKeyDown/onKeyPress
73 73 // handlers and is used to provide custom key handling. Its return
74 74 // value is used to determine if CodeMirror should ignore the event:
75 75 // true = ignore, false = don't ignore.
76 76
77 77 if (this.read_only){
78 78 return false;
79 79 }
80 80
81 81 // note that we are comparing and setting the time to wait at each key press.
82 82 // a better wqy might be to generate a new function on each time change and
83 83 // assign it to CodeCell.prototype.request_tooltip_after_time
84 84 var tooltip_wait_time = this.notebook.time_before_tooltip;
85 85 var tooltip_on_tab = this.notebook.tooltip_on_tab;
86 86 var that = this;
87 87 // whatever key is pressed, first, cancel the tooltip request before
88 88 // they are sent, and remove tooltip if any
89 89 if(event.type === 'keydown' ) {
90 90 that.remove_and_cancel_tooltip();
91 91 };
92 92
93 93
94 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
94 if (event.keyCode === key.enter && (event.shiftKey || event.ctrlKey)) {
95 95 // Always ignore shift-enter in CodeMirror as we handle it.
96 96 return true;
97 97 } else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
98 98 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
99 99 // browser and keyboard layout !
100 100 // Pressing '(' , request tooltip, don't forget to reappend it
101 101 var cursor = editor.getCursor();
102 102 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
103 103 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
104 104 } else if (event.which === key.upArrow) {
105 105 // If we are not at the top, let CM handle the up arrow and
106 106 // prevent the global keydown handler from handling it.
107 107 if (!that.at_top()) {
108 108 event.stop();
109 109 return false;
110 110 } else {
111 111 return true;
112 112 };
113 113 } else if (event.which === key.downArrow) {
114 114 // If we are not at the bottom, let CM handle the down arrow and
115 115 // prevent the global keydown handler from handling it.
116 116 if (!that.at_bottom()) {
117 117 event.stop();
118 118 return false;
119 119 } else {
120 120 return true;
121 121 };
122 122 } else if (event.keyCode === key.tab && event.type == 'keydown') {
123 123 // Tab completion.
124 124 var cur = editor.getCursor();
125 125 //Do not trim here because of tooltip
126 126 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
127 127 if (pre_cursor.trim() === "") {
128 128 // Don't autocomplete if the part of the line before the cursor
129 129 // is empty. In this case, let CodeMirror handle indentation.
130 130 return false;
131 131 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
132 132 that.request_tooltip_after_time(pre_cursor,0);
133 133 // Prevent the event from bubbling up.
134 134 event.stop();
135 135 // Prevent CodeMirror from handling the tab.
136 136 return true;
137 137 } else {
138 138 event.stop();
139 139 this.completer.startCompletion();
140 140 return true;
141 141 };
142 142 } else if (event.keyCode === key.backspace && event.type == 'keydown') {
143 143 // If backspace and the line ends with 4 spaces, remove them.
144 144 var cur = editor.getCursor();
145 145 var line = editor.getLine(cur.line);
146 146 var ending = line.slice(-4);
147 147 if (ending === ' ') {
148 148 editor.replaceRange('',
149 149 {line: cur.line, ch: cur.ch-4},
150 150 {line: cur.line, ch: cur.ch}
151 151 );
152 152 event.stop();
153 153 return true;
154 154 } else {
155 155 return false;
156 156 };
157 157 } else {
158 158 // keypress/keyup also trigger on TAB press, and we don't want to
159 159 // use those to disable tab completion.
160 160 return false;
161 161 };
162 162 return false;
163 163 };
164 164
165 165 CodeCell.prototype.remove_and_cancel_tooltip = function() {
166 166 // note that we don't handle closing directly inside the calltip
167 167 // as in the completer, because it is not focusable, so won't
168 168 // get the event.
169 169 if (this.tooltip_timeout != null){
170 170 clearTimeout(this.tooltip_timeout);
171 171 $('#tooltip').remove();
172 172 this.tooltip_timeout = null;
173 173 }
174 174 }
175 175
176 176 CodeCell.prototype.finish_tooltip = function (reply) {
177 177 // Extract call tip data; the priority is call, init, main.
178 178 defstring = reply.call_def;
179 179 if (defstring == null) { defstring = reply.init_definition; }
180 180 if (defstring == null) { defstring = reply.definition; }
181 181
182 182 docstring = reply.call_docstring;
183 183 if (docstring == null) { docstring = reply.init_docstring; }
184 184 if (docstring == null) { docstring = reply.docstring; }
185 185 if (docstring == null) { docstring = "<empty docstring>"; }
186 186
187 187 name=reply.name;
188 188
189 189 var that = this;
190 190 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
191 191 // remove to have the tooltip not Limited in X and Y
192 192 tooltip.addClass('smalltooltip');
193 193 var pre=$('<pre/>').html(utils.fixConsole(docstring));
194 194 var expandlink=$('<a/>').attr('href',"#");
195 195 expandlink.addClass("ui-corner-all"); //rounded corner
196 196 expandlink.attr('role',"button");
197 197 //expandlink.addClass('ui-button');
198 198 //expandlink.addClass('ui-state-default');
199 199 var expandspan=$('<span/>').text('Expand');
200 200 expandspan.addClass('ui-icon');
201 201 expandspan.addClass('ui-icon-plus');
202 202 expandlink.append(expandspan);
203 203 expandlink.attr('id','expanbutton');
204 204 expandlink.click(function(){
205 205 tooltip.removeClass('smalltooltip');
206 206 tooltip.addClass('bigtooltip');
207 207 $('#expanbutton').remove();
208 208 setTimeout(function(){that.code_mirror.focus();}, 50);
209 209 });
210 210 var morelink=$('<a/>').attr('href',"#");
211 211 morelink.attr('role',"button");
212 212 morelink.addClass('ui-button');
213 213 //morelink.addClass("ui-corner-all"); //rounded corner
214 214 //morelink.addClass('ui-state-default');
215 215 var morespan=$('<span/>').text('Open in Pager');
216 216 morespan.addClass('ui-icon');
217 217 morespan.addClass('ui-icon-arrowstop-l-n');
218 218 morelink.append(morespan);
219 219 morelink.click(function(){
220 220 var msg_id = IPython.notebook.kernel.execute(name+"?");
221 221 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.get_selected_cell().cell_id;
222 222 that.remove_and_cancel_tooltip();
223 223 setTimeout(function(){that.code_mirror.focus();}, 50);
224 224 });
225 225
226 226 var closelink=$('<a/>').attr('href',"#");
227 227 closelink.attr('role',"button");
228 228 closelink.addClass('ui-button');
229 229 //closelink.addClass("ui-corner-all"); //rounded corner
230 230 //closelink.adClass('ui-state-default'); // grey background and blue cross
231 231 var closespan=$('<span/>').text('Close');
232 232 closespan.addClass('ui-icon');
233 233 closespan.addClass('ui-icon-close');
234 234 closelink.append(closespan);
235 235 closelink.click(function(){
236 236 that.remove_and_cancel_tooltip();
237 237 setTimeout(function(){that.code_mirror.focus();}, 50);
238 238 });
239 239 //construct the tooltip
240 240 tooltip.append(closelink);
241 241 tooltip.append(expandlink);
242 242 tooltip.append(morelink);
243 243 if(defstring){
244 244 defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
245 245 tooltip.append(defstring_html);
246 246 }
247 247 tooltip.append(pre);
248 248 var pos = this.code_mirror.cursorCoords();
249 249 tooltip.css('left',pos.x+'px');
250 250 tooltip.css('top',pos.yBot+'px');
251 251 $('body').append(tooltip);
252 252
253 253 // issues with cross-closing if multiple tooltip in less than 5sec
254 254 // keep it comented for now
255 255 // setTimeout(that.remove_and_cancel_tooltip, 5000);
256 256 };
257 257
258 258 // As you type completer
259 259 // this should be called by the completer, that in return will
260 260 // be reclled by finish_completing
261 261 CodeCell.prototype.requestCompletion= function(callback)
262 262 {
263 263 this._compcallback = callback;
264 264 var cur = this.code_mirror.getCursor();
265 265 var pre_cursor = this.code_mirror.getRange({line:cur.line,ch:0},cur);
266 266 pre_cursor.trim();
267 267 // Autocomplete the current line.
268 268 var line = this.code_mirror.getLine(cur.line);
269 269 // one could fork here and directly call finish completing
270 270 // if kernel is busy
271 271 IPython.notebook.complete_cell(this, line, cur.ch);
272 272 }
273 273
274 274 // called when completion came back from the kernel. this will inspect the
275 275 // curent cell for (more) completion merge the resuults with the ones
276 276 // comming from the kernel and forward it to the completer
277 277 CodeCell.prototype.finish_completing = function (matched_text, matches) {
278 278 // let's build a function that wrap all that stuff into what is needed for the
279 279 // new completer:
280 280 //
281 281 var cur = this.code_mirror.getCursor();
282 282 var res = CodeMirror.contextHint(this.code_mirror);
283 283
284 284 // append the introspection result, in order, at
285 285 // at the beginning of the table and compute the replacement rance
286 286 // from current cursor positon and matched_text length.
287 287 for(var i= matches.length-1; i>=0 ;--i)
288 288 {
289 289 res.unshift(
290 290 {
291 291 str : matches[i],
292 292 type : "introspection",
293 293 from : {line: cur.line, ch: cur.ch-matched_text.length},
294 294 to : {line: cur.line, ch: cur.ch}
295 295 }
296 296 )
297 297 }
298 298 this._compcallback(res);
299 299 };
300 300
301 301
302 302 CodeCell.prototype.select = function () {
303 303 IPython.Cell.prototype.select.apply(this);
304 304 this.code_mirror.refresh();
305 305 this.code_mirror.focus();
306 306 // We used to need an additional refresh() after the focus, but
307 307 // it appears that this has been fixed in CM. This bug would show
308 308 // up on FF when a newly loaded markdown cell was edited.
309 309 };
310 310
311 311
312 312 CodeCell.prototype.select_all = function () {
313 313 var start = {line: 0, ch: 0};
314 314 var nlines = this.code_mirror.lineCount();
315 315 var last_line = this.code_mirror.getLine(nlines-1);
316 316 var end = {line: nlines-1, ch: last_line.length};
317 317 this.code_mirror.setSelection(start, end);
318 318 };
319 319
320 320
321 321 CodeCell.prototype.append_output = function (json, dynamic) {
322 322 // If dynamic is true, javascript output will be eval'd.
323 323 this.expand();
324 324 this.flush_clear_timeout();
325 325 if (json.output_type === 'pyout') {
326 326 this.append_pyout(json, dynamic);
327 327 } else if (json.output_type === 'pyerr') {
328 328 this.append_pyerr(json);
329 329 } else if (json.output_type === 'display_data') {
330 330 this.append_display_data(json, dynamic);
331 331 } else if (json.output_type === 'stream') {
332 332 this.append_stream(json);
333 333 };
334 334 this.outputs.push(json);
335 335 };
336 336
337 337
338 338 CodeCell.prototype.create_output_area = function () {
339 339 var oa = $("<div/>").addClass("hbox output_area");
340 340 oa.append($('<div/>').addClass('prompt'));
341 341 return oa;
342 342 };
343 343
344 344
345 345 CodeCell.prototype.append_pyout = function (json, dynamic) {
346 346 var n = json.prompt_number || ' ';
347 347 var toinsert = this.create_output_area();
348 348 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
349 349 this.append_mime_type(json, toinsert, dynamic);
350 350 this.element.find('div.output').append(toinsert);
351 351 // If we just output latex, typeset it.
352 352 if ((json.latex !== undefined) || (json.html !== undefined)) {
353 353 this.typeset();
354 354 };
355 355 };
356 356
357 357
358 358 CodeCell.prototype.append_pyerr = function (json) {
359 359 var tb = json.traceback;
360 360 if (tb !== undefined && tb.length > 0) {
361 361 var s = '';
362 362 var len = tb.length;
363 363 for (var i=0; i<len; i++) {
364 364 s = s + tb[i] + '\n';
365 365 }
366 366 s = s + '\n';
367 367 var toinsert = this.create_output_area();
368 368 this.append_text(s, toinsert);
369 369 this.element.find('div.output').append(toinsert);
370 370 };
371 371 };
372 372
373 373
374 374 CodeCell.prototype.append_stream = function (json) {
375 375 // temporary fix: if stream undefined (json file written prior to this patch),
376 376 // default to most likely stdout:
377 377 if (json.stream == undefined){
378 378 json.stream = 'stdout';
379 379 }
380 380 if (!utils.fixConsole(json.text)){
381 381 // fixConsole gives nothing (empty string, \r, etc.)
382 382 // so don't append any elements, which might add undesirable space
383 383 return;
384 384 }
385 385 var subclass = "output_"+json.stream;
386 386 if (this.outputs.length > 0){
387 387 // have at least one output to consider
388 388 var last = this.outputs[this.outputs.length-1];
389 389 if (last.output_type == 'stream' && json.stream == last.stream){
390 390 // latest output was in the same stream,
391 391 // so append directly into its pre tag
392 392 // escape ANSI & HTML specials:
393 393 var text = utils.fixConsole(json.text);
394 394 this.element.find('div.'+subclass).last().find('pre').append(text);
395 395 return;
396 396 }
397 397 }
398 398
399 399 // If we got here, attach a new div
400 400 var toinsert = this.create_output_area();
401 401 this.append_text(json.text, toinsert, "output_stream "+subclass);
402 402 this.element.find('div.output').append(toinsert);
403 403 };
404 404
405 405
406 406 CodeCell.prototype.append_display_data = function (json, dynamic) {
407 407 var toinsert = this.create_output_area();
408 408 this.append_mime_type(json, toinsert, dynamic);
409 409 this.element.find('div.output').append(toinsert);
410 410 // If we just output latex, typeset it.
411 411 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
412 412 this.typeset();
413 413 };
414 414 };
415 415
416 416
417 417 CodeCell.prototype.append_mime_type = function (json, element, dynamic) {
418 418 if (json.javascript !== undefined && dynamic) {
419 419 this.append_javascript(json.javascript, element, dynamic);
420 420 } else if (json.html !== undefined) {
421 421 this.append_html(json.html, element);
422 422 } else if (json.latex !== undefined) {
423 423 this.append_latex(json.latex, element);
424 424 } else if (json.svg !== undefined) {
425 425 this.append_svg(json.svg, element);
426 426 } else if (json.png !== undefined) {
427 427 this.append_png(json.png, element);
428 428 } else if (json.jpeg !== undefined) {
429 429 this.append_jpeg(json.jpeg, element);
430 430 } else if (json.text !== undefined) {
431 431 this.append_text(json.text, element);
432 432 };
433 433 };
434 434
435 435
436 436 CodeCell.prototype.append_html = function (html, element) {
437 437 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
438 438 toinsert.append(html);
439 439 element.append(toinsert);
440 440 };
441 441
442 442
443 443 CodeCell.prototype.append_javascript = function (js, container) {
444 444 // We just eval the JS code, element appears in the local scope.
445 445 var element = $("<div/>").addClass("box_flex1 output_subarea");
446 446 container.append(element);
447 447 // Div for js shouldn't be drawn, as it will add empty height to the area.
448 448 container.hide();
449 449 // If the Javascript appends content to `element` that should be drawn, then
450 450 // it must also call `container.show()`.
451 451 eval(js);
452 452 }
453 453
454 454
455 455 CodeCell.prototype.append_text = function (data, element, extra_class) {
456 456 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
457 457 // escape ANSI & HTML specials in plaintext:
458 458 data = utils.fixConsole(data);
459 459 if (extra_class){
460 460 toinsert.addClass(extra_class);
461 461 }
462 462 toinsert.append($("<pre/>").html(data));
463 463 element.append(toinsert);
464 464 };
465 465
466 466
467 467 CodeCell.prototype.append_svg = function (svg, element) {
468 468 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
469 469 toinsert.append(svg);
470 470 element.append(toinsert);
471 471 };
472 472
473 473
474 474 CodeCell.prototype.append_png = function (png, element) {
475 475 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
476 476 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
477 477 element.append(toinsert);
478 478 };
479 479
480 480
481 481 CodeCell.prototype.append_jpeg = function (jpeg, element) {
482 482 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
483 483 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
484 484 element.append(toinsert);
485 485 };
486 486
487 487
488 488 CodeCell.prototype.append_latex = function (latex, element) {
489 489 // This method cannot do the typesetting because the latex first has to
490 490 // be on the page.
491 491 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
492 492 toinsert.append(latex);
493 493 element.append(toinsert);
494 494 };
495 495
496 496
497 497 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
498 498 var that = this;
499 499 if (this.clear_out_timeout != null){
500 500 // fire previous pending clear *immediately*
501 501 clearTimeout(this.clear_out_timeout);
502 502 this.clear_out_timeout = null;
503 503 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
504 504 }
505 505 // store flags for flushing the timeout
506 506 this._clear_stdout = stdout;
507 507 this._clear_stderr = stderr;
508 508 this._clear_other = other;
509 509 this.clear_out_timeout = setTimeout(function(){
510 510 // really clear timeout only after a short delay
511 511 // this reduces flicker in 'clear_output; print' cases
512 512 that.clear_out_timeout = null;
513 513 that._clear_stdout = that._clear_stderr = that._clear_other = null;
514 514 that.clear_output_callback(stdout, stderr, other);
515 515 }, 500
516 516 );
517 517 };
518 518
519 519 CodeCell.prototype.clear_output_callback = function (stdout, stderr, other) {
520 520 var output_div = this.element.find("div.output");
521 521
522 522 if (stdout && stderr && other){
523 523 // clear all, no need for logic
524 524 output_div.html("");
525 525 this.outputs = [];
526 526 return;
527 527 }
528 528 // remove html output
529 529 // each output_subarea that has an identifying class is in an output_area
530 530 // which is the element to be removed.
531 531 if (stdout){
532 532 output_div.find("div.output_stdout").parent().remove();
533 533 }
534 534 if (stderr){
535 535 output_div.find("div.output_stderr").parent().remove();
536 536 }
537 537 if (other){
538 538 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
539 539 }
540 540
541 541 // remove cleared outputs from JSON list:
542 542 for (var i = this.outputs.length - 1; i >= 0; i--){
543 543 var out = this.outputs[i];
544 544 var output_type = out.output_type;
545 545 if (output_type == "display_data" && other){
546 546 this.outputs.splice(i,1);
547 547 }else if (output_type == "stream"){
548 548 if (stdout && out.stream == "stdout"){
549 549 this.outputs.splice(i,1);
550 550 }else if (stderr && out.stream == "stderr"){
551 551 this.outputs.splice(i,1);
552 552 }
553 553 }
554 554 }
555 555 };
556 556
557 557
558 558 CodeCell.prototype.clear_input = function () {
559 559 this.code_mirror.setValue('');
560 560 };
561 561
562 562 CodeCell.prototype.flush_clear_timeout = function() {
563 563 var output_div = this.element.find('div.output');
564 564 if (this.clear_out_timeout){
565 565 clearTimeout(this.clear_out_timeout);
566 566 this.clear_out_timeout = null;
567 567 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
568 568 };
569 569 }
570 570
571 571
572 572 CodeCell.prototype.collapse = function () {
573 573 if (!this.collapsed) {
574 574 this.element.find('div.output').hide();
575 575 this.collapsed = true;
576 576 };
577 577 };
578 578
579 579
580 580 CodeCell.prototype.expand = function () {
581 581 if (this.collapsed) {
582 582 this.element.find('div.output').show();
583 583 this.collapsed = false;
584 584 };
585 585 };
586 586
587 587
588 588 CodeCell.prototype.toggle_output = function () {
589 589 if (this.collapsed) {
590 590 this.expand();
591 591 } else {
592 592 this.collapse();
593 593 };
594 594 };
595 595
596 596 CodeCell.prototype.set_input_prompt = function (number) {
597 597 this.input_prompt_number = number;
598 598 var ns = number || "&nbsp;";
599 599 this.element.find('div.input_prompt').html('In&nbsp;[' + ns + ']:');
600 600 };
601 601
602 602
603 603 CodeCell.prototype.get_text = function () {
604 604 return this.code_mirror.getValue();
605 605 };
606 606
607 607
608 608 CodeCell.prototype.set_text = function (code) {
609 609 return this.code_mirror.setValue(code);
610 610 };
611 611
612 612
613 613 CodeCell.prototype.at_top = function () {
614 614 var cursor = this.code_mirror.getCursor();
615 615 if (cursor.line === 0) {
616 616 return true;
617 617 } else {
618 618 return false;
619 619 }
620 620 };
621 621
622 622
623 623 CodeCell.prototype.at_bottom = function () {
624 624 var cursor = this.code_mirror.getCursor();
625 625 if (cursor.line === (this.code_mirror.lineCount()-1)) {
626 626 return true;
627 627 } else {
628 628 return false;
629 629 }
630 630 };
631 631
632 632
633 633 CodeCell.prototype.fromJSON = function (data) {
634 634 if (data.cell_type === 'code') {
635 635 if (data.input !== undefined) {
636 636 this.set_text(data.input);
637 637 }
638 638 if (data.prompt_number !== undefined) {
639 639 this.set_input_prompt(data.prompt_number);
640 640 } else {
641 641 this.set_input_prompt();
642 642 };
643 643 var len = data.outputs.length;
644 644 for (var i=0; i<len; i++) {
645 645 // append with dynamic=false.
646 646 this.append_output(data.outputs[i], false);
647 647 };
648 648 if (data.collapsed !== undefined) {
649 649 if (data.collapsed) {
650 650 this.collapse();
651 651 } else {
652 652 this.expand();
653 653 };
654 654 };
655 655 };
656 656 };
657 657
658 658
659 659 CodeCell.prototype.toJSON = function () {
660 660 var data = {};
661 661 data.input = this.get_text();
662 662 data.cell_type = 'code';
663 663 if (this.input_prompt_number) {
664 664 data.prompt_number = this.input_prompt_number;
665 665 };
666 666 var outputs = [];
667 667 var len = this.outputs.length;
668 668 for (var i=0; i<len; i++) {
669 669 outputs[i] = this.outputs[i];
670 670 };
671 671 data.outputs = outputs;
672 672 data.language = 'python';
673 673 data.collapsed = this.collapsed;
674 674 return data;
675 675 };
676 676
677 677
678 678 IPython.CodeCell = CodeCell;
679 679
680 680 return IPython;
681 681 }(IPython));
@@ -1,1361 +1,1362
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 var key = IPython.utils.keycodes;
15 16
16 17 var Notebook = function (selector) {
17 18 this.read_only = IPython.read_only;
18 19 this.element = $(selector);
19 20 this.element.scroll();
20 21 this.element.data("notebook", this);
21 22 this.next_prompt_number = 1;
22 23 this.kernel = null;
23 24 this.clipboard = null;
24 25 this.paste_enabled = false;
25 26 this.dirty = false;
26 27 this.msg_cell_map = {};
27 28 this.metadata = {};
28 29 this.control_key_active = false;
29 30 this.notebook_id = null;
30 31 this.notebook_name = null;
31 32 this.notebook_name_blacklist_re = /[\/\\]/;
32 33 this.nbformat = 3 // Increment this when changing the nbformat
33 34 this.style();
34 35 this.create_elements();
35 36 this.bind_events();
36 37 this.set_tooltipontab(true);
37 38 this.set_smartcompleter(true);
38 39 this.set_timebeforetooltip(1200);
39 40 };
40 41
41 42
42 43 Notebook.prototype.style = function () {
43 44 $('div#notebook').addClass('border-box-sizing');
44 45 };
45 46
46 47
47 48 Notebook.prototype.create_elements = function () {
48 49 // We add this end_space div to the end of the notebook div to:
49 50 // i) provide a margin between the last cell and the end of the notebook
50 51 // ii) to prevent the div from scrolling up when the last cell is being
51 52 // edited, but is too low on the page, which browsers will do automatically.
52 53 var that = this;
53 54 var end_space = $('<div/>').addClass('end_space').height("30%");
54 55 end_space.dblclick(function (e) {
55 56 if (that.read_only) return;
56 57 var ncells = that.ncells();
57 58 that.insert_cell_below('code',ncells-1);
58 59 });
59 60 this.element.append(end_space);
60 61 $('div#notebook').addClass('border-box-sizing');
61 62 };
62 63
63 64
64 65 Notebook.prototype.bind_events = function () {
65 66 var that = this;
66 67 $(document).keydown(function (event) {
67 68 // console.log(event);
68 69 if (that.read_only) return true;
69 70
70 71 // Save (CTRL+S) or (AppleKey+S)
71 72 //metaKey = applekey on mac
72 73 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
73 74 that.save_notebook();
74 75 event.preventDefault();
75 76 return false;
76 } else if (event.which === 27) {
77 } else if (event.which === key.esc) {
77 78 // Intercept escape at highest level to avoid closing
78 79 // websocket connection with firefox
79 80 event.preventDefault();
80 81 }
81 if (event.which === 38 && !event.shiftKey) {
82 if (event.which === key.upArrow && !event.shiftKey) {
82 83 var cell = that.get_selected_cell();
83 84 if (cell.at_top()) {
84 85 event.preventDefault();
85 86 that.select_prev();
86 87 };
87 } else if (event.which === 40 && !event.shiftKey) {
88 } else if (event.which === key.downArrow && !event.shiftKey) {
88 89 var cell = that.get_selected_cell();
89 90 if (cell.at_bottom()) {
90 91 event.preventDefault();
91 92 that.select_next();
92 93 };
93 } else if (event.which === 13 && event.shiftKey) {
94 } else if (event.which === key.enter && event.shiftKey) {
94 95 that.execute_selected_cell();
95 96 return false;
96 } else if (event.which === 13 && event.ctrlKey) {
97 } else if (event.which === key.enter && event.ctrlKey) {
97 98 that.execute_selected_cell({terminal:true});
98 99 return false;
99 100 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
100 101 that.control_key_active = true;
101 102 return false;
102 103 } else if (event.which === 88 && that.control_key_active) {
103 104 // Cut selected cell = x
104 105 that.cut_cell();
105 106 that.control_key_active = false;
106 107 return false;
107 108 } else if (event.which === 67 && that.control_key_active) {
108 109 // Copy selected cell = c
109 110 that.copy_cell();
110 111 that.control_key_active = false;
111 112 return false;
112 113 } else if (event.which === 86 && that.control_key_active) {
113 114 // Paste selected cell = v
114 115 that.paste_cell();
115 116 that.control_key_active = false;
116 117 return false;
117 118 } else if (event.which === 68 && that.control_key_active) {
118 119 // Delete selected cell = d
119 120 that.delete_cell();
120 121 that.control_key_active = false;
121 122 return false;
122 123 } else if (event.which === 65 && that.control_key_active) {
123 124 // Insert code cell above selected = a
124 125 that.insert_cell_above('code');
125 126 that.control_key_active = false;
126 127 return false;
127 128 } else if (event.which === 66 && that.control_key_active) {
128 129 // Insert code cell below selected = b
129 130 that.insert_cell_below('code');
130 131 that.control_key_active = false;
131 132 return false;
132 133 } else if (event.which === 89 && that.control_key_active) {
133 134 // To code = y
134 135 that.to_code();
135 136 that.control_key_active = false;
136 137 return false;
137 138 } else if (event.which === 77 && that.control_key_active) {
138 139 // To markdown = m
139 140 that.to_markdown();
140 141 that.control_key_active = false;
141 142 return false;
142 143 } else if (event.which === 84 && that.control_key_active) {
143 144 // To Raw = t
144 145 that.to_raw();
145 146 that.control_key_active = false;
146 147 return false;
147 148 } else if (event.which === 49 && that.control_key_active) {
148 149 // To Heading 1 = 1
149 150 that.to_heading(undefined, 1);
150 151 that.control_key_active = false;
151 152 return false;
152 153 } else if (event.which === 50 && that.control_key_active) {
153 154 // To Heading 2 = 2
154 155 that.to_heading(undefined, 2);
155 156 that.control_key_active = false;
156 157 return false;
157 158 } else if (event.which === 51 && that.control_key_active) {
158 159 // To Heading 3 = 3
159 160 that.to_heading(undefined, 3);
160 161 that.control_key_active = false;
161 162 return false;
162 163 } else if (event.which === 52 && that.control_key_active) {
163 164 // To Heading 4 = 4
164 165 that.to_heading(undefined, 4);
165 166 that.control_key_active = false;
166 167 return false;
167 168 } else if (event.which === 53 && that.control_key_active) {
168 169 // To Heading 5 = 5
169 170 that.to_heading(undefined, 5);
170 171 that.control_key_active = false;
171 172 return false;
172 173 } else if (event.which === 54 && that.control_key_active) {
173 174 // To Heading 6 = 6
174 175 that.to_heading(undefined, 6);
175 176 that.control_key_active = false;
176 177 return false;
177 178 } else if (event.which === 79 && that.control_key_active) {
178 179 // Toggle output = o
179 180 that.toggle_output();
180 181 that.control_key_active = false;
181 182 return false;
182 183 } else if (event.which === 83 && that.control_key_active) {
183 184 // Save notebook = s
184 185 that.save_notebook();
185 186 that.control_key_active = false;
186 187 return false;
187 188 } else if (event.which === 74 && that.control_key_active) {
188 189 // Move cell down = j
189 190 that.move_cell_down();
190 191 that.control_key_active = false;
191 192 return false;
192 193 } else if (event.which === 75 && that.control_key_active) {
193 194 // Move cell up = k
194 195 that.move_cell_up();
195 196 that.control_key_active = false;
196 197 return false;
197 198 } else if (event.which === 80 && that.control_key_active) {
198 199 // Select previous = p
199 200 that.select_prev();
200 201 that.control_key_active = false;
201 202 return false;
202 203 } else if (event.which === 78 && that.control_key_active) {
203 204 // Select next = n
204 205 that.select_next();
205 206 that.control_key_active = false;
206 207 return false;
207 208 } else if (event.which === 76 && that.control_key_active) {
208 209 // Toggle line numbers = l
209 210 that.cell_toggle_line_numbers();
210 211 that.control_key_active = false;
211 212 return false;
212 213 } else if (event.which === 73 && that.control_key_active) {
213 214 // Interrupt kernel = i
214 215 that.kernel.interrupt();
215 216 that.control_key_active = false;
216 217 return false;
217 218 } else if (event.which === 190 && that.control_key_active) {
218 219 // Restart kernel = . # matches qt console
219 220 that.restart_kernel();
220 221 that.control_key_active = false;
221 222 return false;
222 223 } else if (event.which === 72 && that.control_key_active) {
223 224 // Show keyboard shortcuts = h
224 225 IPython.quick_help.show_keyboard_shortcuts();
225 226 that.control_key_active = false;
226 227 return false;
227 228 } else if (that.control_key_active) {
228 229 that.control_key_active = false;
229 230 return true;
230 231 };
231 232 return true;
232 233 });
233 234
234 235 var collapse_time = function(time){
235 236 var app_height = $('div#main_app').height(); // content height
236 237 var splitter_height = $('div#pager_splitter').outerHeight(true);
237 238 var new_height = app_height - splitter_height;
238 239 that.element.animate({height : new_height + 'px'}, time);
239 240 }
240 241
241 242 this.element.bind('collapse_pager', function (event,extrap) {
242 243 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
243 244 collapse_time(time);
244 245 });
245 246
246 247 var expand_time = function(time) {
247 248 var app_height = $('div#main_app').height(); // content height
248 249 var splitter_height = $('div#pager_splitter').outerHeight(true);
249 250 var pager_height = $('div#pager').outerHeight(true);
250 251 var new_height = app_height - pager_height - splitter_height;
251 252 that.element.animate({height : new_height + 'px'}, time);
252 253 }
253 254
254 255 this.element.bind('expand_pager', function (event, extrap) {
255 256 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
256 257 expand_time(time);
257 258 });
258 259
259 260 $(window).bind('beforeunload', function () {
260 261 // TODO: Make killing the kernel configurable.
261 262 var kill_kernel = false;
262 263 if (kill_kernel) {
263 264 that.kernel.kill();
264 265 }
265 266 if (that.dirty && ! that.read_only) {
266 267 return "You have unsaved changes that will be lost if you leave this page.";
267 268 };
268 269 // Null is the *only* return value that will make the browser not
269 270 // pop up the "don't leave" dialog.
270 271 return null;
271 272 });
272 273 };
273 274
274 275
275 276 Notebook.prototype.scroll_to_bottom = function () {
276 277 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
277 278 };
278 279
279 280
280 281 Notebook.prototype.scroll_to_top = function () {
281 282 this.element.animate({scrollTop:0}, 0);
282 283 };
283 284
284 285
285 286 // Cell indexing, retrieval, etc.
286 287
287 288 Notebook.prototype.get_cell_elements = function () {
288 289 return this.element.children("div.cell");
289 290 };
290 291
291 292
292 293 Notebook.prototype.get_cell_element = function (index) {
293 294 var result = null;
294 295 var e = this.get_cell_elements().eq(index);
295 296 if (e.length !== 0) {
296 297 result = e;
297 298 }
298 299 return result;
299 300 };
300 301
301 302
302 303 Notebook.prototype.ncells = function (cell) {
303 304 return this.get_cell_elements().length;
304 305 };
305 306
306 307
307 308 // TODO: we are often calling cells as cells()[i], which we should optimize
308 309 // to cells(i) or a new method.
309 310 Notebook.prototype.get_cells = function () {
310 311 return this.get_cell_elements().toArray().map(function (e) {
311 312 return $(e).data("cell");
312 313 });
313 314 };
314 315
315 316
316 317 Notebook.prototype.get_cell = function (index) {
317 318 var result = null;
318 319 var ce = this.get_cell_element(index);
319 320 if (ce !== null) {
320 321 result = ce.data('cell');
321 322 }
322 323 return result;
323 324 }
324 325
325 326
326 327 Notebook.prototype.get_next_cell = function (cell) {
327 328 var result = null;
328 329 var index = this.find_cell_index(cell);
329 330 if (index !== null && index < this.ncells()) {
330 331 result = this.get_cell(index+1);
331 332 }
332 333 return result;
333 334 }
334 335
335 336
336 337 Notebook.prototype.get_prev_cell = function (cell) {
337 338 var result = null;
338 339 var index = this.find_cell_index(cell);
339 340 if (index !== null && index > 1) {
340 341 result = this.get_cell(index-1);
341 342 }
342 343 return result;
343 344 }
344 345
345 346 Notebook.prototype.find_cell_index = function (cell) {
346 347 var result = null;
347 348 this.get_cell_elements().filter(function (index) {
348 349 if ($(this).data("cell") === cell) {
349 350 result = index;
350 351 };
351 352 });
352 353 return result;
353 354 };
354 355
355 356
356 357 Notebook.prototype.index_or_selected = function (index) {
357 358 var i;
358 359 if (index === undefined || index === null) {
359 360 i = this.get_selected_index();
360 361 if (i === null) {
361 362 i = 0;
362 363 }
363 364 } else {
364 365 i = index;
365 366 }
366 367 return i;
367 368 };
368 369
369 370
370 371 Notebook.prototype.get_selected_cell = function () {
371 372 var index = this.get_selected_index();
372 373 return this.get_cell(index);
373 374 };
374 375
375 376
376 377 Notebook.prototype.is_valid_cell_index = function (index) {
377 378 if (index !== null && index >= 0 && index < this.ncells()) {
378 379 return true;
379 380 } else {
380 381 return false;
381 382 };
382 383 }
383 384
384 385 Notebook.prototype.get_selected_index = function () {
385 386 var result = null;
386 387 this.get_cell_elements().filter(function (index) {
387 388 if ($(this).data("cell").selected === true) {
388 389 result = index;
389 390 };
390 391 });
391 392 return result;
392 393 };
393 394
394 395
395 396 Notebook.prototype.cell_for_msg = function (msg_id) {
396 397 var cell_id = this.msg_cell_map[msg_id];
397 398 var result = null;
398 399 this.get_cell_elements().filter(function (index) {
399 400 cell = $(this).data("cell");
400 401 if (cell.cell_id === cell_id) {
401 402 result = cell;
402 403 };
403 404 });
404 405 return result;
405 406 };
406 407
407 408
408 409 // Cell selection.
409 410
410 411 Notebook.prototype.select = function (index) {
411 412 if (index !== undefined && index >= 0 && index < this.ncells()) {
412 413 sindex = this.get_selected_index()
413 414 if (sindex !== null && index !== sindex) {
414 415 this.get_cell(sindex).unselect();
415 416 };
416 417 var cell = this.get_cell(index)
417 418 cell.select();
418 419 if (cell.cell_type === 'heading') {
419 420 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
420 421 {'cell_type':cell.cell_type,level:cell.level}
421 422 );
422 423 } else {
423 424 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
424 425 {'cell_type':cell.cell_type}
425 426 );
426 427 };
427 428 };
428 429 return this;
429 430 };
430 431
431 432
432 433 Notebook.prototype.select_next = function () {
433 434 var index = this.get_selected_index();
434 435 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
435 436 this.select(index+1);
436 437 };
437 438 return this;
438 439 };
439 440
440 441
441 442 Notebook.prototype.select_prev = function () {
442 443 var index = this.get_selected_index();
443 444 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
444 445 this.select(index-1);
445 446 };
446 447 return this;
447 448 };
448 449
449 450
450 451 // Cell movement
451 452
452 453 Notebook.prototype.move_cell_up = function (index) {
453 454 var i = this.index_or_selected();
454 455 if (i !== null && i < this.ncells() && i > 0) {
455 456 var pivot = this.get_cell_element(i-1);
456 457 var tomove = this.get_cell_element(i);
457 458 if (pivot !== null && tomove !== null) {
458 459 tomove.detach();
459 460 pivot.before(tomove);
460 461 this.select(i-1);
461 462 };
462 463 };
463 464 this.dirty = true;
464 465 return this;
465 466 };
466 467
467 468
468 469 Notebook.prototype.move_cell_down = function (index) {
469 470 var i = this.index_or_selected();
470 471 if (i !== null && i < (this.ncells()-1) && i >= 0) {
471 472 var pivot = this.get_cell_element(i+1);
472 473 var tomove = this.get_cell_element(i);
473 474 if (pivot !== null && tomove !== null) {
474 475 tomove.detach();
475 476 pivot.after(tomove);
476 477 this.select(i+1);
477 478 };
478 479 };
479 480 this.dirty = true;
480 481 return this;
481 482 };
482 483
483 484
484 485 Notebook.prototype.sort_cells = function () {
485 486 // This is not working right now. Calling this will actually crash
486 487 // the browser. I think there is an infinite loop in here...
487 488 var ncells = this.ncells();
488 489 var sindex = this.get_selected_index();
489 490 var swapped;
490 491 do {
491 492 swapped = false;
492 493 for (var i=1; i<ncells; i++) {
493 494 current = this.get_cell(i);
494 495 previous = this.get_cell(i-1);
495 496 if (previous.input_prompt_number > current.input_prompt_number) {
496 497 this.move_cell_up(i);
497 498 swapped = true;
498 499 };
499 500 };
500 501 } while (swapped);
501 502 this.select(sindex);
502 503 return this;
503 504 };
504 505
505 506 // Insertion, deletion.
506 507
507 508 Notebook.prototype.delete_cell = function (index) {
508 509 var i = this.index_or_selected(index);
509 510 if (this.is_valid_cell_index(i)) {
510 511 var ce = this.get_cell_element(i);
511 512 ce.remove();
512 513 if (i === (this.ncells())) {
513 514 this.select(i-1);
514 515 } else {
515 516 this.select(i);
516 517 };
517 518 this.dirty = true;
518 519 };
519 520 return this;
520 521 };
521 522
522 523
523 524 Notebook.prototype.insert_cell_below = function (type, index) {
524 525 // type = ('code','html','markdown')
525 526 // index = cell index or undefined to insert below selected
526 527 index = this.index_or_selected(index);
527 528 var cell = null;
528 529 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
529 530 if (type === 'code') {
530 531 cell = new IPython.CodeCell(this);
531 532 cell.set_input_prompt();
532 533 } else if (type === 'markdown') {
533 534 cell = new IPython.MarkdownCell(this);
534 535 } else if (type === 'html') {
535 536 cell = new IPython.HTMLCell(this);
536 537 } else if (type === 'raw') {
537 538 cell = new IPython.RawCell(this);
538 539 } else if (type === 'heading') {
539 540 cell = new IPython.HeadingCell(this);
540 541 };
541 542 if (cell !== null) {
542 543 if (this.ncells() === 0) {
543 544 this.element.find('div.end_space').before(cell.element);
544 545 } else if (this.is_valid_cell_index(index)) {
545 546 this.get_cell_element(index).after(cell.element);
546 547 };
547 548 cell.render();
548 549 this.select(this.find_cell_index(cell));
549 550 this.dirty = true;
550 551 return cell;
551 552 };
552 553 };
553 554 return cell;
554 555 };
555 556
556 557
557 558 Notebook.prototype.insert_cell_above = function (type, index) {
558 559 // type = ('code','html','markdown')
559 560 // index = cell index or undefined to insert above selected
560 561 index = this.index_or_selected(index);
561 562 var cell = null;
562 563 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
563 564 if (type === 'code') {
564 565 cell = new IPython.CodeCell(this);
565 566 cell.set_input_prompt();
566 567 } else if (type === 'markdown') {
567 568 cell = new IPython.MarkdownCell(this);
568 569 } else if (type === 'html') {
569 570 cell = new IPython.HTMLCell(this);
570 571 } else if (type === 'raw') {
571 572 cell = new IPython.RawCell(this);
572 573 } else if (type === 'heading') {
573 574 cell = new IPython.HeadingCell(this);
574 575 };
575 576 if (cell !== null) {
576 577 if (this.ncells() === 0) {
577 578 this.element.find('div.end_space').before(cell.element);
578 579 } else if (this.is_valid_cell_index(index)) {
579 580 this.get_cell_element(index).before(cell.element);
580 581 };
581 582 cell.render();
582 583 this.select(this.find_cell_index(cell));
583 584 this.dirty = true;
584 585 return cell;
585 586 };
586 587 };
587 588 return cell;
588 589 };
589 590
590 591
591 592 Notebook.prototype.to_code = function (index) {
592 593 var i = this.index_or_selected(index);
593 594 if (this.is_valid_cell_index(i)) {
594 595 var source_element = this.get_cell_element(i);
595 596 var source_cell = source_element.data("cell");
596 597 if (!(source_cell instanceof IPython.CodeCell)) {
597 598 target_cell = this.insert_cell_below('code',i);
598 599 var text = source_cell.get_text();
599 600 if (text === source_cell.placeholder) {
600 601 text = '';
601 602 }
602 603 target_cell.set_text(text);
603 604 source_element.remove();
604 605 this.dirty = true;
605 606 };
606 607 };
607 608 };
608 609
609 610
610 611 Notebook.prototype.to_markdown = function (index) {
611 612 var i = this.index_or_selected(index);
612 613 if (this.is_valid_cell_index(i)) {
613 614 var source_element = this.get_cell_element(i);
614 615 var source_cell = source_element.data("cell");
615 616 if (!(source_cell instanceof IPython.MarkdownCell)) {
616 617 target_cell = this.insert_cell_below('markdown',i);
617 618 var text = source_cell.get_text();
618 619 if (text === source_cell.placeholder) {
619 620 text = '';
620 621 };
621 622 // The edit must come before the set_text.
622 623 target_cell.edit();
623 624 target_cell.set_text(text);
624 625 source_element.remove();
625 626 this.dirty = true;
626 627 };
627 628 };
628 629 };
629 630
630 631
631 632 Notebook.prototype.to_html = function (index) {
632 633 var i = this.index_or_selected(index);
633 634 if (this.is_valid_cell_index(i)) {
634 635 var source_element = this.get_cell_element(i);
635 636 var source_cell = source_element.data("cell");
636 637 var target_cell = null;
637 638 if (!(source_cell instanceof IPython.HTMLCell)) {
638 639 target_cell = this.insert_cell_below('html',i);
639 640 var text = source_cell.get_text();
640 641 if (text === source_cell.placeholder) {
641 642 text = '';
642 643 };
643 644 // The edit must come before the set_text.
644 645 target_cell.edit();
645 646 target_cell.set_text(text);
646 647 source_element.remove();
647 648 this.dirty = true;
648 649 };
649 650 };
650 651 };
651 652
652 653
653 654 Notebook.prototype.to_raw = function (index) {
654 655 var i = this.index_or_selected(index);
655 656 if (this.is_valid_cell_index(i)) {
656 657 var source_element = this.get_cell_element(i);
657 658 var source_cell = source_element.data("cell");
658 659 var target_cell = null;
659 660 if (!(source_cell instanceof IPython.RawCell)) {
660 661 target_cell = this.insert_cell_below('raw',i);
661 662 var text = source_cell.get_text();
662 663 if (text === source_cell.placeholder) {
663 664 text = '';
664 665 };
665 666 // The edit must come before the set_text.
666 667 target_cell.edit();
667 668 target_cell.set_text(text);
668 669 source_element.remove();
669 670 this.dirty = true;
670 671 };
671 672 };
672 673 };
673 674
674 675
675 676 Notebook.prototype.to_heading = function (index, level) {
676 677 level = level || 1;
677 678 var i = this.index_or_selected(index);
678 679 if (this.is_valid_cell_index(i)) {
679 680 var source_element = this.get_cell_element(i);
680 681 var source_cell = source_element.data("cell");
681 682 var target_cell = null;
682 683 if (source_cell instanceof IPython.HeadingCell) {
683 684 source_cell.set_level(level);
684 685 } else {
685 686 target_cell = this.insert_cell_below('heading',i);
686 687 var text = source_cell.get_text();
687 688 if (text === source_cell.placeholder) {
688 689 text = '';
689 690 };
690 691 // The edit must come before the set_text.
691 692 target_cell.set_level(level);
692 693 target_cell.edit();
693 694 target_cell.set_text(text);
694 695 source_element.remove();
695 696 this.dirty = true;
696 697 };
697 698 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
698 699 {'cell_type':'heading',level:level}
699 700 );
700 701 };
701 702 };
702 703
703 704
704 705 // Cut/Copy/Paste
705 706
706 707 Notebook.prototype.enable_paste = function () {
707 708 var that = this;
708 709 if (!this.paste_enabled) {
709 710 $('#paste_cell').removeClass('ui-state-disabled')
710 711 .on('click', function () {that.paste_cell();});
711 712 $('#paste_cell_above').removeClass('ui-state-disabled')
712 713 .on('click', function () {that.paste_cell_above();});
713 714 $('#paste_cell_below').removeClass('ui-state-disabled')
714 715 .on('click', function () {that.paste_cell_below();});
715 716 this.paste_enabled = true;
716 717 };
717 718 };
718 719
719 720
720 721 Notebook.prototype.disable_paste = function () {
721 722 if (this.paste_enabled) {
722 723 $('#paste_cell').addClass('ui-state-disabled').off('click');
723 724 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
724 725 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
725 726 this.paste_enabled = false;
726 727 };
727 728 };
728 729
729 730
730 731 Notebook.prototype.cut_cell = function () {
731 732 this.copy_cell();
732 733 this.delete_cell();
733 734 }
734 735
735 736 Notebook.prototype.copy_cell = function () {
736 737 var cell = this.get_selected_cell();
737 738 this.clipboard = cell.toJSON();
738 739 this.enable_paste();
739 740 };
740 741
741 742
742 743 Notebook.prototype.paste_cell = function () {
743 744 if (this.clipboard !== null && this.paste_enabled) {
744 745 var cell_data = this.clipboard;
745 746 var new_cell = this.insert_cell_above(cell_data.cell_type);
746 747 new_cell.fromJSON(cell_data);
747 748 old_cell = this.get_next_cell(new_cell);
748 749 this.delete_cell(this.find_cell_index(old_cell));
749 750 this.select(this.find_cell_index(new_cell));
750 751 };
751 752 };
752 753
753 754
754 755 Notebook.prototype.paste_cell_above = function () {
755 756 if (this.clipboard !== null && this.paste_enabled) {
756 757 var cell_data = this.clipboard;
757 758 var new_cell = this.insert_cell_above(cell_data.cell_type);
758 759 new_cell.fromJSON(cell_data);
759 760 };
760 761 };
761 762
762 763
763 764 Notebook.prototype.paste_cell_below = function () {
764 765 if (this.clipboard !== null && this.paste_enabled) {
765 766 var cell_data = this.clipboard;
766 767 var new_cell = this.insert_cell_below(cell_data.cell_type);
767 768 new_cell.fromJSON(cell_data);
768 769 };
769 770 };
770 771
771 772
772 773 // Split/merge
773 774
774 775 Notebook.prototype.split_cell = function () {
775 776 // Todo: implement spliting for other cell types.
776 777 var cell = this.get_selected_cell();
777 778 if (cell.is_splittable()) {
778 779 texta = cell.get_pre_cursor();
779 780 textb = cell.get_post_cursor();
780 781 if (cell instanceof IPython.CodeCell) {
781 782 cell.set_text(texta);
782 783 var new_cell = this.insert_cell_below('code');
783 784 new_cell.set_text(textb);
784 785 } else if (cell instanceof IPython.MarkdownCell) {
785 786 cell.set_text(texta);
786 787 cell.render();
787 788 var new_cell = this.insert_cell_below('markdown');
788 789 new_cell.edit(); // editor must be visible to call set_text
789 790 new_cell.set_text(textb);
790 791 new_cell.render();
791 792 } else if (cell instanceof IPython.HTMLCell) {
792 793 cell.set_text(texta);
793 794 cell.render();
794 795 var new_cell = this.insert_cell_below('html');
795 796 new_cell.edit(); // editor must be visible to call set_text
796 797 new_cell.set_text(textb);
797 798 new_cell.render();
798 799 };
799 800 };
800 801 };
801 802
802 803
803 804 Notebook.prototype.merge_cell_above = function () {
804 805 var index = this.get_selected_index();
805 806 var cell = this.get_cell(index);
806 807 if (index > 0) {
807 808 upper_cell = this.get_cell(index-1);
808 809 upper_text = upper_cell.get_text();
809 810 text = cell.get_text();
810 811 if (cell instanceof IPython.CodeCell) {
811 812 cell.set_text(upper_text+'\n'+text);
812 813 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
813 814 cell.edit();
814 815 cell.set_text(upper_text+'\n'+text);
815 816 cell.render();
816 817 };
817 818 this.delete_cell(index-1);
818 819 this.select(this.find_cell_index(cell));
819 820 };
820 821 };
821 822
822 823
823 824 Notebook.prototype.merge_cell_below = function () {
824 825 var index = this.get_selected_index();
825 826 var cell = this.get_cell(index);
826 827 if (index < this.ncells()-1) {
827 828 lower_cell = this.get_cell(index+1);
828 829 lower_text = lower_cell.get_text();
829 830 text = cell.get_text();
830 831 if (cell instanceof IPython.CodeCell) {
831 832 cell.set_text(text+'\n'+lower_text);
832 833 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
833 834 cell.edit();
834 835 cell.set_text(text+'\n'+lower_text);
835 836 cell.render();
836 837 };
837 838 this.delete_cell(index+1);
838 839 this.select(this.find_cell_index(cell));
839 840 };
840 841 };
841 842
842 843
843 844 // Cell collapsing and output clearing
844 845
845 846 Notebook.prototype.collapse = function (index) {
846 847 var i = this.index_or_selected(index);
847 848 this.get_cell(i).collapse();
848 849 this.dirty = true;
849 850 };
850 851
851 852
852 853 Notebook.prototype.expand = function (index) {
853 854 var i = this.index_or_selected(index);
854 855 this.get_cell(i).expand();
855 856 this.dirty = true;
856 857 };
857 858
858 859
859 860 Notebook.prototype.toggle_output = function (index) {
860 861 var i = this.index_or_selected(index);
861 862 this.get_cell(i).toggle_output();
862 863 this.dirty = true;
863 864 };
864 865
865 866
866 867 Notebook.prototype.set_timebeforetooltip = function (time) {
867 868 this.time_before_tooltip = time;
868 869 };
869 870
870 871
871 872 Notebook.prototype.set_tooltipontab = function (state) {
872 873 this.tooltip_on_tab = state;
873 874 };
874 875
875 876
876 877 Notebook.prototype.set_smartcompleter = function (state) {
877 878 this.smart_completer = state;
878 879 };
879 880
880 881
881 882 Notebook.prototype.clear_all_output = function () {
882 883 var ncells = this.ncells();
883 884 var cells = this.get_cells();
884 885 for (var i=0; i<ncells; i++) {
885 886 if (cells[i] instanceof IPython.CodeCell) {
886 887 cells[i].clear_output(true,true,true);
887 888 // Make all In[] prompts blank, as well
888 889 // TODO: make this configurable (via checkbox?)
889 890 cells[i].set_input_prompt();
890 891 }
891 892 };
892 893 this.dirty = true;
893 894 };
894 895
895 896
896 897 // Other cell functions: line numbers, ...
897 898
898 899 Notebook.prototype.cell_toggle_line_numbers = function() {
899 900 this.get_selected_cell().toggle_line_numbers();
900 901 };
901 902
902 903 // Kernel related things
903 904
904 905 Notebook.prototype.start_kernel = function () {
905 906 this.kernel = new IPython.Kernel();
906 907 this.kernel.start(this.notebook_id, $.proxy(this.kernel_started, this));
907 908 };
908 909
909 910
910 911 Notebook.prototype.restart_kernel = function () {
911 912 var that = this;
912 913 var dialog = $('<div/>');
913 914 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
914 915 $(document).append(dialog);
915 916 dialog.dialog({
916 917 resizable: false,
917 918 modal: true,
918 919 title: "Restart kernel or continue running?",
919 920 closeText: '',
920 921 buttons : {
921 922 "Restart": function () {
922 923 that.kernel.restart($.proxy(that.kernel_started, that));
923 924 $(this).dialog('close');
924 925 },
925 926 "Continue running": function () {
926 927 $(this).dialog('close');
927 928 }
928 929 }
929 930 });
930 931 };
931 932
932 933
933 934 Notebook.prototype.kernel_started = function () {
934 935 console.log("Kernel started: ", this.kernel.kernel_id);
935 936 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
936 937 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
937 938 };
938 939
939 940
940 941 Notebook.prototype.handle_shell_reply = function (e) {
941 942 reply = $.parseJSON(e.data);
942 943 var header = reply.header;
943 944 var content = reply.content;
944 945 var msg_type = header.msg_type;
945 946 // console.log(reply);
946 947 var cell = this.cell_for_msg(reply.parent_header.msg_id);
947 948 if (msg_type === "execute_reply") {
948 949 cell.set_input_prompt(content.execution_count);
949 950 cell.element.removeClass("running");
950 951 this.dirty = true;
951 952 } else if (msg_type === "complete_reply") {
952 953 cell.finish_completing(content.matched_text, content.matches);
953 954 } else if (msg_type === "object_info_reply"){
954 955 //console.log('back from object_info_request : ')
955 956 rep = reply.content;
956 957 if(rep.found)
957 958 {
958 959 cell.finish_tooltip(rep);
959 960 }
960 961 } else {
961 962 //console.log("unknown reply:"+msg_type);
962 963 }
963 964 // when having a rely from object_info_reply,
964 965 // no payload so no nned to handle it
965 966 if(typeof(content.payload)!='undefined') {
966 967 var payload = content.payload || [];
967 968 this.handle_payload(cell, payload);
968 969 }
969 970 };
970 971
971 972
972 973 Notebook.prototype.handle_payload = function (cell, payload) {
973 974 var l = payload.length;
974 975 for (var i=0; i<l; i++) {
975 976 if (payload[i].source === 'IPython.zmq.page.page') {
976 977 if (payload[i].text.trim() !== '') {
977 978 IPython.pager.clear();
978 979 IPython.pager.expand();
979 980 IPython.pager.append_text(payload[i].text);
980 981 }
981 982 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
982 983 var index = this.find_cell_index(cell);
983 984 var new_cell = this.insert_cell_below('code',index);
984 985 new_cell.set_text(payload[i].text);
985 986 this.dirty = true;
986 987 }
987 988 };
988 989 };
989 990
990 991
991 992 Notebook.prototype.handle_iopub_reply = function (e) {
992 993 reply = $.parseJSON(e.data);
993 994 var content = reply.content;
994 995 // console.log(reply);
995 996 var msg_type = reply.header.msg_type;
996 997 var cell = this.cell_for_msg(reply.parent_header.msg_id);
997 998 if (msg_type !== 'status' && !cell){
998 999 // message not from this notebook, but should be attached to a cell
999 1000 // console.log("Received IOPub message not caused by one of my cells");
1000 1001 // console.log(reply);
1001 1002 return;
1002 1003 }
1003 1004 var output_types = ['stream','display_data','pyout','pyerr'];
1004 1005 if (output_types.indexOf(msg_type) >= 0) {
1005 1006 this.handle_output(cell, msg_type, content);
1006 1007 } else if (msg_type === 'status') {
1007 1008 if (content.execution_state === 'busy') {
1008 1009 $([IPython.events]).trigger('status_busy.Kernel');
1009 1010 } else if (content.execution_state === 'idle') {
1010 1011 $([IPython.events]).trigger('status_idle.Kernel');
1011 1012 } else if (content.execution_state === 'dead') {
1012 1013 this.handle_status_dead();
1013 1014 };
1014 1015 } else if (msg_type === 'clear_output') {
1015 1016 cell.clear_output(content.stdout, content.stderr, content.other);
1016 1017 };
1017 1018 };
1018 1019
1019 1020
1020 1021 Notebook.prototype.handle_status_dead = function () {
1021 1022 var that = this;
1022 1023 this.kernel.stop_channels();
1023 1024 var dialog = $('<div/>');
1024 1025 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
1025 1026 $(document).append(dialog);
1026 1027 dialog.dialog({
1027 1028 resizable: false,
1028 1029 modal: true,
1029 1030 title: "Dead kernel",
1030 1031 buttons : {
1031 1032 "Restart": function () {
1032 1033 that.start_kernel();
1033 1034 $(this).dialog('close');
1034 1035 },
1035 1036 "Continue running": function () {
1036 1037 $(this).dialog('close');
1037 1038 }
1038 1039 }
1039 1040 });
1040 1041 };
1041 1042
1042 1043
1043 1044 Notebook.prototype.handle_output = function (cell, msg_type, content) {
1044 1045 var json = {};
1045 1046 json.output_type = msg_type;
1046 1047 if (msg_type === "stream") {
1047 1048 json.text = content.data;
1048 1049 json.stream = content.name;
1049 1050 } else if (msg_type === "display_data") {
1050 1051 json = this.convert_mime_types(json, content.data);
1051 1052 } else if (msg_type === "pyout") {
1052 1053 json.prompt_number = content.execution_count;
1053 1054 json = this.convert_mime_types(json, content.data);
1054 1055 } else if (msg_type === "pyerr") {
1055 1056 json.ename = content.ename;
1056 1057 json.evalue = content.evalue;
1057 1058 json.traceback = content.traceback;
1058 1059 };
1059 1060 // append with dynamic=true
1060 1061 cell.append_output(json, true);
1061 1062 this.dirty = true;
1062 1063 };
1063 1064
1064 1065
1065 1066 Notebook.prototype.convert_mime_types = function (json, data) {
1066 1067 if (data['text/plain'] !== undefined) {
1067 1068 json.text = data['text/plain'];
1068 1069 };
1069 1070 if (data['text/html'] !== undefined) {
1070 1071 json.html = data['text/html'];
1071 1072 };
1072 1073 if (data['image/svg+xml'] !== undefined) {
1073 1074 json.svg = data['image/svg+xml'];
1074 1075 };
1075 1076 if (data['image/png'] !== undefined) {
1076 1077 json.png = data['image/png'];
1077 1078 };
1078 1079 if (data['image/jpeg'] !== undefined) {
1079 1080 json.jpeg = data['image/jpeg'];
1080 1081 };
1081 1082 if (data['text/latex'] !== undefined) {
1082 1083 json.latex = data['text/latex'];
1083 1084 };
1084 1085 if (data['application/json'] !== undefined) {
1085 1086 json.json = data['application/json'];
1086 1087 };
1087 1088 if (data['application/javascript'] !== undefined) {
1088 1089 json.javascript = data['application/javascript'];
1089 1090 }
1090 1091 return json;
1091 1092 };
1092 1093
1093 1094
1094 1095 Notebook.prototype.execute_selected_cell = function (options) {
1095 1096 // add_new: should a new cell be added if we are at the end of the nb
1096 1097 // terminal: execute in terminal mode, which stays in the current cell
1097 1098 default_options = {terminal: false, add_new: true};
1098 1099 $.extend(default_options, options);
1099 1100 var that = this;
1100 1101 var cell = that.get_selected_cell();
1101 1102 var cell_index = that.find_cell_index(cell);
1102 1103 if (cell instanceof IPython.CodeCell) {
1103 1104 cell.clear_output(true, true, true);
1104 1105 cell.set_input_prompt('*');
1105 1106 cell.element.addClass("running");
1106 1107 var code = cell.get_text();
1107 1108 var msg_id = that.kernel.execute(cell.get_text());
1108 1109 that.msg_cell_map[msg_id] = cell.cell_id;
1109 1110 } else if (cell instanceof IPython.HTMLCell) {
1110 1111 cell.render();
1111 1112 }
1112 1113 if (default_options.terminal) {
1113 1114 cell.select_all();
1114 1115 } else {
1115 1116 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1116 1117 that.insert_cell_below('code');
1117 1118 // If we are adding a new cell at the end, scroll down to show it.
1118 1119 that.scroll_to_bottom();
1119 1120 } else {
1120 1121 that.select(cell_index+1);
1121 1122 };
1122 1123 };
1123 1124 this.dirty = true;
1124 1125 };
1125 1126
1126 1127
1127 1128 Notebook.prototype.execute_all_cells = function () {
1128 1129 var ncells = this.ncells();
1129 1130 for (var i=0; i<ncells; i++) {
1130 1131 this.select(i);
1131 1132 this.execute_selected_cell({add_new:false});
1132 1133 };
1133 1134 this.scroll_to_bottom();
1134 1135 };
1135 1136
1136 1137
1137 1138 Notebook.prototype.request_tool_tip = function (cell,func) {
1138 1139 // Feel free to shorten this logic if you are better
1139 1140 // than me in regEx
1140 1141 // basicaly you shoul be able to get xxx.xxx.xxx from
1141 1142 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
1142 1143 // remove everything between matchin bracket (need to iterate)
1143 1144 matchBracket = /\([^\(\)]+\)/g;
1144 1145 oldfunc = func;
1145 1146 func = func.replace(matchBracket,"");
1146 1147 while( oldfunc != func )
1147 1148 {
1148 1149 oldfunc = func;
1149 1150 func = func.replace(matchBracket,"");
1150 1151 }
1151 1152 // remove everythin after last open bracket
1152 1153 endBracket = /\([^\(]*$/g;
1153 1154 func = func.replace(endBracket,"");
1154 1155 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
1155 1156 var msg_id = this.kernel.object_info_request(re.exec(func));
1156 1157 if(typeof(msg_id)!='undefined'){
1157 1158 this.msg_cell_map[msg_id] = cell.cell_id;
1158 1159 }
1159 1160 };
1160 1161
1161 1162 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
1162 1163 var msg_id = this.kernel.complete(line, cursor_pos);
1163 1164 this.msg_cell_map[msg_id] = cell.cell_id;
1164 1165 };
1165 1166
1166 1167
1167 1168 // Persistance and loading
1168 1169
1169 1170 Notebook.prototype.get_notebook_id = function () {
1170 1171 return this.notebook_id;
1171 1172 };
1172 1173
1173 1174
1174 1175 Notebook.prototype.get_notebook_name = function () {
1175 1176 return this.notebook_name;
1176 1177 };
1177 1178
1178 1179
1179 1180 Notebook.prototype.set_notebook_name = function (name) {
1180 1181 this.notebook_name = name;
1181 1182 };
1182 1183
1183 1184
1184 1185 Notebook.prototype.test_notebook_name = function (nbname) {
1185 1186 nbname = nbname || '';
1186 1187 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1187 1188 return true;
1188 1189 } else {
1189 1190 return false;
1190 1191 };
1191 1192 };
1192 1193
1193 1194
1194 1195 Notebook.prototype.fromJSON = function (data) {
1195 1196 var ncells = this.ncells();
1196 1197 var i;
1197 1198 for (i=0; i<ncells; i++) {
1198 1199 // Always delete cell 0 as they get renumbered as they are deleted.
1199 1200 this.delete_cell(0);
1200 1201 };
1201 1202 // Save the metadata and name.
1202 1203 this.metadata = data.metadata;
1203 1204 this.notebook_name = data.metadata.name;
1204 1205 // Only handle 1 worksheet for now.
1205 1206 var worksheet = data.worksheets[0];
1206 1207 if (worksheet !== undefined) {
1207 1208 var new_cells = worksheet.cells;
1208 1209 ncells = new_cells.length;
1209 1210 var cell_data = null;
1210 1211 var new_cell = null;
1211 1212 for (i=0; i<ncells; i++) {
1212 1213 cell_data = new_cells[i];
1213 1214 // VERSIONHACK: plaintext -> raw
1214 1215 // handle never-released plaintext name for raw cells
1215 1216 if (cell_data.cell_type === 'plaintext'){
1216 1217 cell_data.cell_type = 'raw';
1217 1218 }
1218 1219
1219 1220 new_cell = this.insert_cell_below(cell_data.cell_type);
1220 1221 new_cell.fromJSON(cell_data);
1221 1222 };
1222 1223 };
1223 1224 };
1224 1225
1225 1226
1226 1227 Notebook.prototype.toJSON = function () {
1227 1228 var cells = this.get_cells();
1228 1229 var ncells = cells.length;
1229 1230 cell_array = new Array(ncells);
1230 1231 for (var i=0; i<ncells; i++) {
1231 1232 cell_array[i] = cells[i].toJSON();
1232 1233 };
1233 1234 data = {
1234 1235 // Only handle 1 worksheet for now.
1235 1236 worksheets : [{cells:cell_array}],
1236 1237 metadata : this.metadata
1237 1238 };
1238 1239 return data;
1239 1240 };
1240 1241
1241 1242 Notebook.prototype.save_notebook = function () {
1242 1243 // We may want to move the name/id/nbformat logic inside toJSON?
1243 1244 var data = this.toJSON();
1244 1245 data.metadata.name = this.notebook_name;
1245 1246 data.nbformat = this.nbformat;
1246 1247 // We do the call with settings so we can set cache to false.
1247 1248 var settings = {
1248 1249 processData : false,
1249 1250 cache : false,
1250 1251 type : "PUT",
1251 1252 data : JSON.stringify(data),
1252 1253 headers : {'Content-Type': 'application/json'},
1253 1254 success : $.proxy(this.save_notebook_success,this),
1254 1255 error : $.proxy(this.save_notebook_error,this)
1255 1256 };
1256 1257 $([IPython.events]).trigger('notebook_saving.Notebook');
1257 1258 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1258 1259 $.ajax(url, settings);
1259 1260 };
1260 1261
1261 1262
1262 1263 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1263 1264 this.dirty = false;
1264 1265 $([IPython.events]).trigger('notebook_saved.Notebook');
1265 1266 };
1266 1267
1267 1268
1268 1269 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1269 1270 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1270 1271 };
1271 1272
1272 1273
1273 1274 Notebook.prototype.load_notebook = function (notebook_id) {
1274 1275 var that = this;
1275 1276 this.notebook_id = notebook_id;
1276 1277 // We do the call with settings so we can set cache to false.
1277 1278 var settings = {
1278 1279 processData : false,
1279 1280 cache : false,
1280 1281 type : "GET",
1281 1282 dataType : "json",
1282 1283 success : $.proxy(this.load_notebook_success,this),
1283 1284 error : $.proxy(this.load_notebook_error,this),
1284 1285 };
1285 1286 $([IPython.events]).trigger('notebook_loading.Notebook');
1286 1287 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1287 1288 $.ajax(url, settings);
1288 1289 };
1289 1290
1290 1291
1291 1292 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1292 1293 this.fromJSON(data);
1293 1294 if (this.ncells() === 0) {
1294 1295 this.insert_cell_below('code');
1295 1296 };
1296 1297 this.dirty = false;
1297 1298 if (! this.read_only) {
1298 1299 this.start_kernel();
1299 1300 }
1300 1301 this.select(0);
1301 1302 this.scroll_to_top();
1302 1303 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1303 1304 msg = "This notebook has been converted from an older " +
1304 1305 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1305 1306 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1306 1307 "newer notebook format will be used and older verions of IPython " +
1307 1308 "may not be able to read it. To keep the older version, close the " +
1308 1309 "notebook without saving it.";
1309 1310 var dialog = $('<div/>');
1310 1311 dialog.html(msg);
1311 1312 this.element.append(dialog);
1312 1313 dialog.dialog({
1313 1314 resizable: false,
1314 1315 modal: true,
1315 1316 title: "Notebook converted",
1316 1317 closeText: "",
1317 1318 close: function(event, ui) {$(this).dialog('destroy').remove();},
1318 1319 buttons : {
1319 1320 "OK": function () {
1320 1321 $(this).dialog('close');
1321 1322 }
1322 1323 },
1323 1324 width: 400
1324 1325 });
1325 1326 }
1326 1327 $([IPython.events]).trigger('notebook_loaded.Notebook');
1327 1328 };
1328 1329
1329 1330
1330 1331 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1331 1332 if (xhr.status === 500) {
1332 1333 msg = "An error occurred while loading this notebook. Most likely " +
1333 1334 "this notebook is in a newer format than is supported by this " +
1334 1335 "version of IPython. This version can load notebook formats " +
1335 1336 "v"+this.nbformat+" or earlier.";
1336 1337 var dialog = $('<div/>');
1337 1338 dialog.html(msg);
1338 1339 this.element.append(dialog);
1339 1340 dialog.dialog({
1340 1341 resizable: false,
1341 1342 modal: true,
1342 1343 title: "Error loading notebook",
1343 1344 closeText: "",
1344 1345 close: function(event, ui) {$(this).dialog('destroy').remove();},
1345 1346 buttons : {
1346 1347 "OK": function () {
1347 1348 $(this).dialog('close');
1348 1349 }
1349 1350 },
1350 1351 width: 400
1351 1352 });
1352 1353 }
1353 1354 }
1354 1355
1355 1356 IPython.Notebook = Notebook;
1356 1357
1357 1358
1358 1359 return IPython;
1359 1360
1360 1361 }(IPython));
1361 1362
@@ -1,120 +1,122
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Utilities
10 10 //============================================================================
11 11
12 12 IPython.namespace('IPython.utils');
13 13
14 14 IPython.utils = (function (IPython) {
15 15
16 16 var uuid = function () {
17 17 // http://www.ietf.org/rfc/rfc4122.txt
18 18 var s = [];
19 19 var hexDigits = "0123456789ABCDEF";
20 20 for (var i = 0; i < 32; i++) {
21 21 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
22 22 }
23 23 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
24 24 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
25 25
26 26 var uuid = s.join("");
27 27 return uuid;
28 28 };
29 29
30 30
31 31 //Fix raw text to parse correctly in crazy XML
32 32 function xmlencode(string) {
33 33 return string.replace(/\&/g,'&'+'amp;')
34 34 .replace(/</g,'&'+'lt;')
35 35 .replace(/>/g,'&'+'gt;')
36 36 .replace(/\'/g,'&'+'apos;')
37 37 .replace(/\"/g,'&'+'quot;')
38 38 .replace(/`/g,'&'+'#96;');
39 39 }
40 40
41 41
42 42 //Map from terminal commands to CSS classes
43 43 ansi_colormap = {
44 44 "30":"ansiblack", "31":"ansired",
45 45 "32":"ansigreen", "33":"ansiyellow",
46 46 "34":"ansiblue", "35":"ansipurple","36":"ansicyan",
47 47 "37":"ansigrey", "01":"ansibold"
48 48 };
49 49
50 50 // Transform ANI color escape codes into HTML <span> tags with css
51 51 // classes listed in the above ansi_colormap object. The actual color used
52 52 // are set in the css file.
53 53 function fixConsole(txt) {
54 54 txt = xmlencode(txt);
55 55 var re = /\033\[([\dA-Fa-f;]*?)m/;
56 56 var opened = false;
57 57 var cmds = [];
58 58 var opener = "";
59 59 var closer = "";
60 60 // \r does nothing, so shouldn't be included
61 61 txt = txt.replace('\r', '');
62 62 while (re.test(txt)) {
63 63 var cmds = txt.match(re)[1].split(";");
64 64 closer = opened?"</span>":"";
65 65 opened = cmds.length > 1 || cmds[0] != 0;
66 66 var rep = [];
67 67 for (var i in cmds)
68 68 if (typeof(ansi_colormap[cmds[i]]) != "undefined")
69 69 rep.push(ansi_colormap[cmds[i]]);
70 70 opener = rep.length > 0?"<span class=\""+rep.join(" ")+"\">":"";
71 71 txt = txt.replace(re, closer + opener);
72 72 }
73 73 if (opened) txt += "</span>";
74 74 return txt;
75 75 }
76 76
77 77
78 78 grow = function(element) {
79 79 // Grow the cell by hand. This is used upon reloading from JSON, when the
80 80 // autogrow handler is not called.
81 81 var dom = element.get(0);
82 82 var lines_count = 0;
83 83 // modified split rule from
84 84 // http://stackoverflow.com/questions/2035910/how-to-get-the-number-of-lines-in-a-textarea/2036424#2036424
85 85 var lines = dom.value.split(/\r|\r\n|\n/);
86 86 lines_count = lines.length;
87 87 if (lines_count >= 1) {
88 88 dom.rows = lines_count;
89 89 } else {
90 90 dom.rows = 1;
91 91 }
92 92 };
93 93
94 94 // some keycodes that seem to be platform/browser independant
95 95 var keycodes ={
96 96 backspace: 8,
97 97 tab : 9,
98 98 enter : 13,
99 99 shift : 16,
100 ctrl : 17,
101 control : 17,
100 102 esc : 27,
101 103 space : 32,
102 104 pgUp : 33,
103 105 pgDown : 34,
104 106 leftArrow: 37,
105 107 left : 37,
106 108 upArrow : 38,
107 109 rightArrow:39,
108 110 right : 39,
109 111 downArrow: 40,
110 112 };
111 113
112 114 return {
113 115 uuid : uuid,
114 116 fixConsole : fixConsole,
115 117 keycodes : keycodes,
116 118 grow : grow,
117 119 };
118 120
119 121 }(IPython));
120 122
General Comments 0
You need to be logged in to leave comments. Login now