diff --git a/IPython/frontend/html/notebook/static/css/notebook.css b/IPython/frontend/html/notebook/static/css/notebook.css index 22458a7..924a6c1 100644 --- a/IPython/frontend/html/notebook/static/css/notebook.css +++ b/IPython/frontend/html/notebook/static/css/notebook.css @@ -115,6 +115,18 @@ span.section_row_buttons a { float: right; } +#timebeforetooltip_span { + float: right; +} + +#tooltipontab_span { + float: right; +} + +#smartcompleter_span { + float: right; +} + .checkbox_label { font-size: 85%; float: right; @@ -321,7 +333,7 @@ div.text_cell_render { .ansigrey {color: grey;} .ansibold {font-weight: bold;} -.completions { +.completions , .tooltip{ position: absolute; z-index: 10; overflow: auto; @@ -337,6 +349,63 @@ div.text_cell_render { font-family: monospace; } +@-moz-keyframes fadeIn { + from {opacity:0;} + to {opacity:1;} +} + +@-webkit-keyframes fadeIn { + from {opacity:0;} + to {opacity:1;} +} + +@keyframes fadeIn { + from {opacity:0;} + to {opacity:1;} +} + +/*"close" "expand" and "Open in pager button" of +/* the tooltip*/ +.tooltip a{ + float:right; +} + +/*properties of tooltip after "expand"*/ +.bigtooltip{ + height:60%; +} + +/*properties of tooltip before "expand"*/ +.smalltooltip{ + text-overflow: ellipsis; + overflow: hidden; + height:15%; +} + +.tooltip{ + /*transition when "expand"ing tooltip */ + -webkit-transition-property: height; + -webkit-transition-duration: 1s; + -moz-transition-property: height; + -moz-transition-duration: 1s; + transition-property: height; + transition-duration: 1s; + max-width:700px; + border-radius: 0px 10px 10px 10px; + box-shadow: 3px 3px 5px #999; + /*fade-in animation when inserted*/ + -webkit-animation: fadeIn 200ms; + -moz-animation: fadeIn 200ms; + animation: fadeIn 200ms; + vertical-align: middle; + background: #FDFDD8; + outline: none; + padding: 3px; + margin: 0px; + font-family: monospace; + min-height:50px; +} + @media print { body { overflow: visible !important; } .ui-widget-content { border: 0px; } diff --git a/IPython/frontend/html/notebook/static/js/cell.js b/IPython/frontend/html/notebook/static/js/cell.js index 2d86c62..b186bd8 100644 --- a/IPython/frontend/html/notebook/static/js/cell.js +++ b/IPython/frontend/html/notebook/static/js/cell.js @@ -85,7 +85,6 @@ var IPython = (function (IPython) { } }; - // Subclasses must implement create_element. Cell.prototype.create_element = function () {}; diff --git a/IPython/frontend/html/notebook/static/js/codecell.js b/IPython/frontend/html/notebook/static/js/codecell.js index 62534e5..dcd81a3 100644 --- a/IPython/frontend/html/notebook/static/js/codecell.js +++ b/IPython/frontend/html/notebook/static/js/codecell.js @@ -47,24 +47,60 @@ var IPython = (function (IPython) { this.collapse() }; + //TODO, try to diminish the number of parameters. + CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time,that){ + if (pre_cursor === "" || pre_cursor === "(" ) { + // don't do anything if line beggin with '(' or is empty + } else { + // Will set a timer to request tooltip in `time` + that.tooltip_timeout = setTimeout(function(){ + IPython.notebook.request_tool_tip(that, pre_cursor) + },time); + } + }; CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) { // This method gets called in CodeMirror's onKeyDown/onKeyPress // handlers and is used to provide custom key handling. Its return // value is used to determine if CodeMirror should ignore the event: // true = ignore, false = don't ignore. + + // note that we are comparing and setting the time to wait at each key press. + // a better wqy might be to generate a new function on each time change and + // assign it to CodeCell.prototype.request_tooltip_after_time + tooltip_wait_time = this.notebook.time_before_tooltip; + tooltip_on_tab = this.notebook.tooltip_on_tab; + var that = this; + // whatever key is pressed, first, cancel the tooltip request before + // they are sent, and remove tooltip if any + if(event.type === 'keydown' && this.tooltip_timeout != null){ + CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout); + that.tooltip_timeout=null; + } + if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) { // Always ignore shift-enter in CodeMirror as we handle it. return true; + }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) { + // triger aon keypress (!) otherwise inconsistent event.which depending on plateform + // browser and keyboard layout ! + // Pressing '(' , request tooltip, don't forget to reappend it + var cursor = editor.getCursor(); + var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'('; + CodeCell.prototype.request_tooltip_after_time(pre_cursor,tooltip_wait_time,that); } else if (event.keyCode === 9 && event.type == 'keydown') { // Tab completion. var cur = editor.getCursor(); - var pre_cursor = editor.getRange({line:cur.line,ch:0},cur).trim(); - if (pre_cursor === "") { + //Do not trim here because of tooltip + var pre_cursor = editor.getRange({line:cur.line,ch:0},cur); + if (pre_cursor.trim() === "") { // Don't autocomplete if the part of the line before the cursor // is empty. In this case, let CodeMirror handle indentation. return false; + } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) { + CodeCell.prototype.request_tooltip_after_time(pre_cursor,0,that); } else { + pre_cursor.trim(); // Autocomplete the current line. event.stop(); var line = editor.getLine(cur.line); @@ -108,11 +144,130 @@ var IPython = (function (IPython) { }; }; + CodeCell.prototype.remove_and_cancell_tooltip = function(timeout) + { + // 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. + clearTimeout(timeout); + $('#tooltip').remove(); + } + + CodeCell.prototype.finish_tooltip = function (reply) { + defstring=reply.definition; + docstring=reply.docstring; + if(docstring == null){docstring=""}; + name=reply.name; + + var that = this; + var tooltip = $('').attr('id', 'tooltip').addClass('tooltip'); + // remove to have the tooltip not Limited in X and Y + tooltip.addClass('smalltooltip'); + var pre=$('').html(utils.fixConsole(docstring)); + var expandlink=$('').attr('href',"#"); + expandlink.addClass("ui-corner-all"); //rounded corner + expandlink.attr('role',"button"); + //expandlink.addClass('ui-button'); + //expandlink.addClass('ui-state-default'); + var expandspan=$('').text('Expand'); + expandspan.addClass('ui-icon'); + expandspan.addClass('ui-icon-plus'); + expandlink.append(expandspan); + expandlink.attr('id','expanbutton'); + expandlink.click(function(){ + tooltip.removeClass('smalltooltip'); + tooltip.addClass('bigtooltip'); + $('#expanbutton').remove(); + setTimeout(function(){that.code_mirror.focus();}, 50); + }); + var morelink=$('').attr('href',"#"); + morelink.attr('role',"button"); + morelink.addClass('ui-button'); + //morelink.addClass("ui-corner-all"); //rounded corner + //morelink.addClass('ui-state-default'); + var morespan=$('').text('Open in Pager'); + morespan.addClass('ui-icon'); + morespan.addClass('ui-icon-arrowstop-l-n'); + morelink.append(morespan); + morelink.click(function(){ + var msg_id = IPython.notebook.kernel.execute(name+"?"); + IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id; + CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout); + setTimeout(function(){that.code_mirror.focus();}, 50); + }); + + var closelink=$('').attr('href',"#"); + closelink.attr('role',"button"); + closelink.addClass('ui-button'); + //closelink.addClass("ui-corner-all"); //rounded corner + //closelink.adClass('ui-state-default'); // grey background and blue cross + var closespan=$('').text('Close'); + closespan.addClass('ui-icon'); + closespan.addClass('ui-icon-close'); + closelink.append(closespan); + closelink.click(function(){ + CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout); + setTimeout(function(){that.code_mirror.focus();}, 50); + }); + //construct the tooltip + tooltip.append(closelink); + tooltip.append(expandlink); + tooltip.append(morelink); + if(defstring){ + defstring_html= $('').html(utils.fixConsole(defstring)); + tooltip.append(defstring_html); + } + tooltip.append(pre); + var pos = this.code_mirror.cursorCoords(); + tooltip.css('left',pos.x+'px'); + tooltip.css('top',pos.yBot+'px'); + $('body').append(tooltip); + + // issues with cross-closing if multiple tooltip in less than 5sec + // keep it comented for now + // setTimeout(CodeCell.prototype.remove_and_cancell_tooltip, 5000); + }; + CodeCell.prototype.finish_completing = function (matched_text, matches) { // console.log("Got matches", matched_text, matches); + var newm = new Array(); + if(this.notebook.smart_completer) + { + kwargs = new Array(); + other = new Array(); + for(var i=0;ifallback_on_tooltip_after && this.prevmatch==matched_text) + { + console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !'); + console.log('You should understand that there is no (more) completion for that !'); + console.log("I'll show you the tooltip, will you stop bothering me ?"); + this.request_tooltip_after_time(matched_text+'(',0,this); + return; + } + this.prevmatch=matched_text + this.npressed=this.npressed+1; + } + else + { + this.prevmatch=""; + this.npressed=0; + } + var that = this; var cur = this.completion_cursor; diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index 1d74ac1..b3ae6ba 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -170,6 +170,19 @@ var IPython = (function (IPython) { }; }; + Kernel.prototype.object_info_request = function (objname) { + if(typeof(objname)!=null) + { + var content = { + oname : objname.toString(), + }; + var msg = this.get_msg("object_info_request", content); + this.shell_channel.send(JSON.stringify(msg)); + return msg.header.msg_id; + } + return; + } + Kernel.prototype.execute = function (code) { var content = { code : code, diff --git a/IPython/frontend/html/notebook/static/js/leftpanel.js b/IPython/frontend/html/notebook/static/js/leftpanel.js index 380510a..5eb02e1 100644 --- a/IPython/frontend/html/notebook/static/js/leftpanel.js +++ b/IPython/frontend/html/notebook/static/js/leftpanel.js @@ -67,6 +67,7 @@ var IPython = (function (IPython) { this.notebook_section = new IPython.NotebookSection('div#notebook_section'); if (! IPython.read_only){ this.cell_section = new IPython.CellSection('div#cell_section'); + this.config_section = new IPython.ConfigSection('div#config_section'); this.kernel_section = new IPython.KernelSection('div#kernel_section'); } this.help_section = new IPython.HelpSection('div#help_section'); diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index a50dcb3..d59105e 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -27,6 +27,9 @@ var IPython = (function (IPython) { this.style(); this.create_elements(); this.bind_events(); + this.set_tooltipontab(true); + this.set_smartcompleter(true); + this.set_timebeforetooltip(1200); }; @@ -621,6 +624,21 @@ var IPython = (function (IPython) { }; + Notebook.prototype.set_timebeforetooltip = function (time) { + console.log("change time before tooltip to : "+time); + this.time_before_tooltip = time; + }; + + Notebook.prototype.set_tooltipontab = function (state) { + console.log("change tooltip on tab to : "+state); + this.tooltip_on_tab = state; + }; + + Notebook.prototype.set_smartcompleter = function (state) { + console.log("Smart completion (kwargs first) changed to to : "+state); + this.smart_completer = state; + }; + Notebook.prototype.set_autoindent = function (state) { var cells = this.cells(); len = cells.length; @@ -700,9 +718,22 @@ var IPython = (function (IPython) { this.dirty = true; } else if (msg_type === "complete_reply") { cell.finish_completing(content.matched_text, content.matches); - }; - var payload = content.payload || []; - this.handle_payload(cell, payload); + } else if (msg_type === "object_info_reply"){ + //console.log('back from object_info_request : ') + rep = reply.content; + if(rep.found) + { + cell.finish_tooltip(rep); + } + } else { + //console.log("unknown reply:"+msg_type); + } + // when having a rely from object_info_reply, + // no payload so no nned to handle it + if(typeof(content.payload)!='undefined') { + var payload = content.payload || []; + this.handle_payload(cell, payload); + } }; @@ -868,6 +899,30 @@ var IPython = (function (IPython) { }; + Notebook.prototype.request_tool_tip = function (cell,func) { + // Feel free to shorten this logic if you are better + // than me in regEx + // 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, + // remove everything between matchin bracket (need to iterate) + matchBracket = /\([^\(\)]+\)/g; + oldfunc = func; + func = func.replace(matchBracket,""); + while( oldfunc != func ) + { + oldfunc = func; + func = func.replace(matchBracket,""); + } + // remove everythin after last open bracket + endBracket = /\([^\(]*$/g; + func = func.replace(endBracket,""); + var re = /[a-zA-Z._]+$/g; + var msg_id = this.kernel.object_info_request(re.exec(func)); + if(typeof(msg_id)!='undefined'){ + this.msg_cell_map[msg_id] = cell.cell_id; + } + }; + Notebook.prototype.complete_cell = function (cell, line, cursor_pos) { var msg_id = this.kernel.complete(line, cursor_pos); this.msg_cell_map[msg_id] = cell.cell_id; diff --git a/IPython/frontend/html/notebook/static/js/notebookmain.js b/IPython/frontend/html/notebook/static/js/notebookmain.js index 11c0db6..78e4c05 100644 --- a/IPython/frontend/html/notebook/static/js/notebookmain.js +++ b/IPython/frontend/html/notebook/static/js/notebookmain.js @@ -51,6 +51,7 @@ $(document).ready(function () { IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only $('button#new_notebook').addClass('hidden'); $('div#cell_section').addClass('hidden'); + $('div#config_section').addClass('hidden'); $('div#kernel_section').addClass('hidden'); $('span#login_widget').removeClass('hidden'); // left panel starts collapsed, but the collapse must happen after diff --git a/IPython/frontend/html/notebook/static/js/panelsection.js b/IPython/frontend/html/notebook/static/js/panelsection.js index e7cfcab..a99e828 100644 --- a/IPython/frontend/html/notebook/static/js/panelsection.js +++ b/IPython/frontend/html/notebook/static/js/panelsection.js @@ -121,13 +121,52 @@ var IPython = (function (IPython) { }); }; + // ConfigSection + + var ConfigSection = function () { + PanelSection.apply(this, arguments); + }; + + ConfigSection.prototype = new PanelSection(); + + ConfigSection.prototype.style = function () { + PanelSection.prototype.style.apply(this); + this.content.addClass('ui-helper-clearfix'); + this.content.find('div.section_row').addClass('ui-helper-clearfix'); + + this.content.find('#tooltipontab').attr('title', 'Show tooltip if you press after "(" or a white space'); + this.content.find('#tooltipontab_label').attr('title', 'Show Tooltip when pressing Tab'); + + this.content.find('#timebeforetooltip').attr('title', 'Time before a tooltip auto-appear when "(" is pressed (negative value supress tooltip)'); + this.content.find('#timebeforetooltip_label').attr('title', 'Time before a tooltip auto-appear when "(" is pressed (negative value supress tooltip)'); + + this.content.find('#smartcompleter').attr('title', 'When inside function call, completer try to propose kwargs first'); + this.content.find('#smartcompleter_label').attr('title', 'When inside function call, completer try to propose kwargs first'); + }; + + + ConfigSection.prototype.bind_events = function () { + PanelSection.prototype.bind_events.apply(this); + this.content.find('#tooltipontab').change(function () { + var state = $('#tooltipontab').prop('checked'); + IPython.notebook.set_tooltipontab(state); + }); + this.content.find('#timebeforetooltip').change(function () { + var state = $('#timebeforetooltip').prop('value'); + IPython.notebook.set_timebeforetooltip(state); + }); + this.content.find('#smartcompleter').change(function () { + var state = $('#smartcompleter').prop('checked'); + IPython.notebook.set_smartcompleter(state); + }); + }; + // CellSection var CellSection = function () { PanelSection.apply(this, arguments); }; - CellSection.prototype = new PanelSection(); @@ -201,6 +240,10 @@ var IPython = (function (IPython) { var state = $('#autoindent').prop('checked'); IPython.notebook.set_autoindent(state); }); + this.content.find('#tooltipontab').change(function () { + var state = $('#tooltipontab').prop('checked'); + IPython.notebook.set_tooltipontab(state); + }); }; @@ -280,6 +323,7 @@ var IPython = (function (IPython) { IPython.PanelSection = PanelSection; IPython.NotebookSection = NotebookSection; IPython.CellSection = CellSection; + IPython.ConfigSection = ConfigSection; IPython.KernelSection = KernelSection; IPython.HelpSection = HelpSection; diff --git a/IPython/frontend/html/notebook/templates/notebook.html b/IPython/frontend/html/notebook/templates/notebook.html index e2fdc3b..0655e5b 100644 --- a/IPython/frontend/html/notebook/templates/notebook.html +++ b/IPython/frontend/html/notebook/templates/notebook.html @@ -251,6 +251,32 @@ + + + Config + + + + + + + Tooltip on tab: + + + + + + Smart completer: + + + + + + Time before tooltip : + + + +