##// END OF EJS Templates
Getting a lot closer...
Jonathan Frederic -
Show More
@@ -1,578 +1,581
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 CodeCell.prototype.select_all = function () {
405 CodeCell.prototype.select_all = function () {
406 var start = {line: 0, ch: 0};
406 var start = {line: 0, ch: 0};
407 var nlines = this.code_mirror.lineCount();
407 var nlines = this.code_mirror.lineCount();
408 var last_line = this.code_mirror.getLine(nlines-1);
408 var last_line = this.code_mirror.getLine(nlines-1);
409 var end = {line: nlines-1, ch: last_line.length};
409 var end = {line: nlines-1, ch: last_line.length};
410 this.code_mirror.setSelection(start, end);
410 this.code_mirror.setSelection(start, end);
411 };
411 };
412
412
413
413
414 CodeCell.prototype.collapse_output = function () {
414 CodeCell.prototype.collapse_output = function () {
415 this.collapsed = true;
415 this.collapsed = true;
416 this.output_area.collapse();
416 this.output_area.collapse();
417 };
417 };
418
418
419
419
420 CodeCell.prototype.expand_output = function () {
420 CodeCell.prototype.expand_output = function () {
421 this.collapsed = false;
421 this.collapsed = false;
422 this.output_area.expand();
422 this.output_area.expand();
423 this.output_area.unscroll_area();
423 this.output_area.unscroll_area();
424 };
424 };
425
425
426 CodeCell.prototype.scroll_output = function () {
426 CodeCell.prototype.scroll_output = function () {
427 this.output_area.expand();
427 this.output_area.expand();
428 this.output_area.scroll_if_long();
428 this.output_area.scroll_if_long();
429 };
429 };
430
430
431 CodeCell.prototype.toggle_output = function () {
431 CodeCell.prototype.toggle_output = function () {
432 this.collapsed = Boolean(1 - this.collapsed);
432 this.collapsed = Boolean(1 - this.collapsed);
433 this.output_area.toggle_output();
433 this.output_area.toggle_output();
434 };
434 };
435
435
436 CodeCell.prototype.toggle_output_scroll = function () {
436 CodeCell.prototype.toggle_output_scroll = function () {
437 this.output_area.toggle_scroll();
437 this.output_area.toggle_scroll();
438 };
438 };
439
439
440
440
441 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
441 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
442 var ns;
442 var ns;
443 if (prompt_value == undefined) {
443 if (prompt_value == undefined) {
444 ns = "&nbsp;";
444 ns = "&nbsp;";
445 } else {
445 } else {
446 ns = encodeURIComponent(prompt_value);
446 ns = encodeURIComponent(prompt_value);
447 }
447 }
448 return 'In&nbsp;[' + ns + ']:';
448 return 'In&nbsp;[' + ns + ']:';
449 };
449 };
450
450
451 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
451 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
452 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
452 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
453 for(var i=1; i < lines_number; i++) {
453 for(var i=1; i < lines_number; i++) {
454 html.push(['...:']);
454 html.push(['...:']);
455 }
455 }
456 return html.join('<br/>');
456 return html.join('<br/>');
457 };
457 };
458
458
459 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
459 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
460
460
461
461
462 CodeCell.prototype.set_input_prompt = function (number) {
462 CodeCell.prototype.set_input_prompt = function (number) {
463 var nline = 1;
463 var nline = 1;
464 if (this.code_mirror !== undefined) {
464 if (this.code_mirror !== undefined) {
465 nline = this.code_mirror.lineCount();
465 nline = this.code_mirror.lineCount();
466 }
466 }
467 this.input_prompt_number = number;
467 this.input_prompt_number = number;
468 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);
469 // This HTML call is okay because the user contents are escaped.
469 // This HTML call is okay because the user contents are escaped.
470 this.element.find('div.input_prompt').html(prompt_html);
470 this.element.find('div.input_prompt').html(prompt_html);
471 };
471 };
472
472
473
473
474 CodeCell.prototype.clear_input = function () {
474 CodeCell.prototype.clear_input = function () {
475 this.code_mirror.setValue('');
475 this.code_mirror.setValue('');
476 };
476 };
477
477
478
478
479 CodeCell.prototype.get_text = function () {
479 CodeCell.prototype.get_text = function () {
480 return this.code_mirror.getValue();
480 return this.code_mirror.getValue();
481 };
481 };
482
482
483
483
484 CodeCell.prototype.set_text = function (code) {
484 CodeCell.prototype.set_text = function (code) {
485 return this.code_mirror.setValue(code);
485 return this.code_mirror.setValue(code);
486 };
486 };
487
487
488
488
489 CodeCell.prototype.at_top = function () {
489 CodeCell.prototype.at_top = function () {
490 var cursor = this.code_mirror.getCursor();
490 var cursor = this.code_mirror.getCursor();
491 if (cursor.line === 0 && cursor.ch === 0) {
491 if (cursor.line === 0 && cursor.ch === 0) {
492 return true;
492 return true;
493 } else {
493 } else {
494 return false;
494 return false;
495 }
495 }
496 };
496 };
497
497
498
498
499 CodeCell.prototype.at_bottom = function () {
499 CodeCell.prototype.at_bottom = function () {
500 var cursor = this.code_mirror.getCursor();
500 var cursor = this.code_mirror.getCursor();
501 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) {
502 return true;
502 return true;
503 } else {
503 } else {
504 return false;
504 return false;
505 }
505 }
506 };
506 };
507
507
508
508
509 CodeCell.prototype.clear_output = function (wait) {
509 CodeCell.prototype.clear_output = function (wait) {
510 this.output_area.clear_output(wait);
510 this.output_area.clear_output(wait);
511 this.set_input_prompt();
511 this.set_input_prompt();
512 };
512 };
513
513
514
514
515 // JSON serialization
515 // JSON serialization
516
516
517 CodeCell.prototype.fromJSON = function (data) {
517 CodeCell.prototype.fromJSON = function (data) {
518 IPython.Cell.prototype.fromJSON.apply(this, arguments);
518 IPython.Cell.prototype.fromJSON.apply(this, arguments);
519 if (data.cell_type === 'code') {
519 if (data.cell_type === 'code') {
520 if (data.input !== undefined) {
520 if (data.input !== undefined) {
521 this.set_text(data.input);
521 this.set_text(data.input);
522 // 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
523 // to this state, instead of a blank cell
523 // to this state, instead of a blank cell
524 this.code_mirror.clearHistory();
524 this.code_mirror.clearHistory();
525 this.auto_highlight();
525 this.auto_highlight();
526 }
526 }
527 if (data.prompt_number !== undefined) {
527 if (data.prompt_number !== undefined) {
528 this.set_input_prompt(data.prompt_number);
528 this.set_input_prompt(data.prompt_number);
529 } else {
529 } else {
530 this.set_input_prompt();
530 this.set_input_prompt();
531 }
531 }
532 this.output_area.trusted = data.trusted || false;
532 this.output_area.trusted = data.trusted || false;
533 this.output_area.fromJSON(data.outputs);
533 this.output_area.fromJSON(data.outputs);
534 if (data.collapsed !== undefined) {
534 if (data.collapsed !== undefined) {
535 if (data.collapsed) {
535 if (data.collapsed) {
536 this.collapse_output();
536 this.collapse_output();
537 } else {
537 } else {
538 this.expand_output();
538 this.expand_output();
539 }
539 }
540 }
540 }
541 }
541 }
542 };
542 };
543
543
544
544
545 CodeCell.prototype.toJSON = function () {
545 CodeCell.prototype.toJSON = function () {
546 var data = IPython.Cell.prototype.toJSON.apply(this);
546 var data = IPython.Cell.prototype.toJSON.apply(this);
547 data.input = this.get_text();
547 data.input = this.get_text();
548 // is finite protect against undefined and '*' value
548 // is finite protect against undefined and '*' value
549 if (isFinite(this.input_prompt_number)) {
549 if (isFinite(this.input_prompt_number)) {
550 data.prompt_number = this.input_prompt_number;
550 data.prompt_number = this.input_prompt_number;
551 }
551 }
552 var outputs = this.output_area.toJSON();
552 var outputs = this.output_area.toJSON();
553 data.outputs = outputs;
553 data.outputs = outputs;
554 data.language = 'python';
554 data.language = 'python';
555 data.trusted = this.output_area.trusted;
555 data.trusted = this.output_area.trusted;
556 data.collapsed = this.collapsed;
556 data.collapsed = this.collapsed;
557 return data;
557 return data;
558 };
558 };
559
559
560 /**
560 /**
561 * handle cell level logic when a cell is unselected
561 * handle cell level logic when a cell is unselected
562 * @method unselect
562 * @method unselect
563 * @return is the action being taken
563 * @return is the action being taken
564 */
564 */
565 CodeCell.prototype.unselect = function () {
565 CodeCell.prototype.unselect = function () {
566 var cont = IPython.Cell.prototype.unselect.apply(this);
566 var cont = IPython.Cell.prototype.unselect.apply(this);
567 if (cont) {
567 if (cont) {
568 // When a code cell is usnelected, make sure that the corresponding
568 // When a code cell is usnelected, make sure that the corresponding
569 // tooltip to that cell is closed.
569 // tooltip and completer to that cell is closed.
570 IPython.tooltip.remove_and_cancel_tooltip(true);
570 IPython.tooltip.remove_and_cancel_tooltip(true);
571 if (this.completer !== null) {
572 this.completer.close();
573 }
571 }
574 }
572 return cont;
575 return cont;
573 };
576 };
574
577
575 IPython.CodeCell = CodeCell;
578 IPython.CodeCell = CodeCell;
576
579
577 return IPython;
580 return IPython;
578 }(IPython));
581 }(IPython));
@@ -1,373 +1,357
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.cell = cell;
76 this.cell = cell;
77 this.editor = cell.code_mirror;
77 this.editor = cell.code_mirror;
78 var that = this;
78 var that = this;
79 $([IPython.events]).on('status_busy.Kernel', function () {
79 $([IPython.events]).on('status_busy.Kernel', function () {
80 that.skip_kernel_completion = true;
80 that.skip_kernel_completion = true;
81 });
81 });
82 $([IPython.events]).on('status_idle.Kernel', function () {
82 $([IPython.events]).on('status_idle.Kernel', function () {
83 that.skip_kernel_completion = false;
83 that.skip_kernel_completion = false;
84 });
84 });
85 };
85 };
86
86
87 Completer.prototype.startCompletion = function () {
87 Completer.prototype.startCompletion = function () {
88 // call for a 'first' completion, that will set the editor and do some
88 // call for a 'first' completion, that will set the editor and do some
89 // special behaviour like autopicking if only one completion availlable
89 // special behavior like autopicking if only one completion available.
90 //
91 if (this.editor.somethingSelected()) return;
90 if (this.editor.somethingSelected()) return;
92 this.done = false;
91 this.done = false;
93 // use to get focus back on opera
92 // use to get focus back on opera
94 this.carry_on_completion(true);
93 this.carry_on_completion(true);
95 };
94 };
96
95
97
96
98 // easy access for julia to monkeypatch
97 // easy access for julia to monkeypatch
99 //
98 //
100 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
99 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
101
100
102 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
101 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
103 return Completer.reinvoke_re.test(pre_cursor);
102 return Completer.reinvoke_re.test(pre_cursor);
104 };
103 };
105
104
106 /**
105 /**
107 *
106 *
108 * pass true as parameter if this is the first invocation of the completer
107 * pass true as parameter if this is the first invocation of the completer
109 * this will prevent the completer to dissmiss itself if it is not on a
108 * this will prevent the completer to dissmiss itself if it is not on a
110 * word boundary like pressing tab after a space, and make it autopick the
109 * word boundary like pressing tab after a space, and make it autopick the
111 * only choice if there is only one which prevent from popping the UI. as
110 * only choice if there is only one which prevent from popping the UI. as
112 * well as fast-forwarding the typing if all completion have a common
111 * well as fast-forwarding the typing if all completion have a common
113 * shared start
112 * shared start
114 **/
113 **/
115 Completer.prototype.carry_on_completion = function (first_invocation) {
114 Completer.prototype.carry_on_completion = function (first_invocation) {
116 // Pass true as parameter if you want the completer to autopick when
115 // Pass true as parameter if you want the completer to autopick when
117 // only one completion. This function is automatically reinvoked at
116 // only one completion. This function is automatically reinvoked at
118 // each keystroke with first_invocation = false
117 // each keystroke with first_invocation = false
119 var cur = this.editor.getCursor();
118 var cur = this.editor.getCursor();
120 var line = this.editor.getLine(cur.line);
119 var line = this.editor.getLine(cur.line);
121 var pre_cursor = this.editor.getRange({
120 var pre_cursor = this.editor.getRange({
122 line: cur.line,
121 line: cur.line,
123 ch: cur.ch - 1
122 ch: cur.ch - 1
124 }, cur);
123 }, cur);
125
124
126 // we need to check that we are still on a word boundary
125 // we need to check that we are still on a word boundary
127 // because while typing the completer is still reinvoking itself
126 // because while typing the completer is still reinvoking itself
128 // so dismiss if we are on a "bad" caracter
127 // so dismiss if we are on a "bad" caracter
129 if (!this.reinvoke(pre_cursor) && !first_invocation) {
128 if (!this.reinvoke(pre_cursor) && !first_invocation) {
130 this.close();
129 this.close();
131 return;
130 return;
132 }
131 }
133
132
134 this.autopick = false;
133 this.autopick = false;
135 if (first_invocation) {
134 if (first_invocation) {
136 this.autopick = true;
135 this.autopick = true;
137 }
136 }
138
137
139 // We want a single cursor position.
138 // We want a single cursor position.
140 if (this.editor.somethingSelected()) {
139 if (this.editor.somethingSelected()) {
141 return;
140 return;
142 }
141 }
143
142
144 // one kernel completion came back, finish_completing will be called with the results
143 // one kernel completion came back, finish_completing will be called with the results
145 // we fork here and directly call finish completing if kernel is busy
144 // we fork here and directly call finish completing if kernel is busy
146 if (this.skip_kernel_completion) {
145 if (this.skip_kernel_completion) {
147 this.finish_completing({
146 this.finish_completing({
148 'matches': [],
147 'matches': [],
149 matched_text: ""
148 matched_text: ""
150 });
149 });
151 } else {
150 } else {
152 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
151 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
153 }
152 }
154 };
153 };
155
154
156 Completer.prototype.finish_completing = function (msg) {
155 Completer.prototype.finish_completing = function (msg) {
157 // let's build a function that wrap all that stuff into what is needed
156 // let's build a function that wrap all that stuff into what is needed
158 // for the new completer:
157 // for the new completer:
159 var content = msg.content;
158 var content = msg.content;
160 var matched_text = content.matched_text;
159 var matched_text = content.matched_text;
161 var matches = content.matches;
160 var matches = content.matches;
162
161
163 var cur = this.editor.getCursor();
162 var cur = this.editor.getCursor();
164 var results = CodeMirror.contextHint(this.editor);
163 var results = CodeMirror.contextHint(this.editor);
165 var filtered_results = [];
164 var filtered_results = [];
166 //remove results from context completion
165 //remove results from context completion
167 //that are already in kernel completion
166 //that are already in kernel completion
168 for (var elm in results) {
167 for (var elm in results) {
169 if (!_existing_completion(results[elm].str, matches)) {
168 if (!_existing_completion(results[elm].str, matches)) {
170 filtered_results.push(results[elm]);
169 filtered_results.push(results[elm]);
171 }
170 }
172 }
171 }
173
172
174 // append the introspection result, in order, at at the beginning of
173 // append the introspection result, in order, at at the beginning of
175 // the table and compute the replacement range from current cursor
174 // the table and compute the replacement range from current cursor
176 // positon and matched_text length.
175 // positon and matched_text length.
177 for (var i = matches.length - 1; i >= 0; --i) {
176 for (var i = matches.length - 1; i >= 0; --i) {
178 filtered_results.unshift({
177 filtered_results.unshift({
179 str: matches[i],
178 str: matches[i],
180 type: "introspection",
179 type: "introspection",
181 from: {
180 from: {
182 line: cur.line,
181 line: cur.line,
183 ch: cur.ch - matched_text.length
182 ch: cur.ch - matched_text.length
184 },
183 },
185 to: {
184 to: {
186 line: cur.line,
185 line: cur.line,
187 ch: cur.ch
186 ch: cur.ch
188 }
187 }
189 });
188 });
190 }
189 }
191
190
192 // one the 2 sources results have been merge, deal with it
191 // one the 2 sources results have been merge, deal with it
193 this.raw_result = filtered_results;
192 this.raw_result = filtered_results;
194
193
195 // if empty result return
194 // if empty result return
196 if (!this.raw_result || !this.raw_result.length) return;
195 if (!this.raw_result || !this.raw_result.length) return;
197
196
198 // When there is only one completion, use it directly.
197 // When there is only one completion, use it directly.
199 if (this.autopick && this.raw_result.length == 1) {
198 if (this.autopick && this.raw_result.length == 1) {
200 this.insert(this.raw_result[0]);
199 this.insert(this.raw_result[0]);
201 return;
200 return;
202 }
201 }
203
202
204 if (this.raw_result.length == 1) {
203 if (this.raw_result.length == 1) {
205 // test if first and only completion totally matches
204 // test if first and only completion totally matches
206 // what is typed, in this case dismiss
205 // what is typed, in this case dismiss
207 var str = this.raw_result[0].str;
206 var str = this.raw_result[0].str;
208 var pre_cursor = this.editor.getRange({
207 var pre_cursor = this.editor.getRange({
209 line: cur.line,
208 line: cur.line,
210 ch: cur.ch - str.length
209 ch: cur.ch - str.length
211 }, cur);
210 }, cur);
212 if (pre_cursor == str) {
211 if (pre_cursor == str) {
213 this.close();
212 this.close();
214 return;
213 return;
215 }
214 }
216 }
215 }
217
216
218 this.complete = $('<div/>').addClass('completions');
217 if (!this.visible) {
219 this.complete.attr('id', 'complete');
218 console.log('add div');
220
219 this.complete = $('<div/>').addClass('completions');
221 // Currently webkit doesn't use the size attr correctly. See:
220 this.complete.attr('id', 'complete');
222 // https://code.google.com/p/chromium/issues/detail?id=4579
221
223 this.sel = $('<select style="width: auto"/>')
222 // Currently webkit doesn't use the size attr correctly. See:
224 .attr('tabindex', -1)
223 // https://code.google.com/p/chromium/issues/detail?id=4579
225 .attr('multiple', 'true')
224 this.sel = $('<select style="width: auto"/>')
226 .attr('size', Math.min(10, this.raw_result.length));
225 .attr('tabindex', -1)
227 this.complete.append(this.sel);
226 .attr('multiple', 'true');
228 $('body').append(this.complete);
227 this.complete.append(this.sel);
228 this.visible = true;
229 $('body').append(this.complete);
230
231 //build the container
232 var that = this;
233 this.sel.dblclick(function () {
234 that.pick();
235 });
236 this.editor.on('keydown', function (event) {
237 that.keydown(event);
238 });
239 this.editor.on('keypress', function (event) {
240 that.keypress(event);
241 });
242 }
243 this.sel.attr('size', Math.min(10, this.raw_result.length));
229
244
230 // After everything is on the page, compute the postion.
245 // After everything is on the page, compute the postion.
231 // We put it above the code if it is too close to the bottom of the page.
246 // We put it above the code if it is too close to the bottom of the page.
232 cur.ch = cur.ch-matched_text.length;
247 cur.ch = cur.ch-matched_text.length;
233 var pos = this.editor.cursorCoords(cur);
248 var pos = this.editor.cursorCoords(cur);
234 var left = pos.left-3;
249 var left = pos.left-3;
235 var top;
250 var top;
236 var cheight = this.complete.height();
251 var cheight = this.complete.height();
237 var wheight = $(window).height();
252 var wheight = $(window).height();
238 if (pos.bottom+cheight+5 > wheight) {
253 if (pos.bottom+cheight+5 > wheight) {
239 top = pos.top-cheight-4;
254 top = pos.top-cheight-4;
240 } else {
255 } else {
241 top = pos.bottom+1;
256 top = pos.bottom+1;
242 }
257 }
243 this.complete.css('left', left + 'px');
258 this.complete.css('left', left + 'px');
244 this.complete.css('top', top + 'px');
259 this.complete.css('top', top + 'px');
245
260
246
261 // Clear and fill the list.
247 //build the container
262 this.sel.text('');
248 var that = this;
249 this.sel.dblclick(function () {
250 that.pick();
251 });
252 this.editor.on('keydown', function (event) {
253 that.keydown(event);
254 });
255 this.editor.on('keypress', function (event) {
256 that.keypress(event);
257 });
258
259 this.build_gui_list(this.raw_result);
263 this.build_gui_list(this.raw_result);
260
261 this.sel.focus();
262 // Opera sometimes ignores focusing a freshly created node
263 if (window.opera) setTimeout(function () {
264 if (!this.done) this.sel.focus();
265 }, 100);
266 return true;
264 return true;
267 };
265 };
268
266
269 Completer.prototype.insert = function (completion) {
267 Completer.prototype.insert = function (completion) {
270 this.editor.replaceRange(completion.str, completion.from, completion.to);
268 this.editor.replaceRange(completion.str, completion.from, completion.to);
271 };
269 };
272
270
273 Completer.prototype.build_gui_list = function (completions) {
271 Completer.prototype.build_gui_list = function (completions) {
274 for (var i = 0; i < completions.length; ++i) {
272 for (var i = 0; i < completions.length; ++i) {
275 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
273 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
276 this.sel.append(opt);
274 this.sel.append(opt);
277 }
275 }
278 this.sel.children().first().attr('selected', 'true');
276 this.sel.children().first().attr('selected', 'true');
279 this.sel.scrollTop(0);
277 this.sel.scrollTop(0);
280 };
278 };
281
279
282 Completer.prototype.close = function () {
280 Completer.prototype.close = function () {
283 this.done = true;
281 this.done = true;
284 $('#complete').remove();
282 $('#complete').remove();
283 this.visible = false;
285 };
284 };
286
285
287 Completer.prototype.pick = function () {
286 Completer.prototype.pick = function () {
288 this.insert(this.raw_result[this.sel[0].selectedIndex]);
287 this.insert(this.raw_result[this.sel[0].selectedIndex]);
289 this.close();
288 this.close();
290 var that = this;
289 var that = this;
291 setTimeout(function () {
292 that.editor.focus();
293 }, 50);
294 };
290 };
295
291
296 Completer.prototype.keydown = function (event) {
292 Completer.prototype.keydown = function (event) {
297 var code = event.keyCode;
293 var code = event.keyCode;
298 var that = this;
294 var that = this;
299
295
300 // Enter
296 // Enter
301 if (code == keycodes.enter) {
297 if (code == keycodes.enter) {
302 CodeMirror.e_stop(event);
298 CodeMirror.e_stop(event);
303 this.pick();
299 this.pick();
304 }
300 }
305 // Escape or backspace
301 // Escape or backspace
306 else if (code == keycodes.esc) {
302 else if (code == keycodes.esc) {
307 CodeMirror.e_stop(event);
303 CodeMirror.e_stop(event);
308 this.close();
304 this.close();
309 this.editor.focus();
310
305
311 } else if (code == keycodes.backspace) {
306 } else if (code == keycodes.backspace) {
312 this.close();
307 this.close();
313 this.editor.focus();
314 } else if (code == keycodes.tab) {
308 } else if (code == keycodes.tab) {
315 //all the fastforwarding operation,
309 //all the fastforwarding operation,
316 //Check that shared start is not null which can append with prefixed completion
310 //Check that shared start is not null which can append with prefixed completion
317 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
311 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
318 // to erase py
312 // to erase py
319 var sh = shared_start(this.raw_result, true);
313 var sh = shared_start(this.raw_result, true);
320 if (sh) {
314 if (sh) {
321 this.insert(sh);
315 this.insert(sh);
322 }
316 }
323 this.close();
317 this.close();
324 CodeMirror.e_stop(event);
318 CodeMirror.e_stop(event);
325 this.editor.focus();
326 //reinvoke self
319 //reinvoke self
327 setTimeout(function () {
320 setTimeout(function () {
328 that.carry_on_completion();
321 that.carry_on_completion();
329 }, 50);
322 }, 50);
330 } else if (code == keycodes.up || code == keycodes.down) {
323 } else if (code == keycodes.up || code == keycodes.down) {
331 // need to do that to be able to move the arrow
324 // need to do that to be able to move the arrow
332 // when on the first or last line ofo a code cell
325 // when on the first or last line ofo a code cell
333 event.stopPropagation();
326 event.stopPropagation();
334 }
327 }
335 };
328 };
336
329
337 Completer.prototype.keypress = function (event) {
330 Completer.prototype.keypress = function (event) {
338 // FIXME: This is a band-aid.
331 // FIXME: This is a band-aid.
339 // on keypress, trigger insertion of a single character.
332 // on keypress, trigger insertion of a single character.
340 // This simulates the old behavior of completion as you type,
333 // This simulates the old behavior of completion as you type,
341 // before events were disconnected and CodeMirror stopped
334 // before events were disconnected and CodeMirror stopped
342 // receiving events while the completer is focused.
335 // receiving events while the completer is focused.
343
336 if (!this.visible) return;
337
344 var that = this;
338 var that = this;
345 var code = event.keyCode;
339 var code = event.keyCode;
346
340
347 // don't handle keypress if it's not a character (arrows on FF)
341 // don't handle keypress if it's not a character (arrows on FF)
348 // or ENTER/TAB
342 // or ENTER/TAB
349 if (event.charCode === 0 ||
343 if (event.charCode === 0 ||
350 code == keycodes.enter ||
344 code == keycodes.enter ||
351 code == keycodes.tab
345 code == keycodes.tab
352 ) return;
346 ) return;
353
347
354 var cur = this.editor.getCursor();
355 var completion = {
356 str: String.fromCharCode(event.which),
357 type: "introspection",
358 from: cur,
359 to: cur,
360 };
361 this.insert(completion);
362
363 this.close();
348 this.close();
364 this.editor.focus();
365 setTimeout(function () {
349 setTimeout(function () {
366 that.carry_on_completion();
350 that.carry_on_completion();
367 }, 50);
351 }, 50);
368 };
352 };
369
353
370 IPython.Completer = Completer;
354 IPython.Completer = Completer;
371
355
372 return IPython;
356 return IPython;
373 }(IPython));
357 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now