##// END OF EJS Templates
tab pick if only one match left
Matthias BUSSONNIER -
Show More
@@ -1,771 +1,778
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 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var CodeCell = function (notebook) {
16 var CodeCell = function (notebook) {
17 this.code_mirror = null;
17 this.code_mirror = null;
18 this.input_prompt_number = ' ';
18 this.input_prompt_number = ' ';
19 this.is_completing = false;
19 this.is_completing = false;
20 this.completion_cursor = null;
20 this.completion_cursor = null;
21 this.outputs = [];
21 this.outputs = [];
22 this.collapsed = false;
22 this.collapsed = false;
23 IPython.Cell.apply(this, arguments);
23 IPython.Cell.apply(this, arguments);
24 };
24 };
25
25
26
26
27 CodeCell.prototype = new IPython.Cell();
27 CodeCell.prototype = new IPython.Cell();
28
28
29
29
30 CodeCell.prototype.create_element = function () {
30 CodeCell.prototype.create_element = function () {
31 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
31 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
32 cell.attr('tabindex','2');
32 cell.attr('tabindex','2');
33 var input = $('<div></div>').addClass('input hbox');
33 var input = $('<div></div>').addClass('input hbox');
34 input.append($('<div/>').addClass('prompt input_prompt'));
34 input.append($('<div/>').addClass('prompt input_prompt'));
35 var input_area = $('<div/>').addClass('input_area box-flex1');
35 var input_area = $('<div/>').addClass('input_area box-flex1');
36 this.code_mirror = CodeMirror(input_area.get(0), {
36 this.code_mirror = CodeMirror(input_area.get(0), {
37 indentUnit : 4,
37 indentUnit : 4,
38 mode: 'python',
38 mode: 'python',
39 theme: 'ipython',
39 theme: 'ipython',
40 readOnly: this.read_only,
40 readOnly: this.read_only,
41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
42 });
42 });
43 input.append(input_area);
43 input.append(input_area);
44 var output = $('<div></div>').addClass('output vbox');
44 var output = $('<div></div>').addClass('output vbox');
45 cell.append(input).append(output);
45 cell.append(input).append(output);
46 this.element = cell;
46 this.element = cell;
47 this.collapse();
47 this.collapse();
48 };
48 };
49
49
50 //TODO, try to diminish the number of parameters.
50 //TODO, try to diminish the number of parameters.
51 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time,that){
51 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time,that){
52 if (pre_cursor === "" || pre_cursor === "(" ) {
52 if (pre_cursor === "" || pre_cursor === "(" ) {
53 // don't do anything if line beggin with '(' or is empty
53 // don't do anything if line beggin with '(' or is empty
54 } else {
54 } else {
55 // Will set a timer to request tooltip in `time`
55 // Will set a timer to request tooltip in `time`
56 that.tooltip_timeout = setTimeout(function(){
56 that.tooltip_timeout = setTimeout(function(){
57 IPython.notebook.request_tool_tip(that, pre_cursor)
57 IPython.notebook.request_tool_tip(that, pre_cursor)
58 },time);
58 },time);
59 }
59 }
60 };
60 };
61
61
62 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
62 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
64 // handlers and is used to provide custom key handling. Its return
64 // handlers and is used to provide custom key handling. Its return
65 // value is used to determine if CodeMirror should ignore the event:
65 // value is used to determine if CodeMirror should ignore the event:
66 // true = ignore, false = don't ignore.
66 // true = ignore, false = don't ignore.
67
67
68 // note that we are comparing and setting the time to wait at each key press.
68 // note that we are comparing and setting the time to wait at each key press.
69 // a better wqy might be to generate a new function on each time change and
69 // a better wqy might be to generate a new function on each time change and
70 // assign it to CodeCell.prototype.request_tooltip_after_time
70 // assign it to CodeCell.prototype.request_tooltip_after_time
71 tooltip_wait_time = this.notebook.time_before_tooltip;
71 tooltip_wait_time = this.notebook.time_before_tooltip;
72 tooltip_on_tab = this.notebook.tooltip_on_tab;
72 tooltip_on_tab = this.notebook.tooltip_on_tab;
73 var that = this;
73 var that = this;
74 // whatever key is pressed, first, cancel the tooltip request before
74 // whatever key is pressed, first, cancel the tooltip request before
75 // they are sent, and remove tooltip if any
75 // they are sent, and remove tooltip if any
76 if(event.type === 'keydown' && this.tooltip_timeout != null){
76 if(event.type === 'keydown' && this.tooltip_timeout != null){
77 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
77 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
78 that.tooltip_timeout=null;
78 that.tooltip_timeout=null;
79 }
79 }
80
80
81 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
81 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
82 // Always ignore shift-enter in CodeMirror as we handle it.
82 // Always ignore shift-enter in CodeMirror as we handle it.
83 return true;
83 return true;
84 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
84 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
85 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
85 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
86 // browser and keyboard layout !
86 // browser and keyboard layout !
87 // Pressing '(' , request tooltip, don't forget to reappend it
87 // Pressing '(' , request tooltip, don't forget to reappend it
88 var cursor = editor.getCursor();
88 var cursor = editor.getCursor();
89 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
89 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
90 CodeCell.prototype.request_tooltip_after_time(pre_cursor,tooltip_wait_time,that);
90 CodeCell.prototype.request_tooltip_after_time(pre_cursor,tooltip_wait_time,that);
91 } else if (event.keyCode === 9 && event.type == 'keydown') {
91 } else if (event.keyCode === 9 && event.type == 'keydown') {
92 // Tab completion.
92 // Tab completion.
93 var cur = editor.getCursor();
93 var cur = editor.getCursor();
94 //Do not trim here because of tooltip
94 //Do not trim here because of tooltip
95 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
95 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
96 if (pre_cursor.trim() === "") {
96 if (pre_cursor.trim() === "") {
97 // Don't autocomplete if the part of the line before the cursor
97 // Don't autocomplete if the part of the line before the cursor
98 // is empty. In this case, let CodeMirror handle indentation.
98 // is empty. In this case, let CodeMirror handle indentation.
99 return false;
99 return false;
100 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
100 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
101 CodeCell.prototype.request_tooltip_after_time(pre_cursor,0,that);
101 CodeCell.prototype.request_tooltip_after_time(pre_cursor,0,that);
102 } else {
102 } else {
103 pre_cursor.trim();
103 pre_cursor.trim();
104 // Autocomplete the current line.
104 // Autocomplete the current line.
105 event.stop();
105 event.stop();
106 var line = editor.getLine(cur.line);
106 var line = editor.getLine(cur.line);
107 this.is_completing = true;
107 this.is_completing = true;
108 this.completion_cursor = cur;
108 this.completion_cursor = cur;
109 IPython.notebook.complete_cell(this, line, cur.ch);
109 IPython.notebook.complete_cell(this, line, cur.ch);
110 return true;
110 return true;
111 }
111 }
112 } else if (event.keyCode === 8 && event.type == 'keydown') {
112 } else if (event.keyCode === 8 && event.type == 'keydown') {
113 // If backspace and the line ends with 4 spaces, remove them.
113 // If backspace and the line ends with 4 spaces, remove them.
114 var cur = editor.getCursor();
114 var cur = editor.getCursor();
115 var line = editor.getLine(cur.line);
115 var line = editor.getLine(cur.line);
116 var ending = line.slice(-4);
116 var ending = line.slice(-4);
117 if (ending === ' ') {
117 if (ending === ' ') {
118 editor.replaceRange('',
118 editor.replaceRange('',
119 {line: cur.line, ch: cur.ch-4},
119 {line: cur.line, ch: cur.ch-4},
120 {line: cur.line, ch: cur.ch}
120 {line: cur.line, ch: cur.ch}
121 );
121 );
122 event.stop();
122 event.stop();
123 return true;
123 return true;
124 } else {
124 } else {
125 return false;
125 return false;
126 }
126 }
127 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
127 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
128 && event.type == 'keydown') {
128 && event.type == 'keydown') {
129 // toggle line numbers with Ctrl-Shift-L
129 // toggle line numbers with Ctrl-Shift-L
130 this.toggle_line_numbers();
130 this.toggle_line_numbers();
131 }
131 }
132 else {
132 else {
133 // keypress/keyup also trigger on TAB press, and we don't want to
133 // keypress/keyup also trigger on TAB press, and we don't want to
134 // use those to disable tab completion.
134 // use those to disable tab completion.
135 if (this.is_completing && event.keyCode !== 9) {
135 if (this.is_completing && event.keyCode !== 9) {
136 var ed_cur = editor.getCursor();
136 var ed_cur = editor.getCursor();
137 var cc_cur = this.completion_cursor;
137 var cc_cur = this.completion_cursor;
138 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
138 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
139 this.is_completing = false;
139 this.is_completing = false;
140 this.completion_cursor = null;
140 this.completion_cursor = null;
141 }
141 }
142 }
142 }
143 return false;
143 return false;
144 };
144 };
145 return false;
145 return false;
146 };
146 };
147
147
148 CodeCell.prototype.remove_and_cancell_tooltip = function(timeout)
148 CodeCell.prototype.remove_and_cancell_tooltip = function(timeout)
149 {
149 {
150 // note that we don't handle closing directly inside the calltip
150 // note that we don't handle closing directly inside the calltip
151 // as in the completer, because it is not focusable, so won't
151 // as in the completer, because it is not focusable, so won't
152 // get the event.
152 // get the event.
153 clearTimeout(timeout);
153 clearTimeout(timeout);
154 $('#tooltip').remove();
154 $('#tooltip').remove();
155 }
155 }
156
156
157 CodeCell.prototype.finish_tooltip = function (reply) {
157 CodeCell.prototype.finish_tooltip = function (reply) {
158 defstring=reply.definition;
158 defstring=reply.definition;
159 docstring=reply.docstring;
159 docstring=reply.docstring;
160 if(docstring == null){docstring="<empty docstring>"};
160 if(docstring == null){docstring="<empty docstring>"};
161 name=reply.name;
161 name=reply.name;
162
162
163 var that = this;
163 var that = this;
164 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
164 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
165 // remove to have the tooltip not Limited in X and Y
165 // remove to have the tooltip not Limited in X and Y
166 tooltip.addClass('smalltooltip');
166 tooltip.addClass('smalltooltip');
167 var pre=$('<pre/>').html(utils.fixConsole(docstring));
167 var pre=$('<pre/>').html(utils.fixConsole(docstring));
168 var expandlink=$('<a/>').attr('href',"#");
168 var expandlink=$('<a/>').attr('href',"#");
169 expandlink.addClass("ui-corner-all"); //rounded corner
169 expandlink.addClass("ui-corner-all"); //rounded corner
170 expandlink.attr('role',"button");
170 expandlink.attr('role',"button");
171 //expandlink.addClass('ui-button');
171 //expandlink.addClass('ui-button');
172 //expandlink.addClass('ui-state-default');
172 //expandlink.addClass('ui-state-default');
173 var expandspan=$('<span/>').text('Expand');
173 var expandspan=$('<span/>').text('Expand');
174 expandspan.addClass('ui-icon');
174 expandspan.addClass('ui-icon');
175 expandspan.addClass('ui-icon-plus');
175 expandspan.addClass('ui-icon-plus');
176 expandlink.append(expandspan);
176 expandlink.append(expandspan);
177 expandlink.attr('id','expanbutton');
177 expandlink.attr('id','expanbutton');
178 expandlink.click(function(){
178 expandlink.click(function(){
179 tooltip.removeClass('smalltooltip');
179 tooltip.removeClass('smalltooltip');
180 tooltip.addClass('bigtooltip');
180 tooltip.addClass('bigtooltip');
181 $('#expanbutton').remove();
181 $('#expanbutton').remove();
182 setTimeout(function(){that.code_mirror.focus();}, 50);
182 setTimeout(function(){that.code_mirror.focus();}, 50);
183 });
183 });
184 var morelink=$('<a/>').attr('href',"#");
184 var morelink=$('<a/>').attr('href',"#");
185 morelink.attr('role',"button");
185 morelink.attr('role',"button");
186 morelink.addClass('ui-button');
186 morelink.addClass('ui-button');
187 //morelink.addClass("ui-corner-all"); //rounded corner
187 //morelink.addClass("ui-corner-all"); //rounded corner
188 //morelink.addClass('ui-state-default');
188 //morelink.addClass('ui-state-default');
189 var morespan=$('<span/>').text('Open in Pager');
189 var morespan=$('<span/>').text('Open in Pager');
190 morespan.addClass('ui-icon');
190 morespan.addClass('ui-icon');
191 morespan.addClass('ui-icon-arrowstop-l-n');
191 morespan.addClass('ui-icon-arrowstop-l-n');
192 morelink.append(morespan);
192 morelink.append(morespan);
193 morelink.click(function(){
193 morelink.click(function(){
194 var msg_id = IPython.notebook.kernel.execute(name+"?");
194 var msg_id = IPython.notebook.kernel.execute(name+"?");
195 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
195 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
196 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
196 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
197 setTimeout(function(){that.code_mirror.focus();}, 50);
197 setTimeout(function(){that.code_mirror.focus();}, 50);
198 });
198 });
199
199
200 var closelink=$('<a/>').attr('href',"#");
200 var closelink=$('<a/>').attr('href',"#");
201 closelink.attr('role',"button");
201 closelink.attr('role',"button");
202 closelink.addClass('ui-button');
202 closelink.addClass('ui-button');
203 //closelink.addClass("ui-corner-all"); //rounded corner
203 //closelink.addClass("ui-corner-all"); //rounded corner
204 //closelink.adClass('ui-state-default'); // grey background and blue cross
204 //closelink.adClass('ui-state-default'); // grey background and blue cross
205 var closespan=$('<span/>').text('Close');
205 var closespan=$('<span/>').text('Close');
206 closespan.addClass('ui-icon');
206 closespan.addClass('ui-icon');
207 closespan.addClass('ui-icon-close');
207 closespan.addClass('ui-icon-close');
208 closelink.append(closespan);
208 closelink.append(closespan);
209 closelink.click(function(){
209 closelink.click(function(){
210 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
210 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
211 setTimeout(function(){that.code_mirror.focus();}, 50);
211 setTimeout(function(){that.code_mirror.focus();}, 50);
212 });
212 });
213 //construct the tooltip
213 //construct the tooltip
214 tooltip.append(closelink);
214 tooltip.append(closelink);
215 tooltip.append(expandlink);
215 tooltip.append(expandlink);
216 tooltip.append(morelink);
216 tooltip.append(morelink);
217 if(defstring){
217 if(defstring){
218 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
218 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
219 tooltip.append(defstring_html);
219 tooltip.append(defstring_html);
220 }
220 }
221 tooltip.append(pre);
221 tooltip.append(pre);
222 var pos = this.code_mirror.cursorCoords();
222 var pos = this.code_mirror.cursorCoords();
223 tooltip.css('left',pos.x+'px');
223 tooltip.css('left',pos.x+'px');
224 tooltip.css('top',pos.yBot+'px');
224 tooltip.css('top',pos.yBot+'px');
225 $('body').append(tooltip);
225 $('body').append(tooltip);
226
226
227 // issues with cross-closing if multiple tooltip in less than 5sec
227 // issues with cross-closing if multiple tooltip in less than 5sec
228 // keep it comented for now
228 // keep it comented for now
229 // setTimeout(CodeCell.prototype.remove_and_cancell_tooltip, 5000);
229 // setTimeout(CodeCell.prototype.remove_and_cancell_tooltip, 5000);
230 };
230 };
231
231
232 // As you type completer
232 // As you type completer
233 CodeCell.prototype.finish_completing = function (matched_text, matches) {
233 CodeCell.prototype.finish_completing = function (matched_text, matches) {
234
234
235 // smart completion, sort kwarg ending with '='
235 // smart completion, sort kwarg ending with '='
236 var key = { tab:9,
237 esc:8,
238 space:13,
239 shift:16,
240 enter:32,
241 // _ is 189
242 isCompSymbol : function (code) {return ((code>64 && code <=122)|| code == 189)}
243 }
236 var newm = new Array();
244 var newm = new Array();
237 if(this.notebook.smart_completer)
245 if(this.notebook.smart_completer)
238 {
246 {
239 kwargs = new Array();
247 kwargs = new Array();
240 other = new Array();
248 other = new Array();
241 for(var i=0;i<matches.length; ++i){
249 for(var i=0;i<matches.length; ++i){
242 if(matches[i].substr(-1) === '='){
250 if(matches[i].substr(-1) === '='){
243 kwargs.push(matches[i]);
251 kwargs.push(matches[i]);
244 }else{other.push(matches[i]);}
252 }else{other.push(matches[i]);}
245 }
253 }
246 newm = kwargs.concat(other);
254 newm = kwargs.concat(other);
247 matches=newm;
255 matches=newm;
248 }
256 }
249 // end sort kwargs
257 // end sort kwargs
250
258
251 function sharedStart(A){
259 function sharedStart(A){
252 if(A.length > 1 ){
260 if(A.length > 1 ){
253 var tem1, tem2, s, A= A.slice(0).sort();
261 var tem1, tem2, s, A= A.slice(0).sort();
254 tem1= A[0];
262 tem1= A[0];
255 s= tem1.length;
263 s= tem1.length;
256 tem2= A.pop();
264 tem2= A.pop();
257 while(s && tem2.indexOf(tem1)== -1){
265 while(s && tem2.indexOf(tem1)== -1){
258 tem1= tem1.substring(0, --s);
266 tem1= tem1.substring(0, --s);
259 }
267 }
260 return tem1;
268 return tem1;
261 }
269 }
262 return "";
270 return "";
263 }
271 }
264
272
265 if (!this.is_completing || matches.length === 0) {return;}
273 if (!this.is_completing || matches.length === 0) {return;}
266
274
267 //try to check if the user is typing tab at least twice after a word
275 //try to check if the user is typing tab at least twice after a word
268 // and completion is "done"
276 // and completion is "done"
269 fallback_on_tooltip_after=2
277 fallback_on_tooltip_after=2
270 if(matches.length==1 && matched_text === matches[0])
278 if(matches.length==1 && matched_text === matches[0])
271 {
279 {
272 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
280 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
273 {
281 {
274 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
282 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
275 console.log('You should understand that there is no (more) completion for that !');
283 console.log('You should understand that there is no (more) completion for that !');
276 console.log("I'll show you the tooltip, will you stop bothering me ?");
284 console.log("I'll show you the tooltip, will you stop bothering me ?");
277 this.request_tooltip_after_time(matched_text+'(',0,this);
285 this.request_tooltip_after_time(matched_text+'(',0,this);
278 return;
286 return;
279 }
287 }
280 this.prevmatch=matched_text
288 this.prevmatch=matched_text
281 this.npressed=this.npressed+1;
289 this.npressed=this.npressed+1;
282 }
290 }
283 else
291 else
284 {
292 {
285 this.prevmatch="";
293 this.prevmatch="";
286 this.npressed=0;
294 this.npressed=0;
287 }
295 }
288 // end fallback on tooltip
296 // end fallback on tooltip
289
297
290 // Real completion logic start here
298 // Real completion logic start here
291 var that = this;
299 var that = this;
292 var cur = this.completion_cursor;
300 var cur = this.completion_cursor;
293 var done = false;
301 var done = false;
294
302
295 // call to dismmiss the completer
303 // call to dismmiss the completer
296 var close = function () {
304 var close = function () {
297 if (done) return;
305 if (done) return;
298 done = true;
306 done = true;
299 if (complete!=undefined)
307 if (complete!=undefined)
300 {complete.remove();}
308 {complete.remove();}
301 that.is_completing = false;
309 that.is_completing = false;
302 that.completion_cursor = null;
310 that.completion_cursor = null;
303 };
311 };
304
312
305 // insert the given text and exit the completer
313 // insert the given text and exit the completer
306 var insert = function (selected_text) {
314 var insert = function (selected_text) {
307 that.code_mirror.replaceRange(
315 that.code_mirror.replaceRange(
308 selected_text,
316 selected_text,
309 {line: cur.line, ch: (cur.ch-matched_text.length)},
317 {line: cur.line, ch: (cur.ch-matched_text.length)},
310 {line: cur.line, ch: cur.ch}
318 {line: cur.line, ch: cur.ch}
311 );
319 );
312 event.stopPropagation();
320 event.stopPropagation();
313 event.preventDefault();
321 event.preventDefault();
314 close();
322 close();
315 setTimeout(function(){that.code_mirror.focus();}, 50);
323 setTimeout(function(){that.code_mirror.focus();}, 50);
316 };
324 };
317
325
318 // insert the curent highlited selection and exit
326 // insert the curent highlited selection and exit
319 var pick = function () {
327 var pick = function () {
320 insert(select.val()[0]);
328 insert(select.val()[0]);
321 };
329 };
322
330
323 // if only one match, complete to it, don't ask user
324 if (matches.length === 1) {
325 insert(matches[0]);
326 return;
327 };
328
329
331
330 // Define function to clear the completer, refill it with the new
332 // Define function to clear the completer, refill it with the new
331 // matches, update the pseuso typing field. Note that this is case
333 // matches, update the pseuso typing field. Note that this is case
332 // insensitive for now
334 // insensitive for now
333 var complete_with = function(matches,typed_text)
335 var complete_with = function(matches,typed_text,autopick)
334 {
336 {
335 //clear the previous completion if any
337 // If autopick an only one match, past.
338 // Used to 'pick' when pressing tab
336 if (matches.length < 1) {
339 if (matches.length < 1) {
337 insert(typed_text);
340 insert(typed_text);
341 } else if (autopick && matches.length==1) {
342 insert(matches[0]);
338 }
343 }
344 //clear the previous completion if any
339 complete.children().children().remove();
345 complete.children().children().remove();
340 $('#asyoutype').text(typed_text);
346 $('#asyoutype').text(typed_text);
341 select=$('#asyoutypeselect');
347 select=$('#asyoutypeselect');
342 for (var i=0; i<matches.length; ++i) {
348 for (var i=0; i<matches.length; ++i) {
343 select.append($('<option/>').html(matches[i]));
349 select.append($('<option/>').html(matches[i]));
344 }
350 }
345 select.children().first().attr('selected','true');
351 select.children().first().attr('selected','true');
346 }
352 }
347
353
348 // create html for completer
354 // create html for completer
349 var complete = $('<div/>').addClass('completions');
355 var complete = $('<div/>').addClass('completions');
350 complete.attr('id','complete');
356 complete.attr('id','complete');
351 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
357 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
352
358
353 var select = $('<select/>').attr('multiple','true');
359 var select = $('<select/>').attr('multiple','true');
354 select.attr('id', 'asyoutypeselect')
360 select.attr('id', 'asyoutypeselect')
355 select.attr('size',Math.min(10,matches.length));
361 select.attr('size',Math.min(10,matches.length));
356 var pos = this.code_mirror.cursorCoords();
362 var pos = this.code_mirror.cursorCoords();
357
363
358 // TODO: I propose to remove enough horizontal pixel
364 // TODO: I propose to remove enough horizontal pixel
359 // to align the text later
365 // to align the text later
360 complete.css('left',pos.x+'px');
366 complete.css('left',pos.x+'px');
361 complete.css('top',pos.yBot+'px');
367 complete.css('top',pos.yBot+'px');
362 complete.append(select);
368 complete.append(select);
363
369
364 $('body').append(complete);
370 $('body').append(complete);
365
371
366 //do a first actual completion
372 //do a first actual completion
367 complete_with(matches,matched_text);
373 fastForward = sharedStart(matches)
368
374 typed_characters= fastForward.substr(matched_text.length);
375 complete_with(matches,matched_text+typed_characters,true);
376 filterd=matches;
369 // Give focus to select, and make it filter the match as the user type
377 // Give focus to select, and make it filter the match as the user type
370 // by filtering the previous matches
378 // by filtering the previous matches
371 typed_characters = "";
372 var downandpress = function (event,press_or_down) {
379 var downandpress = function (event,press_or_down) {
380 var code = event.which;
381 var autopick = false; // auto 'pick' if only one match
373 if (press_or_down === 0){
382 if (press_or_down === 0){
374 press=true;
383 press=true; down=false; //Are we called from keypress or keydown
375 down=false;
376 } else if (press_or_down == 1){
384 } else if (press_or_down == 1){
377 press=false;
385 press=false; down=true;
378 down=true;
379 }
386 }
380 var code = event.which;
387 if (code === key.shift) {
381 if (code === 16) {
382 // nothing on Shift
388 // nothing on Shift
383 return;
389 return;
384 }
390 }
385 if (code === 13 || code === 32) {
391 if (code === key.space || code === key.enter) {
386 // Pressing SPACE or ENTER will cause a pick
392 // Pressing SPACE or ENTER will cause a pick
387 event.stopPropagation();
393 event.stopPropagation();
388 event.preventDefault();
394 event.preventDefault();
389 pick();
395 pick();
390 } else if (code === 38 || code === 40) {
396 } else if (code === 38 || code === 40) {
391 // We don't want the document keydown handler to handle UP/DOWN,
397 // We don't want the document keydown handler to handle UP/DOWN,
392 // but we want the default action.
398 // but we want the default action.
393 event.stopPropagation();
399 event.stopPropagation();
394 } else if ((code>64 && code <=122)|| (code==8 && down)||(code==9 && down)){
400 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
401 } else if ( (code==key.backspace)||(code==key.tab) || press || key.isCompSymbol(code)){
395 // issues with _-.. on chrome at least
402 // issues with _-.. on chrome at least
396 if(code != 8 && press)
403 if((code != key.backspace) && (code != key.tab) && press)
397 {
404 {
398 var newchar = String.fromCharCode(code);
405 var newchar = String.fromCharCode(code);
399 typed_characters=typed_characters+newchar;
406 typed_characters=typed_characters+newchar;
400 } else if (code == 9) {
407 } else if (code == key.tab) {
401 fastForward = sharedStart(filterd)
408 fastForward = sharedStart(filterd)
402 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
409 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
403 typed_characters=typed_characters+ffsub;
410 typed_characters=typed_characters+ffsub;
404 console.log("Fast forded by :"+ffsub);
411 autopick=true;
405 event.stopPropagation();
412 event.stopPropagation();
406 event.preventDefault();
413 event.preventDefault();
407 } else if (code == 8) {
414 } else if (code == key.backspace) {
408 // 8 is backspace remove 1 char cancel if
415 // 8 is backspace remove 1 char cancel if
409 // user have erase everything, otherwise
416 // user have erase everything, otherwise
410 // decrease what we filter with
417 // decrease what we filter with
411 if (typed_characters.length <= 0)
418 if (typed_characters.length <= 0)
412 {
419 {
413 insert(matched_text)
420 insert(matched_text)
414 }
421 }
415 typed_characters=typed_characters.substr(0,typed_characters.length-1);
422 typed_characters=typed_characters.substr(0,typed_characters.length-1);
416 }
423 }
417 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
424 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
418 filterd= matches.filter(function(x){return re.test(x)});
425 filterd= matches.filter(function(x){return re.test(x)});
419 complete_with(filterd,matched_text+typed_characters);
426 complete_with(filterd,matched_text+typed_characters,autopick);
420 } else if(down){ // abort only on press
427 } else if(down){ // abort only on press
421 // abort with what the user have pressed until now
428 // abort with what the user have pressed until now
422 console.log('aborting with keycode : '+code);
429 console.log('aborting with keycode : '+code+press);
423 insert(matched_text+typed_characters);
430 insert(matched_text+typed_characters);
424 }
431 }
425 }
432 }
426 select.keydown(function (event) {
433 select.keydown(function (event) {
427 downandpress(event,1)
434 downandpress(event,1)
428 });
435 });
429 select.keypress(function (event) {
436 select.keypress(function (event) {
430 downandpress(event,0)
437 downandpress(event,0)
431 });
438 });
432 // Double click also causes a pick.
439 // Double click also causes a pick.
433 // and bind the last actions.
440 // and bind the last actions.
434 select.dblclick(pick);
441 select.dblclick(pick);
435 select.blur(close);
442 select.blur(close);
436 select.focus();
443 select.focus();
437 };
444 };
438
445
439 CodeCell.prototype.toggle_line_numbers = function () {
446 CodeCell.prototype.toggle_line_numbers = function () {
440 if (this.code_mirror.getOption('lineNumbers') == false) {
447 if (this.code_mirror.getOption('lineNumbers') == false) {
441 this.code_mirror.setOption('lineNumbers', true);
448 this.code_mirror.setOption('lineNumbers', true);
442 } else {
449 } else {
443 this.code_mirror.setOption('lineNumbers', false);
450 this.code_mirror.setOption('lineNumbers', false);
444 }
451 }
445 this.code_mirror.refresh();
452 this.code_mirror.refresh();
446 };
453 };
447
454
448 CodeCell.prototype.select = function () {
455 CodeCell.prototype.select = function () {
449 IPython.Cell.prototype.select.apply(this);
456 IPython.Cell.prototype.select.apply(this);
450 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
457 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
451 // not causing the cursor to blink if the editor is empty initially.
458 // not causing the cursor to blink if the editor is empty initially.
452 // While this seems to fix the issue, this should be fixed
459 // While this seems to fix the issue, this should be fixed
453 // in CodeMirror proper.
460 // in CodeMirror proper.
454 var s = this.code_mirror.getValue();
461 var s = this.code_mirror.getValue();
455 this.code_mirror.focus();
462 this.code_mirror.focus();
456 if (s === '') this.code_mirror.setValue('');
463 if (s === '') this.code_mirror.setValue('');
457 };
464 };
458
465
459
466
460 CodeCell.prototype.select_all = function () {
467 CodeCell.prototype.select_all = function () {
461 var start = {line: 0, ch: 0};
468 var start = {line: 0, ch: 0};
462 var nlines = this.code_mirror.lineCount();
469 var nlines = this.code_mirror.lineCount();
463 var last_line = this.code_mirror.getLine(nlines-1);
470 var last_line = this.code_mirror.getLine(nlines-1);
464 var end = {line: nlines-1, ch: last_line.length};
471 var end = {line: nlines-1, ch: last_line.length};
465 this.code_mirror.setSelection(start, end);
472 this.code_mirror.setSelection(start, end);
466 };
473 };
467
474
468
475
469 CodeCell.prototype.append_output = function (json) {
476 CodeCell.prototype.append_output = function (json) {
470 this.expand();
477 this.expand();
471 if (json.output_type === 'pyout') {
478 if (json.output_type === 'pyout') {
472 this.append_pyout(json);
479 this.append_pyout(json);
473 } else if (json.output_type === 'pyerr') {
480 } else if (json.output_type === 'pyerr') {
474 this.append_pyerr(json);
481 this.append_pyerr(json);
475 } else if (json.output_type === 'display_data') {
482 } else if (json.output_type === 'display_data') {
476 this.append_display_data(json);
483 this.append_display_data(json);
477 } else if (json.output_type === 'stream') {
484 } else if (json.output_type === 'stream') {
478 this.append_stream(json);
485 this.append_stream(json);
479 };
486 };
480 this.outputs.push(json);
487 this.outputs.push(json);
481 };
488 };
482
489
483
490
484 CodeCell.prototype.create_output_area = function () {
491 CodeCell.prototype.create_output_area = function () {
485 var oa = $("<div/>").addClass("hbox output_area");
492 var oa = $("<div/>").addClass("hbox output_area");
486 oa.append($('<div/>').addClass('prompt'));
493 oa.append($('<div/>').addClass('prompt'));
487 return oa;
494 return oa;
488 };
495 };
489
496
490
497
491 CodeCell.prototype.append_pyout = function (json) {
498 CodeCell.prototype.append_pyout = function (json) {
492 n = json.prompt_number || ' ';
499 n = json.prompt_number || ' ';
493 var toinsert = this.create_output_area();
500 var toinsert = this.create_output_area();
494 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
501 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
495 this.append_mime_type(json, toinsert);
502 this.append_mime_type(json, toinsert);
496 this.element.find('div.output').append(toinsert);
503 this.element.find('div.output').append(toinsert);
497 // If we just output latex, typeset it.
504 // If we just output latex, typeset it.
498 if ((json.latex !== undefined) || (json.html !== undefined)) {
505 if ((json.latex !== undefined) || (json.html !== undefined)) {
499 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
506 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
500 };
507 };
501 };
508 };
502
509
503
510
504 CodeCell.prototype.append_pyerr = function (json) {
511 CodeCell.prototype.append_pyerr = function (json) {
505 var tb = json.traceback;
512 var tb = json.traceback;
506 if (tb !== undefined && tb.length > 0) {
513 if (tb !== undefined && tb.length > 0) {
507 var s = '';
514 var s = '';
508 var len = tb.length;
515 var len = tb.length;
509 for (var i=0; i<len; i++) {
516 for (var i=0; i<len; i++) {
510 s = s + tb[i] + '\n';
517 s = s + tb[i] + '\n';
511 }
518 }
512 s = s + '\n';
519 s = s + '\n';
513 var toinsert = this.create_output_area();
520 var toinsert = this.create_output_area();
514 this.append_text(s, toinsert);
521 this.append_text(s, toinsert);
515 this.element.find('div.output').append(toinsert);
522 this.element.find('div.output').append(toinsert);
516 };
523 };
517 };
524 };
518
525
519
526
520 CodeCell.prototype.append_stream = function (json) {
527 CodeCell.prototype.append_stream = function (json) {
521 // temporary fix: if stream undefined (json file written prior to this patch),
528 // temporary fix: if stream undefined (json file written prior to this patch),
522 // default to most likely stdout:
529 // default to most likely stdout:
523 if (json.stream == undefined){
530 if (json.stream == undefined){
524 json.stream = 'stdout';
531 json.stream = 'stdout';
525 }
532 }
526 var subclass = "output_"+json.stream;
533 var subclass = "output_"+json.stream;
527 if (this.outputs.length > 0){
534 if (this.outputs.length > 0){
528 // have at least one output to consider
535 // have at least one output to consider
529 var last = this.outputs[this.outputs.length-1];
536 var last = this.outputs[this.outputs.length-1];
530 if (last.output_type == 'stream' && json.stream == last.stream){
537 if (last.output_type == 'stream' && json.stream == last.stream){
531 // latest output was in the same stream,
538 // latest output was in the same stream,
532 // so append directly into its pre tag
539 // so append directly into its pre tag
533 this.element.find('div.'+subclass).last().find('pre').append(json.text);
540 this.element.find('div.'+subclass).last().find('pre').append(json.text);
534 return;
541 return;
535 }
542 }
536 }
543 }
537
544
538 // If we got here, attach a new div
545 // If we got here, attach a new div
539 var toinsert = this.create_output_area();
546 var toinsert = this.create_output_area();
540 this.append_text(json.text, toinsert, "output_stream "+subclass);
547 this.append_text(json.text, toinsert, "output_stream "+subclass);
541 this.element.find('div.output').append(toinsert);
548 this.element.find('div.output').append(toinsert);
542 };
549 };
543
550
544
551
545 CodeCell.prototype.append_display_data = function (json) {
552 CodeCell.prototype.append_display_data = function (json) {
546 var toinsert = this.create_output_area();
553 var toinsert = this.create_output_area();
547 this.append_mime_type(json, toinsert);
554 this.append_mime_type(json, toinsert);
548 this.element.find('div.output').append(toinsert);
555 this.element.find('div.output').append(toinsert);
549 // If we just output latex, typeset it.
556 // If we just output latex, typeset it.
550 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
557 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
551 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
558 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
552 };
559 };
553 };
560 };
554
561
555
562
556 CodeCell.prototype.append_mime_type = function (json, element) {
563 CodeCell.prototype.append_mime_type = function (json, element) {
557 if (json.html !== undefined) {
564 if (json.html !== undefined) {
558 this.append_html(json.html, element);
565 this.append_html(json.html, element);
559 } else if (json.latex !== undefined) {
566 } else if (json.latex !== undefined) {
560 this.append_latex(json.latex, element);
567 this.append_latex(json.latex, element);
561 } else if (json.svg !== undefined) {
568 } else if (json.svg !== undefined) {
562 this.append_svg(json.svg, element);
569 this.append_svg(json.svg, element);
563 } else if (json.png !== undefined) {
570 } else if (json.png !== undefined) {
564 this.append_png(json.png, element);
571 this.append_png(json.png, element);
565 } else if (json.jpeg !== undefined) {
572 } else if (json.jpeg !== undefined) {
566 this.append_jpeg(json.jpeg, element);
573 this.append_jpeg(json.jpeg, element);
567 } else if (json.text !== undefined) {
574 } else if (json.text !== undefined) {
568 this.append_text(json.text, element);
575 this.append_text(json.text, element);
569 };
576 };
570 };
577 };
571
578
572
579
573 CodeCell.prototype.append_html = function (html, element) {
580 CodeCell.prototype.append_html = function (html, element) {
574 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
581 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
575 toinsert.append(html);
582 toinsert.append(html);
576 element.append(toinsert);
583 element.append(toinsert);
577 };
584 };
578
585
579
586
580 CodeCell.prototype.append_text = function (data, element, extra_class) {
587 CodeCell.prototype.append_text = function (data, element, extra_class) {
581 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
588 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
582 if (extra_class){
589 if (extra_class){
583 toinsert.addClass(extra_class);
590 toinsert.addClass(extra_class);
584 }
591 }
585 toinsert.append($("<pre/>").html(data));
592 toinsert.append($("<pre/>").html(data));
586 element.append(toinsert);
593 element.append(toinsert);
587 };
594 };
588
595
589
596
590 CodeCell.prototype.append_svg = function (svg, element) {
597 CodeCell.prototype.append_svg = function (svg, element) {
591 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
598 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
592 toinsert.append(svg);
599 toinsert.append(svg);
593 element.append(toinsert);
600 element.append(toinsert);
594 };
601 };
595
602
596
603
597 CodeCell.prototype.append_png = function (png, element) {
604 CodeCell.prototype.append_png = function (png, element) {
598 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
605 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
599 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
606 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
600 element.append(toinsert);
607 element.append(toinsert);
601 };
608 };
602
609
603
610
604 CodeCell.prototype.append_jpeg = function (jpeg, element) {
611 CodeCell.prototype.append_jpeg = function (jpeg, element) {
605 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
612 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
606 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
613 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
607 element.append(toinsert);
614 element.append(toinsert);
608 };
615 };
609
616
610
617
611 CodeCell.prototype.append_latex = function (latex, element) {
618 CodeCell.prototype.append_latex = function (latex, element) {
612 // This method cannot do the typesetting because the latex first has to
619 // This method cannot do the typesetting because the latex first has to
613 // be on the page.
620 // be on the page.
614 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
621 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
615 toinsert.append(latex);
622 toinsert.append(latex);
616 element.append(toinsert);
623 element.append(toinsert);
617 };
624 };
618
625
619
626
620 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
627 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
621 var output_div = this.element.find("div.output");
628 var output_div = this.element.find("div.output");
622 if (stdout && stderr && other){
629 if (stdout && stderr && other){
623 // clear all, no need for logic
630 // clear all, no need for logic
624 output_div.html("");
631 output_div.html("");
625 this.outputs = [];
632 this.outputs = [];
626 return;
633 return;
627 }
634 }
628 // remove html output
635 // remove html output
629 // each output_subarea that has an identifying class is in an output_area
636 // each output_subarea that has an identifying class is in an output_area
630 // which is the element to be removed.
637 // which is the element to be removed.
631 if (stdout){
638 if (stdout){
632 output_div.find("div.output_stdout").parent().remove();
639 output_div.find("div.output_stdout").parent().remove();
633 }
640 }
634 if (stderr){
641 if (stderr){
635 output_div.find("div.output_stderr").parent().remove();
642 output_div.find("div.output_stderr").parent().remove();
636 }
643 }
637 if (other){
644 if (other){
638 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
645 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
639 }
646 }
640
647
641 // remove cleared outputs from JSON list:
648 // remove cleared outputs from JSON list:
642 for (var i = this.outputs.length - 1; i >= 0; i--){
649 for (var i = this.outputs.length - 1; i >= 0; i--){
643 var out = this.outputs[i];
650 var out = this.outputs[i];
644 var output_type = out.output_type;
651 var output_type = out.output_type;
645 if (output_type == "display_data" && other){
652 if (output_type == "display_data" && other){
646 this.outputs.splice(i,1);
653 this.outputs.splice(i,1);
647 }else if (output_type == "stream"){
654 }else if (output_type == "stream"){
648 if (stdout && out.stream == "stdout"){
655 if (stdout && out.stream == "stdout"){
649 this.outputs.splice(i,1);
656 this.outputs.splice(i,1);
650 }else if (stderr && out.stream == "stderr"){
657 }else if (stderr && out.stream == "stderr"){
651 this.outputs.splice(i,1);
658 this.outputs.splice(i,1);
652 }
659 }
653 }
660 }
654 }
661 }
655 };
662 };
656
663
657
664
658 CodeCell.prototype.clear_input = function () {
665 CodeCell.prototype.clear_input = function () {
659 this.code_mirror.setValue('');
666 this.code_mirror.setValue('');
660 };
667 };
661
668
662
669
663 CodeCell.prototype.collapse = function () {
670 CodeCell.prototype.collapse = function () {
664 if (!this.collapsed) {
671 if (!this.collapsed) {
665 this.element.find('div.output').hide();
672 this.element.find('div.output').hide();
666 this.collapsed = true;
673 this.collapsed = true;
667 };
674 };
668 };
675 };
669
676
670
677
671 CodeCell.prototype.expand = function () {
678 CodeCell.prototype.expand = function () {
672 if (this.collapsed) {
679 if (this.collapsed) {
673 this.element.find('div.output').show();
680 this.element.find('div.output').show();
674 this.collapsed = false;
681 this.collapsed = false;
675 };
682 };
676 };
683 };
677
684
678
685
679 CodeCell.prototype.toggle_output = function () {
686 CodeCell.prototype.toggle_output = function () {
680 if (this.collapsed) {
687 if (this.collapsed) {
681 this.expand();
688 this.expand();
682 } else {
689 } else {
683 this.collapse();
690 this.collapse();
684 };
691 };
685 };
692 };
686
693
687 CodeCell.prototype.set_input_prompt = function (number) {
694 CodeCell.prototype.set_input_prompt = function (number) {
688 var n = number || '&nbsp;';
695 var n = number || '&nbsp;';
689 this.input_prompt_number = n;
696 this.input_prompt_number = n;
690 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
697 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
691 };
698 };
692
699
693
700
694 CodeCell.prototype.get_code = function () {
701 CodeCell.prototype.get_code = function () {
695 return this.code_mirror.getValue();
702 return this.code_mirror.getValue();
696 };
703 };
697
704
698
705
699 CodeCell.prototype.set_code = function (code) {
706 CodeCell.prototype.set_code = function (code) {
700 return this.code_mirror.setValue(code);
707 return this.code_mirror.setValue(code);
701 };
708 };
702
709
703
710
704 CodeCell.prototype.at_top = function () {
711 CodeCell.prototype.at_top = function () {
705 var cursor = this.code_mirror.getCursor();
712 var cursor = this.code_mirror.getCursor();
706 if (cursor.line === 0) {
713 if (cursor.line === 0) {
707 return true;
714 return true;
708 } else {
715 } else {
709 return false;
716 return false;
710 }
717 }
711 };
718 };
712
719
713
720
714 CodeCell.prototype.at_bottom = function () {
721 CodeCell.prototype.at_bottom = function () {
715 var cursor = this.code_mirror.getCursor();
722 var cursor = this.code_mirror.getCursor();
716 if (cursor.line === (this.code_mirror.lineCount()-1)) {
723 if (cursor.line === (this.code_mirror.lineCount()-1)) {
717 return true;
724 return true;
718 } else {
725 } else {
719 return false;
726 return false;
720 }
727 }
721 };
728 };
722
729
723
730
724 CodeCell.prototype.fromJSON = function (data) {
731 CodeCell.prototype.fromJSON = function (data) {
725 console.log('Import from JSON:', data);
732 console.log('Import from JSON:', data);
726 if (data.cell_type === 'code') {
733 if (data.cell_type === 'code') {
727 if (data.input !== undefined) {
734 if (data.input !== undefined) {
728 this.set_code(data.input);
735 this.set_code(data.input);
729 }
736 }
730 if (data.prompt_number !== undefined) {
737 if (data.prompt_number !== undefined) {
731 this.set_input_prompt(data.prompt_number);
738 this.set_input_prompt(data.prompt_number);
732 } else {
739 } else {
733 this.set_input_prompt();
740 this.set_input_prompt();
734 };
741 };
735 var len = data.outputs.length;
742 var len = data.outputs.length;
736 for (var i=0; i<len; i++) {
743 for (var i=0; i<len; i++) {
737 this.append_output(data.outputs[i]);
744 this.append_output(data.outputs[i]);
738 };
745 };
739 if (data.collapsed !== undefined) {
746 if (data.collapsed !== undefined) {
740 if (data.collapsed) {
747 if (data.collapsed) {
741 this.collapse();
748 this.collapse();
742 };
749 };
743 };
750 };
744 };
751 };
745 };
752 };
746
753
747
754
748 CodeCell.prototype.toJSON = function () {
755 CodeCell.prototype.toJSON = function () {
749 var data = {};
756 var data = {};
750 data.input = this.get_code();
757 data.input = this.get_code();
751 data.cell_type = 'code';
758 data.cell_type = 'code';
752 if (this.input_prompt_number !== ' ') {
759 if (this.input_prompt_number !== ' ') {
753 data.prompt_number = this.input_prompt_number;
760 data.prompt_number = this.input_prompt_number;
754 };
761 };
755 var outputs = [];
762 var outputs = [];
756 var len = this.outputs.length;
763 var len = this.outputs.length;
757 for (var i=0; i<len; i++) {
764 for (var i=0; i<len; i++) {
758 outputs[i] = this.outputs[i];
765 outputs[i] = this.outputs[i];
759 };
766 };
760 data.outputs = outputs;
767 data.outputs = outputs;
761 data.language = 'python';
768 data.language = 'python';
762 data.collapsed = this.collapsed;
769 data.collapsed = this.collapsed;
763 // console.log('Export to JSON:',data);
770 // console.log('Export to JSON:',data);
764 return data;
771 return data;
765 };
772 };
766
773
767
774
768 IPython.CodeCell = CodeCell;
775 IPython.CodeCell = CodeCell;
769
776
770 return IPython;
777 return IPython;
771 }(IPython));
778 }(IPython));
@@ -1,235 +1,235
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 // Kernel
9 // Kernel
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var Kernel = function () {
16 var Kernel = function () {
17 this.kernel_id = null;
17 this.kernel_id = null;
18 this.shell_channel = null;
18 this.shell_channel = null;
19 this.iopub_channel = null;
19 this.iopub_channel = null;
20 this.base_url = $('body').data('baseKernelUrl') + "kernels";
20 this.base_url = $('body').data('baseKernelUrl') + "kernels";
21 this.running = false;
21 this.running = false;
22 this.username = "username";
22 this.username = "username";
23 this.session_id = utils.uuid();
23 this.session_id = utils.uuid();
24
24
25 if (typeof(WebSocket) !== 'undefined') {
25 if (typeof(WebSocket) !== 'undefined') {
26 this.WebSocket = WebSocket;
26 this.WebSocket = WebSocket;
27 } else if (typeof(MozWebSocket) !== 'undefined') {
27 } else if (typeof(MozWebSocket) !== 'undefined') {
28 this.WebSocket = MozWebSocket;
28 this.WebSocket = MozWebSocket;
29 } else {
29 } else {
30 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
30 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
31 };
31 };
32 };
32 };
33
33
34
34
35 Kernel.prototype.get_msg = function (msg_type, content) {
35 Kernel.prototype.get_msg = function (msg_type, content) {
36 var msg = {
36 var msg = {
37 header : {
37 header : {
38 msg_id : utils.uuid(),
38 msg_id : utils.uuid(),
39 username : this.username,
39 username : this.username,
40 session : this.session_id,
40 session : this.session_id,
41 msg_type : msg_type
41 msg_type : msg_type
42 },
42 },
43 content : content,
43 content : content,
44 parent_header : {}
44 parent_header : {}
45 };
45 };
46 return msg;
46 return msg;
47 };
47 };
48
48
49 Kernel.prototype.start = function (notebook_id, callback) {
49 Kernel.prototype.start = function (notebook_id, callback) {
50 var that = this;
50 var that = this;
51 if (!this.running) {
51 if (!this.running) {
52 var qs = $.param({notebook:notebook_id});
52 var qs = $.param({notebook:notebook_id});
53 var url = this.base_url + '?' + qs;
53 var url = this.base_url + '?' + qs;
54 $.post(url,
54 $.post(url,
55 function (kernel_id) {
55 function (kernel_id) {
56 that._handle_start_kernel(kernel_id, callback);
56 that._handle_start_kernel(kernel_id, callback);
57 },
57 },
58 'json'
58 'json'
59 );
59 );
60 };
60 };
61 };
61 };
62
62
63
63
64 Kernel.prototype.restart = function (callback) {
64 Kernel.prototype.restart = function (callback) {
65 IPython.kernel_status_widget.status_restarting();
65 IPython.kernel_status_widget.status_restarting();
66 var url = this.kernel_url + "/restart";
66 var url = this.kernel_url + "/restart";
67 var that = this;
67 var that = this;
68 if (this.running) {
68 if (this.running) {
69 this.stop_channels();
69 this.stop_channels();
70 $.post(url,
70 $.post(url,
71 function (kernel_id) {
71 function (kernel_id) {
72 that._handle_start_kernel(kernel_id, callback);
72 that._handle_start_kernel(kernel_id, callback);
73 },
73 },
74 'json'
74 'json'
75 );
75 );
76 };
76 };
77 };
77 };
78
78
79
79
80 Kernel.prototype._handle_start_kernel = function (json, callback) {
80 Kernel.prototype._handle_start_kernel = function (json, callback) {
81 this.running = true;
81 this.running = true;
82 this.kernel_id = json.kernel_id;
82 this.kernel_id = json.kernel_id;
83 this.ws_url = json.ws_url;
83 this.ws_url = json.ws_url;
84 this.kernel_url = this.base_url + "/" + this.kernel_id;
84 this.kernel_url = this.base_url + "/" + this.kernel_id;
85 this.start_channels();
85 this.start_channels();
86 callback();
86 callback();
87 IPython.kernel_status_widget.status_idle();
87 IPython.kernel_status_widget.status_idle();
88 };
88 };
89
89
90 Kernel.prototype._websocket_closed = function(ws_url, early){
90 Kernel.prototype._websocket_closed = function(ws_url, early){
91 var msg;
91 var msg;
92 var parent_item = $('body');
92 var parent_item = $('body');
93 if (early) {
93 if (early) {
94 msg = "Websocket connection to " + ws_url + " could not be established.<br/>" +
94 msg = "Websocket connection to " + ws_url + " could not be established.<br/>" +
95 " You will NOT be able to run code.<br/>" +
95 " You will NOT be able to run code.<br/>" +
96 " Your browser may not be compatible with the websocket version in the server," +
96 " Your browser may not be compatible with the websocket version in the server," +
97 " or if the url does not look right, there could be an error in the" +
97 " or if the url does not look right, there could be an error in the" +
98 " server's configuration.";
98 " server's configuration.";
99 } else {
99 } else {
100 msg = "Websocket connection closed unexpectedly.<br/>" +
100 msg = "Websocket connection closed unexpectedly.<br/>" +
101 " The kernel will no longer be responsive.";
101 " The kernel will no longer be responsive.";
102 }
102 }
103 var dialog = $('<div/>');
103 var dialog = $('<div/>');
104 dialog.html(msg);
104 dialog.html(msg);
105 parent_item.append(dialog);
105 parent_item.append(dialog);
106 dialog.dialog({
106 dialog.dialog({
107 resizable: false,
107 resizable: false,
108 modal: true,
108 modal: true,
109 title: "Websocket closed",
109 title: "Websocket closed",
110 buttons : {
110 buttons : {
111 "Okay": function () {
111 "Okay": function () {
112 $(this).dialog('close');
112 $(this).dialog('close');
113 }
113 }
114 }
114 }
115 });
115 });
116
116
117 };
117 };
118
118
119 Kernel.prototype.start_channels = function () {
119 Kernel.prototype.start_channels = function () {
120 var that = this;
120 var that = this;
121 this.stop_channels();
121 this.stop_channels();
122 var ws_url = this.ws_url + this.kernel_url;
122 var ws_url = this.ws_url + this.kernel_url;
123 console.log("Starting WS:", ws_url);
123 console.log("Starting WS:", ws_url);
124 this.shell_channel = new this.WebSocket(ws_url + "/shell");
124 this.shell_channel = new this.WebSocket(ws_url + "/shell");
125 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
125 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
126 send_cookie = function(){
126 send_cookie = function(){
127 this.send(document.cookie);
127 this.send(document.cookie);
128 };
128 };
129 var already_called_onclose = false; // only alert once
129 var already_called_onclose = false; // only alert once
130 ws_closed_early = function(evt){
130 ws_closed_early = function(evt){
131 if (already_called_onclose){
131 if (already_called_onclose){
132 return;
132 return;
133 }
133 }
134 already_called_onclose = true;
134 already_called_onclose = true;
135 if ( ! evt.wasClean ){
135 if ( ! evt.wasClean ){
136 that._websocket_closed(ws_url, true);
136 that._websocket_closed(ws_url, true);
137 }
137 }
138 };
138 };
139 ws_closed_late = function(evt){
139 ws_closed_late = function(evt){
140 if (already_called_onclose){
140 if (already_called_onclose){
141 return;
141 return;
142 }
142 }
143 already_called_onclose = true;
143 already_called_onclose = true;
144 if ( ! evt.wasClean ){
144 if ( ! evt.wasClean ){
145 that._websocket_closed(ws_url, false);
145 that._websocket_closed(ws_url, false);
146 }
146 }
147 };
147 };
148 this.shell_channel.onopen = send_cookie;
148 this.shell_channel.onopen = send_cookie;
149 this.shell_channel.onclose = ws_closed_early;
149 this.shell_channel.onclose = ws_closed_early;
150 this.iopub_channel.onopen = send_cookie;
150 this.iopub_channel.onopen = send_cookie;
151 this.iopub_channel.onclose = ws_closed_early;
151 this.iopub_channel.onclose = ws_closed_early;
152 // switch from early-close to late-close message after 1s
152 // switch from early-close to late-close message after 1s
153 setTimeout(function(){
153 setTimeout(function(){
154 that.shell_channel.onclose = ws_closed_late;
154 that.shell_channel.onclose = ws_closed_late;
155 that.iopub_channel.onclose = ws_closed_late;
155 that.iopub_channel.onclose = ws_closed_late;
156 }, 1000);
156 }, 1000);
157 };
157 };
158
158
159
159
160 Kernel.prototype.stop_channels = function () {
160 Kernel.prototype.stop_channels = function () {
161 if (this.shell_channel !== null) {
161 if (this.shell_channel !== null) {
162 this.shell_channel.onclose = function (evt) {};
162 this.shell_channel.onclose = function (evt) {};
163 this.shell_channel.close();
163 this.shell_channel.close();
164 this.shell_channel = null;
164 this.shell_channel = null;
165 };
165 };
166 if (this.iopub_channel !== null) {
166 if (this.iopub_channel !== null) {
167 this.iopub_channel.onclose = function (evt) {};
167 this.iopub_channel.onclose = function (evt) {};
168 this.iopub_channel.close();
168 this.iopub_channel.close();
169 this.iopub_channel = null;
169 this.iopub_channel = null;
170 };
170 };
171 };
171 };
172
172
173 Kernel.prototype.object_info_request = function (objname) {
173 Kernel.prototype.object_info_request = function (objname) {
174 if(typeof(objname)!=null)
174 if(typeof(objname)!=null && objname!=null)
175 {
175 {
176 var content = {
176 var content = {
177 oname : objname.toString(),
177 oname : objname.toString(),
178 };
178 };
179 var msg = this.get_msg("object_info_request", content);
179 var msg = this.get_msg("object_info_request", content);
180 this.shell_channel.send(JSON.stringify(msg));
180 this.shell_channel.send(JSON.stringify(msg));
181 return msg.header.msg_id;
181 return msg.header.msg_id;
182 }
182 }
183 return;
183 return;
184 }
184 }
185
185
186 Kernel.prototype.execute = function (code) {
186 Kernel.prototype.execute = function (code) {
187 var content = {
187 var content = {
188 code : code,
188 code : code,
189 silent : false,
189 silent : false,
190 user_variables : [],
190 user_variables : [],
191 user_expressions : {},
191 user_expressions : {},
192 allow_stdin : false
192 allow_stdin : false
193 };
193 };
194 var msg = this.get_msg("execute_request", content);
194 var msg = this.get_msg("execute_request", content);
195 this.shell_channel.send(JSON.stringify(msg));
195 this.shell_channel.send(JSON.stringify(msg));
196 return msg.header.msg_id;
196 return msg.header.msg_id;
197 };
197 };
198
198
199
199
200 Kernel.prototype.complete = function (line, cursor_pos) {
200 Kernel.prototype.complete = function (line, cursor_pos) {
201 var content = {
201 var content = {
202 text : '',
202 text : '',
203 line : line,
203 line : line,
204 cursor_pos : cursor_pos
204 cursor_pos : cursor_pos
205 };
205 };
206 var msg = this.get_msg("complete_request", content);
206 var msg = this.get_msg("complete_request", content);
207 this.shell_channel.send(JSON.stringify(msg));
207 this.shell_channel.send(JSON.stringify(msg));
208 return msg.header.msg_id;
208 return msg.header.msg_id;
209 };
209 };
210
210
211
211
212 Kernel.prototype.interrupt = function () {
212 Kernel.prototype.interrupt = function () {
213 if (this.running) {
213 if (this.running) {
214 $.post(this.kernel_url + "/interrupt");
214 $.post(this.kernel_url + "/interrupt");
215 };
215 };
216 };
216 };
217
217
218
218
219 Kernel.prototype.kill = function () {
219 Kernel.prototype.kill = function () {
220 if (this.running) {
220 if (this.running) {
221 this.running = false;
221 this.running = false;
222 var settings = {
222 var settings = {
223 cache : false,
223 cache : false,
224 type : "DELETE"
224 type : "DELETE"
225 };
225 };
226 $.ajax(this.kernel_url, settings);
226 $.ajax(this.kernel_url, settings);
227 };
227 };
228 };
228 };
229
229
230 IPython.Kernel = Kernel;
230 IPython.Kernel = Kernel;
231
231
232 return IPython;
232 return IPython;
233
233
234 }(IPython));
234 }(IPython));
235
235
General Comments 0
You need to be logged in to leave comments. Login now