tooltip.js
360 lines
| 12.3 KiB
| application/javascript
|
JavascriptLexer
|
r7145 | //---------------------------------------------------------------------------- | ||
// Copyright (C) 2008-2011 The IPython Development Team | ||||
// | ||||
// Distributed under the terms of the BSD License. The full license is in | ||||
// the file COPYING, distributed as part of this software. | ||||
//---------------------------------------------------------------------------- | ||||
//============================================================================ | ||||
// Tooltip | ||||
//============================================================================ | ||||
|
r7151 | // | ||
|
r7170 | // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms | ||
|
r7172 | // | ||
|
r7173 | // you can configure the differents action of pressing tab several times in a row by | ||
// setting/appending different fonction in the array | ||||
|
r7172 | // IPython.tooltip.tabs_functions | ||
// | ||||
// eg : | ||||
// IPython.tooltip.tabs_functions[4] = function(){console.log('this is the action of the 4th tab pressing')} | ||||
// | ||||
|
r7162 | |||
|
r7145 | var IPython = (function (IPython) { | ||
|
r7173 | "use strict"; | ||
|
r7145 | |||
var utils = IPython.utils; | ||||
|
r7173 | |||
|
r7162 | // tooltip constructor | ||
|
r7168 | var Tooltip = function () { | ||
|
r7153 | var that = this; | ||
|
r7168 | this.time_before_tooltip = 1200; | ||
|
r7162 | |||
// handle to html | ||||
this.tooltip = $('#tooltip'); | ||||
|
r7153 | this._hidden = true; | ||
|
r7160 | // variable for consecutive call | ||
this._old_cell = null ; | ||||
this._old_request = null ; | ||||
|
r7172 | this._consecutive_counter = 0; | ||
|
r7160 | |||
// 'sticky ?' | ||||
this._sticky = false; | ||||
|
r7153 | // contain the button in the upper right corner | ||
|
r7148 | this.buttons = $('<div/>') | ||
.addClass('tooltipbuttons'); | ||||
|
r7153 | |||
|
r7162 | // will contain the docstring | ||
|
r7147 | this.text = $('<div/>') | ||
|
r7148 | .addClass('tooltiptext') | ||
|
r7147 | .addClass('smalltooltip'); | ||
|
r7162 | // build the buttons menu on the upper right | ||
// expand the tooltip to see more | ||||
|
r7147 | var expandlink=$('<a/>').attr('href',"#") | ||
.addClass("ui-corner-all") //rounded corner | ||||
.attr('role',"button") | ||||
.attr('id','expanbutton') | ||||
|
r7173 | .attr('title','Grow the tooltip vertically (press tab 2 times)') | ||
|
r7160 | .click(function(){that.expand()}) | ||
|
r7150 | .append( | ||
|
r7155 | $('<span/>').text('Expand') | ||
.addClass('ui-icon') | ||||
.addClass('ui-icon-plus') | ||||
); | ||||
|
r7147 | |||
|
r7155 | // open in pager | ||
|
r7150 | var morelink=$('<a/>').attr('href',"#") | ||
.attr('role',"button") | ||||
|
r7173 | .addClass('ui-button') | ||
.attr('title','show the current docstring in pager (press tab 4 times)'); | ||||
|
r7150 | var morespan=$('<span/>').text('Open in Pager') | ||
.addClass('ui-icon') | ||||
.addClass('ui-icon-arrowstop-l-n'); | ||||
|
r7147 | morelink.append(morespan); | ||
morelink.click(function(){ | ||||
|
r7160 | that.showInPager(); | ||
|
r7147 | }); | ||
|
r7155 | // close the tooltip | ||
|
r7173 | var closelink=$('<a/>').attr('href',"#") | ||
.attr('role',"button") | ||||
.addClass('ui-button'); | ||||
var closespan=$('<span/>').text('Close') | ||||
.addClass('ui-icon') | ||||
.addClass('ui-icon-close'); | ||||
|
r7147 | closelink.append(closespan); | ||
closelink.click(function(){ | ||||
|
r7161 | that.remove_and_cancel_tooltip(true); | ||
|
r7147 | }); | ||
|
r7162 | |||
|
r7173 | this._clocklink=$('<a/>').attr('href',"#"); | ||
this._clocklink.attr('role',"button"); | ||||
this._clocklink.addClass('ui-button'); | ||||
this._clocklink.attr('title','Tootip is not dismissed while typing for 10 seconds'); | ||||
var clockspan=$('<span/>').text('Close'); | ||||
clockspan.addClass('ui-icon'); | ||||
clockspan.addClass('ui-icon-clock'); | ||||
this._clocklink.append(clockspan); | ||||
this._clocklink.click(function(){ | ||||
that.cancel_stick(); | ||||
}); | ||||
|
r7155 | //construct the tooltip | ||
// add in the reverse order you want them to appear | ||||
|
r7148 | this.buttons.append(closelink); | ||
this.buttons.append(expandlink); | ||||
this.buttons.append(morelink); | ||||
|
r7173 | this.buttons.append(this._clocklink); | ||
this._clocklink.hide(); | ||||
|
r7155 | |||
// we need a phony element to make the small arrow | ||||
// of the tooltip in css | ||||
|
r7162 | // we will move the arrow later | ||
|
r7155 | this.arrow = $('<div/>').addClass('pretooltiparrow'); | ||
|
r7148 | this.tooltip.append(this.buttons); | ||
|
r7155 | this.tooltip.append(this.arrow); | ||
|
r7148 | this.tooltip.append(this.text); | ||
|
r7172 | |||
|
r7173 | // function that will be called if you press tab 1, 2, 3... times in a row | ||
this.tabs_functions = [ function(cell,text){that._request_tooltip(cell,text)}, | ||||
|
r7172 | function(){that.expand()}, | ||
function(){that.stick()}, | ||||
function(){ | ||||
that.cancel_stick(); | ||||
that.showInPager(); | ||||
that._cmfocus(); | ||||
} | ||||
]; | ||||
// call after all the tabs function above have bee call to clean their effects | ||||
// if necessary | ||||
this.reset_tabs_function = function(cell,text){ | ||||
this._old_cell = (cell)? cell : null ; | ||||
this._old_request = (text) ? text : null ; | ||||
this._consecutive_counter = 0; | ||||
} | ||||
|
r7145 | }; | ||
|
r7160 | Tooltip.prototype.showInPager = function() | ||
{ | ||||
|
r7172 | // reexecute last call in pager by appending ? to show back in pager | ||
var that = this; | ||||
var callbacks = {'execute_reply': $.proxy(that._handle_execute_reply,that)} | ||||
var msg_id = IPython.notebook.kernel.execute(this.name+"?", callbacks); | ||||
|
r7168 | |||
|
r7172 | this.remove_and_cancel_tooltip(); | ||
this._cmfocus(); | ||||
|
r7160 | } | ||
|
r7162 | // grow the tooltip verticaly | ||
|
r7160 | Tooltip.prototype.expand = function(){ | ||
this.text.removeClass('smalltooltip'); | ||||
this.text.addClass('bigtooltip'); | ||||
|
r7173 | $('#expanbutton').hide('slow'); | ||
|
r7160 | this._cmfocus(); | ||
} | ||||
|
r7151 | // deal with all the logic of hiding the tooltip | ||
// and reset it's status | ||||
Tooltip.prototype.hide = function() | ||||
{ | ||||
|
r7153 | this.tooltip.addClass('hide'); | ||
|
r7173 | $('#expanbutton').show('slow'); | ||
|
r7153 | this.text.removeClass('bigtooltip'); | ||
this.text.addClass('smalltooltip'); | ||||
// keep scroll top to be sure to always see the first line | ||||
this.text.scrollTop(0); | ||||
this._hidden = true; | ||||
|
r7151 | } | ||
|
r7145 | |||
|
r7160 | Tooltip.prototype.remove_and_cancel_tooltip = function(force) { | ||
|
r7145 | // note that we don't handle closing directly inside the calltip | ||
// as in the completer, because it is not focusable, so won't | ||||
// get the event. | ||||
|
r7160 | if(this._sticky == false || force == true) | ||
{ | ||||
this.hide(); | ||||
} | ||||
this.cancel_pending(); | ||||
|
r7173 | this.reset_tabs_function(); | ||
|
r7160 | } | ||
|
r7162 | // cancel autocall done after '(' for example. | ||
|
r7160 | Tooltip.prototype.cancel_pending = function(){ | ||
|
r7173 | if (this._tooltip_timeout != null){ | ||
clearTimeout(this._tooltip_timeout); | ||||
this._tooltip_timeout = null; | ||||
|
r7145 | } | ||
} | ||||
|
r7162 | |||
// will trigger tooltip after timeout | ||||
|
r7173 | Tooltip.prototype.pending = function(cell) | ||
|
r7157 | { | ||
var that = this; | ||||
|
r7173 | this._tooltip_timeout = setTimeout(function(){that.request(cell)} , that.time_before_tooltip); | ||
|
r7157 | } | ||
|
r7173 | |||
Tooltip.prototype._request_tooltip = function(cell,func) | ||||
|
r7171 | { | ||
|
r7173 | // use internally just to make the request to the kernel | ||
|
r7172 | |||
|
r7171 | // Feel free to shorten this logic if you are better | ||
// than me in regEx | ||||
|
r7173 | // basicaly you shoul be able to get xxx.xxx.xxx from | ||
// something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2, | ||||
|
r7171 | // remove everything between matchin bracket (need to iterate) | ||
var matchBracket = /\([^\(\)]+\)/g; | ||||
var endBracket = /\([^\(]*$/g; | ||||
var oldfunc = func; | ||||
func = func.replace(matchBracket,""); | ||||
while( oldfunc != func ) | ||||
{ | ||||
|
r7173 | oldfunc = func; | ||
func = func.replace(matchBracket,""); | ||||
|
r7171 | } | ||
|
r7173 | // remove everything after last open bracket | ||
|
r7171 | func = func.replace(endBracket,""); | ||
|
r7173 | |||
|
r7171 | var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive | ||
var callbacks = {'object_info_reply': $.proxy(this.show,this)} | ||||
var msg_id = IPython.notebook.kernel.object_info_request(re.exec(func), callbacks); | ||||
} | ||||
|
r7162 | |||
// make an imediate completion request | ||||
|
r7160 | Tooltip.prototype.request = function(cell) | ||
|
r7156 | { | ||
|
r7173 | // request(codecell) | ||
|
r7172 | // Deal with extracting the text from the cell and counting | ||
// call in a row | ||||
|
r7173 | |||
|
r7160 | this.cancel_pending(); | ||
var editor = cell.code_mirror; | ||||
var cursor = editor.getCursor(); | ||||
var text = editor.getRange({line:cursor.line,ch:0},cursor).trim(); | ||||
|
r7173 | |||
|
r7172 | // need a permanent handel to codemirror for future auto recall | ||
this.code_mirror = editor; | ||||
|
r7173 | // now we treat the different number of keypress | ||
|
r7172 | // first if same cell, same text, increment counter by 1 | ||
|
r7160 | if( this._old_cell == cell && this._old_request == text && this._hidden == false) | ||
{ | ||||
|
r7173 | this._consecutive_counter++; | ||
|
r7160 | } else { | ||
|
r7172 | // else reset | ||
|
r7173 | this.cancel_stick(); | ||
|
r7172 | this.reset_tabs_function(cell,text); | ||
|
r7160 | } | ||
|
r7173 | |||
|
r7172 | // don't do anything if line beggin with '(' or is empty | ||
|
r7160 | if (text === "" || text === "(" ) { | ||
return; | ||||
} | ||||
|
r7173 | |||
this.tabs_functions[this._consecutive_counter](cell,text); | ||||
// then if we are at the end of list function, reset | ||||
if (this._consecutive_counter == this.tabs_functions.length) | ||||
|
r7172 | this.reset_tabs_function(cell,text); | ||
|
r7173 | |||
|
r7172 | return; | ||
|
r7160 | } | ||
|
r7162 | |||
// cancel the option of having the tooltip to stick | ||||
|
r7160 | Tooltip.prototype.cancel_stick = function() | ||
{ | ||||
|
r7173 | console.log('cancel stick'); | ||
|
r7160 | clearTimeout(this._stick_timeout); | ||
|
r7173 | this._stick_timeout = null; | ||
this._clocklink.hide('slow'); | ||||
|
r7160 | this._sticky = false; | ||
} | ||||
|
r7162 | // put the tooltip in a sicky state for 10 seconds | ||
// it won't be removed by remove_and_cancell() unless you called with | ||||
// the first parameter set to true. | ||||
// remove_and_cancell_tooltip(true) | ||||
Tooltip.prototype.stick = function() | ||||
|
r7160 | { | ||
|
r7162 | var that = this; | ||
|
r7160 | this._sticky = true; | ||
|
r7173 | this._clocklink.show('slow'); | ||
|
r7160 | this._stick_timeout = setTimeout( function(){ | ||
that._sticky = false; | ||||
|
r7173 | that._clocklink.hide('slow'); | ||
|
r7160 | }, 10*1000 | ||
); | ||||
|
r7156 | } | ||
|
r7162 | // should be called with the kernel reply to actually show the tooltip | ||
|
r7171 | Tooltip.prototype.show = function(reply) | ||
|
r7145 | { | ||
|
r7153 | // move the bubble if it is not hidden | ||
// otherwise fade it | ||||
|
r7172 | this.name = reply.name; | ||
|
r7160 | |||
// do some math to have the tooltip arrow on more or less on left or right | ||||
// width of the editor | ||||
|
r7173 | var w = $(this.code_mirror.getScrollerElement()).width(); | ||
|
r7160 | // ofset of the editor | ||
|
r7173 | var o = $(this.code_mirror.getScrollerElement()).offset(); | ||
var pos = this.code_mirror.cursorCoords(); | ||||
|
r7155 | var xinit = pos.x; | ||
|
r7160 | var xinter = o.left + (xinit-o.left)/w*(w-450); | ||
|
r7155 | var posarrowleft = xinit - xinter; | ||
|
r7162 | |||
|
r7155 | |||
|
r7153 | if( this._hidden == false) | ||
{ | ||||
|
r7155 | this.tooltip.animate({'left' : xinter-30+'px','top' :(pos.yBot+10)+'px'}); | ||
|
r7162 | } else | ||
|
r7153 | { | ||
|
r7155 | this.tooltip.css({'left' : xinter-30+'px'}); | ||
|
r7153 | this.tooltip.css({'top' :(pos.yBot+10)+'px'}); | ||
} | ||||
|
r7155 | this.arrow.animate({'left' : posarrowleft+'px'}); | ||
|
r7153 | this.tooltip.removeClass('hidden') | ||
this.tooltip.removeClass('hide'); | ||||
this._hidden = false; | ||||
|
r7147 | |||
// build docstring | ||||
|
r7173 | var defstring = reply.call_def; | ||
|
r7147 | if (defstring == null) { defstring = reply.init_definition; } | ||
if (defstring == null) { defstring = reply.definition; } | ||||
|
r7173 | var docstring = reply.call_docstring; | ||
|
r7147 | if (docstring == null) { docstring = reply.init_docstring; } | ||
if (docstring == null) { docstring = reply.docstring; } | ||||
if (docstring == null) { docstring = "<empty docstring>"; } | ||||
this.text.children().remove(); | ||||
var pre=$('<pre/>').html(utils.fixConsole(docstring)); | ||||
if(defstring){ | ||||
var defstring_html = $('<pre/>').html(utils.fixConsole(defstring)); | ||||
this.text.append(defstring_html); | ||||
} | ||||
|
r7151 | this.text.append(pre); | ||
|
r7158 | // keep scroll top to be sure to always see the first line | ||
this.text.scrollTop(0); | ||||
|
r7145 | } | ||
|
r7162 | // convenient funciton to have the correct codemirror back into focus | ||
|
r7156 | Tooltip.prototype._cmfocus = function() | ||
{ | ||||
var cm = this.code_mirror; | ||||
setTimeout(function(){cm.focus();}, 50); | ||||
} | ||||
|
r7145 | IPython.Tooltip = Tooltip; | ||
|
r7173 | |||
|
r7145 | return IPython; | ||
|
r7173 | |||
|
r7145 | }(IPython)); | ||