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