##// END OF EJS Templates
handle somehting selected and multiple cursors and 4 tabs show content in pager
Matthias Bussonnier -
Show More
@@ -1,530 +1,532 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3 /**
3 /**
4 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/cell',
17 'notebook/js/cell',
18 'notebook/js/outputarea',
18 'notebook/js/outputarea',
19 'notebook/js/completer',
19 'notebook/js/completer',
20 'notebook/js/celltoolbar',
20 'notebook/js/celltoolbar',
21 'codemirror/lib/codemirror',
21 'codemirror/lib/codemirror',
22 'codemirror/mode/python/python',
22 'codemirror/mode/python/python',
23 'notebook/js/codemirror-ipython'
23 'notebook/js/codemirror-ipython'
24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
25 "use strict";
25 "use strict";
26 var Cell = cell.Cell;
26 var Cell = cell.Cell;
27
27
28 /* local util for codemirror */
28 /* local util for codemirror */
29 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
29 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
30
30
31 /**
31 /**
32 *
32 *
33 * function to delete until previous non blanking space character
33 * function to delete until previous non blanking space character
34 * or first multiple of 4 tabstop.
34 * or first multiple of 4 tabstop.
35 * @private
35 * @private
36 */
36 */
37 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
37 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
38 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
38 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
39 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
39 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
40 var cur = cm.getCursor(), line = cm.getLine(cur.line);
40 var cur = cm.getCursor(), line = cm.getLine(cur.line);
41 var tabsize = cm.getOption('tabSize');
41 var tabsize = cm.getOption('tabSize');
42 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
42 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
43 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
43 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
44 var select = cm.getRange(from,cur);
44 var select = cm.getRange(from,cur);
45 if( select.match(/^\ +$/) !== null){
45 if( select.match(/^\ +$/) !== null){
46 cm.replaceRange("",from,cur);
46 cm.replaceRange("",from,cur);
47 } else {
47 } else {
48 cm.deleteH(-1,"char");
48 cm.deleteH(-1,"char");
49 }
49 }
50 };
50 };
51
51
52 var keycodes = keyboard.keycodes;
52 var keycodes = keyboard.keycodes;
53
53
54 var CodeCell = function (kernel, options) {
54 var CodeCell = function (kernel, options) {
55 // Constructor
55 // Constructor
56 //
56 //
57 // A Cell conceived to write code.
57 // A Cell conceived to write code.
58 //
58 //
59 // Parameters:
59 // Parameters:
60 // kernel: Kernel instance
60 // kernel: Kernel instance
61 // The kernel doesn't have to be set at creation time, in that case
61 // The kernel doesn't have to be set at creation time, in that case
62 // it will be null and set_kernel has to be called later.
62 // it will be null and set_kernel has to be called later.
63 // options: dictionary
63 // options: dictionary
64 // Dictionary of keyword arguments.
64 // Dictionary of keyword arguments.
65 // events: $(Events) instance
65 // events: $(Events) instance
66 // config: dictionary
66 // config: dictionary
67 // keyboard_manager: KeyboardManager instance
67 // keyboard_manager: KeyboardManager instance
68 // notebook: Notebook instance
68 // notebook: Notebook instance
69 // tooltip: Tooltip instance
69 // tooltip: Tooltip instance
70 this.kernel = kernel || null;
70 this.kernel = kernel || null;
71 this.notebook = options.notebook;
71 this.notebook = options.notebook;
72 this.collapsed = false;
72 this.collapsed = false;
73 this.events = options.events;
73 this.events = options.events;
74 this.tooltip = options.tooltip;
74 this.tooltip = options.tooltip;
75 this.config = options.config;
75 this.config = options.config;
76
76
77 // create all attributed in constructor function
77 // create all attributed in constructor function
78 // even if null for V8 VM optimisation
78 // even if null for V8 VM optimisation
79 this.input_prompt_number = null;
79 this.input_prompt_number = null;
80 this.celltoolbar = null;
80 this.celltoolbar = null;
81 this.output_area = null;
81 this.output_area = null;
82 this.last_msg_id = null;
82 this.last_msg_id = null;
83 this.completer = null;
83 this.completer = null;
84
84
85
85
86 var config = utils.mergeopt(CodeCell, this.config);
86 var config = utils.mergeopt(CodeCell, this.config);
87 Cell.apply(this,[{
87 Cell.apply(this,[{
88 config: config,
88 config: config,
89 keyboard_manager: options.keyboard_manager,
89 keyboard_manager: options.keyboard_manager,
90 events: this.events}]);
90 events: this.events}]);
91
91
92 // Attributes we want to override in this subclass.
92 // Attributes we want to override in this subclass.
93 this.cell_type = "code";
93 this.cell_type = "code";
94
94
95 var that = this;
95 var that = this;
96 this.element.focusout(
96 this.element.focusout(
97 function() { that.auto_highlight(); }
97 function() { that.auto_highlight(); }
98 );
98 );
99 };
99 };
100
100
101 CodeCell.options_default = {
101 CodeCell.options_default = {
102 cm_config : {
102 cm_config : {
103 extraKeys: {
103 extraKeys: {
104 "Tab" : "indentMore",
104 "Tab" : "indentMore",
105 "Shift-Tab" : "indentLess",
105 "Shift-Tab" : "indentLess",
106 "Backspace" : "delSpaceToPrevTabStop",
106 "Backspace" : "delSpaceToPrevTabStop",
107 "Cmd-/" : "toggleComment",
107 "Cmd-/" : "toggleComment",
108 "Ctrl-/" : "toggleComment"
108 "Ctrl-/" : "toggleComment"
109 },
109 },
110 mode: 'ipython',
110 mode: 'ipython',
111 theme: 'ipython',
111 theme: 'ipython',
112 matchBrackets: true
112 matchBrackets: true
113 }
113 }
114 };
114 };
115
115
116 CodeCell.msg_cells = {};
116 CodeCell.msg_cells = {};
117
117
118 CodeCell.prototype = new Cell();
118 CodeCell.prototype = new Cell();
119
119
120 /**
120 /**
121 * @method auto_highlight
121 * @method auto_highlight
122 */
122 */
123 CodeCell.prototype.auto_highlight = function () {
123 CodeCell.prototype.auto_highlight = function () {
124 this._auto_highlight(this.config.cell_magic_highlight);
124 this._auto_highlight(this.config.cell_magic_highlight);
125 };
125 };
126
126
127 /** @method create_element */
127 /** @method create_element */
128 CodeCell.prototype.create_element = function () {
128 CodeCell.prototype.create_element = function () {
129 Cell.prototype.create_element.apply(this, arguments);
129 Cell.prototype.create_element.apply(this, arguments);
130
130
131 var cell = $('<div></div>').addClass('cell code_cell');
131 var cell = $('<div></div>').addClass('cell code_cell');
132 cell.attr('tabindex','2');
132 cell.attr('tabindex','2');
133
133
134 var input = $('<div></div>').addClass('input');
134 var input = $('<div></div>').addClass('input');
135 var prompt = $('<div/>').addClass('prompt input_prompt');
135 var prompt = $('<div/>').addClass('prompt input_prompt');
136 var inner_cell = $('<div/>').addClass('inner_cell');
136 var inner_cell = $('<div/>').addClass('inner_cell');
137 this.celltoolbar = new celltoolbar.CellToolbar({
137 this.celltoolbar = new celltoolbar.CellToolbar({
138 cell: this,
138 cell: this,
139 notebook: this.notebook});
139 notebook: this.notebook});
140 inner_cell.append(this.celltoolbar.element);
140 inner_cell.append(this.celltoolbar.element);
141 var input_area = $('<div/>').addClass('input_area');
141 var input_area = $('<div/>').addClass('input_area');
142 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
142 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
143 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
143 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
144 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
144 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
145 inner_cell.append(input_area);
145 inner_cell.append(input_area);
146 input.append(prompt).append(inner_cell);
146 input.append(prompt).append(inner_cell);
147
147
148 var widget_area = $('<div/>')
148 var widget_area = $('<div/>')
149 .addClass('widget-area')
149 .addClass('widget-area')
150 .hide();
150 .hide();
151 this.widget_area = widget_area;
151 this.widget_area = widget_area;
152 var widget_prompt = $('<div/>')
152 var widget_prompt = $('<div/>')
153 .addClass('prompt')
153 .addClass('prompt')
154 .appendTo(widget_area);
154 .appendTo(widget_area);
155 var widget_subarea = $('<div/>')
155 var widget_subarea = $('<div/>')
156 .addClass('widget-subarea')
156 .addClass('widget-subarea')
157 .appendTo(widget_area);
157 .appendTo(widget_area);
158 this.widget_subarea = widget_subarea;
158 this.widget_subarea = widget_subarea;
159 var widget_clear_buton = $('<button />')
159 var widget_clear_buton = $('<button />')
160 .addClass('close')
160 .addClass('close')
161 .html('&times;')
161 .html('&times;')
162 .click(function() {
162 .click(function() {
163 widget_area.slideUp('', function(){ widget_subarea.html(''); });
163 widget_area.slideUp('', function(){ widget_subarea.html(''); });
164 })
164 })
165 .appendTo(widget_prompt);
165 .appendTo(widget_prompt);
166
166
167 var output = $('<div></div>');
167 var output = $('<div></div>');
168 cell.append(input).append(widget_area).append(output);
168 cell.append(input).append(widget_area).append(output);
169 this.element = cell;
169 this.element = cell;
170 this.output_area = new outputarea.OutputArea({
170 this.output_area = new outputarea.OutputArea({
171 selector: output,
171 selector: output,
172 prompt_area: true,
172 prompt_area: true,
173 events: this.events,
173 events: this.events,
174 keyboard_manager: this.keyboard_manager});
174 keyboard_manager: this.keyboard_manager});
175 this.completer = new completer.Completer(this, this.events);
175 this.completer = new completer.Completer(this, this.events);
176 };
176 };
177
177
178 /** @method bind_events */
178 /** @method bind_events */
179 CodeCell.prototype.bind_events = function () {
179 CodeCell.prototype.bind_events = function () {
180 Cell.prototype.bind_events.apply(this);
180 Cell.prototype.bind_events.apply(this);
181 var that = this;
181 var that = this;
182
182
183 this.element.focusout(
183 this.element.focusout(
184 function() { that.auto_highlight(); }
184 function() { that.auto_highlight(); }
185 );
185 );
186 };
186 };
187
187
188
188
189 /**
189 /**
190 * This method gets called in CodeMirror's onKeyDown/onKeyPress
190 * This method gets called in CodeMirror's onKeyDown/onKeyPress
191 * handlers and is used to provide custom key handling. Its return
191 * handlers and is used to provide custom key handling. Its return
192 * value is used to determine if CodeMirror should ignore the event:
192 * value is used to determine if CodeMirror should ignore the event:
193 * true = ignore, false = don't ignore.
193 * true = ignore, false = don't ignore.
194 * @method handle_codemirror_keyevent
194 * @method handle_codemirror_keyevent
195 */
195 */
196 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
196 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
197
197
198 var that = this;
198 var that = this;
199 // whatever key is pressed, first, cancel the tooltip request before
199 // whatever key is pressed, first, cancel the tooltip request before
200 // they are sent, and remove tooltip if any, except for tab again
200 // they are sent, and remove tooltip if any, except for tab again
201 var tooltip_closed = null;
201 var tooltip_closed = null;
202 if (event.type === 'keydown' && event.which != keycodes.tab ) {
202 if (event.type === 'keydown' && event.which != keycodes.tab ) {
203 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
203 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
204 }
204 }
205
205
206 var cur = editor.getCursor();
206 var cur = editor.getCursor();
207 if (event.keyCode === keycodes.enter){
207 if (event.keyCode === keycodes.enter){
208 this.auto_highlight();
208 this.auto_highlight();
209 }
209 }
210
210
211 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
211 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
212 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
212 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
213 // browser and keyboard layout !
213 // browser and keyboard layout !
214 // Pressing '(' , request tooltip, don't forget to reappend it
214 // Pressing '(' , request tooltip, don't forget to reappend it
215 // The second argument says to hide the tooltip if the docstring
215 // The second argument says to hide the tooltip if the docstring
216 // is actually empty
216 // is actually empty
217 this.tooltip.pending(that, true);
217 this.tooltip.pending(that, true);
218 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
218 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
219 // If tooltip is active, cancel it. The call to
219 // If tooltip is active, cancel it. The call to
220 // remove_and_cancel_tooltip above doesn't pass, force=true.
220 // remove_and_cancel_tooltip above doesn't pass, force=true.
221 // Because of this it won't actually close the tooltip
221 // Because of this it won't actually close the tooltip
222 // if it is in sticky mode. Thus, we have to check again if it is open
222 // if it is in sticky mode. Thus, we have to check again if it is open
223 // and close it with force=true.
223 // and close it with force=true.
224 if (!this.tooltip._hidden) {
224 if (!this.tooltip._hidden) {
225 this.tooltip.remove_and_cancel_tooltip(true);
225 this.tooltip.remove_and_cancel_tooltip(true);
226 }
226 }
227 // If we closed the tooltip, don't let CM or the global handlers
227 // If we closed the tooltip, don't let CM or the global handlers
228 // handle this event.
228 // handle this event.
229 event.codemirrorIgnore = true;
229 event.codemirrorIgnore = true;
230 event.preventDefault();
230 event.preventDefault();
231 return true;
231 return true;
232 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
232 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
233 if (editor.somethingSelected()){
233 if (editor.somethingSelected() || editor.getSelections().length !== 1){
234 var anchor = editor.getCursor("anchor");
234 var anchor = editor.getCursor("anchor");
235 var head = editor.getCursor("head");
235 var head = editor.getCursor("head");
236 if( anchor.line != head.line){
236 if( anchor.line != head.line){
237 return false;
237 return false;
238 }
238 }
239 }
239 }
240 this.tooltip.request(that);
240 this.tooltip.request(that);
241 event.codemirrorIgnore = true;
241 event.codemirrorIgnore = true;
242 event.preventDefault();
242 event.preventDefault();
243 return true;
243 return true;
244 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
244 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
245 // Tab completion.
245 // Tab completion.
246 this.tooltip.remove_and_cancel_tooltip();
246 this.tooltip.remove_and_cancel_tooltip();
247 if (editor.somethingSelected()) {
247
248 // completion does not work on multicursor, it might be possible though in some cases
249 if (editor.somethingSelected() || editor.getSelections().length > 1) {
248 return false;
250 return false;
249 }
251 }
250 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
252 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
251 if (pre_cursor.trim() === "") {
253 if (pre_cursor.trim() === "") {
252 // Don't autocomplete if the part of the line before the cursor
254 // Don't autocomplete if the part of the line before the cursor
253 // is empty. In this case, let CodeMirror handle indentation.
255 // is empty. In this case, let CodeMirror handle indentation.
254 return false;
256 return false;
255 } else {
257 } else {
256 event.codemirrorIgnore = true;
258 event.codemirrorIgnore = true;
257 event.preventDefault();
259 event.preventDefault();
258 this.completer.startCompletion();
260 this.completer.startCompletion();
259 return true;
261 return true;
260 }
262 }
261 }
263 }
262
264
263 // keyboard event wasn't one of those unique to code cells, let's see
265 // keyboard event wasn't one of those unique to code cells, let's see
264 // if it's one of the generic ones (i.e. check edit mode shortcuts)
266 // if it's one of the generic ones (i.e. check edit mode shortcuts)
265 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
267 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
266 };
268 };
267
269
268 // Kernel related calls.
270 // Kernel related calls.
269
271
270 CodeCell.prototype.set_kernel = function (kernel) {
272 CodeCell.prototype.set_kernel = function (kernel) {
271 this.kernel = kernel;
273 this.kernel = kernel;
272 };
274 };
273
275
274 /**
276 /**
275 * Execute current code cell to the kernel
277 * Execute current code cell to the kernel
276 * @method execute
278 * @method execute
277 */
279 */
278 CodeCell.prototype.execute = function () {
280 CodeCell.prototype.execute = function () {
279 this.output_area.clear_output();
281 this.output_area.clear_output();
280
282
281 // Clear widget area
283 // Clear widget area
282 this.widget_subarea.html('');
284 this.widget_subarea.html('');
283 this.widget_subarea.height('');
285 this.widget_subarea.height('');
284 this.widget_area.height('');
286 this.widget_area.height('');
285 this.widget_area.hide();
287 this.widget_area.hide();
286
288
287 this.set_input_prompt('*');
289 this.set_input_prompt('*');
288 this.element.addClass("running");
290 this.element.addClass("running");
289 if (this.last_msg_id) {
291 if (this.last_msg_id) {
290 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
292 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
291 }
293 }
292 var callbacks = this.get_callbacks();
294 var callbacks = this.get_callbacks();
293
295
294 var old_msg_id = this.last_msg_id;
296 var old_msg_id = this.last_msg_id;
295 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
297 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
296 if (old_msg_id) {
298 if (old_msg_id) {
297 delete CodeCell.msg_cells[old_msg_id];
299 delete CodeCell.msg_cells[old_msg_id];
298 }
300 }
299 CodeCell.msg_cells[this.last_msg_id] = this;
301 CodeCell.msg_cells[this.last_msg_id] = this;
300 this.render();
302 this.render();
301 };
303 };
302
304
303 /**
305 /**
304 * Construct the default callbacks for
306 * Construct the default callbacks for
305 * @method get_callbacks
307 * @method get_callbacks
306 */
308 */
307 CodeCell.prototype.get_callbacks = function () {
309 CodeCell.prototype.get_callbacks = function () {
308 return {
310 return {
309 shell : {
311 shell : {
310 reply : $.proxy(this._handle_execute_reply, this),
312 reply : $.proxy(this._handle_execute_reply, this),
311 payload : {
313 payload : {
312 set_next_input : $.proxy(this._handle_set_next_input, this),
314 set_next_input : $.proxy(this._handle_set_next_input, this),
313 page : $.proxy(this._open_with_pager, this)
315 page : $.proxy(this._open_with_pager, this)
314 }
316 }
315 },
317 },
316 iopub : {
318 iopub : {
317 output : $.proxy(this.output_area.handle_output, this.output_area),
319 output : $.proxy(this.output_area.handle_output, this.output_area),
318 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
320 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
319 },
321 },
320 input : $.proxy(this._handle_input_request, this)
322 input : $.proxy(this._handle_input_request, this)
321 };
323 };
322 };
324 };
323
325
324 CodeCell.prototype._open_with_pager = function (payload) {
326 CodeCell.prototype._open_with_pager = function (payload) {
325 this.events.trigger('open_with_text.Pager', payload);
327 this.events.trigger('open_with_text.Pager', payload);
326 };
328 };
327
329
328 /**
330 /**
329 * @method _handle_execute_reply
331 * @method _handle_execute_reply
330 * @private
332 * @private
331 */
333 */
332 CodeCell.prototype._handle_execute_reply = function (msg) {
334 CodeCell.prototype._handle_execute_reply = function (msg) {
333 this.set_input_prompt(msg.content.execution_count);
335 this.set_input_prompt(msg.content.execution_count);
334 this.element.removeClass("running");
336 this.element.removeClass("running");
335 this.events.trigger('set_dirty.Notebook', {value: true});
337 this.events.trigger('set_dirty.Notebook', {value: true});
336 };
338 };
337
339
338 /**
340 /**
339 * @method _handle_set_next_input
341 * @method _handle_set_next_input
340 * @private
342 * @private
341 */
343 */
342 CodeCell.prototype._handle_set_next_input = function (payload) {
344 CodeCell.prototype._handle_set_next_input = function (payload) {
343 var data = {'cell': this, 'text': payload.text};
345 var data = {'cell': this, 'text': payload.text};
344 this.events.trigger('set_next_input.Notebook', data);
346 this.events.trigger('set_next_input.Notebook', data);
345 };
347 };
346
348
347 /**
349 /**
348 * @method _handle_input_request
350 * @method _handle_input_request
349 * @private
351 * @private
350 */
352 */
351 CodeCell.prototype._handle_input_request = function (msg) {
353 CodeCell.prototype._handle_input_request = function (msg) {
352 this.output_area.append_raw_input(msg);
354 this.output_area.append_raw_input(msg);
353 };
355 };
354
356
355
357
356 // Basic cell manipulation.
358 // Basic cell manipulation.
357
359
358 CodeCell.prototype.select = function () {
360 CodeCell.prototype.select = function () {
359 var cont = Cell.prototype.select.apply(this);
361 var cont = Cell.prototype.select.apply(this);
360 if (cont) {
362 if (cont) {
361 this.code_mirror.refresh();
363 this.code_mirror.refresh();
362 this.auto_highlight();
364 this.auto_highlight();
363 }
365 }
364 return cont;
366 return cont;
365 };
367 };
366
368
367 CodeCell.prototype.render = function () {
369 CodeCell.prototype.render = function () {
368 var cont = Cell.prototype.render.apply(this);
370 var cont = Cell.prototype.render.apply(this);
369 // Always execute, even if we are already in the rendered state
371 // Always execute, even if we are already in the rendered state
370 return cont;
372 return cont;
371 };
373 };
372
374
373 CodeCell.prototype.select_all = function () {
375 CodeCell.prototype.select_all = function () {
374 var start = {line: 0, ch: 0};
376 var start = {line: 0, ch: 0};
375 var nlines = this.code_mirror.lineCount();
377 var nlines = this.code_mirror.lineCount();
376 var last_line = this.code_mirror.getLine(nlines-1);
378 var last_line = this.code_mirror.getLine(nlines-1);
377 var end = {line: nlines-1, ch: last_line.length};
379 var end = {line: nlines-1, ch: last_line.length};
378 this.code_mirror.setSelection(start, end);
380 this.code_mirror.setSelection(start, end);
379 };
381 };
380
382
381
383
382 CodeCell.prototype.collapse_output = function () {
384 CodeCell.prototype.collapse_output = function () {
383 this.collapsed = true;
385 this.collapsed = true;
384 this.output_area.collapse();
386 this.output_area.collapse();
385 };
387 };
386
388
387
389
388 CodeCell.prototype.expand_output = function () {
390 CodeCell.prototype.expand_output = function () {
389 this.collapsed = false;
391 this.collapsed = false;
390 this.output_area.expand();
392 this.output_area.expand();
391 this.output_area.unscroll_area();
393 this.output_area.unscroll_area();
392 };
394 };
393
395
394 CodeCell.prototype.scroll_output = function () {
396 CodeCell.prototype.scroll_output = function () {
395 this.output_area.expand();
397 this.output_area.expand();
396 this.output_area.scroll_if_long();
398 this.output_area.scroll_if_long();
397 };
399 };
398
400
399 CodeCell.prototype.toggle_output = function () {
401 CodeCell.prototype.toggle_output = function () {
400 this.collapsed = Boolean(1 - this.collapsed);
402 this.collapsed = Boolean(1 - this.collapsed);
401 this.output_area.toggle_output();
403 this.output_area.toggle_output();
402 };
404 };
403
405
404 CodeCell.prototype.toggle_output_scroll = function () {
406 CodeCell.prototype.toggle_output_scroll = function () {
405 this.output_area.toggle_scroll();
407 this.output_area.toggle_scroll();
406 };
408 };
407
409
408
410
409 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
411 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
410 var ns;
412 var ns;
411 if (prompt_value === undefined) {
413 if (prompt_value === undefined) {
412 ns = "&nbsp;";
414 ns = "&nbsp;";
413 } else {
415 } else {
414 ns = encodeURIComponent(prompt_value);
416 ns = encodeURIComponent(prompt_value);
415 }
417 }
416 return 'In&nbsp;[' + ns + ']:';
418 return 'In&nbsp;[' + ns + ']:';
417 };
419 };
418
420
419 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
421 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
420 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
422 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
421 for(var i=1; i < lines_number; i++) {
423 for(var i=1; i < lines_number; i++) {
422 html.push(['...:']);
424 html.push(['...:']);
423 }
425 }
424 return html.join('<br/>');
426 return html.join('<br/>');
425 };
427 };
426
428
427 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
429 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
428
430
429
431
430 CodeCell.prototype.set_input_prompt = function (number) {
432 CodeCell.prototype.set_input_prompt = function (number) {
431 var nline = 1;
433 var nline = 1;
432 if (this.code_mirror !== undefined) {
434 if (this.code_mirror !== undefined) {
433 nline = this.code_mirror.lineCount();
435 nline = this.code_mirror.lineCount();
434 }
436 }
435 this.input_prompt_number = number;
437 this.input_prompt_number = number;
436 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
438 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
437 // This HTML call is okay because the user contents are escaped.
439 // This HTML call is okay because the user contents are escaped.
438 this.element.find('div.input_prompt').html(prompt_html);
440 this.element.find('div.input_prompt').html(prompt_html);
439 };
441 };
440
442
441
443
442 CodeCell.prototype.clear_input = function () {
444 CodeCell.prototype.clear_input = function () {
443 this.code_mirror.setValue('');
445 this.code_mirror.setValue('');
444 };
446 };
445
447
446
448
447 CodeCell.prototype.get_text = function () {
449 CodeCell.prototype.get_text = function () {
448 return this.code_mirror.getValue();
450 return this.code_mirror.getValue();
449 };
451 };
450
452
451
453
452 CodeCell.prototype.set_text = function (code) {
454 CodeCell.prototype.set_text = function (code) {
453 return this.code_mirror.setValue(code);
455 return this.code_mirror.setValue(code);
454 };
456 };
455
457
456
458
457 CodeCell.prototype.clear_output = function (wait) {
459 CodeCell.prototype.clear_output = function (wait) {
458 this.output_area.clear_output(wait);
460 this.output_area.clear_output(wait);
459 this.set_input_prompt();
461 this.set_input_prompt();
460 };
462 };
461
463
462
464
463 // JSON serialization
465 // JSON serialization
464
466
465 CodeCell.prototype.fromJSON = function (data) {
467 CodeCell.prototype.fromJSON = function (data) {
466 Cell.prototype.fromJSON.apply(this, arguments);
468 Cell.prototype.fromJSON.apply(this, arguments);
467 if (data.cell_type === 'code') {
469 if (data.cell_type === 'code') {
468 if (data.input !== undefined) {
470 if (data.input !== undefined) {
469 this.set_text(data.input);
471 this.set_text(data.input);
470 // make this value the starting point, so that we can only undo
472 // make this value the starting point, so that we can only undo
471 // to this state, instead of a blank cell
473 // to this state, instead of a blank cell
472 this.code_mirror.clearHistory();
474 this.code_mirror.clearHistory();
473 this.auto_highlight();
475 this.auto_highlight();
474 }
476 }
475 if (data.prompt_number !== undefined) {
477 if (data.prompt_number !== undefined) {
476 this.set_input_prompt(data.prompt_number);
478 this.set_input_prompt(data.prompt_number);
477 } else {
479 } else {
478 this.set_input_prompt();
480 this.set_input_prompt();
479 }
481 }
480 this.output_area.trusted = data.metadata.trusted || false;
482 this.output_area.trusted = data.metadata.trusted || false;
481 this.output_area.fromJSON(data.outputs);
483 this.output_area.fromJSON(data.outputs);
482 if (data.collapsed !== undefined) {
484 if (data.collapsed !== undefined) {
483 if (data.collapsed) {
485 if (data.collapsed) {
484 this.collapse_output();
486 this.collapse_output();
485 } else {
487 } else {
486 this.expand_output();
488 this.expand_output();
487 }
489 }
488 }
490 }
489 }
491 }
490 };
492 };
491
493
492
494
493 CodeCell.prototype.toJSON = function () {
495 CodeCell.prototype.toJSON = function () {
494 var data = Cell.prototype.toJSON.apply(this);
496 var data = Cell.prototype.toJSON.apply(this);
495 data.input = this.get_text();
497 data.input = this.get_text();
496 // is finite protect against undefined and '*' value
498 // is finite protect against undefined and '*' value
497 if (isFinite(this.input_prompt_number)) {
499 if (isFinite(this.input_prompt_number)) {
498 data.prompt_number = this.input_prompt_number;
500 data.prompt_number = this.input_prompt_number;
499 }
501 }
500 var outputs = this.output_area.toJSON();
502 var outputs = this.output_area.toJSON();
501 data.outputs = outputs;
503 data.outputs = outputs;
502 data.language = 'python';
504 data.language = 'python';
503 data.metadata.trusted = this.output_area.trusted;
505 data.metadata.trusted = this.output_area.trusted;
504 data.collapsed = this.output_area.collapsed;
506 data.collapsed = this.output_area.collapsed;
505 return data;
507 return data;
506 };
508 };
507
509
508 /**
510 /**
509 * handle cell level logic when a cell is unselected
511 * handle cell level logic when a cell is unselected
510 * @method unselect
512 * @method unselect
511 * @return is the action being taken
513 * @return is the action being taken
512 */
514 */
513 CodeCell.prototype.unselect = function () {
515 CodeCell.prototype.unselect = function () {
514 var cont = Cell.prototype.unselect.apply(this);
516 var cont = Cell.prototype.unselect.apply(this);
515 if (cont) {
517 if (cont) {
516 // When a code cell is usnelected, make sure that the corresponding
518 // When a code cell is usnelected, make sure that the corresponding
517 // tooltip and completer to that cell is closed.
519 // tooltip and completer to that cell is closed.
518 this.tooltip.remove_and_cancel_tooltip(true);
520 this.tooltip.remove_and_cancel_tooltip(true);
519 if (this.completer !== null) {
521 if (this.completer !== null) {
520 this.completer.close();
522 this.completer.close();
521 }
523 }
522 }
524 }
523 return cont;
525 return cont;
524 };
526 };
525
527
526 // Backwards compatability.
528 // Backwards compatability.
527 IPython.CodeCell = CodeCell;
529 IPython.CodeCell = CodeCell;
528
530
529 return {'CodeCell': CodeCell};
531 return {'CodeCell': CodeCell};
530 });
532 });
@@ -1,406 +1,406 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 'notebook/js/contexthint',
9 'notebook/js/contexthint',
10 'codemirror/lib/codemirror',
10 'codemirror/lib/codemirror',
11 ], function(IPython, $, utils, keyboard, CodeMirror) {
11 ], function(IPython, $, utils, keyboard, CodeMirror) {
12 "use strict";
12 "use strict";
13
13
14 // easier key mapping
14 // easier key mapping
15 var keycodes = keyboard.keycodes;
15 var keycodes = keyboard.keycodes;
16
16
17 var prepend_n_prc = function(str, n) {
17 var prepend_n_prc = function(str, n) {
18 for( var i =0 ; i< n ; i++){
18 for( var i =0 ; i< n ; i++){
19 str = '%'+str ;
19 str = '%'+str ;
20 }
20 }
21 return str;
21 return str;
22 };
22 };
23
23
24 var _existing_completion = function(item, completion_array){
24 var _existing_completion = function(item, completion_array){
25 for( var i=0; i < completion_array.length; i++) {
25 for( var i=0; i < completion_array.length; i++) {
26 if (completion_array[i].trim().substr(-item.length) == item) {
26 if (completion_array[i].trim().substr(-item.length) == item) {
27 return true;
27 return true;
28 }
28 }
29 }
29 }
30 return false;
30 return false;
31 };
31 };
32
32
33 // what is the common start of all completions
33 // what is the common start of all completions
34 function shared_start(B, drop_prct) {
34 function shared_start(B, drop_prct) {
35 if (B.length == 1) {
35 if (B.length == 1) {
36 return B[0];
36 return B[0];
37 }
37 }
38 var A = [];
38 var A = [];
39 var common;
39 var common;
40 var min_lead_prct = 10;
40 var min_lead_prct = 10;
41 for (var i = 0; i < B.length; i++) {
41 for (var i = 0; i < B.length; i++) {
42 var str = B[i].str;
42 var str = B[i].str;
43 var localmin = 0;
43 var localmin = 0;
44 if(drop_prct === true){
44 if(drop_prct === true){
45 while ( str.substr(0, 1) == '%') {
45 while ( str.substr(0, 1) == '%') {
46 localmin = localmin+1;
46 localmin = localmin+1;
47 str = str.substring(1);
47 str = str.substring(1);
48 }
48 }
49 }
49 }
50 min_lead_prct = Math.min(min_lead_prct, localmin);
50 min_lead_prct = Math.min(min_lead_prct, localmin);
51 A.push(str);
51 A.push(str);
52 }
52 }
53
53
54 if (A.length > 1) {
54 if (A.length > 1) {
55 var tem1, tem2, s;
55 var tem1, tem2, s;
56 A = A.slice(0).sort();
56 A = A.slice(0).sort();
57 tem1 = A[0];
57 tem1 = A[0];
58 s = tem1.length;
58 s = tem1.length;
59 tem2 = A.pop();
59 tem2 = A.pop();
60 while (s && tem2.indexOf(tem1) == -1) {
60 while (s && tem2.indexOf(tem1) == -1) {
61 tem1 = tem1.substring(0, --s);
61 tem1 = tem1.substring(0, --s);
62 }
62 }
63 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
63 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
64 return {
64 return {
65 str:prepend_n_prc('', min_lead_prct),
65 str:prepend_n_prc('', 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 {
71 return {
72 str: prepend_n_prc(tem1, min_lead_prct),
72 str: prepend_n_prc(tem1, min_lead_prct),
73 type: "computed",
73 type: "computed",
74 from: B[0].from,
74 from: B[0].from,
75 to: B[0].to
75 to: B[0].to
76 };
76 };
77 }
77 }
78 return null;
78 return null;
79 }
79 }
80
80
81
81
82 var Completer = function (cell, events) {
82 var Completer = function (cell, events) {
83 this.cell = cell;
83 this.cell = cell;
84 this.editor = cell.code_mirror;
84 this.editor = cell.code_mirror;
85 var that = this;
85 var that = this;
86 events.on('kernel_busy.Kernel', function () {
86 events.on('kernel_busy.Kernel', function () {
87 that.skip_kernel_completion = true;
87 that.skip_kernel_completion = true;
88 });
88 });
89 events.on('kernel_idle.Kernel', function () {
89 events.on('kernel_idle.Kernel', function () {
90 that.skip_kernel_completion = false;
90 that.skip_kernel_completion = false;
91 });
91 });
92 };
92 };
93
93
94 Completer.prototype.startCompletion = function () {
94 Completer.prototype.startCompletion = function () {
95 // call for a 'first' completion, that will set the editor and do some
95 // call for a 'first' completion, that will set the editor and do some
96 // special behavior like autopicking if only one completion available.
96 // special behavior like autopicking if only one completion available.
97 if (this.editor.somethingSelected()) return;
97 if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) return;
98 this.done = false;
98 this.done = false;
99 // use to get focus back on opera
99 // use to get focus back on opera
100 this.carry_on_completion(true);
100 this.carry_on_completion(true);
101 };
101 };
102
102
103
103
104 // easy access for julia to monkeypatch
104 // easy access for julia to monkeypatch
105 //
105 //
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
107
107
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
109 return Completer.reinvoke_re.test(pre_cursor);
109 return Completer.reinvoke_re.test(pre_cursor);
110 };
110 };
111
111
112 /**
112 /**
113 *
113 *
114 * 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
115 * 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
116 * 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
117 * 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
118 * 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
119 * shared start
119 * shared start
120 **/
120 **/
121 Completer.prototype.carry_on_completion = function (first_invocation) {
121 Completer.prototype.carry_on_completion = function (first_invocation) {
122 // 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
123 // only one completion. This function is automatically reinvoked at
123 // only one completion. This function is automatically reinvoked at
124 // each keystroke with first_invocation = false
124 // each keystroke with first_invocation = false
125 var cur = this.editor.getCursor();
125 var cur = this.editor.getCursor();
126 var line = this.editor.getLine(cur.line);
126 var line = this.editor.getLine(cur.line);
127 var pre_cursor = this.editor.getRange({
127 var pre_cursor = this.editor.getRange({
128 line: cur.line,
128 line: cur.line,
129 ch: cur.ch - 1
129 ch: cur.ch - 1
130 }, cur);
130 }, cur);
131
131
132 // 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
133 // because while typing the completer is still reinvoking itself
133 // because while typing the completer is still reinvoking itself
134 // so dismiss if we are on a "bad" caracter
134 // so dismiss if we are on a "bad" caracter
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
136 this.close();
136 this.close();
137 return;
137 return;
138 }
138 }
139
139
140 this.autopick = false;
140 this.autopick = false;
141 if (first_invocation) {
141 if (first_invocation) {
142 this.autopick = true;
142 this.autopick = true;
143 }
143 }
144
144
145 // We want a single cursor position.
145 // We want a single cursor position.
146 if (this.editor.somethingSelected()) {
146 if (this.editor.somethingSelected()|| editor.getSelections().length > 1) {
147 return;
147 return;
148 }
148 }
149
149
150 // 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
151 // 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
152 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
152 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
153 if (this.skip_kernel_completion) {
153 if (this.skip_kernel_completion) {
154 this.finish_completing({ content: {
154 this.finish_completing({ content: {
155 matches: [],
155 matches: [],
156 cursor_start: cursor_pos,
156 cursor_start: cursor_pos,
157 cursor_end: cursor_pos,
157 cursor_end: cursor_pos,
158 }});
158 }});
159 } else {
159 } else {
160 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
160 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
161 $.proxy(this.finish_completing, this)
161 $.proxy(this.finish_completing, this)
162 );
162 );
163 }
163 }
164 };
164 };
165
165
166 Completer.prototype.finish_completing = function (msg) {
166 Completer.prototype.finish_completing = function (msg) {
167 // let's build a function that wrap all that stuff into what is needed
167 // let's build a function that wrap all that stuff into what is needed
168 // for the new completer:
168 // for the new completer:
169 var content = msg.content;
169 var content = msg.content;
170 var start = content.cursor_start;
170 var start = content.cursor_start;
171 var end = content.cursor_end;
171 var end = content.cursor_end;
172 var matches = content.matches;
172 var matches = content.matches;
173
173
174 var cur = this.editor.getCursor();
174 var cur = this.editor.getCursor();
175 if (end === null) {
175 if (end === null) {
176 // adapted message spec replies don't have cursor position info,
176 // adapted message spec replies don't have cursor position info,
177 // interpret end=null as current position,
177 // interpret end=null as current position,
178 // and negative start relative to that
178 // and negative start relative to that
179 end = utils.to_absolute_cursor_pos(this.editor, cur);
179 end = utils.to_absolute_cursor_pos(this.editor, cur);
180 if (start < 0) {
180 if (start < 0) {
181 start = end + start;
181 start = end + start;
182 }
182 }
183 }
183 }
184 var results = CodeMirror.contextHint(this.editor);
184 var results = CodeMirror.contextHint(this.editor);
185 var filtered_results = [];
185 var filtered_results = [];
186 //remove results from context completion
186 //remove results from context completion
187 //that are already in kernel completion
187 //that are already in kernel completion
188 var i;
188 var i;
189 for (i=0; i < results.length; i++) {
189 for (i=0; i < results.length; i++) {
190 if (!_existing_completion(results[i].str, matches)) {
190 if (!_existing_completion(results[i].str, matches)) {
191 filtered_results.push(results[i]);
191 filtered_results.push(results[i]);
192 }
192 }
193 }
193 }
194
194
195 // append the introspection result, in order, at at the beginning of
195 // append the introspection result, in order, at at the beginning of
196 // the table and compute the replacement range from current cursor
196 // the table and compute the replacement range from current cursor
197 // positon and matched_text length.
197 // positon and matched_text length.
198 for (i = matches.length - 1; i >= 0; --i) {
198 for (i = matches.length - 1; i >= 0; --i) {
199 filtered_results.unshift({
199 filtered_results.unshift({
200 str: matches[i],
200 str: matches[i],
201 type: "introspection",
201 type: "introspection",
202 from: utils.from_absolute_cursor_pos(this.editor, start),
202 from: utils.from_absolute_cursor_pos(this.editor, start),
203 to: utils.from_absolute_cursor_pos(this.editor, end)
203 to: utils.from_absolute_cursor_pos(this.editor, end)
204 });
204 });
205 }
205 }
206
206
207 // one the 2 sources results have been merge, deal with it
207 // one the 2 sources results have been merge, deal with it
208 this.raw_result = filtered_results;
208 this.raw_result = filtered_results;
209
209
210 // if empty result return
210 // if empty result return
211 if (!this.raw_result || !this.raw_result.length) return;
211 if (!this.raw_result || !this.raw_result.length) return;
212
212
213 // When there is only one completion, use it directly.
213 // When there is only one completion, use it directly.
214 if (this.autopick && this.raw_result.length == 1) {
214 if (this.autopick && this.raw_result.length == 1) {
215 this.insert(this.raw_result[0]);
215 this.insert(this.raw_result[0]);
216 return;
216 return;
217 }
217 }
218
218
219 if (this.raw_result.length == 1) {
219 if (this.raw_result.length == 1) {
220 // test if first and only completion totally matches
220 // test if first and only completion totally matches
221 // what is typed, in this case dismiss
221 // what is typed, in this case dismiss
222 var str = this.raw_result[0].str;
222 var str = this.raw_result[0].str;
223 var pre_cursor = this.editor.getRange({
223 var pre_cursor = this.editor.getRange({
224 line: cur.line,
224 line: cur.line,
225 ch: cur.ch - str.length
225 ch: cur.ch - str.length
226 }, cur);
226 }, cur);
227 if (pre_cursor == str) {
227 if (pre_cursor == str) {
228 this.close();
228 this.close();
229 return;
229 return;
230 }
230 }
231 }
231 }
232
232
233 if (!this.visible) {
233 if (!this.visible) {
234 this.complete = $('<div/>').addClass('completions');
234 this.complete = $('<div/>').addClass('completions');
235 this.complete.attr('id', 'complete');
235 this.complete.attr('id', 'complete');
236
236
237 // Currently webkit doesn't use the size attr correctly. See:
237 // Currently webkit doesn't use the size attr correctly. See:
238 // https://code.google.com/p/chromium/issues/detail?id=4579
238 // https://code.google.com/p/chromium/issues/detail?id=4579
239 this.sel = $('<select/>')
239 this.sel = $('<select/>')
240 .attr('tabindex', -1)
240 .attr('tabindex', -1)
241 .attr('multiple', 'true');
241 .attr('multiple', 'true');
242 this.complete.append(this.sel);
242 this.complete.append(this.sel);
243 this.visible = true;
243 this.visible = true;
244 $('body').append(this.complete);
244 $('body').append(this.complete);
245
245
246 //build the container
246 //build the container
247 var that = this;
247 var that = this;
248 this.sel.dblclick(function () {
248 this.sel.dblclick(function () {
249 that.pick();
249 that.pick();
250 });
250 });
251 this.sel.focus(function () {
251 this.sel.focus(function () {
252 that.editor.focus();
252 that.editor.focus();
253 });
253 });
254 this._handle_keydown = function (cm, event) {
254 this._handle_keydown = function (cm, event) {
255 that.keydown(event);
255 that.keydown(event);
256 };
256 };
257 this.editor.on('keydown', this._handle_keydown);
257 this.editor.on('keydown', this._handle_keydown);
258 this._handle_keypress = function (cm, event) {
258 this._handle_keypress = function (cm, event) {
259 that.keypress(event);
259 that.keypress(event);
260 };
260 };
261 this.editor.on('keypress', this._handle_keypress);
261 this.editor.on('keypress', this._handle_keypress);
262 }
262 }
263 this.sel.attr('size', Math.min(10, this.raw_result.length));
263 this.sel.attr('size', Math.min(10, this.raw_result.length));
264
264
265 // After everything is on the page, compute the postion.
265 // After everything is on the page, compute the postion.
266 // We put it above the code if it is too close to the bottom of the page.
266 // We put it above the code if it is too close to the bottom of the page.
267 var pos = this.editor.cursorCoords(
267 var pos = this.editor.cursorCoords(
268 utils.from_absolute_cursor_pos(this.editor, start)
268 utils.from_absolute_cursor_pos(this.editor, start)
269 );
269 );
270 var left = pos.left-3;
270 var left = pos.left-3;
271 var top;
271 var top;
272 var cheight = this.complete.height();
272 var cheight = this.complete.height();
273 var wheight = $(window).height();
273 var wheight = $(window).height();
274 if (pos.bottom+cheight+5 > wheight) {
274 if (pos.bottom+cheight+5 > wheight) {
275 top = pos.top-cheight-4;
275 top = pos.top-cheight-4;
276 } else {
276 } else {
277 top = pos.bottom+1;
277 top = pos.bottom+1;
278 }
278 }
279 this.complete.css('left', left + 'px');
279 this.complete.css('left', left + 'px');
280 this.complete.css('top', top + 'px');
280 this.complete.css('top', top + 'px');
281
281
282 // Clear and fill the list.
282 // Clear and fill the list.
283 this.sel.text('');
283 this.sel.text('');
284 this.build_gui_list(this.raw_result);
284 this.build_gui_list(this.raw_result);
285 return true;
285 return true;
286 };
286 };
287
287
288 Completer.prototype.insert = function (completion) {
288 Completer.prototype.insert = function (completion) {
289 this.editor.replaceRange(completion.str, completion.from, completion.to);
289 this.editor.replaceRange(completion.str, completion.from, completion.to);
290 };
290 };
291
291
292 Completer.prototype.build_gui_list = function (completions) {
292 Completer.prototype.build_gui_list = function (completions) {
293 for (var i = 0; i < completions.length; ++i) {
293 for (var i = 0; i < completions.length; ++i) {
294 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
294 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
295 this.sel.append(opt);
295 this.sel.append(opt);
296 }
296 }
297 this.sel.children().first().attr('selected', 'true');
297 this.sel.children().first().attr('selected', 'true');
298 this.sel.scrollTop(0);
298 this.sel.scrollTop(0);
299 };
299 };
300
300
301 Completer.prototype.close = function () {
301 Completer.prototype.close = function () {
302 this.done = true;
302 this.done = true;
303 $('#complete').remove();
303 $('#complete').remove();
304 this.editor.off('keydown', this._handle_keydown);
304 this.editor.off('keydown', this._handle_keydown);
305 this.editor.off('keypress', this._handle_keypress);
305 this.editor.off('keypress', this._handle_keypress);
306 this.visible = false;
306 this.visible = false;
307 };
307 };
308
308
309 Completer.prototype.pick = function () {
309 Completer.prototype.pick = function () {
310 this.insert(this.raw_result[this.sel[0].selectedIndex]);
310 this.insert(this.raw_result[this.sel[0].selectedIndex]);
311 this.close();
311 this.close();
312 };
312 };
313
313
314 Completer.prototype.keydown = function (event) {
314 Completer.prototype.keydown = function (event) {
315 var code = event.keyCode;
315 var code = event.keyCode;
316 var that = this;
316 var that = this;
317
317
318 // Enter
318 // Enter
319 if (code == keycodes.enter) {
319 if (code == keycodes.enter) {
320 event.codemirrorIgnore = true;
320 event.codemirrorIgnore = true;
321 event._ipkmIgnore = true;
321 event._ipkmIgnore = true;
322 event.preventDefault();
322 event.preventDefault();
323 this.pick();
323 this.pick();
324 // Escape or backspace
324 // Escape or backspace
325 } else if (code == keycodes.esc || code == keycodes.backspace) {
325 } else if (code == keycodes.esc || code == keycodes.backspace) {
326 event.codemirrorIgnore = true;
326 event.codemirrorIgnore = true;
327 event._ipkmIgnore = true;
327 event._ipkmIgnore = true;
328 event.preventDefault();
328 event.preventDefault();
329 this.close();
329 this.close();
330 } else if (code == keycodes.tab) {
330 } else if (code == keycodes.tab) {
331 //all the fastforwarding operation,
331 //all the fastforwarding operation,
332 //Check that shared start is not null which can append with prefixed completion
332 //Check that shared start is not null which can append with prefixed completion
333 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
333 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
334 // to erase py
334 // to erase py
335 var sh = shared_start(this.raw_result, true);
335 var sh = shared_start(this.raw_result, true);
336 if (sh) {
336 if (sh) {
337 this.insert(sh);
337 this.insert(sh);
338 }
338 }
339 this.close();
339 this.close();
340 //reinvoke self
340 //reinvoke self
341 setTimeout(function () {
341 setTimeout(function () {
342 that.carry_on_completion();
342 that.carry_on_completion();
343 }, 50);
343 }, 50);
344 } else if (code == keycodes.up || code == keycodes.down) {
344 } else if (code == keycodes.up || code == keycodes.down) {
345 // need to do that to be able to move the arrow
345 // need to do that to be able to move the arrow
346 // when on the first or last line ofo a code cell
346 // when on the first or last line ofo a code cell
347 event.codemirrorIgnore = true;
347 event.codemirrorIgnore = true;
348 event._ipkmIgnore = true;
348 event._ipkmIgnore = true;
349 event.preventDefault();
349 event.preventDefault();
350
350
351 var options = this.sel.find('option');
351 var options = this.sel.find('option');
352 var index = this.sel[0].selectedIndex;
352 var index = this.sel[0].selectedIndex;
353 if (code == keycodes.up) {
353 if (code == keycodes.up) {
354 index--;
354 index--;
355 }
355 }
356 if (code == keycodes.down) {
356 if (code == keycodes.down) {
357 index++;
357 index++;
358 }
358 }
359 index = Math.min(Math.max(index, 0), options.length-1);
359 index = Math.min(Math.max(index, 0), options.length-1);
360 this.sel[0].selectedIndex = index;
360 this.sel[0].selectedIndex = index;
361 } else if (code == keycodes.pageup || code == keycodes.pagedown) {
361 } else if (code == keycodes.pageup || code == keycodes.pagedown) {
362 event._ipkmIgnore = true;
362 event._ipkmIgnore = true;
363
363
364 var options = this.sel.find('option');
364 var options = this.sel.find('option');
365 var index = this.sel[0].selectedIndex;
365 var index = this.sel[0].selectedIndex;
366 if (code == keycodes.pageup) {
366 if (code == keycodes.pageup) {
367 index -= 10; // As 10 is the hard coded size of the drop down menu
367 index -= 10; // As 10 is the hard coded size of the drop down menu
368 } else {
368 } else {
369 index += 10;
369 index += 10;
370 }
370 }
371 index = Math.min(Math.max(index, 0), options.length-1);
371 index = Math.min(Math.max(index, 0), options.length-1);
372 this.sel[0].selectedIndex = index;
372 this.sel[0].selectedIndex = index;
373 } else if (code == keycodes.left || code == keycodes.right) {
373 } else if (code == keycodes.left || code == keycodes.right) {
374 this.close();
374 this.close();
375 }
375 }
376 };
376 };
377
377
378 Completer.prototype.keypress = function (event) {
378 Completer.prototype.keypress = function (event) {
379 // FIXME: This is a band-aid.
379 // FIXME: This is a band-aid.
380 // on keypress, trigger insertion of a single character.
380 // on keypress, trigger insertion of a single character.
381 // This simulates the old behavior of completion as you type,
381 // This simulates the old behavior of completion as you type,
382 // before events were disconnected and CodeMirror stopped
382 // before events were disconnected and CodeMirror stopped
383 // receiving events while the completer is focused.
383 // receiving events while the completer is focused.
384
384
385 var that = this;
385 var that = this;
386 var code = event.keyCode;
386 var code = event.keyCode;
387
387
388 // don't handle keypress if it's not a character (arrows on FF)
388 // don't handle keypress if it's not a character (arrows on FF)
389 // or ENTER/TAB
389 // or ENTER/TAB
390 if (event.charCode === 0 ||
390 if (event.charCode === 0 ||
391 code == keycodes.tab ||
391 code == keycodes.tab ||
392 code == keycodes.enter
392 code == keycodes.enter
393 ) return;
393 ) return;
394
394
395 this.close();
395 this.close();
396 this.editor.focus();
396 this.editor.focus();
397 setTimeout(function () {
397 setTimeout(function () {
398 that.carry_on_completion();
398 that.carry_on_completion();
399 }, 50);
399 }, 50);
400 };
400 };
401
401
402 // For backwards compatability.
402 // For backwards compatability.
403 IPython.Completer = Completer;
403 IPython.Completer = Completer;
404
404
405 return {'Completer': Completer};
405 return {'Completer': Completer};
406 });
406 });
@@ -1,332 +1,332 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 // tooltip constructor
11 // tooltip constructor
12 var Tooltip = function (events) {
12 var Tooltip = function (events) {
13 var that = this;
13 var that = this;
14 this.events = events;
14 this.events = events;
15 this.time_before_tooltip = 1200;
15 this.time_before_tooltip = 1200;
16
16
17 // handle to html
17 // handle to html
18 this.tooltip = $('#tooltip');
18 this.tooltip = $('#tooltip');
19 this._hidden = true;
19 this._hidden = true;
20
20
21 // variable for consecutive call
21 // variable for consecutive call
22 this._old_cell = null;
22 this._old_cell = null;
23 this._old_request = null;
23 this._old_request = null;
24 this._consecutive_counter = 0;
24 this._consecutive_counter = 0;
25
25
26 // 'sticky ?'
26 // 'sticky ?'
27 this._sticky = false;
27 this._sticky = false;
28
28
29 // display tooltip if the docstring is empty?
29 // display tooltip if the docstring is empty?
30 this._hide_if_no_docstring = false;
30 this._hide_if_no_docstring = false;
31
31
32 // contain the button in the upper right corner
32 // contain the button in the upper right corner
33 this.buttons = $('<div/>').addClass('tooltipbuttons');
33 this.buttons = $('<div/>').addClass('tooltipbuttons');
34
34
35 // will contain the docstring
35 // will contain the docstring
36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
37
37
38 // build the buttons menu on the upper right
38 // build the buttons menu on the upper right
39 // expand the tooltip to see more
39 // expand the tooltip to see more
40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
42 that.expand();
42 that.expand();
43 }).append(
43 }).append(
44 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
44 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
45
45
46 // open in pager
46 // open in pager
47 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
47 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
48 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
48 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
49 morelink.append(morespan);
49 morelink.append(morespan);
50 morelink.click(function () {
50 morelink.click(function () {
51 that.showInPager(that._old_cell);
51 that.showInPager(that._old_cell);
52 });
52 });
53
53
54 // close the tooltip
54 // close the tooltip
55 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
55 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
56 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
56 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
57 closelink.append(closespan);
57 closelink.append(closespan);
58 closelink.click(function () {
58 closelink.click(function () {
59 that.remove_and_cancel_tooltip(true);
59 that.remove_and_cancel_tooltip(true);
60 });
60 });
61
61
62 this._clocklink = $('<a/>').attr('href', "#");
62 this._clocklink = $('<a/>').attr('href', "#");
63 this._clocklink.attr('role', "button");
63 this._clocklink.attr('role', "button");
64 this._clocklink.addClass('ui-button');
64 this._clocklink.addClass('ui-button');
65 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
65 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
66 var clockspan = $('<span/>').text('Close');
66 var clockspan = $('<span/>').text('Close');
67 clockspan.addClass('ui-icon');
67 clockspan.addClass('ui-icon');
68 clockspan.addClass('ui-icon-clock');
68 clockspan.addClass('ui-icon-clock');
69 this._clocklink.append(clockspan);
69 this._clocklink.append(clockspan);
70 this._clocklink.click(function () {
70 this._clocklink.click(function () {
71 that.cancel_stick();
71 that.cancel_stick();
72 });
72 });
73
73
74
74
75
75
76
76
77 //construct the tooltip
77 //construct the tooltip
78 // add in the reverse order you want them to appear
78 // add in the reverse order you want them to appear
79 this.buttons.append(closelink);
79 this.buttons.append(closelink);
80 this.buttons.append(expandlink);
80 this.buttons.append(expandlink);
81 this.buttons.append(morelink);
81 this.buttons.append(morelink);
82 this.buttons.append(this._clocklink);
82 this.buttons.append(this._clocklink);
83 this._clocklink.hide();
83 this._clocklink.hide();
84
84
85
85
86 // we need a phony element to make the small arrow
86 // we need a phony element to make the small arrow
87 // of the tooltip in css
87 // of the tooltip in css
88 // we will move the arrow later
88 // we will move the arrow later
89 this.arrow = $('<div/>').addClass('pretooltiparrow');
89 this.arrow = $('<div/>').addClass('pretooltiparrow');
90 this.tooltip.append(this.buttons);
90 this.tooltip.append(this.buttons);
91 this.tooltip.append(this.arrow);
91 this.tooltip.append(this.arrow);
92 this.tooltip.append(this.text);
92 this.tooltip.append(this.text);
93
93
94 // function that will be called if you press tab 1, 2, 3... times in a row
94 // function that will be called if you press tab 1, 2, 3... times in a row
95 this.tabs_functions = [function (cell, text, cursor) {
95 this.tabs_functions = [function (cell, text, cursor) {
96 that._request_tooltip(cell, text, cursor);
96 that._request_tooltip(cell, text, cursor);
97 }, function () {
97 }, function () {
98 that.expand();
98 that.expand();
99 }, function () {
99 }, function () {
100 that.stick();
100 that.stick();
101 }, function (cell) {
101 }, function (cell) {
102 that.cancel_stick();
102 that.cancel_stick();
103 that.showInPager(cell);
103 that.showInPager(cell);
104 }];
104 }];
105 // call after all the tabs function above have bee call to clean their effects
105 // call after all the tabs function above have bee call to clean their effects
106 // if necessary
106 // if necessary
107 this.reset_tabs_function = function (cell, text) {
107 this.reset_tabs_function = function (cell, text) {
108 this._old_cell = (cell) ? cell : null;
108 this._old_cell = (cell) ? cell : null;
109 this._old_request = (text) ? text : null;
109 this._old_request = (text) ? text : null;
110 this._consecutive_counter = 0;
110 this._consecutive_counter = 0;
111 };
111 };
112 };
112 };
113
113
114 Tooltip.prototype.is_visible = function () {
114 Tooltip.prototype.is_visible = function () {
115 return !this._hidden;
115 return !this._hidden;
116 };
116 };
117
117
118 Tooltip.prototype.showInPager = function (cell) {
118 Tooltip.prototype.showInPager = function (cell) {
119 // reexecute last call in pager by appending ? to show back in pager
119 // reexecute last call in pager by appending ? to show back in pager
120 var that = this;
120 this.events.trigger('open_with_text.Pager', this._reply.content);
121 this.events.trigger('open_with_text.Pager', that._reply.content);
122 this.remove_and_cancel_tooltip();
121 this.remove_and_cancel_tooltip();
123 };
122 };
124
123
125 // grow the tooltip verticaly
124 // grow the tooltip verticaly
126 Tooltip.prototype.expand = function () {
125 Tooltip.prototype.expand = function () {
127 this.text.removeClass('smalltooltip');
126 this.text.removeClass('smalltooltip');
128 this.text.addClass('bigtooltip');
127 this.text.addClass('bigtooltip');
129 $('#expanbutton').hide('slow');
128 $('#expanbutton').hide('slow');
130 };
129 };
131
130
132 // deal with all the logic of hiding the tooltip
131 // deal with all the logic of hiding the tooltip
133 // and reset it's status
132 // and reset it's status
134 Tooltip.prototype._hide = function () {
133 Tooltip.prototype._hide = function () {
135 this._hidden = true;
134 this._hidden = true;
136 this.tooltip.fadeOut('fast');
135 this.tooltip.fadeOut('fast');
137 $('#expanbutton').show('slow');
136 $('#expanbutton').show('slow');
138 this.text.removeClass('bigtooltip');
137 this.text.removeClass('bigtooltip');
139 this.text.addClass('smalltooltip');
138 this.text.addClass('smalltooltip');
140 // keep scroll top to be sure to always see the first line
139 // keep scroll top to be sure to always see the first line
141 this.text.scrollTop(0);
140 this.text.scrollTop(0);
142 this.code_mirror = null;
141 this.code_mirror = null;
143 };
142 };
144
143
145 // return true on successfully removing a visible tooltip; otherwise return
144 // return true on successfully removing a visible tooltip; otherwise return
146 // false.
145 // false.
147 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
146 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
148 // note that we don't handle closing directly inside the calltip
147 // note that we don't handle closing directly inside the calltip
149 // as in the completer, because it is not focusable, so won't
148 // as in the completer, because it is not focusable, so won't
150 // get the event.
149 // get the event.
151 this.cancel_pending();
150 this.cancel_pending();
152 if (!this._hidden) {
151 if (!this._hidden) {
153 if (force || !this._sticky) {
152 if (force || !this._sticky) {
154 this.cancel_stick();
153 this.cancel_stick();
155 this._hide();
154 this._hide();
156 }
155 }
157 this.reset_tabs_function();
156 this.reset_tabs_function();
158 return true;
157 return true;
159 } else {
158 } else {
160 return false;
159 return false;
161 }
160 }
162 };
161 };
163
162
164 // cancel autocall done after '(' for example.
163 // cancel autocall done after '(' for example.
165 Tooltip.prototype.cancel_pending = function () {
164 Tooltip.prototype.cancel_pending = function () {
166 if (this._tooltip_timeout !== null) {
165 if (this._tooltip_timeout !== null) {
167 clearTimeout(this._tooltip_timeout);
166 clearTimeout(this._tooltip_timeout);
168 this._tooltip_timeout = null;
167 this._tooltip_timeout = null;
169 }
168 }
170 };
169 };
171
170
172 // will trigger tooltip after timeout
171 // will trigger tooltip after timeout
173 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
172 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
174 var that = this;
173 var that = this;
175 this._tooltip_timeout = setTimeout(function () {
174 this._tooltip_timeout = setTimeout(function () {
176 that.request(cell, hide_if_no_docstring);
175 that.request(cell, hide_if_no_docstring);
177 }, that.time_before_tooltip);
176 }, that.time_before_tooltip);
178 };
177 };
179
178
180 // easy access for julia monkey patching.
179 // easy access for julia monkey patching.
181 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
180 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
182
181
183 Tooltip.prototype.extract_oir_token = function(line){
182 Tooltip.prototype.extract_oir_token = function(line){
184 // use internally just to make the request to the kernel
183 // use internally just to make the request to the kernel
185 // Feel free to shorten this logic if you are better
184 // Feel free to shorten this logic if you are better
186 // than me in regEx
185 // than me in regEx
187 // basicaly you shoul be able to get xxx.xxx.xxx from
186 // basicaly you shoul be able to get xxx.xxx.xxx from
188 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
187 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
189 // remove everything between matchin bracket (need to iterate)
188 // remove everything between matchin bracket (need to iterate)
190 var matchBracket = /\([^\(\)]+\)/g;
189 var matchBracket = /\([^\(\)]+\)/g;
191 var endBracket = /\([^\(]*$/g;
190 var endBracket = /\([^\(]*$/g;
192 var oldline = line;
191 var oldline = line;
193
192
194 line = line.replace(matchBracket, "");
193 line = line.replace(matchBracket, "");
195 while (oldline != line) {
194 while (oldline != line) {
196 oldline = line;
195 oldline = line;
197 line = line.replace(matchBracket, "");
196 line = line.replace(matchBracket, "");
198 }
197 }
199 // remove everything after last open bracket
198 // remove everything after last open bracket
200 line = line.replace(endBracket, "");
199 line = line.replace(endBracket, "");
201 // reset the regex object
200 // reset the regex object
202 Tooltip.last_token_re.lastIndex = 0;
201 Tooltip.last_token_re.lastIndex = 0;
203 return Tooltip.last_token_re.exec(line);
202 return Tooltip.last_token_re.exec(line);
204 };
203 };
205
204
206 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
205 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
207 var callbacks = $.proxy(this._show, this);
206 var callbacks = $.proxy(this._show, this);
208 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
207 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
209 };
208 };
210
209
211 // make an imediate completion request
210 // make an immediate completion request
212 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
211 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
213 // request(codecell)
212 // request(codecell)
214 // Deal with extracting the text from the cell and counting
213 // Deal with extracting the text from the cell and counting
215 // call in a row
214 // call in a row
216 this.cancel_pending();
215 this.cancel_pending();
217 var editor = cell.code_mirror;
216 var editor = cell.code_mirror;
218 var cursor = editor.getCursor();
217 var cursor = editor.getCursor();
219 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
218 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
220 var text = cell.get_text();
219 var text = cell.get_text();
221
220
222 this._hide_if_no_docstring = hide_if_no_docstring;
221 this._hide_if_no_docstring = hide_if_no_docstring;
223
222
224 if(editor.somethingSelected()){
223 if(editor.somethingSelected()){
224 // get only the most recent selection.
225 text = editor.getSelection();
225 text = editor.getSelection();
226 }
226 }
227
227
228 // need a permanent handel to code_mirror for future auto recall
228 // need a permanent handle to code_mirror for future auto recall
229 this.code_mirror = editor;
229 this.code_mirror = editor;
230
230
231 // now we treat the different number of keypress
231 // now we treat the different number of keypress
232 // first if same cell, same text, increment counter by 1
232 // first if same cell, same text, increment counter by 1
233 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
233 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
234 this._consecutive_counter++;
234 this._consecutive_counter++;
235 } else {
235 } else {
236 // else reset
236 // else reset
237 this.cancel_stick();
237 this.cancel_stick();
238 this.reset_tabs_function (cell, text);
238 this.reset_tabs_function (cell, text);
239 }
239 }
240
240
241 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
241 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
242
242
243 // then if we are at the end of list function, reset
243 // then if we are at the end of list function, reset
244 if (this._consecutive_counter == this.tabs_functions.length) {
244 if (this._consecutive_counter == this.tabs_functions.length) {
245 this.reset_tabs_function (cell, text, cursor);
245 this.reset_tabs_function (cell, text, cursor);
246 }
246 }
247
247
248 return;
248 return;
249 };
249 };
250
250
251 // cancel the option of having the tooltip to stick
251 // cancel the option of having the tooltip to stick
252 Tooltip.prototype.cancel_stick = function () {
252 Tooltip.prototype.cancel_stick = function () {
253 clearTimeout(this._stick_timeout);
253 clearTimeout(this._stick_timeout);
254 this._stick_timeout = null;
254 this._stick_timeout = null;
255 this._clocklink.hide('slow');
255 this._clocklink.hide('slow');
256 this._sticky = false;
256 this._sticky = false;
257 };
257 };
258
258
259 // put the tooltip in a sicky state for 10 seconds
259 // put the tooltip in a sicky state for 10 seconds
260 // it won't be removed by remove_and_cancell() unless you called with
260 // it won't be removed by remove_and_cancell() unless you called with
261 // the first parameter set to true.
261 // the first parameter set to true.
262 // remove_and_cancell_tooltip(true)
262 // remove_and_cancell_tooltip(true)
263 Tooltip.prototype.stick = function (time) {
263 Tooltip.prototype.stick = function (time) {
264 time = (time !== undefined) ? time : 10;
264 time = (time !== undefined) ? time : 10;
265 var that = this;
265 var that = this;
266 this._sticky = true;
266 this._sticky = true;
267 this._clocklink.show('slow');
267 this._clocklink.show('slow');
268 this._stick_timeout = setTimeout(function () {
268 this._stick_timeout = setTimeout(function () {
269 that._sticky = false;
269 that._sticky = false;
270 that._clocklink.hide('slow');
270 that._clocklink.hide('slow');
271 }, time * 1000);
271 }, time * 1000);
272 };
272 };
273
273
274 // should be called with the kernel reply to actually show the tooltip
274 // should be called with the kernel reply to actually show the tooltip
275 Tooltip.prototype._show = function (reply) {
275 Tooltip.prototype._show = function (reply) {
276 // move the bubble if it is not hidden
276 // move the bubble if it is not hidden
277 // otherwise fade it
277 // otherwise fade it
278 this._reply = reply;
278 this._reply = reply;
279 var content = reply.content;
279 var content = reply.content;
280 if (!content.found) {
280 if (!content.found) {
281 // object not found, nothing to show
281 // object not found, nothing to show
282 return;
282 return;
283 }
283 }
284 this.name = content.name;
284 this.name = content.name;
285
285
286 // do some math to have the tooltip arrow on more or less on left or right
286 // do some math to have the tooltip arrow on more or less on left or right
287 // width of the editor
287 // width of the editor
288 var w = $(this.code_mirror.getScrollerElement()).width();
288 var w = $(this.code_mirror.getScrollerElement()).width();
289 // ofset of the editor
289 // ofset of the editor
290 var o = $(this.code_mirror.getScrollerElement()).offset();
290 var o = $(this.code_mirror.getScrollerElement()).offset();
291
291
292 // whatever anchor/head order but arrow at mid x selection
292 // whatever anchor/head order but arrow at mid x selection
293 var anchor = this.code_mirror.cursorCoords(false);
293 var anchor = this.code_mirror.cursorCoords(false);
294 var head = this.code_mirror.cursorCoords(true);
294 var head = this.code_mirror.cursorCoords(true);
295 var xinit = (head.left+anchor.left)/2;
295 var xinit = (head.left+anchor.left)/2;
296 var xinter = o.left + (xinit - o.left) / w * (w - 450);
296 var xinter = o.left + (xinit - o.left) / w * (w - 450);
297 var posarrowleft = xinit - xinter;
297 var posarrowleft = xinit - xinter;
298
298
299 if (this._hidden === false) {
299 if (this._hidden === false) {
300 this.tooltip.animate({
300 this.tooltip.animate({
301 'left': xinter - 30 + 'px',
301 'left': xinter - 30 + 'px',
302 'top': (head.bottom + 10) + 'px'
302 'top': (head.bottom + 10) + 'px'
303 });
303 });
304 } else {
304 } else {
305 this.tooltip.css({
305 this.tooltip.css({
306 'left': xinter - 30 + 'px'
306 'left': xinter - 30 + 'px'
307 });
307 });
308 this.tooltip.css({
308 this.tooltip.css({
309 'top': (head.bottom + 10) + 'px'
309 'top': (head.bottom + 10) + 'px'
310 });
310 });
311 }
311 }
312 this.arrow.animate({
312 this.arrow.animate({
313 'left': posarrowleft + 'px'
313 'left': posarrowleft + 'px'
314 });
314 });
315
315
316 this._hidden = false;
316 this._hidden = false;
317 this.tooltip.fadeIn('fast');
317 this.tooltip.fadeIn('fast');
318 this.text.children().remove();
318 this.text.children().remove();
319
319
320 // This should support rich data types, but only text/plain for now
320 // This should support rich data types, but only text/plain for now
321 // Any HTML within the docstring is escaped by the fixConsole() method.
321 // Any HTML within the docstring is escaped by the fixConsole() method.
322 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
322 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
323 this.text.append(pre);
323 this.text.append(pre);
324 // keep scroll top to be sure to always see the first line
324 // keep scroll top to be sure to always see the first line
325 this.text.scrollTop(0);
325 this.text.scrollTop(0);
326 };
326 };
327
327
328 // Backwards compatability.
328 // Backwards compatibility.
329 IPython.Tooltip = Tooltip;
329 IPython.Tooltip = Tooltip;
330
330
331 return {'Tooltip': Tooltip};
331 return {'Tooltip': Tooltip};
332 });
332 });
General Comments 0
You need to be logged in to leave comments. Login now