##// END OF EJS Templates
Add logic to close tt when cell is unselected
Jonathan Frederic -
Show More
@@ -1,564 +1,576 b''
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 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule CodeCell
16 16 */
17 17
18 18
19 19 /* local util for codemirror */
20 20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21 21
22 22 /**
23 23 *
24 24 * function to delete until previous non blanking space character
25 25 * or first multiple of 4 tabstop.
26 26 * @private
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 35 var select = cm.getRange(from,cur);
36 36 if( select.match(/^\ +$/) !== null){
37 37 cm.replaceRange("",from,cur);
38 38 } else {
39 39 cm.deleteH(-1,"char");
40 40 }
41 41 };
42 42
43 43
44 44 var IPython = (function (IPython) {
45 45 "use strict";
46 46
47 47 var utils = IPython.utils;
48 48 var keycodes = IPython.keyboard.keycodes;
49 49
50 50 /**
51 51 * A Cell conceived to write code.
52 52 *
53 53 * The kernel doesn't have to be set at creation time, in that case
54 54 * it will be null and set_kernel has to be called later.
55 55 * @class CodeCell
56 56 * @extends IPython.Cell
57 57 *
58 58 * @constructor
59 59 * @param {Object|null} kernel
60 60 * @param {object|undefined} [options]
61 61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 62 */
63 63 var CodeCell = function (kernel, options) {
64 64 this.kernel = kernel || null;
65 65 this.collapsed = false;
66 66
67 67 // create all attributed in constructor function
68 68 // even if null for V8 VM optimisation
69 69 this.input_prompt_number = null;
70 70 this.celltoolbar = null;
71 71 this.output_area = null;
72 72 this.last_msg_id = null;
73 73 this.completer = null;
74 74
75 75
76 76 var cm_overwrite_options = {
77 77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 78 };
79 79
80 80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81 81
82 82 IPython.Cell.apply(this,[options]);
83 83
84 84 // Attributes we want to override in this subclass.
85 85 this.cell_type = "code";
86 86
87 87 var that = this;
88 88 this.element.focusout(
89 89 function() { that.auto_highlight(); }
90 90 );
91 91 };
92 92
93 93 CodeCell.options_default = {
94 94 cm_config : {
95 95 extraKeys: {
96 96 "Tab" : "indentMore",
97 97 "Shift-Tab" : "indentLess",
98 98 "Backspace" : "delSpaceToPrevTabStop",
99 99 "Cmd-/" : "toggleComment",
100 100 "Ctrl-/" : "toggleComment"
101 101 },
102 102 mode: 'ipython',
103 103 theme: 'ipython',
104 104 matchBrackets: true,
105 105 autoCloseBrackets: true
106 106 }
107 107 };
108 108
109 109 CodeCell.msg_cells = {};
110 110
111 111 CodeCell.prototype = new IPython.Cell();
112 112
113 113 /**
114 114 * @method auto_highlight
115 115 */
116 116 CodeCell.prototype.auto_highlight = function () {
117 117 this._auto_highlight(IPython.config.cell_magic_highlight);
118 118 };
119 119
120 120 /** @method create_element */
121 121 CodeCell.prototype.create_element = function () {
122 122 IPython.Cell.prototype.create_element.apply(this, arguments);
123 123
124 124 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
125 125 cell.attr('tabindex','2');
126 126
127 127 var input = $('<div></div>').addClass('input');
128 128 var prompt = $('<div/>').addClass('prompt input_prompt');
129 129 var inner_cell = $('<div/>').addClass('inner_cell');
130 130 this.celltoolbar = new IPython.CellToolbar(this);
131 131 inner_cell.append(this.celltoolbar.element);
132 132 var input_area = $('<div/>').addClass('input_area');
133 133 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
134 134 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
135 135 inner_cell.append(input_area);
136 136 input.append(prompt).append(inner_cell);
137 137
138 138 var widget_area = $('<div/>')
139 139 .addClass('widget-area')
140 140 .hide();
141 141 this.widget_area = widget_area;
142 142 var widget_prompt = $('<div/>')
143 143 .addClass('prompt')
144 144 .appendTo(widget_area);
145 145 var widget_subarea = $('<div/>')
146 146 .addClass('widget-subarea')
147 147 .appendTo(widget_area);
148 148 this.widget_subarea = widget_subarea;
149 149 var widget_clear_buton = $('<button />')
150 150 .addClass('close')
151 151 .html('&times;')
152 152 .click(function() {
153 153 widget_area.slideUp('', function(){ widget_subarea.html(''); });
154 154 })
155 155 .appendTo(widget_prompt);
156 156
157 157 var output = $('<div></div>');
158 158 cell.append(input).append(widget_area).append(output);
159 159 this.element = cell;
160 160 this.output_area = new IPython.OutputArea(output, true);
161 161 this.completer = new IPython.Completer(this);
162 162 };
163 163
164 164 /** @method bind_events */
165 165 CodeCell.prototype.bind_events = function () {
166 166 IPython.Cell.prototype.bind_events.apply(this);
167 167 var that = this;
168 168
169 169 this.element.focusout(
170 170 function() { that.auto_highlight(); }
171 171 );
172 172 };
173 173
174 174 CodeCell.prototype.handle_keyevent = function (editor, event) {
175 175
176 176 // console.log('CM', this.mode, event.which, event.type)
177 177
178 178 if (this.mode === 'command') {
179 179 return true;
180 180 } else if (this.mode === 'edit') {
181 181 return this.handle_codemirror_keyevent(editor, event);
182 182 }
183 183 };
184 184
185 185 /**
186 186 * This method gets called in CodeMirror's onKeyDown/onKeyPress
187 187 * handlers and is used to provide custom key handling. Its return
188 188 * value is used to determine if CodeMirror should ignore the event:
189 189 * true = ignore, false = don't ignore.
190 190 * @method handle_codemirror_keyevent
191 191 */
192 192 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
193 193
194 194 var that = this;
195 195 // whatever key is pressed, first, cancel the tooltip request before
196 196 // they are sent, and remove tooltip if any, except for tab again
197 197 var tooltip_closed = null;
198 198 if (event.type === 'keydown' && event.which != keycodes.tab ) {
199 199 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
200 200 }
201 201
202 202 var cur = editor.getCursor();
203 203 if (event.keyCode === keycodes.enter){
204 204 this.auto_highlight();
205 205 }
206 206
207 207 if (event.keyCode === keycodes.enter && (event.shiftKey || event.ctrlKey || event.altKey)) {
208 208 // Always ignore shift-enter in CodeMirror as we handle it.
209 209 return true;
210 210 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
211 211 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
212 212 // browser and keyboard layout !
213 213 // Pressing '(' , request tooltip, don't forget to reappend it
214 214 // The second argument says to hide the tooltip if the docstring
215 215 // is actually empty
216 216 IPython.tooltip.pending(that, true);
217 217 } else if (event.which === keycodes.up && event.type === 'keydown') {
218 218 // If we are not at the top, let CM handle the up arrow and
219 219 // prevent the global keydown handler from handling it.
220 220 if (!that.at_top()) {
221 221 event.stop();
222 222 return false;
223 223 } else {
224 224 return true;
225 225 }
226 226 } else if (event.which === keycodes.esc && event.type === 'keydown') {
227 227 // First see if the tooltip is active and if so cancel it.
228 228 if (tooltip_closed) {
229 229 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
230 230 // force=true. Because of this it won't actually close the tooltip
231 231 // if it is in sticky mode. Thus, we have to check again if it is open
232 232 // and close it with force=true.
233 233 if (!IPython.tooltip._hidden) {
234 234 IPython.tooltip.remove_and_cancel_tooltip(true);
235 235 }
236 236 // If we closed the tooltip, don't let CM or the global handlers
237 237 // handle this event.
238 238 event.stop();
239 239 return true;
240 240 }
241 241 if (that.code_mirror.options.keyMap === "vim-insert") {
242 242 // vim keyMap is active and in insert mode. In this case we leave vim
243 243 // insert mode, but remain in notebook edit mode.
244 244 // Let' CM handle this event and prevent global handling.
245 245 event.stop();
246 246 return false;
247 247 } else {
248 248 // vim keyMap is not active. Leave notebook edit mode.
249 249 // Don't let CM handle the event, defer to global handling.
250 250 return true;
251 251 }
252 252 } else if (event.which === keycodes.down && event.type === 'keydown') {
253 253 // If we are not at the bottom, let CM handle the down arrow and
254 254 // prevent the global keydown handler from handling it.
255 255 if (!that.at_bottom()) {
256 256 event.stop();
257 257 return false;
258 258 } else {
259 259 return true;
260 260 }
261 261 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
262 262 if (editor.somethingSelected()){
263 263 var anchor = editor.getCursor("anchor");
264 264 var head = editor.getCursor("head");
265 265 if( anchor.line != head.line){
266 266 return false;
267 267 }
268 268 }
269 269 IPython.tooltip.request(that);
270 270 event.stop();
271 271 return true;
272 272 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
273 273 // Tab completion.
274 274 IPython.tooltip.remove_and_cancel_tooltip();
275 275 if (editor.somethingSelected()) {
276 276 return false;
277 277 }
278 278 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
279 279 if (pre_cursor.trim() === "") {
280 280 // Don't autocomplete if the part of the line before the cursor
281 281 // is empty. In this case, let CodeMirror handle indentation.
282 282 return false;
283 283 } else {
284 284 event.stop();
285 285 this.completer.startCompletion();
286 286 return true;
287 287 }
288 288 } else {
289 289 // keypress/keyup also trigger on TAB press, and we don't want to
290 290 // use those to disable tab completion.
291 291 return false;
292 292 }
293 293 return false;
294 294 };
295 295
296 296 // Kernel related calls.
297 297
298 298 CodeCell.prototype.set_kernel = function (kernel) {
299 299 this.kernel = kernel;
300 300 };
301 301
302 302 /**
303 303 * Execute current code cell to the kernel
304 304 * @method execute
305 305 */
306 306 CodeCell.prototype.execute = function () {
307 307 this.output_area.clear_output();
308 308
309 309 // Clear widget area
310 310 this.widget_subarea.html('');
311 311 this.widget_subarea.height('');
312 312 this.widget_area.height('');
313 313 this.widget_area.hide();
314 314
315 315 this.set_input_prompt('*');
316 316 this.element.addClass("running");
317 317 if (this.last_msg_id) {
318 318 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
319 319 }
320 320 var callbacks = this.get_callbacks();
321 321
322 322 var old_msg_id = this.last_msg_id;
323 323 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
324 324 if (old_msg_id) {
325 325 delete CodeCell.msg_cells[old_msg_id];
326 326 }
327 327 CodeCell.msg_cells[this.last_msg_id] = this;
328 328 };
329 329
330 330 /**
331 331 * Construct the default callbacks for
332 332 * @method get_callbacks
333 333 */
334 334 CodeCell.prototype.get_callbacks = function () {
335 335 return {
336 336 shell : {
337 337 reply : $.proxy(this._handle_execute_reply, this),
338 338 payload : {
339 339 set_next_input : $.proxy(this._handle_set_next_input, this),
340 340 page : $.proxy(this._open_with_pager, this)
341 341 }
342 342 },
343 343 iopub : {
344 344 output : $.proxy(this.output_area.handle_output, this.output_area),
345 345 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
346 346 },
347 347 input : $.proxy(this._handle_input_request, this)
348 348 };
349 349 };
350 350
351 351 CodeCell.prototype._open_with_pager = function (payload) {
352 352 $([IPython.events]).trigger('open_with_text.Pager', payload);
353 353 };
354 354
355 355 /**
356 356 * @method _handle_execute_reply
357 357 * @private
358 358 */
359 359 CodeCell.prototype._handle_execute_reply = function (msg) {
360 360 this.set_input_prompt(msg.content.execution_count);
361 361 this.element.removeClass("running");
362 362 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
363 363 };
364 364
365 365 /**
366 366 * @method _handle_set_next_input
367 367 * @private
368 368 */
369 369 CodeCell.prototype._handle_set_next_input = function (payload) {
370 370 var data = {'cell': this, 'text': payload.text};
371 371 $([IPython.events]).trigger('set_next_input.Notebook', data);
372 372 };
373 373
374 374 /**
375 375 * @method _handle_input_request
376 376 * @private
377 377 */
378 378 CodeCell.prototype._handle_input_request = function (msg) {
379 379 this.output_area.append_raw_input(msg);
380 380 };
381 381
382 382
383 383 // Basic cell manipulation.
384 384
385 385 CodeCell.prototype.select = function () {
386 386 var cont = IPython.Cell.prototype.select.apply(this);
387 387 if (cont) {
388 388 this.code_mirror.refresh();
389 389 this.auto_highlight();
390 390 }
391 391 return cont;
392 392 };
393 393
394 394 CodeCell.prototype.render = function () {
395 395 var cont = IPython.Cell.prototype.render.apply(this);
396 396 // Always execute, even if we are already in the rendered state
397 397 return cont;
398 398 };
399 399
400 400 CodeCell.prototype.unrender = function () {
401 401 // CodeCell is always rendered
402 402 return false;
403 403 };
404 404
405 405 CodeCell.prototype.select_all = function () {
406 406 var start = {line: 0, ch: 0};
407 407 var nlines = this.code_mirror.lineCount();
408 408 var last_line = this.code_mirror.getLine(nlines-1);
409 409 var end = {line: nlines-1, ch: last_line.length};
410 410 this.code_mirror.setSelection(start, end);
411 411 };
412 412
413 413
414 414 CodeCell.prototype.collapse_output = function () {
415 415 this.collapsed = true;
416 416 this.output_area.collapse();
417 417 };
418 418
419 419
420 420 CodeCell.prototype.expand_output = function () {
421 421 this.collapsed = false;
422 422 this.output_area.expand();
423 423 this.output_area.unscroll_area();
424 424 };
425 425
426 426 CodeCell.prototype.scroll_output = function () {
427 427 this.output_area.expand();
428 428 this.output_area.scroll_if_long();
429 429 };
430 430
431 431 CodeCell.prototype.toggle_output = function () {
432 432 this.collapsed = Boolean(1 - this.collapsed);
433 433 this.output_area.toggle_output();
434 434 };
435 435
436 436 CodeCell.prototype.toggle_output_scroll = function () {
437 437 this.output_area.toggle_scroll();
438 438 };
439 439
440 440
441 441 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
442 442 var ns;
443 443 if (prompt_value == undefined) {
444 444 ns = "&nbsp;";
445 445 } else {
446 446 ns = encodeURIComponent(prompt_value);
447 447 }
448 448 return 'In&nbsp;[' + ns + ']:';
449 449 };
450 450
451 451 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
452 452 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
453 453 for(var i=1; i < lines_number; i++) {
454 454 html.push(['...:']);
455 455 }
456 456 return html.join('<br/>');
457 457 };
458 458
459 459 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
460 460
461 461
462 462 CodeCell.prototype.set_input_prompt = function (number) {
463 463 var nline = 1;
464 464 if (this.code_mirror !== undefined) {
465 465 nline = this.code_mirror.lineCount();
466 466 }
467 467 this.input_prompt_number = number;
468 468 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
469 469 // This HTML call is okay because the user contents are escaped.
470 470 this.element.find('div.input_prompt').html(prompt_html);
471 471 };
472 472
473 473
474 474 CodeCell.prototype.clear_input = function () {
475 475 this.code_mirror.setValue('');
476 476 };
477 477
478 478
479 479 CodeCell.prototype.get_text = function () {
480 480 return this.code_mirror.getValue();
481 481 };
482 482
483 483
484 484 CodeCell.prototype.set_text = function (code) {
485 485 return this.code_mirror.setValue(code);
486 486 };
487 487
488 488
489 489 CodeCell.prototype.at_top = function () {
490 490 var cursor = this.code_mirror.getCursor();
491 491 if (cursor.line === 0 && cursor.ch === 0) {
492 492 return true;
493 493 } else {
494 494 return false;
495 495 }
496 496 };
497 497
498 498
499 499 CodeCell.prototype.at_bottom = function () {
500 500 var cursor = this.code_mirror.getCursor();
501 501 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
502 502 return true;
503 503 } else {
504 504 return false;
505 505 }
506 506 };
507 507
508 508
509 509 CodeCell.prototype.clear_output = function (wait) {
510 510 this.output_area.clear_output(wait);
511 511 this.set_input_prompt();
512 512 };
513 513
514 514
515 515 // JSON serialization
516 516
517 517 CodeCell.prototype.fromJSON = function (data) {
518 518 IPython.Cell.prototype.fromJSON.apply(this, arguments);
519 519 if (data.cell_type === 'code') {
520 520 if (data.input !== undefined) {
521 521 this.set_text(data.input);
522 522 // make this value the starting point, so that we can only undo
523 523 // to this state, instead of a blank cell
524 524 this.code_mirror.clearHistory();
525 525 this.auto_highlight();
526 526 }
527 527 if (data.prompt_number !== undefined) {
528 528 this.set_input_prompt(data.prompt_number);
529 529 } else {
530 530 this.set_input_prompt();
531 531 }
532 532 this.output_area.trusted = data.trusted || false;
533 533 this.output_area.fromJSON(data.outputs);
534 534 if (data.collapsed !== undefined) {
535 535 if (data.collapsed) {
536 536 this.collapse_output();
537 537 } else {
538 538 this.expand_output();
539 539 }
540 540 }
541 541 }
542 542 };
543 543
544 544
545 545 CodeCell.prototype.toJSON = function () {
546 546 var data = IPython.Cell.prototype.toJSON.apply(this);
547 547 data.input = this.get_text();
548 548 // is finite protect against undefined and '*' value
549 549 if (isFinite(this.input_prompt_number)) {
550 550 data.prompt_number = this.input_prompt_number;
551 551 }
552 552 var outputs = this.output_area.toJSON();
553 553 data.outputs = outputs;
554 554 data.language = 'python';
555 555 data.trusted = this.output_area.trusted;
556 556 data.collapsed = this.collapsed;
557 557 return data;
558 558 };
559 559
560 /**
561 * handle cell level logic when a cell is unselected
562 * @method unselect
563 * @return is the action being taken
564 */
565 CodeCell.prototype.unselect = function () {
566 var cont = Cell.prototype.unselect.apply(this);
567 if (cont) {
568 IPython.tooltip.close();
569 }
570 return cont;
571 };
560 572
561 573 IPython.CodeCell = CodeCell;
562 574
563 575 return IPython;
564 576 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now