##// END OF EJS Templates
Merge pull request #5372 from minrk/no-close-strings...
Fernando Perez -
r15843:9c1c7a7e merge
parent child Browse files
Show More
@@ -1,514 +1,515 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 autoCloseBrackets: true
105 // don't auto-close strings because of CodeMirror #2385
106 autoCloseBrackets: "()[]{}"
106 107 }
107 108 };
108 109
109 110 CodeCell.msg_cells = {};
110 111
111 112 CodeCell.prototype = new IPython.Cell();
112 113
113 114 /**
114 115 * @method auto_highlight
115 116 */
116 117 CodeCell.prototype.auto_highlight = function () {
117 118 this._auto_highlight(IPython.config.cell_magic_highlight);
118 119 };
119 120
120 121 /** @method create_element */
121 122 CodeCell.prototype.create_element = function () {
122 123 IPython.Cell.prototype.create_element.apply(this, arguments);
123 124
124 125 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
125 126 cell.attr('tabindex','2');
126 127
127 128 var input = $('<div></div>').addClass('input');
128 129 var prompt = $('<div/>').addClass('prompt input_prompt');
129 130 var inner_cell = $('<div/>').addClass('inner_cell');
130 131 this.celltoolbar = new IPython.CellToolbar(this);
131 132 inner_cell.append(this.celltoolbar.element);
132 133 var input_area = $('<div/>').addClass('input_area');
133 134 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
134 135 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
135 136 inner_cell.append(input_area);
136 137 input.append(prompt).append(inner_cell);
137 138
138 139 var widget_area = $('<div/>')
139 140 .addClass('widget-area')
140 141 .hide();
141 142 this.widget_area = widget_area;
142 143 var widget_prompt = $('<div/>')
143 144 .addClass('prompt')
144 145 .appendTo(widget_area);
145 146 var widget_subarea = $('<div/>')
146 147 .addClass('widget-subarea')
147 148 .appendTo(widget_area);
148 149 this.widget_subarea = widget_subarea;
149 150 var widget_clear_buton = $('<button />')
150 151 .addClass('close')
151 152 .html('&times;')
152 153 .click(function() {
153 154 widget_area.slideUp('', function(){ widget_subarea.html(''); });
154 155 })
155 156 .appendTo(widget_prompt);
156 157
157 158 var output = $('<div></div>');
158 159 cell.append(input).append(widget_area).append(output);
159 160 this.element = cell;
160 161 this.output_area = new IPython.OutputArea(output, true);
161 162 this.completer = new IPython.Completer(this);
162 163 };
163 164
164 165 /** @method bind_events */
165 166 CodeCell.prototype.bind_events = function () {
166 167 IPython.Cell.prototype.bind_events.apply(this);
167 168 var that = this;
168 169
169 170 this.element.focusout(
170 171 function() { that.auto_highlight(); }
171 172 );
172 173 };
173 174
174 175
175 176 /**
176 177 * This method gets called in CodeMirror's onKeyDown/onKeyPress
177 178 * handlers and is used to provide custom key handling. Its return
178 179 * value is used to determine if CodeMirror should ignore the event:
179 180 * true = ignore, false = don't ignore.
180 181 * @method handle_codemirror_keyevent
181 182 */
182 183 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
183 184
184 185 var that = this;
185 186 // whatever key is pressed, first, cancel the tooltip request before
186 187 // they are sent, and remove tooltip if any, except for tab again
187 188 var tooltip_closed = null;
188 189 if (event.type === 'keydown' && event.which != keycodes.tab ) {
189 190 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
190 191 }
191 192
192 193 var cur = editor.getCursor();
193 194 if (event.keyCode === keycodes.enter){
194 195 this.auto_highlight();
195 196 }
196 197
197 198 if (event.which === keycodes.down && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
198 199 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
199 200 // browser and keyboard layout !
200 201 // Pressing '(' , request tooltip, don't forget to reappend it
201 202 // The second argument says to hide the tooltip if the docstring
202 203 // is actually empty
203 204 IPython.tooltip.pending(that, true);
204 205 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
205 206 // If tooltip is active, cancel it. The call to
206 207 // remove_and_cancel_tooltip above doesn't pass, force=true.
207 208 // Because of this it won't actually close the tooltip
208 209 // if it is in sticky mode. Thus, we have to check again if it is open
209 210 // and close it with force=true.
210 211 if (!IPython.tooltip._hidden) {
211 212 IPython.tooltip.remove_and_cancel_tooltip(true);
212 213 }
213 214 // If we closed the tooltip, don't let CM or the global handlers
214 215 // handle this event.
215 216 event.stop();
216 217 return true;
217 218 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
218 219 if (editor.somethingSelected()){
219 220 var anchor = editor.getCursor("anchor");
220 221 var head = editor.getCursor("head");
221 222 if( anchor.line != head.line){
222 223 return false;
223 224 }
224 225 }
225 226 IPython.tooltip.request(that);
226 227 event.stop();
227 228 return true;
228 229 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
229 230 // Tab completion.
230 231 IPython.tooltip.remove_and_cancel_tooltip();
231 232 if (editor.somethingSelected()) {
232 233 return false;
233 234 }
234 235 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
235 236 if (pre_cursor.trim() === "") {
236 237 // Don't autocomplete if the part of the line before the cursor
237 238 // is empty. In this case, let CodeMirror handle indentation.
238 239 return false;
239 240 } else {
240 241 event.stop();
241 242 this.completer.startCompletion();
242 243 return true;
243 244 }
244 245 }
245 246
246 247 // keyboard event wasn't one of those unique to code cells, let's see
247 248 // if it's one of the generic ones (i.e. check edit mode shortcuts)
248 249 return IPython.Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
249 250 };
250 251
251 252 // Kernel related calls.
252 253
253 254 CodeCell.prototype.set_kernel = function (kernel) {
254 255 this.kernel = kernel;
255 256 };
256 257
257 258 /**
258 259 * Execute current code cell to the kernel
259 260 * @method execute
260 261 */
261 262 CodeCell.prototype.execute = function () {
262 263 this.output_area.clear_output();
263 264
264 265 // Clear widget area
265 266 this.widget_subarea.html('');
266 267 this.widget_subarea.height('');
267 268 this.widget_area.height('');
268 269 this.widget_area.hide();
269 270
270 271 this.set_input_prompt('*');
271 272 this.element.addClass("running");
272 273 if (this.last_msg_id) {
273 274 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
274 275 }
275 276 var callbacks = this.get_callbacks();
276 277
277 278 var old_msg_id = this.last_msg_id;
278 279 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
279 280 if (old_msg_id) {
280 281 delete CodeCell.msg_cells[old_msg_id];
281 282 }
282 283 CodeCell.msg_cells[this.last_msg_id] = this;
283 284 };
284 285
285 286 /**
286 287 * Construct the default callbacks for
287 288 * @method get_callbacks
288 289 */
289 290 CodeCell.prototype.get_callbacks = function () {
290 291 return {
291 292 shell : {
292 293 reply : $.proxy(this._handle_execute_reply, this),
293 294 payload : {
294 295 set_next_input : $.proxy(this._handle_set_next_input, this),
295 296 page : $.proxy(this._open_with_pager, this)
296 297 }
297 298 },
298 299 iopub : {
299 300 output : $.proxy(this.output_area.handle_output, this.output_area),
300 301 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
301 302 },
302 303 input : $.proxy(this._handle_input_request, this)
303 304 };
304 305 };
305 306
306 307 CodeCell.prototype._open_with_pager = function (payload) {
307 308 $([IPython.events]).trigger('open_with_text.Pager', payload);
308 309 };
309 310
310 311 /**
311 312 * @method _handle_execute_reply
312 313 * @private
313 314 */
314 315 CodeCell.prototype._handle_execute_reply = function (msg) {
315 316 this.set_input_prompt(msg.content.execution_count);
316 317 this.element.removeClass("running");
317 318 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
318 319 };
319 320
320 321 /**
321 322 * @method _handle_set_next_input
322 323 * @private
323 324 */
324 325 CodeCell.prototype._handle_set_next_input = function (payload) {
325 326 var data = {'cell': this, 'text': payload.text};
326 327 $([IPython.events]).trigger('set_next_input.Notebook', data);
327 328 };
328 329
329 330 /**
330 331 * @method _handle_input_request
331 332 * @private
332 333 */
333 334 CodeCell.prototype._handle_input_request = function (msg) {
334 335 this.output_area.append_raw_input(msg);
335 336 };
336 337
337 338
338 339 // Basic cell manipulation.
339 340
340 341 CodeCell.prototype.select = function () {
341 342 var cont = IPython.Cell.prototype.select.apply(this);
342 343 if (cont) {
343 344 this.code_mirror.refresh();
344 345 this.auto_highlight();
345 346 }
346 347 return cont;
347 348 };
348 349
349 350 CodeCell.prototype.render = function () {
350 351 var cont = IPython.Cell.prototype.render.apply(this);
351 352 // Always execute, even if we are already in the rendered state
352 353 return cont;
353 354 };
354 355
355 356 CodeCell.prototype.unrender = function () {
356 357 // CodeCell is always rendered
357 358 return false;
358 359 };
359 360
360 361 /**
361 362 * Determine whether or not the unfocus event should be aknowledged.
362 363 *
363 364 * @method should_cancel_blur
364 365 *
365 366 * @return results {bool} Whether or not to ignore the cell's blur event.
366 367 **/
367 368 CodeCell.prototype.should_cancel_blur = function () {
368 369 // Cancel this unfocus event if the base wants to cancel or the cell
369 370 // completer is open or the tooltip is open.
370 371 return IPython.Cell.prototype.should_cancel_blur.apply(this) ||
371 372 (this.completer && this.completer.is_visible()) ||
372 373 (IPython.tooltip && IPython.tooltip.is_visible());
373 374 };
374 375
375 376 CodeCell.prototype.select_all = function () {
376 377 var start = {line: 0, ch: 0};
377 378 var nlines = this.code_mirror.lineCount();
378 379 var last_line = this.code_mirror.getLine(nlines-1);
379 380 var end = {line: nlines-1, ch: last_line.length};
380 381 this.code_mirror.setSelection(start, end);
381 382 };
382 383
383 384
384 385 CodeCell.prototype.collapse_output = function () {
385 386 this.collapsed = true;
386 387 this.output_area.collapse();
387 388 };
388 389
389 390
390 391 CodeCell.prototype.expand_output = function () {
391 392 this.collapsed = false;
392 393 this.output_area.expand();
393 394 this.output_area.unscroll_area();
394 395 };
395 396
396 397 CodeCell.prototype.scroll_output = function () {
397 398 this.output_area.expand();
398 399 this.output_area.scroll_if_long();
399 400 };
400 401
401 402 CodeCell.prototype.toggle_output = function () {
402 403 this.collapsed = Boolean(1 - this.collapsed);
403 404 this.output_area.toggle_output();
404 405 };
405 406
406 407 CodeCell.prototype.toggle_output_scroll = function () {
407 408 this.output_area.toggle_scroll();
408 409 };
409 410
410 411
411 412 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
412 413 var ns;
413 414 if (prompt_value === undefined) {
414 415 ns = "&nbsp;";
415 416 } else {
416 417 ns = encodeURIComponent(prompt_value);
417 418 }
418 419 return 'In&nbsp;[' + ns + ']:';
419 420 };
420 421
421 422 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
422 423 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
423 424 for(var i=1; i < lines_number; i++) {
424 425 html.push(['...:']);
425 426 }
426 427 return html.join('<br/>');
427 428 };
428 429
429 430 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
430 431
431 432
432 433 CodeCell.prototype.set_input_prompt = function (number) {
433 434 var nline = 1;
434 435 if (this.code_mirror !== undefined) {
435 436 nline = this.code_mirror.lineCount();
436 437 }
437 438 this.input_prompt_number = number;
438 439 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
439 440 // This HTML call is okay because the user contents are escaped.
440 441 this.element.find('div.input_prompt').html(prompt_html);
441 442 };
442 443
443 444
444 445 CodeCell.prototype.clear_input = function () {
445 446 this.code_mirror.setValue('');
446 447 };
447 448
448 449
449 450 CodeCell.prototype.get_text = function () {
450 451 return this.code_mirror.getValue();
451 452 };
452 453
453 454
454 455 CodeCell.prototype.set_text = function (code) {
455 456 return this.code_mirror.setValue(code);
456 457 };
457 458
458 459
459 460 CodeCell.prototype.clear_output = function (wait) {
460 461 this.output_area.clear_output(wait);
461 462 this.set_input_prompt();
462 463 };
463 464
464 465
465 466 // JSON serialization
466 467
467 468 CodeCell.prototype.fromJSON = function (data) {
468 469 IPython.Cell.prototype.fromJSON.apply(this, arguments);
469 470 if (data.cell_type === 'code') {
470 471 if (data.input !== undefined) {
471 472 this.set_text(data.input);
472 473 // make this value the starting point, so that we can only undo
473 474 // to this state, instead of a blank cell
474 475 this.code_mirror.clearHistory();
475 476 this.auto_highlight();
476 477 }
477 478 if (data.prompt_number !== undefined) {
478 479 this.set_input_prompt(data.prompt_number);
479 480 } else {
480 481 this.set_input_prompt();
481 482 }
482 483 this.output_area.trusted = data.trusted || false;
483 484 this.output_area.fromJSON(data.outputs);
484 485 if (data.collapsed !== undefined) {
485 486 if (data.collapsed) {
486 487 this.collapse_output();
487 488 } else {
488 489 this.expand_output();
489 490 }
490 491 }
491 492 }
492 493 };
493 494
494 495
495 496 CodeCell.prototype.toJSON = function () {
496 497 var data = IPython.Cell.prototype.toJSON.apply(this);
497 498 data.input = this.get_text();
498 499 // is finite protect against undefined and '*' value
499 500 if (isFinite(this.input_prompt_number)) {
500 501 data.prompt_number = this.input_prompt_number;
501 502 }
502 503 var outputs = this.output_area.toJSON();
503 504 data.outputs = outputs;
504 505 data.language = 'python';
505 506 data.trusted = this.output_area.trusted;
506 507 data.collapsed = this.collapsed;
507 508 return data;
508 509 };
509 510
510 511
511 512 IPython.CodeCell = CodeCell;
512 513
513 514 return IPython;
514 515 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now