jupyter / help

:sparkles: Need some help or have some questions? Please visit our Discourse page.
https://discourse.jupyter.org
291 stars 97 forks source link

add keyboard short-cut within a code cell during editing #71

Open ttimbers opened 8 years ago

ttimbers commented 8 years ago

I want to be able to add a keyboard short-cut within a code cell during editing. Specifically, I want to recreate RStudio's 'alt--' short-cut that results in an in-place text insertion of '<-'.

I have been playing around with using %% javascript magic to do this. With the code below I can manage to get <- output to the output area, but there are two problem with this:

current code:

%%javascript

Jupyter.keyboard_manager.command_shortcuts.add_shortcut('alt--', {
    help : 'add assignment symbol',
    help_index : 'zz',
    handler : function (event) {
        element.text('<-');
        return false;
    }}
);

Any help to point me in the direction of getting this working to further develop this feature would be greatly appreciated (the plan is to add the final working code to custom.js so it works in all my notebooks). Additionally, such a feature would be desired by any R user using Jupyter. Thanks!

bkatiemills commented 8 years ago
%%javascript

Jupyter.keyboard_manager.edit_shortcuts.add_shortcut('alt--', {
    help : 'add assignment symbol',
    help_index : 'zz',
    handler : function (event) {
        var target = Jupyter.notebook.get_selected_cell()
        var cursor = target.code_mirror.getCursor()
        var before = target.get_pre_cursor()
        var after = target.get_post_cursor()
        target.set_text(before + '<-' + after)
        cursor.ch += 2
        target.code_mirror.setCursor(cursor)
        return false;
    }}
);

y/n?

bkatiemills commented 8 years ago

Note that bad things will happen if using this shortcut immediately before or after a linebreak. the get_pre_cursor and get_post_cursor methods seem to ignore the immediately adjacent newline character; the interested reader may be able to work around this, but this feels an awful lot like a bug IMO; for example, check the lengths of the strings returned by get_pre_cursor, get_post_cursor and get_text; the pre and post add up to the total if the cursor is not adjacent to a newline, but falls short by one if it is. Not sure if this is by design or not, but definitely not what I would have expected from these methods.

ttimbers commented 8 years ago

Thanks for this solution @BillMills! I agree that it works in the most common use case (assigning a variable), and so when using it like this, you do not run into the newline character issue. But the newline character issue is very weird, and I agree that this looks like a bug and not an intentional design feature of the method...

ttimbers commented 8 years ago

note - since magics don't work when running the R kernel in Jupyter, you have to wrap the above code in IRdisplay::display_javascript("some javascript code")

Working code in Jupyter using the R kernel looks like this (note - I tweaked the code to create a space before and after the assignment symbol - similar to what R Studio does):

IRdisplay::display_javascript("Jupyter.keyboard_manager.edit_shortcuts.add_shortcut('alt--', {
    help : 'add assignment symbol',
    help_index : 'zz',
    handler : function (event) {
        var target = Jupyter.notebook.get_selected_cell()
        var cursor = target.code_mirror.getCursor()
        var before = target.get_pre_cursor()
        var after = target.get_post_cursor()
        target.set_text(before + ' <- ' + after)
        cursor.ch += 4
        target.code_mirror.setCursor(cursor)
        return false;
    }}
);")
ttimbers commented 8 years ago

To get this feature in all notebooks, you need to add the following code to a file called custom.js whose path is ~/.jupyter/custom/custom.js:

$([IPython.events]).on("app_initialized.NotebookApp", function () {
  Jupyter.keyboard_manager.edit_shortcuts.add_shortcut('alt--', {
      help : 'add assignment symbol',
      help_index : 'zz',
      handler : function (event) {
          var target = Jupyter.notebook.get_selected_cell()
          var cursor = target.code_mirror.getCursor()
          var before = target.get_pre_cursor()
          var after = target.get_post_cursor()
          target.set_text(before + ' <- ' + after)
          cursor.ch += 4
          target.code_mirror.setCursor(cursor)
          return false;
      }}
  );
    return true;
});

essentially this is @BillMills solution wrapped in:

$([IPython.events]).on("app_initialized.NotebookApp", function () {
    <your code>

    return true;
});
ttimbers commented 8 years ago

@minrk @jdfreder @Carreau and @ellisonbg - can any of you comment on the bug identified by @BillMills in get_pre_cursor and get_post_cursor methods regarding their adjacency to line breaks? Specifically he has found that they seem to ignore the immediately adjacent newline character and this results in bad things happening if we use the shortcut we implemented immediately before or after a linebreak.

Since this shortcut would be WIDELY used by almost ALL R Jupyter users, I really think it would be worth our time to fix this upstream bug. Any thoughts/insights/fixes would be much appreciated!

Carreau commented 8 years ago

Hum, it seem like it would be possible, and likely less brittle to register a keyboard shortcut with codemirror directly instead of going through the jupyter keyboard manager.

Plus if you like to have that only on the R kernel, it should be possible to ship it with the R kernel itself using the kernel.js file that ship with the R kernel (if it ships one), so I guess that will likely interest @flying-sheep to ping on that and know if its worth including with the R kernel.

Something along the line of:

if (! doc.somethingSelected()){
    var cur = doc.getCursor()
    doc.replaceRange(' <- ', cur, cur, )
}

Will likely be more efficient, and less error prone, the question is how to propagate the keybinding directly to the code cells from configuration. I can try to have look, but maybe a bit later I need to finish a couple of things for this week-end.

This issue might have some extra hints about CodeMirror keymaps.

flying-sheep commented 8 years ago

indeed i am interested.

i condensed it to this and it works. will be in IRkernel as soon as i got something else in:

const extra_keys = {
    ['Alt--'](cm) {
        cm.replaceSelection(' <- ')
    }
}

define(['base/js/namespace'], jupyter => ({
    onload() {
        for (let cell of jupyter.notebook.get_cells()) {
            cell.code_mirror.setOption('extraKeys', extra_keys)
        }
    }
}))
flying-sheep commented 8 years ago

just one question: will the onload event fire for new cells? if not, what event will?

flying-sheep commented 8 years ago

it works! but badly, as the CodeMirror version is too old to know what a “word” in R is.

i need at least 5.13.0. when will notebook be upgraded to something above it?

jasongrout commented 8 years ago

It looks like the current notebook master has codemirror 5.14: https://github.com/jupyter/notebook/blob/85d7cfba224005d423ff27c0b8e961813ea5016b/bower.json#L8

flying-sheep commented 8 years ago

jup, just found it too! is the next release 5.0? if so: it got a huge heap of open bugs :cry:

flying-sheep commented 8 years ago

@ttimbers once you update the IRkernel package and re-run installspec, you’ll have it :smile:

i also added a F1 shortcut to get help for the word under the cursor: https://github.com/IRkernel/IRkernel/blob/4d4193ff2a041650b24364ecee1493ce595e1b5c/inst/kernelspec/kernel.js

jasongrout commented 8 years ago

Good point. The 4.x branch still is at Codemirror 5.8: https://github.com/jupyter/notebook/blob/9e1459fbc540a70089cab25e9d8ef952f4eb4ca4/bower.json. I put in an issue at https://github.com/jupyter/notebook/issues/1686

flying-sheep commented 8 years ago

thanks! that’s necessary because all getWordAt calls need this to work properly.

it’s also someone we know who laid the foundation to CodeMirror recognizing r words :smile: https://github.com/codemirror/CodeMirror/commit/877b2b1766a40bed35d5f035f15e01d8f9a66e4f

flying-sheep commented 8 years ago

and @Carreau:

just one question: will the onload event fire for new cells? if not, what event will?

Carreau commented 8 years ago

just one question: will the onload event fire for new cells

No.

if not, what event will?

None, but you should be able to also patch the CodeCell class. Add a new key to CodeCell.options_default.cm_config.extraKeys with your custom function, it will affect all the newly created cells.

Sidenote I think your snippet replace the "Backspace" : "delSpaceToPrevTabStop", binding we set.

if so: it got a huge heap of open bugs

We are aware, @gnestor have started going through these and is doing doing a great job at moving things forward !

flying-sheep commented 8 years ago

We are aware, @gnestor have started going through these and is doing doing a great job at moving things forward!

that’s awesome! and i wasn’t insinuating that it goes too slow or something!

None, but you should be able to also patch the CodeCell

sadly my code needs a reference to the specific cell, so it’s not that easy. the functions only get the codemirror instance as parameter, and those don’t reference the cell.

Carreau commented 8 years ago

sadly my code needs a reference to the specific cell, so it’s not that easy. the functions only get the codemirror instance as parameter, and those don’t reference the cell.

Use Jupyter.notebook.get_selected_cell() ?

that’s awesome! and i wasn’t insinuating that it goes too slow or something!

I was not trying to imply you were, just that it should move faster now, and that you should not get surprised if you see @gnestor around !

flying-sheep commented 8 years ago

i wouldn’t depend on a newly added cell to be selected.

i think there should be “cell added” and “cell removed” events, right? right now i have to hook into the edit_mode.Cell event which fires much more often

anywho: @ttimbers and everyone listening: now it works on newly created cells, too: IRkernel/IRkernel@c7d29566834343ebf70e8c339272bf618640a73d

ttimbers commented 8 years ago

Thanks all! This new feature is great!!! Not to get demanding, but could the same strategy be used to add a shortcut for the pipe (%>%) in the IRkernel for Jupyter? The RStudio short-cut for this is "Control + Shift + M" (Windows & Linux) and "Command + Shift + M" (Mac) . I would say this is the second most useful shortcut in R, and again R users would be forever grateful to Jupyter for providing this...

Carreau commented 8 years ago

THat might be hard for these specific shortcuts as the browsers do not allow to override all the shortcuts, and I think Control-Shift-M is one of the ones we can't.

ttimbers commented 8 years ago

OK, good to know.

flying-sheep commented 8 years ago

could you link me to a list of RStudio shortcuts? i’ll try out which ones will work and which don’t.

do you like the F1 shortcut? :wink:

ttimbers commented 8 years ago

@flying-sheep here's a link to all the RStudio short-cuts: https://support.rstudio.com/hc/en-us/articles/200711853-Keyboard-Shortcuts

All will not apply to Jupyter. The ones I think that would be good to add to Jupyter can be found under the heading of "Editing (Console and Source)"

And the F1 shortcut to get the documentation of the word under the cursor is pretty darn nifty! Thanks for this feature also!

flying-sheep commented 8 years ago

OK cool, I’ll have a look at those.

i always wanted that F1 thing, that’s why i was so happy that you pointed me in the right direction with this issue. (also big thanks to @Carreau of course)

flying-sheep commented 8 years ago

for the record:

  1. ShiftCtrl_N_ is lockend, …-M works.

    it’s defined here, im summary, F11 to exit fullscreen mode and everything manipulating and navigating tabs or windows is blocked:

    [Shift]Ctrl(Q|N|W|T|)

  2. codemirror needs modifiers specified in the order ShiftCmdCtrlAlt
flying-sheep commented 7 years ago

oh, @ttimbers: did i tell you that i eventually added all shortcuts?

since i use cm.findWordAt, jupyter/notebook#1921 needs to be pulled in order for F1 to work on names.with.dots

antoine-lizee commented 7 years ago

Hey @flying-sheep et al - this is really awesome. It makes jumping from Rstudio to jupyter a breeze.

I was just wondering if you could also add the cmd + shift + M which is the pipe (%>%) shortcut on mac OSX. It's definitely the most useful shortcut and not the second-most @ttimbers :-p.

I just went and replaced in the kernel file, and it works fine ! But I don't know how to make it platform dependent. The alternative would be to add both.

flying-sheep commented 7 years ago

sure! so it’s “'Shift-Ctrl-M'”?

xappppp commented 6 years ago

I'm wondering if this topic can be extended to some functionality like 'hint' or 'abbreviation', where when I type a keyword (say 'hd' for header) with a enter then it will show up the whole text strings that I intended to insert into the code body.