##// END OF EJS Templates
Remove all should cancel blur logic.
Jonathan Frederic -
Show More
@@ -1,495 +1,481 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 // Cell
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 Cell
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19 "use strict";
20 20
21 21 var utils = IPython.utils;
22 22
23 23 /**
24 24 * The Base `Cell` class from which to inherit
25 25 * @class Cell
26 26 **/
27 27
28 28 /*
29 29 * @constructor
30 30 *
31 31 * @param {object|undefined} [options]
32 32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
33 33 */
34 34 var Cell = function (options) {
35 35
36 36 options = this.mergeopt(Cell, options);
37 37 // superclass default overwrite our default
38 38
39 39 this.placeholder = options.placeholder || '';
40 40 this.read_only = options.cm_config.readOnly;
41 41 this.selected = false;
42 42 this.rendered = false;
43 43 this.mode = 'command';
44 44 this.metadata = {};
45 45 // load this from metadata later ?
46 46 this.user_highlight = 'auto';
47 47 this.cm_config = options.cm_config;
48 48 this.cell_id = utils.uuid();
49 49 this._options = options;
50 50
51 51 // For JS VM engines optimization, attributes should be all set (even
52 52 // to null) in the constructor, and if possible, if different subclass
53 53 // have new attributes with same name, they should be created in the
54 54 // same order. Easiest is to create and set to null in parent class.
55 55
56 56 this.element = null;
57 57 this.cell_type = this.cell_type || null;
58 58 this.code_mirror = null;
59 59
60 60 this.create_element();
61 61 if (this.element !== null) {
62 62 this.element.data("cell", this);
63 63 this.bind_events();
64 64 this.init_classes();
65 65 }
66 66 };
67 67
68 68 Cell.options_default = {
69 69 cm_config : {
70 70 indentUnit : 4,
71 71 readOnly: false,
72 72 theme: "default"
73 73 }
74 74 };
75 75
76 76 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
77 77 // by disabling drag/drop altogether on Safari
78 78 // https://github.com/marijnh/CodeMirror/issues/332
79 79 if (utils.browser[0] == "Safari") {
80 80 Cell.options_default.cm_config.dragDrop = false;
81 81 }
82 82
83 83 Cell.prototype.mergeopt = function(_class, options, overwrite){
84 84 options = options || {};
85 85 overwrite = overwrite || {};
86 86 return $.extend(true, {}, _class.options_default, options, overwrite);
87 87 };
88 88
89 89 /**
90 90 * Empty. Subclasses must implement create_element.
91 91 * This should contain all the code to create the DOM element in notebook
92 92 * and will be called by Base Class constructor.
93 93 * @method create_element
94 94 */
95 95 Cell.prototype.create_element = function () {
96 96 };
97 97
98 98 Cell.prototype.init_classes = function () {
99 99 // Call after this.element exists to initialize the css classes
100 100 // related to selected, rendered and mode.
101 101 if (this.selected) {
102 102 this.element.addClass('selected');
103 103 } else {
104 104 this.element.addClass('unselected');
105 105 }
106 106 if (this.rendered) {
107 107 this.element.addClass('rendered');
108 108 } else {
109 109 this.element.addClass('unrendered');
110 110 }
111 111 if (this.mode === 'edit') {
112 112 this.element.addClass('edit_mode');
113 113 } else {
114 114 this.element.addClass('command_mode');
115 115 }
116 116 };
117 117
118 118 /**
119 119 * Subclasses can implement override bind_events.
120 120 * Be carefull to call the parent method when overwriting as it fires event.
121 121 * this will be triggerd after create_element in constructor.
122 122 * @method bind_events
123 123 */
124 124 Cell.prototype.bind_events = function () {
125 125 var that = this;
126 126 // We trigger events so that Cell doesn't have to depend on Notebook.
127 127 that.element.click(function (event) {
128 128 if (!that.selected) {
129 129 $([IPython.events]).trigger('select.Cell', {'cell':that});
130 130 }
131 131 });
132 132 that.element.focusin(function (event) {
133 133 if (!that.selected) {
134 134 $([IPython.events]).trigger('select.Cell', {'cell':that});
135 135 }
136 136 });
137 137 if (this.code_mirror) {
138 138 this.code_mirror.on("change", function(cm, change) {
139 139 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
140 140 });
141 141 }
142 142 if (this.code_mirror) {
143 143 this.code_mirror.on('focus', function(cm, change) {
144 144 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
145 145 });
146 146 }
147 147 if (this.code_mirror) {
148 148 this.code_mirror.on('blur', function(cm, change) {
149 // Check if this unfocus event is legit.
150 if (!that.should_cancel_blur()) {
151 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
152 }
149 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
153 150 });
154 151 }
155 152 };
156 153
157 154 /**
158 155 * Triger typsetting of math by mathjax on current cell element
159 156 * @method typeset
160 157 */
161 158 Cell.prototype.typeset = function () {
162 159 if (window.MathJax) {
163 160 var cell_math = this.element.get(0);
164 161 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
165 162 }
166 163 };
167 164
168 165 /**
169 166 * handle cell level logic when a cell is selected
170 167 * @method select
171 168 * @return is the action being taken
172 169 */
173 170 Cell.prototype.select = function () {
174 171 if (!this.selected) {
175 172 this.element.addClass('selected');
176 173 this.element.removeClass('unselected');
177 174 this.selected = true;
178 175 return true;
179 176 } else {
180 177 return false;
181 178 }
182 179 };
183 180
184 181 /**
185 182 * handle cell level logic when a cell is unselected
186 183 * @method unselect
187 184 * @return is the action being taken
188 185 */
189 186 Cell.prototype.unselect = function () {
190 187 if (this.selected) {
191 188 this.element.addClass('unselected');
192 189 this.element.removeClass('selected');
193 190 this.selected = false;
194 191 return true;
195 192 } else {
196 193 return false;
197 194 }
198 195 };
199 196
200 197 /**
201 198 * handle cell level logic when a cell is rendered
202 199 * @method render
203 200 * @return is the action being taken
204 201 */
205 202 Cell.prototype.render = function () {
206 203 if (!this.rendered) {
207 204 this.element.addClass('rendered');
208 205 this.element.removeClass('unrendered');
209 206 this.rendered = true;
210 207 return true;
211 208 } else {
212 209 return false;
213 210 }
214 211 };
215 212
216 213 /**
217 214 * handle cell level logic when a cell is unrendered
218 215 * @method unrender
219 216 * @return is the action being taken
220 217 */
221 218 Cell.prototype.unrender = function () {
222 219 if (this.rendered) {
223 220 this.element.addClass('unrendered');
224 221 this.element.removeClass('rendered');
225 222 this.rendered = false;
226 223 return true;
227 224 } else {
228 225 return false;
229 226 }
230 227 };
231 228
232 229 /**
233 230 * enter the command mode for the cell
234 231 * @method command_mode
235 232 * @return is the action being taken
236 233 */
237 234 Cell.prototype.command_mode = function () {
238 235 if (this.mode !== 'command') {
239 236 this.element.addClass('command_mode');
240 237 this.element.removeClass('edit_mode');
241 238 this.mode = 'command';
242 239 return true;
243 240 } else {
244 241 return false;
245 242 }
246 243 };
247 244
248 245 /**
249 246 * enter the edit mode for the cell
250 247 * @method command_mode
251 248 * @return is the action being taken
252 249 */
253 250 Cell.prototype.edit_mode = function () {
254 251 if (this.mode !== 'edit') {
255 252 this.element.addClass('edit_mode');
256 253 this.element.removeClass('command_mode');
257 254 this.mode = 'edit';
258 255 return true;
259 256 } else {
260 257 return false;
261 258 }
262 259 };
263
264 /**
265 * Determine whether or not the unfocus event should be aknowledged.
266 *
267 * @method should_cancel_blur
268 *
269 * @return results {bool} Whether or not to ignore the cell's blur event.
270 **/
271 Cell.prototype.should_cancel_blur = function () {
272 return false;
273 };
274
260
275 261 /**
276 262 * Focus the cell in the DOM sense
277 263 * @method focus_cell
278 264 */
279 265 Cell.prototype.focus_cell = function () {
280 266 this.element.focus();
281 267 };
282 268
283 269 /**
284 270 * Focus the editor area so a user can type
285 271 *
286 272 * NOTE: If codemirror is focused via a mouse click event, you don't want to
287 273 * call this because it will cause a page jump.
288 274 * @method focus_editor
289 275 */
290 276 Cell.prototype.focus_editor = function () {
291 277 this.refresh();
292 278 this.code_mirror.focus();
293 279 };
294 280
295 281 /**
296 282 * Refresh codemirror instance
297 283 * @method refresh
298 284 */
299 285 Cell.prototype.refresh = function () {
300 286 this.code_mirror.refresh();
301 287 };
302 288
303 289 /**
304 290 * should be overritten by subclass
305 291 * @method get_text
306 292 */
307 293 Cell.prototype.get_text = function () {
308 294 };
309 295
310 296 /**
311 297 * should be overritten by subclass
312 298 * @method set_text
313 299 * @param {string} text
314 300 */
315 301 Cell.prototype.set_text = function (text) {
316 302 };
317 303
318 304 /**
319 305 * should be overritten by subclass
320 306 * serialise cell to json.
321 307 * @method toJSON
322 308 **/
323 309 Cell.prototype.toJSON = function () {
324 310 var data = {};
325 311 data.metadata = this.metadata;
326 312 data.cell_type = this.cell_type;
327 313 return data;
328 314 };
329 315
330 316
331 317 /**
332 318 * should be overritten by subclass
333 319 * @method fromJSON
334 320 **/
335 321 Cell.prototype.fromJSON = function (data) {
336 322 if (data.metadata !== undefined) {
337 323 this.metadata = data.metadata;
338 324 }
339 325 this.celltoolbar.rebuild();
340 326 };
341 327
342 328
343 329 /**
344 330 * can the cell be split into two cells
345 331 * @method is_splittable
346 332 **/
347 333 Cell.prototype.is_splittable = function () {
348 334 return true;
349 335 };
350 336
351 337
352 338 /**
353 339 * can the cell be merged with other cells
354 340 * @method is_mergeable
355 341 **/
356 342 Cell.prototype.is_mergeable = function () {
357 343 return true;
358 344 };
359 345
360 346
361 347 /**
362 348 * @return {String} - the text before the cursor
363 349 * @method get_pre_cursor
364 350 **/
365 351 Cell.prototype.get_pre_cursor = function () {
366 352 var cursor = this.code_mirror.getCursor();
367 353 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
368 354 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
369 355 return text;
370 356 };
371 357
372 358
373 359 /**
374 360 * @return {String} - the text after the cursor
375 361 * @method get_post_cursor
376 362 **/
377 363 Cell.prototype.get_post_cursor = function () {
378 364 var cursor = this.code_mirror.getCursor();
379 365 var last_line_num = this.code_mirror.lineCount()-1;
380 366 var last_line_len = this.code_mirror.getLine(last_line_num).length;
381 367 var end = {line:last_line_num, ch:last_line_len};
382 368 var text = this.code_mirror.getRange(cursor, end);
383 369 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
384 370 return text;
385 371 };
386 372
387 373 /**
388 374 * Show/Hide CodeMirror LineNumber
389 375 * @method show_line_numbers
390 376 *
391 377 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
392 378 **/
393 379 Cell.prototype.show_line_numbers = function (value) {
394 380 this.code_mirror.setOption('lineNumbers', value);
395 381 this.code_mirror.refresh();
396 382 };
397 383
398 384 /**
399 385 * Toggle CodeMirror LineNumber
400 386 * @method toggle_line_numbers
401 387 **/
402 388 Cell.prototype.toggle_line_numbers = function () {
403 389 var val = this.code_mirror.getOption('lineNumbers');
404 390 this.show_line_numbers(!val);
405 391 };
406 392
407 393 /**
408 394 * Force codemirror highlight mode
409 395 * @method force_highlight
410 396 * @param {object} - CodeMirror mode
411 397 **/
412 398 Cell.prototype.force_highlight = function(mode) {
413 399 this.user_highlight = mode;
414 400 this.auto_highlight();
415 401 };
416 402
417 403 /**
418 404 * Try to autodetect cell highlight mode, or use selected mode
419 405 * @methods _auto_highlight
420 406 * @private
421 407 * @param {String|object|undefined} - CodeMirror mode | 'auto'
422 408 **/
423 409 Cell.prototype._auto_highlight = function (modes) {
424 410 //Here we handle manually selected modes
425 411 var mode;
426 412 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
427 413 {
428 414 mode = this.user_highlight;
429 415 CodeMirror.autoLoadMode(this.code_mirror, mode);
430 416 this.code_mirror.setOption('mode', mode);
431 417 return;
432 418 }
433 419 var current_mode = this.code_mirror.getOption('mode', mode);
434 420 var first_line = this.code_mirror.getLine(0);
435 421 // loop on every pairs
436 422 for(mode in modes) {
437 423 var regs = modes[mode].reg;
438 424 // only one key every time but regexp can't be keys...
439 425 for(var i=0; i<regs.length; i++) {
440 426 // here we handle non magic_modes
441 427 if(first_line.match(regs[i]) !== null) {
442 428 if(current_mode == mode){
443 429 return;
444 430 }
445 431 if (mode.search('magic_') !== 0) {
446 432 this.code_mirror.setOption('mode', mode);
447 433 CodeMirror.autoLoadMode(this.code_mirror, mode);
448 434 return;
449 435 }
450 436 var open = modes[mode].open || "%%";
451 437 var close = modes[mode].close || "%%end";
452 438 var mmode = mode;
453 439 mode = mmode.substr(6);
454 440 if(current_mode == mode){
455 441 return;
456 442 }
457 443 CodeMirror.autoLoadMode(this.code_mirror, mode);
458 444 // create on the fly a mode that swhitch between
459 445 // plain/text and smth else otherwise `%%` is
460 446 // source of some highlight issues.
461 447 // we use patchedGetMode to circumvent a bug in CM
462 448 CodeMirror.defineMode(mmode , function(config) {
463 449 return CodeMirror.multiplexingMode(
464 450 CodeMirror.patchedGetMode(config, 'text/plain'),
465 451 // always set someting on close
466 452 {open: open, close: close,
467 453 mode: CodeMirror.patchedGetMode(config, mode),
468 454 delimStyle: "delimit"
469 455 }
470 456 );
471 457 });
472 458 this.code_mirror.setOption('mode', mmode);
473 459 return;
474 460 }
475 461 }
476 462 }
477 463 // fallback on default
478 464 var default_mode;
479 465 try {
480 466 default_mode = this._options.cm_config.mode;
481 467 } catch(e) {
482 468 default_mode = 'text/plain';
483 469 }
484 470 if( current_mode === default_mode){
485 471 return;
486 472 }
487 473 this.code_mirror.setOption('mode', default_mode);
488 474 };
489 475
490 476 IPython.Cell = Cell;
491 477
492 478 return IPython;
493 479
494 480 }(IPython));
495 481
@@ -1,579 +1,564 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 /**
406 * Determine whether or not the unfocus event should be aknowledged.
407 *
408 * @method should_cancel_blur
409 *
410 * @return results {bool} Whether or not to ignore the cell's blur event.
411 **/
412 CodeCell.prototype.should_cancel_blur = function () {
413 // Cancel this unfocus event if the base wants to cancel or the cell
414 // completer is open or the tooltip is open.
415 return IPython.Cell.prototype.should_cancel_blur.apply(this) ||
416 (this.completer && this.completer.was_shown()) ||
417 (IPython.tooltip && IPython.tooltip.was_shown());
418 };
419
420 405 CodeCell.prototype.select_all = function () {
421 406 var start = {line: 0, ch: 0};
422 407 var nlines = this.code_mirror.lineCount();
423 408 var last_line = this.code_mirror.getLine(nlines-1);
424 409 var end = {line: nlines-1, ch: last_line.length};
425 410 this.code_mirror.setSelection(start, end);
426 411 };
427 412
428 413
429 414 CodeCell.prototype.collapse_output = function () {
430 415 this.collapsed = true;
431 416 this.output_area.collapse();
432 417 };
433 418
434 419
435 420 CodeCell.prototype.expand_output = function () {
436 421 this.collapsed = false;
437 422 this.output_area.expand();
438 423 this.output_area.unscroll_area();
439 424 };
440 425
441 426 CodeCell.prototype.scroll_output = function () {
442 427 this.output_area.expand();
443 428 this.output_area.scroll_if_long();
444 429 };
445 430
446 431 CodeCell.prototype.toggle_output = function () {
447 432 this.collapsed = Boolean(1 - this.collapsed);
448 433 this.output_area.toggle_output();
449 434 };
450 435
451 436 CodeCell.prototype.toggle_output_scroll = function () {
452 437 this.output_area.toggle_scroll();
453 438 };
454 439
455 440
456 441 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
457 442 var ns;
458 443 if (prompt_value == undefined) {
459 444 ns = "&nbsp;";
460 445 } else {
461 446 ns = encodeURIComponent(prompt_value);
462 447 }
463 448 return 'In&nbsp;[' + ns + ']:';
464 449 };
465 450
466 451 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
467 452 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
468 453 for(var i=1; i < lines_number; i++) {
469 454 html.push(['...:']);
470 455 }
471 456 return html.join('<br/>');
472 457 };
473 458
474 459 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
475 460
476 461
477 462 CodeCell.prototype.set_input_prompt = function (number) {
478 463 var nline = 1;
479 464 if (this.code_mirror !== undefined) {
480 465 nline = this.code_mirror.lineCount();
481 466 }
482 467 this.input_prompt_number = number;
483 468 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
484 469 // This HTML call is okay because the user contents are escaped.
485 470 this.element.find('div.input_prompt').html(prompt_html);
486 471 };
487 472
488 473
489 474 CodeCell.prototype.clear_input = function () {
490 475 this.code_mirror.setValue('');
491 476 };
492 477
493 478
494 479 CodeCell.prototype.get_text = function () {
495 480 return this.code_mirror.getValue();
496 481 };
497 482
498 483
499 484 CodeCell.prototype.set_text = function (code) {
500 485 return this.code_mirror.setValue(code);
501 486 };
502 487
503 488
504 489 CodeCell.prototype.at_top = function () {
505 490 var cursor = this.code_mirror.getCursor();
506 491 if (cursor.line === 0 && cursor.ch === 0) {
507 492 return true;
508 493 } else {
509 494 return false;
510 495 }
511 496 };
512 497
513 498
514 499 CodeCell.prototype.at_bottom = function () {
515 500 var cursor = this.code_mirror.getCursor();
516 501 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
517 502 return true;
518 503 } else {
519 504 return false;
520 505 }
521 506 };
522 507
523 508
524 509 CodeCell.prototype.clear_output = function (wait) {
525 510 this.output_area.clear_output(wait);
526 511 this.set_input_prompt();
527 512 };
528 513
529 514
530 515 // JSON serialization
531 516
532 517 CodeCell.prototype.fromJSON = function (data) {
533 518 IPython.Cell.prototype.fromJSON.apply(this, arguments);
534 519 if (data.cell_type === 'code') {
535 520 if (data.input !== undefined) {
536 521 this.set_text(data.input);
537 522 // make this value the starting point, so that we can only undo
538 523 // to this state, instead of a blank cell
539 524 this.code_mirror.clearHistory();
540 525 this.auto_highlight();
541 526 }
542 527 if (data.prompt_number !== undefined) {
543 528 this.set_input_prompt(data.prompt_number);
544 529 } else {
545 530 this.set_input_prompt();
546 531 }
547 532 this.output_area.trusted = data.trusted || false;
548 533 this.output_area.fromJSON(data.outputs);
549 534 if (data.collapsed !== undefined) {
550 535 if (data.collapsed) {
551 536 this.collapse_output();
552 537 } else {
553 538 this.expand_output();
554 539 }
555 540 }
556 541 }
557 542 };
558 543
559 544
560 545 CodeCell.prototype.toJSON = function () {
561 546 var data = IPython.Cell.prototype.toJSON.apply(this);
562 547 data.input = this.get_text();
563 548 // is finite protect against undefined and '*' value
564 549 if (isFinite(this.input_prompt_number)) {
565 550 data.prompt_number = this.input_prompt_number;
566 551 }
567 552 var outputs = this.output_area.toJSON();
568 553 data.outputs = outputs;
569 554 data.language = 'python';
570 555 data.trusted = this.output_area.trusted;
571 556 data.collapsed = this.collapsed;
572 557 return data;
573 558 };
574 559
575 560
576 561 IPython.CodeCell = CodeCell;
577 562
578 563 return IPython;
579 564 }(IPython));
@@ -1,394 +1,384 b''
1 1 // function completer.
2 2 //
3 3 // completer should be a class that takes an cell instance
4 4 var IPython = (function (IPython) {
5 5 // that will prevent us from misspelling
6 6 "use strict";
7 7
8 8 // easier key mapping
9 9 var keycodes = IPython.keyboard.keycodes;
10 10
11 11 function prepend_n_prc(str, n) {
12 12 for( var i =0 ; i< n ; i++){
13 13 str = '%'+str ;
14 14 }
15 15 return str;
16 16 }
17 17
18 18 function _existing_completion(item, completion_array){
19 19 for( var c in completion_array ) {
20 20 if(completion_array[c].trim().substr(-item.length) == item)
21 21 { return true; }
22 22 }
23 23 return false;
24 24 }
25 25
26 26 // what is the common start of all completions
27 27 function shared_start(B, drop_prct) {
28 28 if (B.length == 1) {
29 29 return B[0];
30 30 }
31 31 var A = [];
32 32 var common;
33 33 var min_lead_prct = 10;
34 34 for (var i = 0; i < B.length; i++) {
35 35 var str = B[i].str;
36 36 var localmin = 0;
37 37 if(drop_prct === true){
38 38 while ( str.substr(0, 1) == '%') {
39 39 localmin = localmin+1;
40 40 str = str.substring(1);
41 41 }
42 42 }
43 43 min_lead_prct = Math.min(min_lead_prct, localmin);
44 44 A.push(str);
45 45 }
46 46
47 47 if (A.length > 1) {
48 48 var tem1, tem2, s;
49 49 A = A.slice(0).sort();
50 50 tem1 = A[0];
51 51 s = tem1.length;
52 52 tem2 = A.pop();
53 53 while (s && tem2.indexOf(tem1) == -1) {
54 54 tem1 = tem1.substring(0, --s);
55 55 }
56 56 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
57 57 return {
58 58 str:prepend_n_prc('', min_lead_prct),
59 59 type: "computed",
60 60 from: B[0].from,
61 61 to: B[0].to
62 62 };
63 63 }
64 64 return {
65 65 str: prepend_n_prc(tem1, min_lead_prct),
66 66 type: "computed",
67 67 from: B[0].from,
68 68 to: B[0].to
69 69 };
70 70 }
71 71 return null;
72 72 }
73 73
74 74
75 75 var Completer = function (cell) {
76 76 this._visible = false;
77 this._shown = false;
78 77 this.cell = cell;
79 78 this.editor = cell.code_mirror;
80 79 var that = this;
81 80 $([IPython.events]).on('status_busy.Kernel', function () {
82 81 that.skip_kernel_completion = true;
83 82 });
84 83 $([IPython.events]).on('status_idle.Kernel', function () {
85 84 that.skip_kernel_completion = false;
86 85 });
87 86 };
88 87
89 88 Completer.prototype.is_visible = function () {
90 89 // Return whether or not the completer is visible.
91 90 return this._visible;
92 91 };
93 92
94 Completer.prototype.was_shown = function () {
95 // Return whether or not the completer was shown.
96 var ret = this._shown;
97 this._shown = false;
98 return ret;
99 };
100
101 93 Completer.prototype.startCompletion = function () {
102 94 // call for a 'first' completion, that will set the editor and do some
103 95 // special behaviour like autopicking if only one completion availlable
104 96 //
105 97 if (this.editor.somethingSelected()) return;
106 98 this.done = false;
107 99 // use to get focus back on opera
108 100 this.carry_on_completion(true);
109 101 };
110 102
111 103
112 104 // easy access for julia to monkeypatch
113 105 //
114 106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
115 107
116 108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
117 109 return Completer.reinvoke_re.test(pre_cursor);
118 110 };
119 111
120 112 /**
121 113 *
122 114 * pass true as parameter if this is the first invocation of the completer
123 115 * this will prevent the completer to dissmiss itself if it is not on a
124 116 * word boundary like pressing tab after a space, and make it autopick the
125 117 * only choice if there is only one which prevent from popping the UI. as
126 118 * well as fast-forwarding the typing if all completion have a common
127 119 * shared start
128 120 **/
129 121 Completer.prototype.carry_on_completion = function (first_invocation) {
130 122 // Pass true as parameter if you want the completer to autopick when
131 123 // only one completion. This function is automatically reinvoked at
132 124 // each keystroke with first_invocation = false
133 125 var cur = this.editor.getCursor();
134 126 var line = this.editor.getLine(cur.line);
135 127 var pre_cursor = this.editor.getRange({
136 128 line: cur.line,
137 129 ch: cur.ch - 1
138 130 }, cur);
139 131
140 132 // we need to check that we are still on a word boundary
141 133 // because while typing the completer is still reinvoking itself
142 134 // so dismiss if we are on a "bad" caracter
143 135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
144 136 this.close();
145 137 return;
146 138 }
147 139
148 140 this.autopick = false;
149 141 if (first_invocation) {
150 142 this.autopick = true;
151 143 }
152 144
153 145 // We want a single cursor position.
154 146 if (this.editor.somethingSelected()) {
155 147 return;
156 148 }
157 149
158 150 // one kernel completion came back, finish_completing will be called with the results
159 151 // we fork here and directly call finish completing if kernel is busy
160 152 if (this.skip_kernel_completion) {
161 153 this.finish_completing({
162 154 'matches': [],
163 155 matched_text: ""
164 156 });
165 157 } else {
166 158 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
167 159 }
168 160 };
169 161
170 162 Completer.prototype.finish_completing = function (msg) {
171 163 // let's build a function that wrap all that stuff into what is needed
172 164 // for the new completer:
173 165 var content = msg.content;
174 166 var matched_text = content.matched_text;
175 167 var matches = content.matches;
176 168
177 169 var cur = this.editor.getCursor();
178 170 var results = CodeMirror.contextHint(this.editor);
179 171 var filtered_results = [];
180 172 //remove results from context completion
181 173 //that are already in kernel completion
182 174 for (var elm in results) {
183 175 if (!_existing_completion(results[elm].str, matches)) {
184 176 filtered_results.push(results[elm]);
185 177 }
186 178 }
187 179
188 180 // append the introspection result, in order, at at the beginning of
189 181 // the table and compute the replacement range from current cursor
190 182 // positon and matched_text length.
191 183 for (var i = matches.length - 1; i >= 0; --i) {
192 184 filtered_results.unshift({
193 185 str: matches[i],
194 186 type: "introspection",
195 187 from: {
196 188 line: cur.line,
197 189 ch: cur.ch - matched_text.length
198 190 },
199 191 to: {
200 192 line: cur.line,
201 193 ch: cur.ch
202 194 }
203 195 });
204 196 }
205 197
206 198 // one the 2 sources results have been merge, deal with it
207 199 this.raw_result = filtered_results;
208 200
209 201 // if empty result return
210 202 if (!this.raw_result || !this.raw_result.length) return;
211 203
212 204 // When there is only one completion, use it directly.
213 205 if (this.autopick && this.raw_result.length == 1) {
214 206 this.insert(this.raw_result[0]);
215 207 return;
216 208 }
217 209
218 210 if (this.raw_result.length == 1) {
219 211 // test if first and only completion totally matches
220 212 // what is typed, in this case dismiss
221 213 var str = this.raw_result[0].str;
222 214 var pre_cursor = this.editor.getRange({
223 215 line: cur.line,
224 216 ch: cur.ch - str.length
225 217 }, cur);
226 218 if (pre_cursor == str) {
227 219 this.close();
228 220 return;
229 221 }
230 222 }
231 223
232 224 this.complete = $('<div/>').addClass('completions');
233 225 this.complete.attr('id', 'complete');
234 226
235 227 // Currently webkit doesn't use the size attr correctly. See:
236 228 // https://code.google.com/p/chromium/issues/detail?id=4579
237 229 this.sel = $('<select style="width: auto"/>')
238 230 .attr('multiple', 'true')
239 231 .attr('size', Math.min(10, this.raw_result.length));
240 232 this.complete.append(this.sel);
241 233 this._visible = true;
242 this._shown = true;
243 234 $('body').append(this.complete);
244 235
245 236 // After everything is on the page, compute the postion.
246 237 // We put it above the code if it is too close to the bottom of the page.
247 238 cur.ch = cur.ch-matched_text.length;
248 239 var pos = this.editor.cursorCoords(cur);
249 240 var left = pos.left-3;
250 241 var top;
251 242 var cheight = this.complete.height();
252 243 var wheight = $(window).height();
253 244 if (pos.bottom+cheight+5 > wheight) {
254 245 top = pos.top-cheight-4;
255 246 } else {
256 247 top = pos.bottom+1;
257 248 }
258 249 this.complete.css('left', left + 'px');
259 250 this.complete.css('top', top + 'px');
260 251
261 252
262 253 //build the container
263 254 var that = this;
264 255 this.sel.dblclick(function () {
265 256 that.pick();
266 257 });
267 258 this.sel.blur(this.close);
268 259 this.sel.keydown(function (event) {
269 260 that.keydown(event);
270 261 });
271 262 this.sel.keypress(function (event) {
272 263 that.keypress(event);
273 264 });
274 265
275 266 this.build_gui_list(this.raw_result);
276 267
277 268 this.sel.focus();
278 269 IPython.keyboard_manager.disable();
279 270 // Opera sometimes ignores focusing a freshly created node
280 271 if (window.opera) setTimeout(function () {
281 272 if (!this.done) this.sel.focus();
282 273 }, 100);
283 274 return true;
284 275 };
285 276
286 277 Completer.prototype.insert = function (completion) {
287 278 this.editor.replaceRange(completion.str, completion.from, completion.to);
288 279 };
289 280
290 281 Completer.prototype.build_gui_list = function (completions) {
291 282 for (var i = 0; i < completions.length; ++i) {
292 283 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
293 284 this.sel.append(opt);
294 285 }
295 286 this.sel.children().first().attr('selected', 'true');
296 287 this.sel.scrollTop(0);
297 288 };
298 289
299 290 Completer.prototype.close = function () {
300 291 this._visible = false;
301 this._shown = false;
302 292 if (this.done) return;
303 293 this.done = true;
304 294 $('.completions').remove();
305 295 IPython.keyboard_manager.enable();
306 296 };
307 297
308 298 Completer.prototype.pick = function () {
309 299 this.insert(this.raw_result[this.sel[0].selectedIndex]);
310 300 this.close();
311 301 var that = this;
312 302 setTimeout(function () {
313 303 that.editor.focus();
314 304 }, 50);
315 305 };
316 306
317 307 Completer.prototype.keydown = function (event) {
318 308 var code = event.keyCode;
319 309 var that = this;
320 310
321 311 // Enter
322 312 if (code == keycodes.enter) {
323 313 CodeMirror.e_stop(event);
324 314 this.pick();
325 315 }
326 316 // Escape or backspace
327 317 else if (code == keycodes.esc) {
328 318 CodeMirror.e_stop(event);
329 319 this.close();
330 320 this.editor.focus();
331 321
332 322 } else if (code == keycodes.backspace) {
333 323 this.close();
334 324 this.editor.focus();
335 325 } else if (code == keycodes.tab) {
336 326 //all the fastforwarding operation,
337 327 //Check that shared start is not null which can append with prefixed completion
338 328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
339 329 // to erase py
340 330 var sh = shared_start(this.raw_result, true);
341 331 if (sh) {
342 332 this.insert(sh);
343 333 }
344 334 this.close();
345 335 CodeMirror.e_stop(event);
346 336 this.editor.focus();
347 337 //reinvoke self
348 338 setTimeout(function () {
349 339 that.carry_on_completion();
350 340 }, 50);
351 341 } else if (code == keycodes.up || code == keycodes.down) {
352 342 // need to do that to be able to move the arrow
353 343 // when on the first or last line ofo a code cell
354 344 event.stopPropagation();
355 345 }
356 346 };
357 347
358 348 Completer.prototype.keypress = function (event) {
359 349 // FIXME: This is a band-aid.
360 350 // on keypress, trigger insertion of a single character.
361 351 // This simulates the old behavior of completion as you type,
362 352 // before events were disconnected and CodeMirror stopped
363 353 // receiving events while the completer is focused.
364 354
365 355 var that = this;
366 356 var code = event.keyCode;
367 357
368 358 // don't handle keypress if it's not a character (arrows on FF)
369 359 // or ENTER/TAB
370 360 if (event.charCode === 0 ||
371 361 code == keycodes.enter ||
372 362 code == keycodes.tab
373 363 ) return;
374 364
375 365 var cur = this.editor.getCursor();
376 366 var completion = {
377 367 str: String.fromCharCode(event.which),
378 368 type: "introspection",
379 369 from: cur,
380 370 to: cur,
381 371 };
382 372 this.insert(completion);
383 373
384 374 this.close();
385 375 this.editor.focus();
386 376 setTimeout(function () {
387 377 that.carry_on_completion();
388 378 }, 50);
389 379 };
390 380
391 381 IPython.Completer = Completer;
392 382
393 383 return IPython;
394 384 }(IPython));
@@ -1,398 +1,391 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 // Tooltip
9 9 //============================================================================
10 10 //
11 11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
12 12 //
13 13 // you can configure the differents action of pressing tab several times in a row by
14 14 // setting/appending different fonction in the array
15 15 // IPython.tooltip.tabs_functions
16 16 //
17 17 // eg :
18 18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
19 19 //
20 20 var IPython = (function (IPython) {
21 21 "use strict";
22 22
23 23 var utils = IPython.utils;
24 24
25 25 // tooltip constructor
26 26 var Tooltip = function () {
27 27 var that = this;
28 28 this.time_before_tooltip = 1200;
29 29
30 30 // handle to html
31 31 this.tooltip = $('#tooltip');
32 32 this._hidden = true;
33 this._shown = false;
34 33
35 34 // variable for consecutive call
36 35 this._old_cell = null;
37 36 this._old_request = null;
38 37 this._consecutive_counter = 0;
39 38
40 39 // 'sticky ?'
41 40 this._sticky = false;
42 41
43 42 // display tooltip if the docstring is empty?
44 43 this._hide_if_no_docstring = false;
45 44
46 45 // contain the button in the upper right corner
47 46 this.buttons = $('<div/>').addClass('tooltipbuttons');
48 47
49 48 // will contain the docstring
50 49 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
51 50
52 51 // build the buttons menu on the upper right
53 52 // expand the tooltip to see more
54 53 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
55 54 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
56 55 that.expand();
57 56 }).append(
58 57 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
59 58
60 59 // open in pager
61 60 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
62 61 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
63 62 morelink.append(morespan);
64 63 morelink.click(function () {
65 64 that.showInPager(that._old_cell);
66 65 });
67 66
68 67 // close the tooltip
69 68 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
70 69 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
71 70 closelink.append(closespan);
72 71 closelink.click(function () {
73 72 that.remove_and_cancel_tooltip(true);
74 73 });
75 74
76 75 this._clocklink = $('<a/>').attr('href', "#");
77 76 this._clocklink.attr('role', "button");
78 77 this._clocklink.addClass('ui-button');
79 78 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
80 79 var clockspan = $('<span/>').text('Close');
81 80 clockspan.addClass('ui-icon');
82 81 clockspan.addClass('ui-icon-clock');
83 82 this._clocklink.append(clockspan);
84 83 this._clocklink.click(function () {
85 84 that.cancel_stick();
86 85 });
87 86
88 87
89 88
90 89
91 90 //construct the tooltip
92 91 // add in the reverse order you want them to appear
93 92 this.buttons.append(closelink);
94 93 this.buttons.append(expandlink);
95 94 this.buttons.append(morelink);
96 95 this.buttons.append(this._clocklink);
97 96 this._clocklink.hide();
98 97
99 98
100 99 // we need a phony element to make the small arrow
101 100 // of the tooltip in css
102 101 // we will move the arrow later
103 102 this.arrow = $('<div/>').addClass('pretooltiparrow');
104 103 this.tooltip.append(this.buttons);
105 104 this.tooltip.append(this.arrow);
106 105 this.tooltip.append(this.text);
107 106
108 107 // function that will be called if you press tab 1, 2, 3... times in a row
109 108 this.tabs_functions = [function (cell, text) {
110 109 that._request_tooltip(cell, text);
111 110 }, function () {
112 111 that.expand();
113 112 }, function () {
114 113 that.stick();
115 114 }, function (cell) {
116 115 that.cancel_stick();
117 116 that.showInPager(cell);
118 117 }];
119 118 // call after all the tabs function above have bee call to clean their effects
120 119 // if necessary
121 120 this.reset_tabs_function = function (cell, text) {
122 121 this._old_cell = (cell) ? cell : null;
123 122 this._old_request = (text) ? text : null;
124 123 this._consecutive_counter = 0;
125 124 };
126 125 };
127 126
128 127 Tooltip.prototype.is_visible = function () {
129 128 return !this._hidden;
130 129 };
131 130
132 Tooltip.prototype.was_shown = function () {
133 return this._shown;
134 };
135
136 131 Tooltip.prototype.showInPager = function (cell) {
137 132 // reexecute last call in pager by appending ? to show back in pager
138 133 var that = this;
139 134 var empty = function () {};
140 135 cell.kernel.execute(
141 136 that.name + '?', {
142 137 'execute_reply': empty,
143 138 'output': empty,
144 139 'clear_output': empty,
145 140 'cell': cell
146 141 }, {
147 142 'silent': false,
148 143 'store_history': true
149 144 });
150 145 this.remove_and_cancel_tooltip();
151 146 };
152 147
153 148 // grow the tooltip verticaly
154 149 Tooltip.prototype.expand = function () {
155 150 this.text.removeClass('smalltooltip');
156 151 this.text.addClass('bigtooltip');
157 152 $('#expanbutton').hide('slow');
158 153 };
159 154
160 155 // deal with all the logic of hiding the tooltip
161 156 // and reset it's status
162 157 Tooltip.prototype._hide = function () {
163 158 this._hidden = true;
164 this._shown = false;
165 159 this.tooltip.fadeOut('fast');
166 160 $('#expanbutton').show('slow');
167 161 this.text.removeClass('bigtooltip');
168 162 this.text.addClass('smalltooltip');
169 163 // keep scroll top to be sure to always see the first line
170 164 this.text.scrollTop(0);
171 165 this.code_mirror = null;
172 166 };
173 167
174 168 // return true on successfully removing a visible tooltip; otherwise return
175 169 // false.
176 170 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
177 171 // note that we don't handle closing directly inside the calltip
178 172 // as in the completer, because it is not focusable, so won't
179 173 // get the event.
180 174 this.cancel_pending();
181 175 if (!this._hidden) {
182 176 if (force || !this._sticky) {
183 177 this.cancel_stick();
184 178 this._hide();
185 179 }
186 180 this.reset_tabs_function();
187 181 return true;
188 182 } else {
189 183 return false;
190 184 }
191 185 };
192 186
193 187 // cancel autocall done after '(' for example.
194 188 Tooltip.prototype.cancel_pending = function () {
195 189 if (this._tooltip_timeout !== null) {
196 190 clearTimeout(this._tooltip_timeout);
197 191 this._tooltip_timeout = null;
198 192 }
199 193 };
200 194
201 195 // will trigger tooltip after timeout
202 196 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
203 197 var that = this;
204 198 this._tooltip_timeout = setTimeout(function () {
205 199 that.request(cell, hide_if_no_docstring);
206 200 }, that.time_before_tooltip);
207 201 };
208 202
209 203 // easy access for julia monkey patching.
210 204 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
211 205
212 206 Tooltip.prototype.extract_oir_token = function(line){
213 207 // use internally just to make the request to the kernel
214 208 // Feel free to shorten this logic if you are better
215 209 // than me in regEx
216 210 // basicaly you shoul be able to get xxx.xxx.xxx from
217 211 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
218 212 // remove everything between matchin bracket (need to iterate)
219 213 var matchBracket = /\([^\(\)]+\)/g;
220 214 var endBracket = /\([^\(]*$/g;
221 215 var oldline = line;
222 216
223 217 line = line.replace(matchBracket, "");
224 218 while (oldline != line) {
225 219 oldline = line;
226 220 line = line.replace(matchBracket, "");
227 221 }
228 222 // remove everything after last open bracket
229 223 line = line.replace(endBracket, "");
230 224 // reset the regex object
231 225 Tooltip.last_token_re.lastIndex = 0;
232 226 return Tooltip.last_token_re.exec(line);
233 227 };
234 228
235 229 Tooltip.prototype._request_tooltip = function (cell, line) {
236 230 var callbacks = $.proxy(this._show, this);
237 231 var oir_token = this.extract_oir_token(line);
238 232 var msg_id = cell.kernel.object_info(oir_token, callbacks);
239 233 };
240 234
241 235 // make an imediate completion request
242 236 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
243 237 // request(codecell)
244 238 // Deal with extracting the text from the cell and counting
245 239 // call in a row
246 240 this.cancel_pending();
247 241 var editor = cell.code_mirror;
248 242 var cursor = editor.getCursor();
249 243 var text = editor.getRange({
250 244 line: cursor.line,
251 245 ch: 0
252 246 }, cursor).trim();
253 247
254 248 this._hide_if_no_docstring = hide_if_no_docstring;
255 249
256 250 if(editor.somethingSelected()){
257 251 text = editor.getSelection();
258 252 }
259 253
260 254 // need a permanent handel to code_mirror for future auto recall
261 255 this.code_mirror = editor;
262 256
263 257 // now we treat the different number of keypress
264 258 // first if same cell, same text, increment counter by 1
265 259 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
266 260 this._consecutive_counter++;
267 261 } else {
268 262 // else reset
269 263 this.cancel_stick();
270 264 this.reset_tabs_function (cell, text);
271 265 }
272 266
273 267 // don't do anything if line beggin with '(' or is empty
274 268 if (text === "" || text === "(") {
275 269 return;
276 270 }
277 271
278 272 this.tabs_functions[this._consecutive_counter](cell, text);
279 273
280 274 // then if we are at the end of list function, reset
281 275 if (this._consecutive_counter == this.tabs_functions.length) {
282 276 this.reset_tabs_function (cell, text);
283 277 }
284 278
285 279 return;
286 280 };
287 281
288 282 // cancel the option of having the tooltip to stick
289 283 Tooltip.prototype.cancel_stick = function () {
290 284 clearTimeout(this._stick_timeout);
291 285 this._stick_timeout = null;
292 286 this._clocklink.hide('slow');
293 287 this._sticky = false;
294 288 };
295 289
296 290 // put the tooltip in a sicky state for 10 seconds
297 291 // it won't be removed by remove_and_cancell() unless you called with
298 292 // the first parameter set to true.
299 293 // remove_and_cancell_tooltip(true)
300 294 Tooltip.prototype.stick = function (time) {
301 295 time = (time !== undefined) ? time : 10;
302 296 var that = this;
303 297 this._sticky = true;
304 298 this._clocklink.show('slow');
305 299 this._stick_timeout = setTimeout(function () {
306 300 that._sticky = false;
307 301 that._clocklink.hide('slow');
308 302 }, time * 1000);
309 303 };
310 304
311 305 // should be called with the kernel reply to actually show the tooltip
312 306 Tooltip.prototype._show = function (reply) {
313 307 // move the bubble if it is not hidden
314 308 // otherwise fade it
315 309 var content = reply.content;
316 310 if (!content.found) {
317 311 // object not found, nothing to show
318 312 return;
319 313 }
320 314 this.name = content.name;
321 315
322 316 // do some math to have the tooltip arrow on more or less on left or right
323 317 // width of the editor
324 318 var w = $(this.code_mirror.getScrollerElement()).width();
325 319 // ofset of the editor
326 320 var o = $(this.code_mirror.getScrollerElement()).offset();
327 321
328 322 // whatever anchor/head order but arrow at mid x selection
329 323 var anchor = this.code_mirror.cursorCoords(false);
330 324 var head = this.code_mirror.cursorCoords(true);
331 325 var xinit = (head.left+anchor.left)/2;
332 326 var xinter = o.left + (xinit - o.left) / w * (w - 450);
333 327 var posarrowleft = xinit - xinter;
334 328
335 329 if (this._hidden === false) {
336 330 this.tooltip.animate({
337 331 'left': xinter - 30 + 'px',
338 332 'top': (head.bottom + 10) + 'px'
339 333 });
340 334 } else {
341 335 this.tooltip.css({
342 336 'left': xinter - 30 + 'px'
343 337 });
344 338 this.tooltip.css({
345 339 'top': (head.bottom + 10) + 'px'
346 340 });
347 341 }
348 342 this.arrow.animate({
349 343 'left': posarrowleft + 'px'
350 344 });
351 345
352 346 // build docstring
353 347 var defstring = content.call_def;
354 348 if (!defstring) {
355 349 defstring = content.init_definition;
356 350 }
357 351 if (!defstring) {
358 352 defstring = content.definition;
359 353 }
360 354
361 355 var docstring = content.call_docstring;
362 356 if (!docstring) {
363 357 docstring = content.init_docstring;
364 358 }
365 359 if (!docstring) {
366 360 docstring = content.docstring;
367 361 }
368 362
369 363 if (!docstring) {
370 364 // For reals this time, no docstring
371 365 if (this._hide_if_no_docstring) {
372 366 return;
373 367 } else {
374 368 docstring = "<empty docstring>";
375 369 }
376 370 }
377 371
378 372 this._hidden = false;
379 this._show = true;
380 373 this.tooltip.fadeIn('fast');
381 374 this.text.children().remove();
382 375
383 376 // Any HTML within the docstring is escaped by the fixConsole() method.
384 377 var pre = $('<pre/>').html(utils.fixConsole(docstring));
385 378 if (defstring) {
386 379 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
387 380 this.text.append(defstring_html);
388 381 }
389 382 this.text.append(pre);
390 383 // keep scroll top to be sure to always see the first line
391 384 this.text.scrollTop(0);
392 385 };
393 386
394 387 IPython.Tooltip = Tooltip;
395 388
396 389 return IPython;
397 390
398 391 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now