Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add Tooltip to notebook. #987

Merged
merged 15 commits into from

5 participants

@Carreau
Owner
When user press '(' and nothing for 1200ms, 'object_info_request' sent to
the kernel. Then tooltip with 'definition' and beggining of 'docstring'
shown to the user if non empty response.

Unlike Completion <select> , <div> is not focusable so event handled in main
cell event handeling function

Add some CSS3 that most browser with websocket should support.

I wasn't able to replicate the 'object oriented' way of dismissing the completer with <div/> because they are not 'focusable' but it works.

I've also add some CSS3, for tooltip round-corner and fade in, if you agree...

@minrk
Owner

Seems like a useful feature. Why 1200ms, though? That's an extremely long time, and if I knew I wanted the info, I would do foo? which would be faster. It should probably be as short as possible, while minimizing the likelihood of coming up while actively typing. I would expect that to be less than 500ms.

@fperez
Owner

I actually would perhaps prefer it to be handled differently: instead of being time-triggered, I think it would be better if it were tab-activated. I actually don't like that the tooltips in the qtconsole come up always. I don't need to see the range( tooltip all the time, it's just an annoyance. What I'd like to have instead is showing the tooltip if I hit tab, so that as I'm coding, if I think I need the call reference, I can just hit tab after (, and instead of attempting to complete a parenthesis (which makes no sense), tab does the useful thing and gives me the tooltip.

Do you think that's doable, @Carreau?

@takluyver
Owner
@Carreau
Owner

@minrk,
1200 ms was to be not to quick to avoid having the tooltip poping up all the time and sending request to the kernel at every (. By trying a little, for me, 1200 ms is the right time to not having it appear when I know what I want to type and small enough to have the docstring if my memory is lacking, 500 was too short.

I see foo? like a more extensive help, which is intrusive (you have to validate the cell, and then come back to the cell) that actually stay when you start writing. if you have foo( arg1 , bar=bar , ..., nthkwarg=nthkwarg) the tooltip will disapear while you are entering the argument while foo? will stay.

I can try to do it configurable...

@fperez
I can try to do it this way, and event do it configurable I think of something that might please you and @minrk.
I propse to be able to set the time before the tooltip appear:

  • time >= 0 current behaviour
  • time =-1 never show tooltip
  • time <-1 show only on tab press

Or something like that.

@Carreau
Owner

Almost done. It works both ways, with pressing tab, waiting ... etc.
I think about using 2 configurable variable, for example tooltip_wait_time and tooltip_on_tab but I need a few advice to how to make them configurable ? In notebook.js ? Should the configuration be in ipython_notebook_config.py ? Or Notebook dependant ?

@takluyver
I have enable tooltip with and without <tab> only just after ( so even if you press tab with the cursor after a opening bracket, you dont have anything to complete... for now of couse, a list of kwarg might be welcome.

@fperez
Your tab suggestion is really great, you can now have completion even when not on the last command, so you can navigate in your line and get the tooltip again ! I'll push as it is for now , with variable hard coded.

@takluyver
Owner

I thought we already had completions for kwargs. Am I misunderstanding what you mean?

Should it only be just after a (? There are other situations, e.g. s.decode('utf-8', ...now I want to know what the options are for the second argument. Similarly for something like compile(). Is it possible to bring up a tooltip any time we're between brackets for a function call, or if there's an unmatched open-bracket before the cursor position?

@Carreau
Owner

@takluyver
About completion on kwarg, I didn't knew there was some ... but you are right, if I try :

>>> rang<tab> # complete to range

but

>>> hist( x , rang<tab> # propose `range` and `range=`

I have done tooltip which activate only when the cursor is just after an opening bracket , matched or unmatched, with characters after the cursor or not.
Tooltip can be triggerd by :

  • Pressing ( , they apear after XXX ms if no keys are pressed in between. (my first implementation cc48f13 )
  • Pressing and the character just befor the cursor is (, (otherwise clasical completion) ( added in 20d764f )

We could try to improve when tooltip can be called, by playing with regular expression. I'm for now matching to something like /[a-zA-Z_.]\+\(?$/ and also have hard coded a if( pre_cursor.substr(-1) === '(' ).
Ways of improving I see might be :

  • Fallback to tooltip if kernel response on completion is empty (too complicated I think)
  • Don't autocomplete if caracter before cursor is whitespace fallback on tooltip (My favorite choice)
  • Promote completion ending with = (kwarg completion) to the top of completer

We can think of a lot of other option, like keeping the tooltip open as long as the user hasn't press) or other, but i don't think the tooltip can really replace something like foo? without becoming over complicated for a not so obvious advantage as you most of the time don't use kwargs you don't know about...

For now, I'm refactoring the current completer, by adding an "as you type" filter, I can try to "merge" it with the tooltip, with something which look like a completer but with the docstring and definition of the 'best match'

@Carreau
Owner

e999f09 improve tooltip triggering
@takluyver
you can now do somthing like

s.decode('utf-8',[space][tab] #to get the tooltip (on s.decode) while
s.decode('utf-8',[tab] # will get you the completer with the whole namespace and
s.decode('utf-8',[spaceOrNot][anyletter][tab] # will also get you a completer

commit message give some example. If someone is better than me at RegExp we can shorten up a few line of code with a while loop in codecell.js

@takluyver
Owner
@Carreau
Owner

That should be possible, but as for now, trying to complete after a white space always give the same list :

  • %alias
  • %autocall
  • %automagic
  • ... Which I don't think is verry usefull. Prompting the user with both the tooltip and the completion as long as the completion is not smarter is IMHO useless. I'll try to get a completion that give you only the kwargs and then maybe merge it in the tooltip.
@takluyver
Owner
@Carreau
Owner

For most of the cases it is not that hard, just have to find the previous non-matched opening bracket and request info on what is before, all done inside the request_tooltip function.
Of course if you start to have some kwarg=')' inside function call it start to be difficult, but that' an edge case...

Having a completer that gives kwargs completion first is really easy, just 10 line of js in the right place... merging it with tooltip start to be annoying as you have to handle both completion_request and object_info_request to merge the result before showing smth to the user...

@takluyver
Owner
@Carreau
Owner

no problem. Issues are that both completion's and tooltip's divs are positionned absolutly, so will overlap, and the completer's select take focus which will prevent the tooltip from beeing dissmissed when the completer give focus back to code mirror.
In the end it might be possible, but the code will be too complicated (at least I think so...). If we really want something that handle a little of completion and a little of tooltip the resonable way is to create a uniq object which deal with all the logic of completion_request/reply and object_info_request/reply

That my opinion,but it's only my 3rd real day of implementing smth in JS which is not just opennig firebug and messing with live code in the debugger...

BTW, do you have any idea of how I can make the all things configurable (time for tooltip and so on ) or to who I need to ask. I have looked at the documentation, but I dont find how to add a configurable for the NotebookWebApp, I did came across c.NotebookApp.password but I wasn't able to follow the track well enough to understand ...

@takluyver
Owner
@Carreau
Owner

No problem, I spend time also because I feel the utility for me also to have such a feature.

@minrk , could you help on how to have a variable going from ipython_notebook_config.py to somwhere in the notebook ?
I intend to add configuration like c.NotebookApp.time_before_tooltip , c.NotebookApp.activate_tooltip_on_tab_press and maybe some other.

@minrk
Owner

Currently no config is forwarded to the browser, yet. Our config is JSON-able, so we should be able to send the whole thing (or any subset) just fine.

Perhaps what makes the most sense, is to create a new Configurable, for purely frontend options, and send just that subset of config.

@Carreau
Owner

Hum, that seem a little to much for now. I have a working copie of a configurable thing with a "configuration" panel below the "help" panel on the left side .. one big commit though, with "tooltip when pressing tabs","time before tooltip appear" and an option to get the kwags first with the completer.
I'll try to give a look at interactive rebasing to get cleaner commits and push soon.
Exchanging the configuration with the web server can come later.

thank you

@Carreau
Owner

Hum, just had the chance to test my code on debian/firefox.
it seem to me that some keycode differs from my mac laptop "(" = 53 on chrome mac, "("=57 on debian firefox ... is it normal or is it because i have a french keyboard ?
How do I get around that? Can it interfear in other cases ?

@Carreau
Owner

Hi,
I've reworked the order of the comits and merge some together then done a force pushed.
I have added a pannel on the Left pannel to change the setting (we can hide it by default)
make the 3 following things configurable :

  • Time before tooltip apear when '(' is pressed.
  • Wether to try or not showing tooltip when is pressed
  • Wether trying to put the kwargs first with the completer

I changed the logic for trigering on '(' from keydown to keypress because with keydown the value might change depending on keyboard layout/system
Could you please test it and told me if the tooltip is triggering after some time when pressing ( on different platform/browser/keyboard layout ?

@Carreau
Owner

Ah, sorry I accidentally pushed a commit from another branch (trying to autotest the copyright date in every .py files)
so I force pushed a branch rebased on a slightly more recent master...
By the way I had once to tooltip at the same time, but wasn't able to reproduce it since then... Did anybody saw it also ?
(It might have been because it was on the last no-so-stable dev of Chrome)

@fperez
Owner

I love it!! Great job, many thanks.

Some feedback before we merge it (but rest assured, this is definitely going in, it's awesome :)

  • could you add a little 'close' button on the top-right of the tooltip so it can be closed with the mouse?

  • can the tooltip be made resizeable?

I think the ideal toolbar would be both resizeable and with a scroll bar. This way, people can leave it alone when reading, but could resize it if they need to see a little more, or scroll down to copy an example.

@Carreau
Owner

-close button I can take a look
-resizable that start to be out of my reach as I have no Idea how I can do that. It certainly can be made resizable through some jquerry that look like magic to me.

What I can try for the time beeing are more.. and close href link with more... bringing the pager up filled with the help, and closeclosing it

@fperez
Owner

If a scroll bar is possible that would help, even if it's not resizeable. And if resizing is hard, then the more... button leading to the help pager is definitely the way to go. Especially because it gives users a way to get the help pager open when they're in the middle of editing a cell (right now they have to go up/down, make a new cell, type foo? and go back to where they were editing).

Thanks! This is great work, much appreciated.

@Carreau
Owner

@fperez
Done, 2 buttons just after where the docstring is cut.
not really happy with the fact that I have to reexecute foo? which increse the prompt number.
the other way around is to totally re-parse the object_info_request to display it in the pager... that will be annoying to maintain two way of displaying the same info in the same place depending on how you get there

Will take a look for the scrollbar.

@Carreau
Owner

@fperez ,
Improve the things a little, ie button on the upper right corner, made some concessions for the scrollbar appearance
can easily be changed

Cherry on the cake, if the completer get only one result, and the same, 3 time in a row, (meaning the completion is done) it shows a tooltip

@fperez
Owner

A few cosmetic comments and then a more serious issue:

  1. Could the buttons be put so that the tooltip doesn't need to be made so wide? I imagine you did this b/c they'd overlap with the text...

So here's a suggestion: for expand and pager, put them at the bottom in a separate div, so the tooltip box can stay as it was. And the close one could go on the top-right, alone, and be an X in a square box instead of a full button. There are lots of little 'x' icons on the net that can be used, here's one for example: close icon (could be made dark gray instead of blue to match the rest of our colors).

  1. Serious problem: I can trivially lock up the notebook by requesting the same tooltip twice. Try this:
some_function( <tab> <esc> <tab>

The second tab doesn't produce a tooltip, and now the kernel won't accept any further input.

I don't know if this is a kernel or frontend issue, but we need to sort it out before merging.

@Carreau
Owner
 `some_function( <tab> <esc> <tab>`

That works for me on Chrome and Safari even 10 times in a row ... I really don't understand why it would make the connexion crash, but i'll try to find a debian to test it. But true, it doen't work on FF, i'll investigate.

(could be made dark gray instead of blue to match the rest of our colors).

They should be (like that)... could you send me a screenshot ?

Could the buttons be put so that the tooltip doesn't need to be made so wide.

Yes they are floating-right, no problem, but I choose the tooltip "max-width" so that the text of the doc would generally no be wraped and if it touch the right border of the screen it should shrink

[...] put them at the bottom in a separate div, so the tooltip box can stay as it was

They will be in the css "overflow area" ant might not always be visible but i'll try to find a way.

@Carreau
Owner

@fperez
That's actually an issues on master for me (ddb3a0f):
open a notebook, press <esc>

La connexion avec ws://127.0.0.1:8888/kernels/13ca3b5b-efb6-4055-890b-61ebec1b015e/iopub a été interrompue pendant le chargement de la page.
this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
kernel.js (ligne 125)
La connexion avec ws://127.0.0.1:8888/kernels/13ca3b5b-efb6-4055-890b-61ebec1b015e/shell a été interrompue pendant le chargement de la page.
this.shell_channel = new this.WebSocket(ws_url + "/shell"); 

i'll open an issue,only on FF

IPython/frontend/html/notebook/static/js/notebook.js
((23 lines not shown))
};
Notebook.prototype.handle_payload = function (cell, payload) {
var l = payload.length;
+ console.log(payload);
@stefanv
stefanv added a note

Forgotten debug?

@Carreau Owner
Carreau added a note

Yes, missed that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/html/notebook/static/js/kernel.js
@@ -170,6 +170,15 @@ var IPython = (function (IPython) {
};
};
+ Kernel.prototype.object_info_request = function (objname) {
+ var content = {
+ oname : objname.toString(),
@stefanv
stefanv added a note

Careful here; objname can be none. E.g., tab-complete on

foo((
@Carreau Owner
Carreau added a note

hum... to I just stay silent in that case, or shall I try to match on foo ?

I'll also add a test to be sure objname is not null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/html/notebook/static/css/notebook.css
((19 lines not shown))
+/*"close" "expand" and "Open in pager button" of
+/* the tooltip*/
+.tooltip a{
+ float:right;
+}
+
+/*properties of tooltip after "expand"*/
+.bigtooltip{
+ height:420px;
+}
+
+/*properties of tooltip before "expand"*/
+.smalltooltip{
+ text-overflow: ellipsis;
+ overflow: hidden;
+ height:100px;
@stefanv
stefanv added a note

The height, by default, seems very small on most systems?

Maybe set it relative to the height of the main editor?

@Carreau Owner
Carreau added a note

That's a good idea, I'm still wondering what we get on really huge screen...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stefanv stefanv commented on the diff
IPython/frontend/html/notebook/static/js/codecell.js
((7 lines not shown))
+ // 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;
+ name=reply.name;
+
+ var that = this;
+ var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
+ // remove to have the tooltip not Limited in X and Y
+ tooltip.addClass('smalltooltip');
+ var pre=$('<pre/>').html(utils.fixConsole(docstring));
@stefanv
stefanv added a note

Careful: docstring may be empty, e.g.

def foo():
    ""
    return 1
@Carreau Owner
Carreau added a note

right, missed the empty docstring... I was thinking it would reply <no docstring>...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/html/notebook/static/js/codecell.js
((103 lines not shown))
if (!this.is_completing || matches.length === 0) {return;}
+ //try to check if the user is typing tab at least twice after a word
+ // and completion is "done"
+ fallback_on_tooltip_after=2
+ if(matches.length==1 && matched_text === matches[0])
+ {
+ if(this.npressed >fallback_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 undersand that there is no (more) completion for that !');
@stefanv
stefanv added a note

"understand"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/html/notebook/static/js/codecell.js
((103 lines not shown))
if (!this.is_completing || matches.length === 0) {return;}
+ //try to check if the user is typing tab at least twice after a word
+ // and completion is "done"
+ fallback_on_tooltip_after=2
+ if(matches.length==1 && matched_text === matches[0])
+ {
+ if(this.npressed >fallback_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 undersand that there is no (more) completion for that !');
+ console.log("I'll show you the tooltip, will you stop bothering me ?");
@stefanv
stefanv added a note

:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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 yuo press <Tab> after "(" or a white space');
@stefanv
stefanv added a note

"you"

Do we want to have this panel at all? The defaults seem pretty reasonable, and this space is very limited. This topic probably warrants a bigger discussion on client configuration. If we do want configuration, it may be better to have a little gear and a pop-up box like Google does.

@Carreau Owner
Carreau added a note

We can just add a visibility : hidden in css, the thing is that the configuration logic is there if we want to bind it later to save the user preferences. And for example Minrk and fperez have 2 totally different view on when a tooltip should appear.

We can also have "hidden preferences“ with one-liner js command that change the notebook behaviour, and put a cookbook somewhere in the doc otherwise

Will try to correct the you but I was certain to already have done this one...

Thanks for all the feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stefanv

This is a great addition, thanks!

Carreau added some commits
@Carreau Carreau Add Tootip to notebook.
	When user press '(' and nothing for 1200ms, 'object_info_request' sent to
	the kernel. Then tooltip with 'definition' and beggining of 'docstring'
	 shown to the user if non empty response.

	Unlike Completion <select> , <div> is not focusable so event handled in main
	cell event handeling function

	Add some CSS3 that most browser with websocket should support.
7155489
@Carreau Carreau tooltip on <tab> c06eaa6
@Carreau Carreau Improve tooltip tringgering,make it configurable
	As until now, when pressing tab and a white space was preceding the cursor
	The completion was triggerd with the whole namespace in it. Now if a
	whitespace or an opening bracket is just befor the cursor it will try to
	display a tooltip. The logic to find what object_info_request is send have
	been sightly changed to try to match the expression just before the last
	unmached openig bracket before the cursor (without considering what is
	after the cursor).

	example (_|_ represent the cursor):
	>>> his_|_<tab> # completion
	>>> hist(_|_<tab> # tooltip on hist
	>>> hist(rand(20),bins=range(_|_ <tab> #tooltip on range
	>>> hist(rand(20),bins=range(10), _|_ <tab> # tooltip on hist (whitespace before cursor)
	>>> hist(rand(20),bins=range(10),_|_ <tab> # completion

	as we dont care of what is after the cursor:

	>>> hist(rand(5000), bins=50, _|_orientaion='horizontal') # and tab, equivalent to
	>>> hist(rand(5000), bins=50, _|_<tab> # onte the space again
	>>> hist(_|_rand(5000), bins=50, orientaion='horizontal') # and tab, equivalent to
	>>> hist(_|_

	the 4 give tooltip on hist

	note that you can get tooltip on things that aren't function by appending a
	'(' like

	>>> matplotlib(<tab>

	Which is kinda weird... so we might want to bound another shortcut for
	tooltip, but which matches without bracket...

	additionnaly I have added a "Config" pannel in the left pannel with a checkbox
	bind to wether or not activate this functionnality

	Note, (rebase and edited commit, might not work perfetly xwithout the following ones)
f73c6ce
@Carreau Carreau Make the time before activating a tooltip configurable
	add a section in the left pannel of the notebook to make the time before
	triggering a tooltip when pressign "(" configurable.  Negative values will
	disable the tooltip (comparaison at each keypress for now, but can be
	improved) The syle of the <input> field should be a little improve, why not
	a slider with jquerry
d388be1
@Carreau Carreau fix timebeforetooltip span and css d31d3cf
@Carreau Carreau smart kwarg completion
	change order of completion element so that the ones ending with '='
	will be at the beginning of the list. When you complete inside a
	fonction call, you then have kwargs first add configuration in the left
	pannel
5c61534
@Carreau Carreau Replace trigering tooltip for cross platform indep
	For opening bracket '(' keyCode/keydown is not sufficient because it depends
	on keyboard layout so rely on charcode/keypress ...
cc20def
@Carreau Carreau add 'more...' and 'close' button to the pager
	'more...' button execute 'foo?' **On the behalf of the current cell**
	which increase it's prompt number to the curent value, and give focus back
bf70f08
@Carreau Carreau improve tooltip
	put "close" and "more..." button in upper right corner,
	rename "more..." to "open in pager", add expand button.

	tooltip don't have scroll bar an are 'small' until pressing "Expand"
	they then change their height and add scrollbars.
	Horizontal scrollbar behaviour seem to depend on the browser
	>>> plot(<tab> and then click on "expand" :
	Firefox : no horizontal scrollbar (wrap line ?)
	Chrome  : horizontal scrollbar

	beware that don't limitting tooltip height or width through css will sometime give
	tooltip that are too heigh and will overflow below the bottom of the screen

	animation are broken with max-height and/or min-height
643700b
@Carreau Carreau show tooltip if completer 'fail' afer n attempt
	if completer get only one match back and the user press tab n time,
	it fallbacks on showing a tooltip

	n=2 , hard coded for know, and completer verbose on the command line
296d02b
@Carreau Carreau Change tooltip button to use jquery css 9ecce42
@Carreau Carreau handle null objectname on tooltip 11f0863
@Carreau Carreau tooltip height in % ed10bd9
@Carreau Carreau handle empty docstring baf40ab
@Carreau Carreau fix 2 typos 0bdcdf1
@Carreau
Owner

@stefanv,
Thank you again for the feedback. I'm still quite not happy with the tooltip height, but having them too high cut the lower part when you are editing things on a cell near the bottom of the screen. I don't think there is a good way to handle that in css, somethink like 'until 200px from the bottom of the screen' or else. It should be do-able in js but tooltip are no replacement for real (foo?) help, and I don't want to put too much effort 'just' for this.

@fperez
Are the top right icons (^/+/x , for pager/expand/close) ok with you ? They should match JQuery theming as I uses jquery css classes to get them.

rebased and pushed, as usual.

@fperez
Owner

This is looking great. In the interest of moving forward, I'm going to merge it now; I think we've mostly converged on something pretty good, and it can (and will) always be fine-tuned further in use. But I don't want to hold things forever on very small details anymore.

@Carreau, thanks for the great work, this is an excellent feature. And thanks everyone for the careful reviewing!

@fperez
Owner

In fact, the positioning of the tooltip when expanded at the bottom of the screen isn't perfect, but hopefully later someone who knows lots of js/css tricks can help us with that.

@fperez fperez merged commit a8ed50a into ipython:master
@fperez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 24, 2011
  1. @Carreau

    Add Tootip to notebook.

    Carreau authored
    	When user press '(' and nothing for 1200ms, 'object_info_request' sent to
    	the kernel. Then tooltip with 'definition' and beggining of 'docstring'
    	 shown to the user if non empty response.
    
    	Unlike Completion <select> , <div> is not focusable so event handled in main
    	cell event handeling function
    
    	Add some CSS3 that most browser with websocket should support.
  2. @Carreau

    tooltip on <tab>

    Carreau authored
  3. @Carreau

    Improve tooltip tringgering,make it configurable

    Carreau authored
    	As until now, when pressing tab and a white space was preceding the cursor
    	The completion was triggerd with the whole namespace in it. Now if a
    	whitespace or an opening bracket is just befor the cursor it will try to
    	display a tooltip. The logic to find what object_info_request is send have
    	been sightly changed to try to match the expression just before the last
    	unmached openig bracket before the cursor (without considering what is
    	after the cursor).
    
    	example (_|_ represent the cursor):
    	>>> his_|_<tab> # completion
    	>>> hist(_|_<tab> # tooltip on hist
    	>>> hist(rand(20),bins=range(_|_ <tab> #tooltip on range
    	>>> hist(rand(20),bins=range(10), _|_ <tab> # tooltip on hist (whitespace before cursor)
    	>>> hist(rand(20),bins=range(10),_|_ <tab> # completion
    
    	as we dont care of what is after the cursor:
    
    	>>> hist(rand(5000), bins=50, _|_orientaion='horizontal') # and tab, equivalent to
    	>>> hist(rand(5000), bins=50, _|_<tab> # onte the space again
    	>>> hist(_|_rand(5000), bins=50, orientaion='horizontal') # and tab, equivalent to
    	>>> hist(_|_
    
    	the 4 give tooltip on hist
    
    	note that you can get tooltip on things that aren't function by appending a
    	'(' like
    
    	>>> matplotlib(<tab>
    
    	Which is kinda weird... so we might want to bound another shortcut for
    	tooltip, but which matches without bracket...
    
    	additionnaly I have added a "Config" pannel in the left pannel with a checkbox
    	bind to wether or not activate this functionnality
    
    	Note, (rebase and edited commit, might not work perfetly xwithout the following ones)
  4. @Carreau

    Make the time before activating a tooltip configurable

    Carreau authored
    	add a section in the left pannel of the notebook to make the time before
    	triggering a tooltip when pressign "(" configurable.  Negative values will
    	disable the tooltip (comparaison at each keypress for now, but can be
    	improved) The syle of the <input> field should be a little improve, why not
    	a slider with jquerry
  5. @Carreau
  6. @Carreau

    smart kwarg completion

    Carreau authored
    	change order of completion element so that the ones ending with '='
    	will be at the beginning of the list. When you complete inside a
    	fonction call, you then have kwargs first add configuration in the left
    	pannel
  7. @Carreau

    Replace trigering tooltip for cross platform indep

    Carreau authored
    	For opening bracket '(' keyCode/keydown is not sufficient because it depends
    	on keyboard layout so rely on charcode/keypress ...
  8. @Carreau

    add 'more...' and 'close' button to the pager

    Carreau authored
    	'more...' button execute 'foo?' **On the behalf of the current cell**
    	which increase it's prompt number to the curent value, and give focus back
  9. @Carreau

    improve tooltip

    Carreau authored
    	put "close" and "more..." button in upper right corner,
    	rename "more..." to "open in pager", add expand button.
    
    	tooltip don't have scroll bar an are 'small' until pressing "Expand"
    	they then change their height and add scrollbars.
    	Horizontal scrollbar behaviour seem to depend on the browser
    	>>> plot(<tab> and then click on "expand" :
    	Firefox : no horizontal scrollbar (wrap line ?)
    	Chrome  : horizontal scrollbar
    
    	beware that don't limitting tooltip height or width through css will sometime give
    	tooltip that are too heigh and will overflow below the bottom of the screen
    
    	animation are broken with max-height and/or min-height
  10. @Carreau

    show tooltip if completer 'fail' afer n attempt

    Carreau authored
    	if completer get only one match back and the user press tab n time,
    	it fallbacks on showing a tooltip
    
    	n=2 , hard coded for know, and completer verbose on the command line
  11. @Carreau
  12. @Carreau
  13. @Carreau

    tooltip height in %

    Carreau authored
  14. @Carreau

    handle empty docstring

    Carreau authored
  15. @Carreau

    fix 2 typos

    Carreau authored
This page is out of date. Refresh to see the latest.
View
71 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; }
View
1  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 () {};
View
159 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="<empty docstring>"};
+ name=reply.name;
+
+ var that = this;
+ var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
+ // remove to have the tooltip not Limited in X and Y
+ tooltip.addClass('smalltooltip');
+ var pre=$('<pre/>').html(utils.fixConsole(docstring));
@stefanv
stefanv added a note

Careful: docstring may be empty, e.g.

def foo():
    ""
    return 1
@Carreau Owner
Carreau added a note

right, missed the empty docstring... I was thinking it would reply <no docstring>...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ var expandlink=$('<a/>').attr('href',"#");
+ expandlink.addClass("ui-corner-all"); //rounded corner
+ expandlink.attr('role',"button");
+ //expandlink.addClass('ui-button');
+ //expandlink.addClass('ui-state-default');
+ var expandspan=$('<span/>').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=$('<a/>').attr('href',"#");
+ morelink.attr('role',"button");
+ morelink.addClass('ui-button');
+ //morelink.addClass("ui-corner-all"); //rounded corner
+ //morelink.addClass('ui-state-default');
+ var morespan=$('<span/>').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=$('<a/>').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=$('<span/>').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= $('<pre/>').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;i<matches.length; ++i){
+ if(matches[i].substr(-1) === '='){
+ kwargs.push(matches[i]);
+ }else{other.push(matches[i]);}
+ }
+ newm = kwargs.concat(other);
+ matches=newm;
+ }
if (!this.is_completing || matches.length === 0) {return;}
+ //try to check if the user is typing tab at least twice after a word
+ // and completion is "done"
+ fallback_on_tooltip_after=2
+ if(matches.length==1 && matched_text === matches[0])
+ {
+ if(this.npressed >fallback_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;
View
13 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,
View
1  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');
View
61 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;
View
1  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
View
46 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 <Tab> 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;
View
26 IPython/frontend/html/notebook/templates/notebook.html
@@ -251,6 +251,32 @@
</div>
</div>
+ <div id="config_section">
+ <div class="section_header">
+ <h3>Config</h3>
+ </div>
+ <div class="section_content">
+ <div class="section_row">
+ <span id="tooltipontab_span">
+ <input type="checkbox" id="tooltipontab" checked="true"></input>
+ </span>
+ <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
+ </div>
+ <div class="section_row">
+ <span id="smartcompleter_span">
+ <input type="checkbox" id="smartcompleter" checked="true"></input>
+ </span>
+ <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
+ </div>
+ <div class="section_row">
+ <span id="timebeforetooltip_span">
+ <input type="text" id="timebeforetooltip" value="1200"></input>
+ </span>
+ <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
+ </div>
+ </div>
+ </div>
+
</div>
<div id="left_panel_splitter"></div>
<div id="notebook_panel">
Something went wrong with that request. Please try again.