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