##// END OF EJS Templates
Removing IPython.notebook references from tooltip.js
Brian E. Granger -
Show More
@@ -1,375 +1,376
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7 //============================================================================
7 //============================================================================
8 // Tooltip
8 // Tooltip
9 //============================================================================
9 //============================================================================
10 //
10 //
11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
12 //
12 //
13 // you can configure the differents action of pressing tab several times in a row by
13 // you can configure the differents action of pressing tab several times in a row by
14 // setting/appending different fonction in the array
14 // setting/appending different fonction in the array
15 // IPython.tooltip.tabs_functions
15 // IPython.tooltip.tabs_functions
16 //
16 //
17 // eg :
17 // eg :
18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
19 //
19 //
20 var IPython = (function (IPython) {
20 var IPython = (function (IPython) {
21 "use strict";
21 "use strict";
22
22
23 var utils = IPython.utils;
23 var utils = IPython.utils;
24
24
25 // tooltip constructor
25 // tooltip constructor
26 var Tooltip = function () {
26 var Tooltip = function () {
27 var that = this;
27 var that = this;
28 this.time_before_tooltip = 1200;
28 this.time_before_tooltip = 1200;
29
29
30 // handle to html
30 // handle to html
31 this.tooltip = $('#tooltip');
31 this.tooltip = $('#tooltip');
32 this._hidden = true;
32 this._hidden = true;
33
33
34 // variable for consecutive call
34 // variable for consecutive call
35 this._old_cell = null;
35 this._old_cell = null;
36 this._old_request = null;
36 this._old_request = null;
37 this._consecutive_counter = 0;
37 this._consecutive_counter = 0;
38
38
39 // 'sticky ?'
39 // 'sticky ?'
40 this._sticky = false;
40 this._sticky = false;
41
41
42 // contain the button in the upper right corner
42 // contain the button in the upper right corner
43 this.buttons = $('<div/>').addClass('tooltipbuttons');
43 this.buttons = $('<div/>').addClass('tooltipbuttons');
44
44
45 // will contain the docstring
45 // will contain the docstring
46 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
46 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
47
47
48 // build the buttons menu on the upper right
48 // build the buttons menu on the upper right
49 // expand the tooltip to see more
49 // expand the tooltip to see more
50 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
50 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
51 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
51 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
52 that.expand()
52 that.expand()
53 }).append(
53 }).append(
54 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
54 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
55
55
56 // open in pager
56 // open in pager
57 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
57 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
58 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
58 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
59 morelink.append(morespan);
59 morelink.append(morespan);
60 morelink.click(function () {
60 morelink.click(function () {
61 that.showInPager();
61 that.showInPager();
62 });
62 });
63
63
64 // close the tooltip
64 // close the tooltip
65 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
65 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
66 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
66 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
67 closelink.append(closespan);
67 closelink.append(closespan);
68 closelink.click(function () {
68 closelink.click(function () {
69 that.remove_and_cancel_tooltip(true);
69 that.remove_and_cancel_tooltip(true);
70 });
70 });
71
71
72 this._clocklink = $('<a/>').attr('href', "#");
72 this._clocklink = $('<a/>').attr('href', "#");
73 this._clocklink.attr('role', "button");
73 this._clocklink.attr('role', "button");
74 this._clocklink.addClass('ui-button');
74 this._clocklink.addClass('ui-button');
75 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
75 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
76 var clockspan = $('<span/>').text('Close');
76 var clockspan = $('<span/>').text('Close');
77 clockspan.addClass('ui-icon');
77 clockspan.addClass('ui-icon');
78 clockspan.addClass('ui-icon-clock');
78 clockspan.addClass('ui-icon-clock');
79 this._clocklink.append(clockspan);
79 this._clocklink.append(clockspan);
80 this._clocklink.click(function () {
80 this._clocklink.click(function () {
81 that.cancel_stick();
81 that.cancel_stick();
82 });
82 });
83
83
84
84
85
85
86
86
87 //construct the tooltip
87 //construct the tooltip
88 // add in the reverse order you want them to appear
88 // add in the reverse order you want them to appear
89 this.buttons.append(closelink);
89 this.buttons.append(closelink);
90 this.buttons.append(expandlink);
90 this.buttons.append(expandlink);
91 this.buttons.append(morelink);
91 this.buttons.append(morelink);
92 this.buttons.append(this._clocklink);
92 this.buttons.append(this._clocklink);
93 this._clocklink.hide();
93 this._clocklink.hide();
94
94
95
95
96 // we need a phony element to make the small arrow
96 // we need a phony element to make the small arrow
97 // of the tooltip in css
97 // of the tooltip in css
98 // we will move the arrow later
98 // we will move the arrow later
99 this.arrow = $('<div/>').addClass('pretooltiparrow');
99 this.arrow = $('<div/>').addClass('pretooltiparrow');
100 this.tooltip.append(this.buttons);
100 this.tooltip.append(this.buttons);
101 this.tooltip.append(this.arrow);
101 this.tooltip.append(this.arrow);
102 this.tooltip.append(this.text);
102 this.tooltip.append(this.text);
103
103
104 // function that will be called if you press tab 1, 2, 3... times in a row
104 // function that will be called if you press tab 1, 2, 3... times in a row
105 this.tabs_functions = [function (cell, text) {
105 this.tabs_functions = [function (cell, text) {
106 that._request_tooltip(cell, text);
106 that._request_tooltip(cell, text);
107 }, function () {
107 }, function () {
108 that.expand();
108 that.expand();
109 }, function () {
109 }, function () {
110 that.stick();
110 that.stick();
111 }, function (cell) {
111 }, function (cell) {
112 that.cancel_stick();
112 that.cancel_stick();
113 that.showInPager(cell);
113 that.showInPager(cell);
114 that._cmfocus();
114 that._cmfocus();
115 }];
115 }];
116 // call after all the tabs function above have bee call to clean their effects
116 // call after all the tabs function above have bee call to clean their effects
117 // if necessary
117 // if necessary
118 this.reset_tabs_function = function (cell, text) {
118 this.reset_tabs_function = function (cell, text) {
119 this._old_cell = (cell) ? cell : null;
119 this._old_cell = (cell) ? cell : null;
120 this._old_request = (text) ? text : null;
120 this._old_request = (text) ? text : null;
121 this._consecutive_counter = 0;
121 this._consecutive_counter = 0;
122 }
122 }
123 };
123 };
124
124
125 Tooltip.prototype.showInPager = function (cell) {
125 Tooltip.prototype.showInPager = function (cell) {
126 // reexecute last call in pager by appending ? to show back in pager
126 // reexecute last call in pager by appending ? to show back in pager
127 var that = this;
127 var that = this;
128 var empty = function () {};
128 var empty = function () {};
129 IPython.notebook.kernel.execute(
129 cell.kernel.execute(
130 that.name + '?', {
130 that.name + '?', {
131 'execute_reply': empty,
131 'execute_reply': empty,
132 'output': empty,
132 'output': empty,
133 'clear_output': empty,
133 'clear_output': empty,
134 'cell': cell
134 'cell': cell
135 }, {
135 }, {
136 'silent': false
136 'silent': false
137 });
137 });
138 this.remove_and_cancel_tooltip();
138 this.remove_and_cancel_tooltip();
139 this._cmfocus();
139 this._cmfocus();
140 }
140 }
141
141
142 // grow the tooltip verticaly
142 // grow the tooltip verticaly
143 Tooltip.prototype.expand = function () {
143 Tooltip.prototype.expand = function () {
144 this.text.removeClass('smalltooltip');
144 this.text.removeClass('smalltooltip');
145 this.text.addClass('bigtooltip');
145 this.text.addClass('bigtooltip');
146 $('#expanbutton').hide('slow');
146 $('#expanbutton').hide('slow');
147 this._cmfocus();
147 this._cmfocus();
148 }
148 }
149
149
150 // deal with all the logic of hiding the tooltip
150 // deal with all the logic of hiding the tooltip
151 // and reset it's status
151 // and reset it's status
152 Tooltip.prototype._hide = function () {
152 Tooltip.prototype._hide = function () {
153 this.tooltip.fadeOut('fast');
153 this.tooltip.fadeOut('fast');
154 $('#expanbutton').show('slow');
154 $('#expanbutton').show('slow');
155 this.text.removeClass('bigtooltip');
155 this.text.removeClass('bigtooltip');
156 this.text.addClass('smalltooltip');
156 this.text.addClass('smalltooltip');
157 // keep scroll top to be sure to always see the first line
157 // keep scroll top to be sure to always see the first line
158 this.text.scrollTop(0);
158 this.text.scrollTop(0);
159 this._hidden = true;
159 this._hidden = true;
160 this.code_mirror = null;
160 this.code_mirror = null;
161 }
161 }
162
162
163 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
163 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
164 // note that we don't handle closing directly inside the calltip
164 // note that we don't handle closing directly inside the calltip
165 // as in the completer, because it is not focusable, so won't
165 // as in the completer, because it is not focusable, so won't
166 // get the event.
166 // get the event.
167 if (this._sticky == false || force == true) {
167 if (this._sticky == false || force == true) {
168 this.cancel_stick();
168 this.cancel_stick();
169 this._hide();
169 this._hide();
170 }
170 }
171 this.cancel_pending();
171 this.cancel_pending();
172 this.reset_tabs_function();
172 this.reset_tabs_function();
173 this._cmfocus();
173 this._cmfocus();
174 }
174 }
175
175
176 // cancel autocall done after '(' for example.
176 // cancel autocall done after '(' for example.
177 Tooltip.prototype.cancel_pending = function () {
177 Tooltip.prototype.cancel_pending = function () {
178 if (this._tooltip_timeout != null) {
178 if (this._tooltip_timeout != null) {
179 clearTimeout(this._tooltip_timeout);
179 clearTimeout(this._tooltip_timeout);
180 this._tooltip_timeout = null;
180 this._tooltip_timeout = null;
181 }
181 }
182 }
182 }
183
183
184 // will trigger tooltip after timeout
184 // will trigger tooltip after timeout
185 Tooltip.prototype.pending = function (cell) {
185 Tooltip.prototype.pending = function (cell) {
186 var that = this;
186 var that = this;
187 this._tooltip_timeout = setTimeout(function () {
187 this._tooltip_timeout = setTimeout(function () {
188 that.request(cell)
188 that.request(cell)
189 }, that.time_before_tooltip);
189 }, that.time_before_tooltip);
190 }
190 }
191
191
192 Tooltip.prototype._request_tooltip = function (cell, func) {
192 Tooltip.prototype._request_tooltip = function (cell, func) {
193 // use internally just to make the request to the kernel
193 // use internally just to make the request to the kernel
194 // Feel free to shorten this logic if you are better
194 // Feel free to shorten this logic if you are better
195 // than me in regEx
195 // than me in regEx
196 // basicaly you shoul be able to get xxx.xxx.xxx from
196 // basicaly you shoul be able to get xxx.xxx.xxx from
197 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
197 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
198 // remove everything between matchin bracket (need to iterate)
198 // remove everything between matchin bracket (need to iterate)
199 var matchBracket = /\([^\(\)]+\)/g;
199 var matchBracket = /\([^\(\)]+\)/g;
200 var endBracket = /\([^\(]*$/g;
200 var endBracket = /\([^\(]*$/g;
201 var oldfunc = func;
201 var oldfunc = func;
202
202
203 func = func.replace(matchBracket, "");
203 func = func.replace(matchBracket, "");
204 while (oldfunc != func) {
204 while (oldfunc != func) {
205 oldfunc = func;
205 oldfunc = func;
206 func = func.replace(matchBracket, "");
206 func = func.replace(matchBracket, "");
207 }
207 }
208 // remove everything after last open bracket
208 // remove everything after last open bracket
209 func = func.replace(endBracket, "");
209 func = func.replace(endBracket, "");
210
210
211 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
211 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
212 var callbacks = {
212 var callbacks = {
213 'object_info_reply': $.proxy(this._show, this)
213 'object_info_reply': $.proxy(this._show, this)
214 }
214 }
215 var msg_id = IPython.notebook.kernel.object_info_request(re.exec(func), callbacks);
215 var msg_id = cell.kernel.object_info_request(re.exec(func), callbacks);
216 }
216 }
217
217
218 // make an imediate completion request
218 // make an imediate completion request
219 Tooltip.prototype.request = function (cell) {
219 Tooltip.prototype.request = function (cell) {
220 // request(codecell)
220 // request(codecell)
221 // Deal with extracting the text from the cell and counting
221 // Deal with extracting the text from the cell and counting
222 // call in a row
222 // call in a row
223 this.cancel_pending();
223 this.cancel_pending();
224 var editor = cell.code_mirror;
224 var editor = cell.code_mirror;
225 var cursor = editor.getCursor();
225 var cursor = editor.getCursor();
226 var text = editor.getRange({
226 var text = editor.getRange({
227 line: cursor.line,
227 line: cursor.line,
228 ch: 0
228 ch: 0
229 }, cursor).trim();
229 }, cursor).trim();
230
230
231 if(editor.somethingSelected()){
231 if(editor.somethingSelected()){
232 text = editor.getSelection();
232 text = editor.getSelection();
233 }
233 }
234
234
235 // need a permanent handel to code_mirror for future auto recall
235 // need a permanent handel to code_mirror for future auto recall
236 this.code_mirror = editor;
236 this.code_mirror = editor;
237
237
238 // now we treat the different number of keypress
238 // now we treat the different number of keypress
239 // first if same cell, same text, increment counter by 1
239 // first if same cell, same text, increment counter by 1
240 if (this._old_cell == cell && this._old_request == text && this._hidden == false) {
240 if (this._old_cell == cell && this._old_request == text && this._hidden == false) {
241 this._consecutive_counter++;
241 this._consecutive_counter++;
242 } else {
242 } else {
243 // else reset
243 // else reset
244 this.cancel_stick();
244 this.cancel_stick();
245 this.reset_tabs_function (cell, text);
245 this.reset_tabs_function (cell, text);
246 }
246 }
247
247
248 // don't do anything if line beggin with '(' or is empty
248 // don't do anything if line beggin with '(' or is empty
249 if (text === "" || text === "(") {
249 if (text === "" || text === "(") {
250 return;
250 return;
251 }
251 }
252
252
253 this.tabs_functions[this._consecutive_counter](cell, text);
253 this.tabs_functions[this._consecutive_counter](cell, text);
254
254
255 // then if we are at the end of list function, reset
255 // then if we are at the end of list function, reset
256 if (this._consecutive_counter == this.tabs_functions.length) this.reset_tabs_function (cell, text);
256 if (this._consecutive_counter == this.tabs_functions.length) this.reset_tabs_function (cell, text);
257
257
258 return;
258 return;
259 }
259 }
260
260
261 // cancel the option of having the tooltip to stick
261 // cancel the option of having the tooltip to stick
262 Tooltip.prototype.cancel_stick = function () {
262 Tooltip.prototype.cancel_stick = function () {
263 clearTimeout(this._stick_timeout);
263 clearTimeout(this._stick_timeout);
264 this._stick_timeout = null;
264 this._stick_timeout = null;
265 this._clocklink.hide('slow');
265 this._clocklink.hide('slow');
266 this._sticky = false;
266 this._sticky = false;
267 }
267 }
268
268
269 // put the tooltip in a sicky state for 10 seconds
269 // put the tooltip in a sicky state for 10 seconds
270 // it won't be removed by remove_and_cancell() unless you called with
270 // it won't be removed by remove_and_cancell() unless you called with
271 // the first parameter set to true.
271 // the first parameter set to true.
272 // remove_and_cancell_tooltip(true)
272 // remove_and_cancell_tooltip(true)
273 Tooltip.prototype.stick = function (time) {
273 Tooltip.prototype.stick = function (time) {
274 time = (time != undefined) ? time : 10;
274 time = (time != undefined) ? time : 10;
275 var that = this;
275 var that = this;
276 this._sticky = true;
276 this._sticky = true;
277 this._clocklink.show('slow');
277 this._clocklink.show('slow');
278 this._stick_timeout = setTimeout(function () {
278 this._stick_timeout = setTimeout(function () {
279 that._sticky = false;
279 that._sticky = false;
280 that._clocklink.hide('slow');
280 that._clocklink.hide('slow');
281 }, time * 1000);
281 }, time * 1000);
282 }
282 }
283
283
284 // should be called with the kernel reply to actually show the tooltip
284 // should be called with the kernel reply to actually show the tooltip
285 Tooltip.prototype._show = function (reply) {
285 Tooltip.prototype._show = function (reply) {
286 // move the bubble if it is not hidden
286 // move the bubble if it is not hidden
287 // otherwise fade it
287 // otherwise fade it
288 this.name = reply.name;
288 this.name = reply.name;
289
289
290 // do some math to have the tooltip arrow on more or less on left or right
290 // do some math to have the tooltip arrow on more or less on left or right
291 // width of the editor
291 // width of the editor
292 var w = $(this.code_mirror.getScrollerElement()).width();
292 var w = $(this.code_mirror.getScrollerElement()).width();
293 // ofset of the editor
293 // ofset of the editor
294 var o = $(this.code_mirror.getScrollerElement()).offset();
294 var o = $(this.code_mirror.getScrollerElement()).offset();
295
295
296 // whatever anchor/head order but arrow at mid x selection
296 // whatever anchor/head order but arrow at mid x selection
297 var anchor = this.code_mirror.cursorCoords(false);
297 var anchor = this.code_mirror.cursorCoords(false);
298 var head = this.code_mirror.cursorCoords(true);
298 var head = this.code_mirror.cursorCoords(true);
299 var pos = {};
299 var pos = {};
300 pos.y = head.y
300 pos.y = head.y
301 pos.yBot = head.yBot
301 pos.yBot = head.yBot
302 pos.x = (head.x+anchor.x)/2;
302 pos.x = (head.x+anchor.x)/2;
303
303
304 var xinit = pos.x;
304 var xinit = pos.x;
305 var xinter = o.left + (xinit - o.left) / w * (w - 450);
305 var xinter = o.left + (xinit - o.left) / w * (w - 450);
306 var posarrowleft = xinit - xinter;
306 var posarrowleft = xinit - xinter;
307
307
308
308
309 if (this._hidden == false) {
309 if (this._hidden == false) {
310 this.tooltip.animate({
310 this.tooltip.animate({
311 'left': xinter - 30 + 'px',
311 'left': xinter - 30 + 'px',
312 'top': (pos.yBot + 10) + 'px'
312 'top': (pos.yBot + 10) + 'px'
313 });
313 });
314 } else {
314 } else {
315 this.tooltip.css({
315 this.tooltip.css({
316 'left': xinter - 30 + 'px'
316 'left': xinter - 30 + 'px'
317 });
317 });
318 this.tooltip.css({
318 this.tooltip.css({
319 'top': (pos.yBot + 10) + 'px'
319 'top': (pos.yBot + 10) + 'px'
320 });
320 });
321 }
321 }
322 this.arrow.animate({
322 this.arrow.animate({
323 'left': posarrowleft + 'px'
323 'left': posarrowleft + 'px'
324 });
324 });
325 this.tooltip.fadeIn('fast');
325 this.tooltip.fadeIn('fast');
326 this._hidden = false;
326 this._hidden = false;
327
327
328 // build docstring
328 // build docstring
329 var defstring = reply.call_def;
329 var defstring = reply.call_def;
330 if (defstring == null) {
330 if (defstring == null) {
331 defstring = reply.init_definition;
331 defstring = reply.init_definition;
332 }
332 }
333 if (defstring == null) {
333 if (defstring == null) {
334 defstring = reply.definition;
334 defstring = reply.definition;
335 }
335 }
336
336
337 var docstring = reply.call_docstring;
337 var docstring = reply.call_docstring;
338 if (docstring == null) {
338 if (docstring == null) {
339 docstring = reply.init_docstring;
339 docstring = reply.init_docstring;
340 }
340 }
341 if (docstring == null) {
341 if (docstring == null) {
342 docstring = reply.docstring;
342 docstring = reply.docstring;
343 }
343 }
344 if (docstring == null) {
344 if (docstring == null) {
345 docstring = "<empty docstring>";
345 docstring = "<empty docstring>";
346 }
346 }
347
347
348 this.text.children().remove();
348 this.text.children().remove();
349
349
350 var pre = $('<pre/>').html(utils.fixConsole(docstring));
350 var pre = $('<pre/>').html(utils.fixConsole(docstring));
351 if (defstring) {
351 if (defstring) {
352 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
352 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
353 this.text.append(defstring_html);
353 this.text.append(defstring_html);
354 }
354 }
355 this.text.append(pre);
355 this.text.append(pre);
356 // keep scroll top to be sure to always see the first line
356 // keep scroll top to be sure to always see the first line
357 this.text.scrollTop(0);
357 this.text.scrollTop(0);
358 }
358 }
359
359
360 // convenient funciton to have the correct code_mirror back into focus
360 // convenient funciton to have the correct code_mirror back into focus
361 Tooltip.prototype._cmfocus = function () {
361 Tooltip.prototype._cmfocus = function () {
362 var cm = this.code_mirror;
362 var cm = this.code_mirror;
363 if (cm == IPython.notebook.get_selected_cell())
363 if (IPython.notebook !== undefined) {
364 {
364 if (cm == IPython.notebook.get_selected_cell()) {
365 setTimeout(function () {
365 setTimeout(function () {
366 cm.focus();
366 cm.focus();
367 }, 50);
367 }, 50);
368 }
368 }
369 }
369 }
370 }
370
371
371 IPython.Tooltip = Tooltip;
372 IPython.Tooltip = Tooltip;
372
373
373 return IPython;
374 return IPython;
374
375
375 }(IPython));
376 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now