Skip to content
This repository

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

Merged
merged 5 commits into from over 2 years ago

3 participants

Matthias Bussonnier Fernando Perez Min RK
Matthias Bussonnier
Collaborator

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
Fernando Perez
Owner

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.

Fernando Perez
Owner

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.

IPython/frontend/html/notebook/static/js/codecell.js
((11 lines not shown))
248 248
                     isCompSymbol : function (code)
249  
-                        {return ((code>64 && code <=122)|| code == 189)}
  249
+                        {
  250
+                        return (code > 64 && code <= 90)
  251
+                            || (code >= 97 && code <= 122)
  252
+                            || (code == 95)
  253
+                        },
  254
+                    dismissAndAppend : function (code)
  255
+                        {
  256
+                        chararr = ['(',')','[',']','+','-','/','\\','.',' '];
3
Fernando Perez Owner
fperez added a note December 06, 2011

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

chararr = [ '(', ')', '[', ']', '+', '-', '/', '\\', '.', ' ' ];
Min RK Owner
minrk added a note December 06, 2011

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

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

Fernando Perez Owner
fperez added a note December 06, 2011

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
IPython/frontend/html/notebook/static/js/codecell.js
((11 lines not shown))
248 248
                     isCompSymbol : function (code)
249  
-                        {return ((code>64 && code <=122)|| code == 189)}
  249
+                        {
  250
+                        return (code > 64 && code <= 90)
  251
+                            || (code >= 97 && code <= 122)
  252
+                            || (code == 95)
  253
+                        },
  254
+                    dismissAndAppend : function (code)
  255
+                        {
  256
+                        chararr = ['(',')','[',']','+','-','/','\\','.',' '];
  257
+                        codearr = chararr.map(function(x){return x.charCodeAt(0)});
1
Fernando Perez Owner
fperez added a note December 06, 2011

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
IPython/frontend/html/notebook/static/js/codecell.js
((11 lines not shown))
248 248
                     isCompSymbol : function (code)
249  
-                        {return ((code>64 && code <=122)|| code == 189)}
  249
+                        {
  250
+                        return (code > 64 && code <= 90)
  251
+                            || (code >= 97 && code <= 122)
  252
+                            || (code == 95)
  253
+                        },
  254
+                    dismissAndAppend : function (code)
  255
+                        {
  256
+                        chararr = ['(',')','[',']','+','-','/','\\','.',' '];
  257
+                        codearr = chararr.map(function(x){return x.charCodeAt(0)});
  258
+                        return jQuery.inArray(code, codearr) != -1;
1
Fernando Perez Owner
fperez added a note December 06, 2011

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
Fernando Perez
Owner

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.

Matthias Bussonnier
Collaborator

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

Fernando Perez
Owner
Min RK
Owner

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.

Fernando Perez
Owner

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!

added some commits December 01, 2011
Matthias Bussonnier 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
Matthias Bussonnier Apply pep8 to js e480aee
Matthias Bussonnier 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
Matthias Bussonnier notebook: fix, only one completion autopick 3a94f81
Matthias Bussonnier 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
Matthias Bussonnier
Collaborator

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

Fernando Perez
Owner

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

Fernando Perez fperez merged commit 964bfe4 into from December 06, 2011
Fernando Perez fperez closed this December 06, 2011
Fernando Perez fperez referenced this pull request from a commit January 10, 2012
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

Showing 5 unique commits by 1 author.

Dec 07, 2011
Matthias Bussonnier 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
Matthias Bussonnier Apply pep8 to js e480aee
Matthias Bussonnier 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
Matthias Bussonnier notebook: fix, only one completion autopick 3a94f81
Matthias Bussonnier 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
This page is out of date. Refresh to see the latest.
5  IPython/frontend/html/notebook/static/css/notebook.css
@@ -419,6 +419,11 @@ div.text_cell_render {
419 419
     min-height:50px;
420 420
 }
421 421
 
  422
+/*fixed part of the completion*/
  423
+.completions p b{
  424
+    font-weight:bold;
  425
+}
  426
+
422 427
 .completions p{
423 428
     background: #DDF;
424 429
     /*outline: none;
127  IPython/frontend/html/notebook/static/js/codecell.js
@@ -218,7 +218,7 @@ var IPython = (function (IPython) {
218 218
         tooltip.append(expandlink);
219 219
         tooltip.append(morelink);
220 220
         if(defstring){
221  
-            defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
  221
+            defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
222 222
             tooltip.append(defstring_html);
223 223
         }
224 224
         tooltip.append(pre);
@@ -241,12 +241,23 @@ var IPython = (function (IPython) {
241 241
         var key = { tab:9,
242 242
                     esc:27,
243 243
                     backspace:8,
244  
-                    space:13,
  244
+                    space:32,
245 245
                     shift:16,
246  
-                    enter:32,
247  
-                    // _ is 189
  246
+                    enter:13,
  247
+                    // _ is 95
248 248
                     isCompSymbol : function (code)
249  
-                        {return ((code>64 && code <=122)|| code == 189)}
  249
+                        {
  250
+                        return (code > 64 && code <= 90)
  251
+                            || (code >= 97 && code <= 122)
  252
+                            || (code == 95)
  253
+                        },
  254
+                    dismissAndAppend : function (code)
  255
+                        {
  256
+                        chararr = '()[]+-/\\. ,=*'.split("");
  257
+                        codearr = chararr.map(function(x){return x.charCodeAt(0)});
  258
+                        return jQuery.inArray(code, codearr) != -1;
  259
+                        }
  260
+
250 261
                     }
251 262
 
252 263
         // smart completion, sort kwarg ending with '='
@@ -255,25 +266,26 @@ var IPython = (function (IPython) {
255 266
         {
256 267
             kwargs = new Array();
257 268
             other = new Array();
258  
-            for(var i=0;i<matches.length; ++i){
  269
+            for(var i = 0 ; i<matches.length ; ++i){
259 270
                 if(matches[i].substr(-1) === '='){
260 271
                     kwargs.push(matches[i]);
261 272
                 }else{other.push(matches[i]);}
262 273
             }
263 274
             newm = kwargs.concat(other);
264  
-            matches=newm;
  275
+            matches = newm;
265 276
         }
266 277
         // end sort kwargs
267 278
 
268 279
         // give common prefix of a array of string
269 280
         function sharedStart(A){
  281
+            if(A.length == 1){return A[0]}
270 282
             if(A.length > 1 ){
271  
-                var tem1, tem2, s, A= A.slice(0).sort();
272  
-                tem1= A[0];
273  
-                s= tem1.length;
274  
-                tem2= A.pop();
275  
-                while(s && tem2.indexOf(tem1)== -1){
276  
-                    tem1= tem1.substring(0, --s);
  283
+                var tem1, tem2, s, A = A.slice(0).sort();
  284
+                tem1 = A[0];
  285
+                s = tem1.length;
  286
+                tem2 = A.pop();
  287
+                while(s && tem2.indexOf(tem1) == -1){
  288
+                    tem1 = tem1.substring(0, --s);
277 289
                 }
278 290
                 return tem1;
279 291
             }
@@ -283,8 +295,8 @@ var IPython = (function (IPython) {
283 295
 
284 296
         //try to check if the user is typing tab at least twice after a word
285 297
         // and completion is "done"
286  
-        fallback_on_tooltip_after=2
287  
-        if(matches.length==1 && matched_text === matches[0])
  298
+        fallback_on_tooltip_after = 2
  299
+        if(matches.length == 1 && matched_text === matches[0])
288 300
         {
289 301
             if(this.npressed >fallback_on_tooltip_after  && this.prevmatch==matched_text)
290 302
             {
@@ -294,13 +306,13 @@ var IPython = (function (IPython) {
294 306
                 this.request_tooltip_after_time(matched_text+'(',0);
295 307
                 return;
296 308
             }
297  
-            this.prevmatch=matched_text
298  
-            this.npressed=this.npressed+1;
  309
+            this.prevmatch = matched_text
  310
+            this.npressed = this.npressed+1;
299 311
         }
300 312
         else
301 313
         {
302  
-            this.prevmatch="";
303  
-            this.npressed=0;
  314
+            this.prevmatch = "";
  315
+            this.npressed = 0;
304 316
         }
305 317
         // end fallback on tooltip
306 318
         //==================================
@@ -313,23 +325,29 @@ var IPython = (function (IPython) {
313 325
         var close = function () {
314 326
             if (done) return;
315 327
             done = true;
316  
-            if (complete!=undefined)
  328
+            if (complete != undefined)
317 329
             {complete.remove();}
318 330
             that.is_completing = false;
319 331
             that.completion_cursor = null;
320 332
         };
321 333
 
322  
-        // insert the given text and exit the completer
323  
-        var insert = function (selected_text, event) {
  334
+        // update codemirror with the typed text
  335
+        prev = matched_text
  336
+        var update = function (inserted_text, event) {
324 337
             that.code_mirror.replaceRange(
325  
-                selected_text,
  338
+                inserted_text,
326 339
                 {line: cur.line, ch: (cur.ch-matched_text.length)},
327  
-                {line: cur.line, ch: cur.ch}
  340
+                {line: cur.line, ch: (cur.ch+prev.length-matched_text.length)}
328 341
             );
  342
+            prev = inserted_text
329 343
             if(event != null){
330 344
                 event.stopPropagation();
331 345
                 event.preventDefault();
332 346
             }
  347
+        };
  348
+        // insert the given text and exit the completer
  349
+        var insert = function (selected_text, event) {
  350
+            update(selected_text)
333 351
             close();
334 352
             setTimeout(function(){that.code_mirror.focus();}, 50);
335 353
         };
@@ -350,22 +368,23 @@ var IPython = (function (IPython) {
350 368
             // Used to 'pick' when pressing tab
351 369
             if (matches.length < 1) {
352 370
                 insert(typed_text,event);
353  
-                if(event !=null){
  371
+                if(event != null){
354 372
                 event.stopPropagation();
355 373
                 event.preventDefault();
356 374
                 }
357  
-            } else if (autopick && matches.length==1) {
  375
+            } else if (autopick && matches.length == 1) {
358 376
                 insert(matches[0],event);
359  
-                if(event !=null){
  377
+                if(event != null){
360 378
                 event.stopPropagation();
361 379
                 event.preventDefault();
362 380
                 }
363 381
             }
364 382
             //clear the previous completion if any
  383
+            update(typed_text,event);
365 384
             complete.children().children().remove();
366  
-            $('#asyoutype').text(typed_text);
367  
-            select=$('#asyoutypeselect');
368  
-            for (var i=0; i<matches.length; ++i) {
  385
+            $('#asyoutype').html("<b>"+matched_text+"</b>"+typed_text.substr(matched_text.length));
  386
+            select = $('#asyoutypeselect');
  387
+            for (var i = 0; i<matches.length; ++i) {
369 388
                     select.append($('<option/>').html(matches[i]));
370 389
             }
371 390
             select.children().first().attr('selected','true');
@@ -374,7 +393,7 @@ var IPython = (function (IPython) {
374 393
         // create html for completer
375 394
         var complete = $('<div/>').addClass('completions');
376 395
             complete.attr('id','complete');
377  
-        complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
  396
+        complete.append($('<p/>').attr('id', 'asyoutype').html('<b>fixed part</b>user part'));//pseudo input field
378 397
 
379 398
         var select = $('<select/>').attr('multiple','true');
380 399
             select.attr('id', 'asyoutypeselect')
@@ -392,25 +411,31 @@ var IPython = (function (IPython) {
392 411
         // So a first actual completion.  see if all the completion start wit
393 412
         // the same letter and complete if necessary
394 413
         fastForward = sharedStart(matches)
395  
-        typed_characters= fastForward.substr(matched_text.length);
  414
+        typed_characters = fastForward.substr(matched_text.length);
396 415
         complete_with(matches,matched_text+typed_characters,true,null);
397  
-        filterd=matches;
  416
+        filterd = matches;
398 417
         // Give focus to select, and make it filter the match as the user type
399 418
         // by filtering the previous matches. Called by .keypress and .keydown
400 419
         var downandpress = function (event,press_or_down) {
401 420
             var code = event.which;
402 421
             var autopick = false; // auto 'pick' if only one match
403 422
             if (press_or_down === 0){
404  
-                press=true; down=false; //Are we called from keypress or keydown
  423
+                press = true; down = false; //Are we called from keypress or keydown
405 424
             } else if (press_or_down == 1){
406  
-                press=false; down=true;
  425
+                press = false; down = true;
407 426
             }
408 427
             if (code === key.shift) {
409 428
                 // nothing on Shift
410 429
                 return;
411 430
             }
412  
-            if (code === key.space || code === key.enter) {
413  
-                // Pressing SPACE or ENTER will cause a pick
  431
+            if (key.dismissAndAppend(code) && press) {
  432
+                var newchar = String.fromCharCode(code);
  433
+                typed_characters = typed_characters+newchar;
  434
+                insert(matched_text+typed_characters,event);
  435
+                return
  436
+            }
  437
+            if (code === key.enter) {
  438
+                // Pressing ENTER will cause a pick
414 439
                 event.stopPropagation();
415 440
                 event.preventDefault();
416 441
                 pick();
@@ -418,35 +443,41 @@ var IPython = (function (IPython) {
418 443
                 // We don't want the document keydown handler to handle UP/DOWN,
419 444
                 // but we want the default action.
420 445
                 event.stopPropagation();
421  
-            //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
422  
-            } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
  446
+            } else if ( (code == key.backspace)||(code == key.tab && down) || press  || key.isCompSymbol(code)){
423 447
                 if( key.isCompSymbol(code) && press)
424 448
                 {
425 449
                     var newchar = String.fromCharCode(code);
426  
-                    typed_characters=typed_characters+newchar;
  450
+                    typed_characters = typed_characters+newchar;
427 451
                 } else if (code == key.tab) {
428 452
                     fastForward = sharedStart(filterd)
429 453
                     ffsub = fastForward.substr(matched_text.length+typed_characters.length);
430  
-                    typed_characters=typed_characters+ffsub;
431  
-                    autopick=true;
432  
-                    event.stopPropagation();
433  
-                    event.preventDefault();
  454
+                    typed_characters = typed_characters+ffsub;
  455
+                    autopick = true;
434 456
                 } else if (code == key.backspace && down) {
435 457
                     // cancel if user have erase everything, otherwise decrease
436 458
                     // what we filter with
  459
+                    event.preventDefault();
437 460
                     if (typed_characters.length <= 0)
438 461
                     {
439 462
                         insert(matched_text,event)
  463
+                        return
440 464
                     }
441  
-                    typed_characters=typed_characters.substr(0,typed_characters.length-1);
442  
-                }else{return}
  465
+                    typed_characters = typed_characters.substr(0,typed_characters.length-1);
  466
+                } else if (press && code != key.backspace && code != key.tab && code != 0){
  467
+                    insert(matched_text+typed_characters,event);
  468
+                    return
  469
+                } else {
  470
+                    return
  471
+                }
443 472
                 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
444 473
                 filterd = matches.filter(function(x){return re.test(x)});
445 474
                 complete_with(filterd,matched_text+typed_characters,autopick,event);
446  
-            } else if(down){ // abort only on .keydown
  475
+            } else if( code == key.esc) {
  476
+                // dismiss the completer and go back to before invoking it
  477
+                insert(matched_text,event);
  478
+            } else if( press ){ // abort only on .keypress or esc
447 479
                 // abort with what the user have pressed until now
448 480
                 console.log('aborting with keycode : '+code+' is down :'+down);
449  
-                insert(matched_text+typed_characters,event);
450 481
             }
451 482
         }
452 483
         select.keydown(function (event) {
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.