Skip to content

Loading…

Completer usability 2 (rebased of pr #1082) #1108

Merged
merged 5 commits into from

3 participants

@Carreau

rebased version of #1082 plus one fix.
ment to fix #1080
short recap:

  • dismiss the completer on .[]{}()...
  • continue to type in codemirror
  • space around =
  • uniformise behaviour between FF and chrome
@fperez
IPython member

I'll test this now, but as a general comment, it's better not to close a PR and open a new one just b/c of a rebase: that breaks the discussion flow of the old one, people who were already being notified stop being so, etc. The only real reason for closing a PR and starting a new one is if a completely new approach is required, and the old branch is more or less abandoned. But since in this case you're just rebasing and continuing, there's really no need for a whole new PR.

Having said that, don't worry this time: let's continue here. I just mention it for future cases.

@fperez
IPython member

OK, behavior-wise this is perfect. Awesome job!

I'd like @minrk to have a quick look at the JS as well, since he has by now picked up better JS skills than mine. I'm still going to review the code, but his eyes are better than mine :)

But unless we spot some minor fixes needed in terms of code/implementation, this is the functionality we needed. Many thanks for the great work.

I've checked on both Chrome and Firefox (on linux) and everything looks good.

@fperez fperez commented on an outdated diff
IPython/frontend/html/notebook/static/js/codecell.js
((11 lines not shown))
isCompSymbol : function (code)
- {return ((code>64 && code <=122)|| code == 189)}
+ {
+ return (code > 64 && code <= 90)
+ || (code >= 97 && code <= 122)
+ || (code == 95)
+ },
+ dismissAndAppend : function (code)
+ {
+ chararr = ['(',')','[',']','+','-','/','\\','.',' '];
@fperez IPython member
fperez added a note

Just for readability, use spaces around the commas here so it's easier to see what each element is:

chararr = [ '(', ')', '[', ']', '+', '-', '/', '\\', '.', ' ' ];
@minrk IPython member
minrk added a note

or better yet, avoid all those quotes with str.split:

chararr = "()[]+-/\. ".split("")

@fperez IPython member
fperez added a note

nice, I didn't know JS had split too :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fperez fperez commented on an outdated diff
IPython/frontend/html/notebook/static/js/codecell.js
((11 lines not shown))
isCompSymbol : function (code)
- {return ((code>64 && code <=122)|| code == 189)}
+ {
+ return (code > 64 && code <= 90)
+ || (code >= 97 && code <= 122)
+ || (code == 95)
+ },
+ dismissAndAppend : function (code)
+ {
+ chararr = ['(',')','[',']','+','-','/','\\','.',' '];
+ codearr = chararr.map(function(x){return x.charCodeAt(0)});
@fperez IPython member
fperez added a note

Is it necessary to do this dynamically or would it be cleaner/simpler to simply put the static code list in there manually? I just don't know enough about JS to tell...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fperez fperez commented on an outdated diff
IPython/frontend/html/notebook/static/js/codecell.js
((11 lines not shown))
isCompSymbol : function (code)
- {return ((code>64 && code <=122)|| code == 189)}
+ {
+ return (code > 64 && code <= 90)
+ || (code >= 97 && code <= 122)
+ || (code == 95)
+ },
+ dismissAndAppend : function (code)
+ {
+ chararr = ['(',')','[',']','+','-','/','\\','.',' '];
+ codearr = chararr.map(function(x){return x.charCodeAt(0)});
+ return jQuery.inArray(code, codearr) != -1;
@fperez IPython member
fperez added a note

Is there no standard JS functionality to do this so that we need to use jQuery for it?

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

I tested changing also the background of the fixed part to a different color (gray), and it doesn't really look very good. Your approach of using just boldface is nice and clean.

I reviewed the code and other than some small questions and quick fixes, it looks good to me. Once we also have @minrk's eyes on it and you address those small points, this can go in.

@Carreau

To respond to your questions :

  • I'll add spaces
  • yes that can be made static, but I preferd readability. I already made enough mistake with keycode/keypress value to think that's worth.
  • I didn't found native Js function to do it... but if it exist it will be great

Sorry for the double PR, but the rebase had a 3 way merge and I preferd to keep the old branch, I could have pushed it on another branch and then override this one but it was too late

@fperez
IPython member
@minrk
IPython member

I'm extremely far from a js expert, but I don't see anything obvious (other than the split() mentioned above). So if the behavior is right, then we should be set.

@fperez
IPython member

Well, every bit helps, since none of us is an expert :). OK @Carreau, go ahead and fix these last few little things, and we'll merge this one!

Carreau added some commits
@Carreau Carreau usability and cross browser compat for completer
	- dissmiss the completer, append what alredy type, **Plus** one caracter in
	  some cases

	  List of special caracter that are handle are in a given list `()[]./\-+`
	  for the moment. usefull for exaple when typing :
	  >>> np.s<tab>in(
	  and not having to type '(' twice.

	  they are handle separately has the [a-zA-Z] ones because otherwise they
	  will screw up the regexp, and are opt-in to avoid bugs with invisible
	  caracters send because some browser have 'keypress' event for meta keys

	  close #1080

		Note to this commit :
	  list of test for the completer across browser with --pylab=inline flag

	  #test direct one completion
	  plt.an<tab>       -> plt.annotate

	  #test filter,tab, only one completion
	  plt.a<tab>n<tab>  -> plt.annotate

	  # test partial common beggining
	  # test dismmised if user erase
	  plt.a<tab>nn<backspace><backspace>u<tab>                -> completer to `aut`
	  ........................................<tab><tab><tab> -> nothing should append
	  .......................................................<backspace><backspace<backspace> -> completer cancelled

	  #test dismiss if no more completion
	  plt.s<tab>c  -> completer 3 choices
	  ...........u -> dismissed whith what user have typed.  `plt.scu`

	  # test dismiss in no completion, special symbol
	  # opt-in list of caracters +-/\()[].
	  np<tab>.s          -> a 'dot' sould dismiss the completer and be appended
	  .........<tab>in(  -> np.sin(
	  np.s<tab>in[       -> np.sin[
c630b34
@Carreau Carreau Apply pep8 to js e480aee
@Carreau Carreau completer update code-miror on the fly
	Following @fperez advice, change the completer apparence to avoid user confusion.

	- Append what the user type in the completer in code-miror, (Almost) as if
	  codemirror still have focus
	- distinguish between "fixed" completion  part, which was sent to the kernel
	  (now written in bold) and filtering one,handled only in JS,that the user
	  can errase without dismissing the completer

	I changed the action of <Space> to dismiss the completer with what have
	already been typed and inserting a space instead of "picking" the currently
	hilighted option

	<Escape> will still dissmiss the completer and remove everything the user as
	typed since the completer invocation

	Note that while the completer is shown, code-mirror does not show any
	blinking cursor
5f3907c
@Carreau Carreau notebook: fix, only one completion autopick 3a94f81
@Carreau Carreau notebook: code Readability. Add dismissing symbols
	* As minrk suggested, changes list of char to string.split()
	* add also a few dismissing symbold like `,` `=` and `*` for
	  the completer
b1cbd5f
@Carreau

fix, rebased and force pushed to be up to date with the recent PR merges.
I've added *,= to the list of dismissing caracters.

@fperez
IPython member

Great, merging now so we can start stabilizing things for the 0.12 RC. Excellent work.

@fperez fperez merged commit 964bfe4 into ipython:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 7, 2011
  1. @Carreau

    usability and cross browser compat for completer

    Carreau committed
    	- dissmiss the completer, append what alredy type, **Plus** one caracter in
    	  some cases
    
    	  List of special caracter that are handle are in a given list `()[]./\-+`
    	  for the moment. usefull for exaple when typing :
    	  >>> np.s<tab>in(
    	  and not having to type '(' twice.
    
    	  they are handle separately has the [a-zA-Z] ones because otherwise they
    	  will screw up the regexp, and are opt-in to avoid bugs with invisible
    	  caracters send because some browser have 'keypress' event for meta keys
    
    	  close #1080
    
    		Note to this commit :
    	  list of test for the completer across browser with --pylab=inline flag
    
    	  #test direct one completion
    	  plt.an<tab>       -> plt.annotate
    
    	  #test filter,tab, only one completion
    	  plt.a<tab>n<tab>  -> plt.annotate
    
    	  # test partial common beggining
    	  # test dismmised if user erase
    	  plt.a<tab>nn<backspace><backspace>u<tab>                -> completer to `aut`
    	  ........................................<tab><tab><tab> -> nothing should append
    	  .......................................................<backspace><backspace<backspace> -> completer cancelled
    
    	  #test dismiss if no more completion
    	  plt.s<tab>c  -> completer 3 choices
    	  ...........u -> dismissed whith what user have typed.  `plt.scu`
    
    	  # test dismiss in no completion, special symbol
    	  # opt-in list of caracters +-/\()[].
    	  np<tab>.s          -> a 'dot' sould dismiss the completer and be appended
    	  .........<tab>in(  -> np.sin(
    	  np.s<tab>in[       -> np.sin[
  2. @Carreau

    Apply pep8 to js

    Carreau committed
  3. @Carreau

    completer update code-miror on the fly

    Carreau committed
    	Following @fperez advice, change the completer apparence to avoid user confusion.
    
    	- Append what the user type in the completer in code-miror, (Almost) as if
    	  codemirror still have focus
    	- distinguish between "fixed" completion  part, which was sent to the kernel
    	  (now written in bold) and filtering one,handled only in JS,that the user
    	  can errase without dismissing the completer
    
    	I changed the action of <Space> to dismiss the completer with what have
    	already been typed and inserting a space instead of "picking" the currently
    	hilighted option
    
    	<Escape> will still dissmiss the completer and remove everything the user as
    	typed since the completer invocation
    
    	Note that while the completer is shown, code-mirror does not show any
    	blinking cursor
  4. @Carreau
  5. @Carreau

    notebook: code Readability. Add dismissing symbols

    Carreau committed
    	* As minrk suggested, changes list of char to string.split()
    	* add also a few dismissing symbold like `,` `=` and `*` for
    	  the completer
View
5 IPython/frontend/html/notebook/static/css/notebook.css
@@ -419,6 +419,11 @@ div.text_cell_render {
min-height:50px;
}
+/*fixed part of the completion*/
+.completions p b{
+ font-weight:bold;
+}
+
.completions p{
background: #DDF;
/*outline: none;
View
127 IPython/frontend/html/notebook/static/js/codecell.js
@@ -218,7 +218,7 @@ var IPython = (function (IPython) {
tooltip.append(expandlink);
tooltip.append(morelink);
if(defstring){
- defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
+ defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
tooltip.append(defstring_html);
}
tooltip.append(pre);
@@ -241,12 +241,23 @@ var IPython = (function (IPython) {
var key = { tab:9,
esc:27,
backspace:8,
- space:13,
+ space:32,
shift:16,
- enter:32,
- // _ is 189
+ enter:13,
+ // _ is 95
isCompSymbol : function (code)
- {return ((code>64 && code <=122)|| code == 189)}
+ {
+ return (code > 64 && code <= 90)
+ || (code >= 97 && code <= 122)
+ || (code == 95)
+ },
+ dismissAndAppend : function (code)
+ {
+ chararr = '()[]+-/\\. ,=*'.split("");
+ codearr = chararr.map(function(x){return x.charCodeAt(0)});
+ return jQuery.inArray(code, codearr) != -1;
+ }
+
}
// smart completion, sort kwarg ending with '='
@@ -255,25 +266,26 @@ var IPython = (function (IPython) {
{
kwargs = new Array();
other = new Array();
- for(var i=0;i<matches.length; ++i){
+ 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;
+ matches = newm;
}
// end sort kwargs
// give common prefix of a array of string
function sharedStart(A){
+ if(A.length == 1){return A[0]}
if(A.length > 1 ){
- var tem1, tem2, s, A= A.slice(0).sort();
- tem1= A[0];
- s= tem1.length;
- tem2= A.pop();
- while(s && tem2.indexOf(tem1)== -1){
- tem1= tem1.substring(0, --s);
+ var tem1, tem2, s, A = A.slice(0).sort();
+ tem1 = A[0];
+ s = tem1.length;
+ tem2 = A.pop();
+ while(s && tem2.indexOf(tem1) == -1){
+ tem1 = tem1.substring(0, --s);
}
return tem1;
}
@@ -283,8 +295,8 @@ var IPython = (function (IPython) {
//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])
+ fallback_on_tooltip_after = 2
+ if(matches.length == 1 && matched_text === matches[0])
{
if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
{
@@ -294,13 +306,13 @@ var IPython = (function (IPython) {
this.request_tooltip_after_time(matched_text+'(',0);
return;
}
- this.prevmatch=matched_text
- this.npressed=this.npressed+1;
+ this.prevmatch = matched_text
+ this.npressed = this.npressed+1;
}
else
{
- this.prevmatch="";
- this.npressed=0;
+ this.prevmatch = "";
+ this.npressed = 0;
}
// end fallback on tooltip
//==================================
@@ -313,23 +325,29 @@ var IPython = (function (IPython) {
var close = function () {
if (done) return;
done = true;
- if (complete!=undefined)
+ if (complete != undefined)
{complete.remove();}
that.is_completing = false;
that.completion_cursor = null;
};
- // insert the given text and exit the completer
- var insert = function (selected_text, event) {
+ // update codemirror with the typed text
+ prev = matched_text
+ var update = function (inserted_text, event) {
that.code_mirror.replaceRange(
- selected_text,
+ inserted_text,
{line: cur.line, ch: (cur.ch-matched_text.length)},
- {line: cur.line, ch: cur.ch}
+ {line: cur.line, ch: (cur.ch+prev.length-matched_text.length)}
);
+ prev = inserted_text
if(event != null){
event.stopPropagation();
event.preventDefault();
}
+ };
+ // insert the given text and exit the completer
+ var insert = function (selected_text, event) {
+ update(selected_text)
close();
setTimeout(function(){that.code_mirror.focus();}, 50);
};
@@ -350,22 +368,23 @@ var IPython = (function (IPython) {
// Used to 'pick' when pressing tab
if (matches.length < 1) {
insert(typed_text,event);
- if(event !=null){
+ if(event != null){
event.stopPropagation();
event.preventDefault();
}
- } else if (autopick && matches.length==1) {
+ } else if (autopick && matches.length == 1) {
insert(matches[0],event);
- if(event !=null){
+ if(event != null){
event.stopPropagation();
event.preventDefault();
}
}
//clear the previous completion if any
+ update(typed_text,event);
complete.children().children().remove();
- $('#asyoutype').text(typed_text);
- select=$('#asyoutypeselect');
- for (var i=0; i<matches.length; ++i) {
+ $('#asyoutype').html("<b>"+matched_text+"</b>"+typed_text.substr(matched_text.length));
+ select = $('#asyoutypeselect');
+ for (var i = 0; i<matches.length; ++i) {
select.append($('<option/>').html(matches[i]));
}
select.children().first().attr('selected','true');
@@ -374,7 +393,7 @@ var IPython = (function (IPython) {
// create html for completer
var complete = $('<div/>').addClass('completions');
complete.attr('id','complete');
- complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
+ complete.append($('<p/>').attr('id', 'asyoutype').html('<b>fixed part</b>user part'));//pseudo input field
var select = $('<select/>').attr('multiple','true');
select.attr('id', 'asyoutypeselect')
@@ -392,25 +411,31 @@ var IPython = (function (IPython) {
// So a first actual completion. see if all the completion start wit
// the same letter and complete if necessary
fastForward = sharedStart(matches)
- typed_characters= fastForward.substr(matched_text.length);
+ typed_characters = fastForward.substr(matched_text.length);
complete_with(matches,matched_text+typed_characters,true,null);
- filterd=matches;
+ filterd = matches;
// Give focus to select, and make it filter the match as the user type
// by filtering the previous matches. Called by .keypress and .keydown
var downandpress = function (event,press_or_down) {
var code = event.which;
var autopick = false; // auto 'pick' if only one match
if (press_or_down === 0){
- press=true; down=false; //Are we called from keypress or keydown
+ press = true; down = false; //Are we called from keypress or keydown
} else if (press_or_down == 1){
- press=false; down=true;
+ press = false; down = true;
}
if (code === key.shift) {
// nothing on Shift
return;
}
- if (code === key.space || code === key.enter) {
- // Pressing SPACE or ENTER will cause a pick
+ if (key.dismissAndAppend(code) && press) {
+ var newchar = String.fromCharCode(code);
+ typed_characters = typed_characters+newchar;
+ insert(matched_text+typed_characters,event);
+ return
+ }
+ if (code === key.enter) {
+ // Pressing ENTER will cause a pick
event.stopPropagation();
event.preventDefault();
pick();
@@ -418,35 +443,41 @@ var IPython = (function (IPython) {
// We don't want the document keydown handler to handle UP/DOWN,
// but we want the default action.
event.stopPropagation();
- //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
- } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
+ } else if ( (code == key.backspace)||(code == key.tab && down) || press || key.isCompSymbol(code)){
if( key.isCompSymbol(code) && press)
{
var newchar = String.fromCharCode(code);
- typed_characters=typed_characters+newchar;
+ typed_characters = typed_characters+newchar;
} else if (code == key.tab) {
fastForward = sharedStart(filterd)
ffsub = fastForward.substr(matched_text.length+typed_characters.length);
- typed_characters=typed_characters+ffsub;
- autopick=true;
- event.stopPropagation();
- event.preventDefault();
+ typed_characters = typed_characters+ffsub;
+ autopick = true;
} else if (code == key.backspace && down) {
// cancel if user have erase everything, otherwise decrease
// what we filter with
+ event.preventDefault();
if (typed_characters.length <= 0)
{
insert(matched_text,event)
+ return
}
- typed_characters=typed_characters.substr(0,typed_characters.length-1);
- }else{return}
+ typed_characters = typed_characters.substr(0,typed_characters.length-1);
+ } else if (press && code != key.backspace && code != key.tab && code != 0){
+ insert(matched_text+typed_characters,event);
+ return
+ } else {
+ return
+ }
re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
filterd = matches.filter(function(x){return re.test(x)});
complete_with(filterd,matched_text+typed_characters,autopick,event);
- } else if(down){ // abort only on .keydown
+ } else if( code == key.esc) {
+ // dismiss the completer and go back to before invoking it
+ insert(matched_text,event);
+ } else if( press ){ // abort only on .keypress or esc
// abort with what the user have pressed until now
console.log('aborting with keycode : '+code+' is down :'+down);
- insert(matched_text+typed_characters,event);
}
}
select.keydown(function (event) {
Something went wrong with that request. Please try again.