randy3k / radian

A 21 century R console
MIT License
1.96k stars 73 forks source link

Adding key bindings in radian.on_load_hooks worked in 0.5.9 but not in 0.6.3 #383

Closed joshuaulrich closed 1 year ago

joshuaulrich commented 1 year ago

I have this in my .radian_profile. I based this on the suggestion in https://github.com/randy3k/radian/issues/153#issuecomment-590619369.

options(radian.on_load_hooks = list(function() {

    getOption("rchitect.py_tools")$attach()

    radian <- import("radian")
    prompt_toolkit <- import("prompt_toolkit")

    KeyPress <- prompt_toolkit$key_binding$key_processor$KeyPress
    Keys <- prompt_toolkit$keys$Keys

    insert_mode <- radian$key_bindings$insert_mode
    app <- radian$get_app()
    app$session$app$timeoutlen <- 0.2

    kb <- app$session$modes$r$key_bindings

    kb$add("j", "k", filter = insert_mode)(function(event) {
        event$app$key_processor$feed(KeyPress(Keys$Escape))
    })
    kb$add(">", ">", filter = insert_mode)(function(event) {
        event$app$key_processor$feed(KeyPress("|"))
        event$app$key_processor$feed(KeyPress(">"))
        event$app$key_processor$feed(KeyPress(" "))
    })

    kb$add(Keys$Enter, Keys$Enter, filter = insert_mode)(function(event) {
        is_multiline <- event$app$current_buffer$multiline()
        if (is_multiline) {
            event$current_buffer$newline()
        }
    })
}))

These work in a 0.5.9 install on Ubuntu 20.04. I get the following error when I start radian 0.6.3 on Ubuntu 22.04:

Error in running user hooks
Error in `$.PyObject`(app$session$modes, r) : 
  'collections.OrderedDict' object has no attribute 'r'
R version 4.2.1 (2022-06-23) -- "Funny-Looking Kid"
Platform: x86_64-pc-linux-gnu (64-bit)

Adding the key bindings "jk" and ">>" work once radian has started. For example, I can add them if I run the commands after radian has started.

local({
    getOption("rchitect.py_tools")$attach()
    radian <- import("radian")
    prompt_toolkit <- import("prompt_toolkit")

    app <- radian$get_app()
    kb <- app$session$modes$r$key_bindings

    KeyPress <- prompt_toolkit$key_binding$key_processor$KeyPress
    Keys <- prompt_toolkit$keys$Keys

    insert_mode <- radian$key_bindings$insert_mode
    kb$add("j", "k", filter = insert_mode)(function(event) {
        event$app$key_processor$feed(KeyPress(Keys$Escape))
    })
    kb$add(">", ">", filter = insert_mode)(function(event) {
        event$app$key_processor$feed(KeyPress("|"))
        event$app$key_processor$feed(KeyPress(">"))
        event$app$key_processor$feed(KeyPress(" "))
    })

    kb$add(Keys$Enter, Keys$Enter, filter = insert_mode)(function(event) {
        is_multiline <- event$app$current_buffer$multiline()
        if (is_multiline) {
            event$current_buffer$newline()
        }
    })
})

But "[Enter][Enter]" still fails when I try to use it.

R$

Unhandled exception in event loop:
  File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/lib/python3/dist-packages/prompt_toolkit/input/vt100.py", line 170, in callback_wrapper
    callback()
  File "/usr/lib/python3/dist-packages/prompt_toolkit/application/application.py", line 711, in read_from_input
    self.key_processor.process_keys()
  File "/usr/lib/python3/dist-packages/prompt_toolkit/key_binding/key_processor.py", line 270, in process_keys
    self._process_coroutine.send(key_press)
  File "/usr/lib/python3/dist-packages/prompt_toolkit/key_binding/key_processor.py", line 185, in _process
    self._call_handler(matches[-1], key_sequence=buffer[:])
  File "/usr/lib/python3/dist-packages/prompt_toolkit/key_binding/key_processor.py", line 320, in _call_handler
    handler.call(event)
  File "/usr/lib/python3/dist-packages/prompt_toolkit/key_binding/key_bindings.py", line 124, in call
    result = self.handler(event)
  File "/home/josh/.local/lib/python3.10/site-packages/rchitect/interface.py", line 512, in f
    return rcall(s, *args, _asis=f.asis, _convert=f.convert, **kwargs)
  File "/home/josh/.local/lib/python3.10/site-packages/rchitect/interface.py", line 270, in rcall
    s = rcall_p(*args, **kwargs)
  File "/home/josh/.local/lib/python3.10/site-packages/rchitect/interface.py", line 264, in rcall_p
    raise RuntimeError("{}".format(err))

Exception Error in if (is_multiline) { : argument is not interpretable as logical
Press ENTER to continue...

I'd appreciate any pointers on how to set these key bindings correctly in 0.6.3.

Thanks a lot for providing radian to the community. I love it!

joshuaulrich commented 1 year ago

I bisected the versions of radian and rchitect and found that adding all the keybindings works with radian-0.5.11, rchitect-0.3.30.

There's no startup error and the "jk" and ">>" keybindings work in radian-0.5.12, rchitect-0.3.35, but the "[Enter][Enter]" keybinding throws the error. The same is true for radian-0.5.13, rchitect-0.3.35.

The startup error appears in radian-0.6.0, rchitect-0.3.36.

So I have a work-around by downgrading for the time being; this isn't a blocker for me.

randy3k commented 1 year ago

It looks like a refectoring introuduced a bug.

Please replace app$session$modes$r$key_bindings by app$session$modes[['r']]$key_bindings for the moment.

joshuaulrich commented 1 year ago

Thanks for the quick reply!

It's still unhappy, but with a different error:

Error in running user hooks
Error in app$session$modes[["r"]] : 
  object of type 'externalptr' is not subsettable
R version 4.2.1 (2022-06-23) -- "Funny-Looking Kid"
Platform: x86_64-pc-linux-gnu (64-bit)

I added print(app$session$modes) before trying to access the 'r' member and this is the output:

OrderedDict([('r', <radian.prompt_session.RadianModeSpec object at 0x7f4cbfb939d0>),
    ('browse', <radian.prompt_session.RadianModeSpec object at 0x7f4cbfbc8610>),
    ('shell', <radian.prompt_session.RadianModeSpec object at 0x7f4cbfbc8790>),
    ('unknown', <radian.prompt_session.RadianModeSpec object at 0x7f4cbfbc81f0>)])
randy3k commented 1 year ago

Oh, it was actually due to another bug in the line app$session$app$timeoutlen <- 0.2.

Forget my last comment. Try this

   getOption("rchitect.py_tools")$attach()

    radian <- import("radian")
    prompt_toolkit <- import("prompt_toolkit")

    KeyPress <- prompt_toolkit$key_binding$key_processor$KeyPress
    Keys <- prompt_toolkit$keys$Keys

    insert_mode <- radian$key_bindings$insert_mode
    app <- radian$get_app()
    local({
        ## do not modify the app variable
        app$session$app$timeoutlen <- 0.2
    })

    kb <- app$session$modes$r$key_bindings

    kb$add("j", "k", filter = insert_mode)(function(event) {
        event$app$key_processor$feed(KeyPress(Keys$Escape))
    })
    kb$add(">", ">", filter = insert_mode)(function(event) {
        event$app$key_processor$feed(KeyPress("|"))
        event$app$key_processor$feed(KeyPress(">"))
        event$app$key_processor$feed(KeyPress(" "))
    })

    kb$add(Keys$Enter, Keys$Enter, filter = insert_mode)(function(event) {
        is_multiline <- event$app$current_buffer$multiline()
        if (is_multiline) {
            event$current_buffer$newline()
        }
    })
joshuaulrich commented 1 year ago

That was the issue causing the errors! Thanks!

However, now it takes ~1 second for a command to be sent to R when I hit "[Enter]" and have added the "[Enter][Enter]" key binding. I suspect this is because it's waiting for the second "[Enter]" before sending the line to R. That's likely why I tried to change the timeout to 0.2 seconds.

Is there a better way to continue typing code on a new line without sending the first line to R? That's what I'm trying to accomplish with the "[Enter][Enter]" key binding. That could very well be a poor solution. I'm using vim key bindings, so I can work-around by going to command mode and then hit "o" to add a new line.

joshuaulrich commented 1 year ago

Is there a better way to continue typing code on a new line without sending the first line to R?

Using Alt+Enter works well. The terminal sees Alt as a leading escape, so this is the code to add that key binding:

    kb$add(Keys$Escape, Keys$Enter, filter = insert_mode)(function(event) {
        is_multiline <- event$app$current_buffer$multiline()
        if (is_multiline) {
            event$current_buffer$newline()
        }
    })

Now to re-train my muscle memory... ;)

randy3k commented 1 year ago

The new rchitect commit should fix the root of the issue https://github.com/randy3k/rchitect/commit/986c2f25c910f0d26ee4b6e594bf68fc2db03bc5

randy3k commented 1 year ago

FYI, escape-enter for new line is already defined in radian https://github.com/randy3k/radian/blob/6b2e554de76cd0a4f7f3c080301047852ef1c7a1/radian/key_bindings.py#L353-L353

Update: Oh, sorry. It was only defined in emacs mode...

joshuaulrich commented 1 year ago

It was only defined in emacs mode...

Would it make sense to define it for vim mode too?

randy3k commented 1 year ago

It was only defined in emacs mode...

Would it make sense to define it for vim mode too?

I am not a vim mode user, so I didn't know if it makes sense for vim mode. If you think that it is useful, I could extend it to vim mode.

joshuaulrich commented 1 year ago

I'd use it (obviously) and Alt+Enter isn't mapped to anything by default in insert mode, so it seems unlikely it would break other's code.

So I'd appreciate it if you extended it. But, as a fellow maintainer, I'd completely understand if you didn't. I have a working solution, and you're the one who would have to deal with any potential user breakage by the change. Your choice. ;)

Thanks again for all the help today!

januz commented 7 months ago

@randy3k I was able to use your code in https://github.com/randy3k/radian/issues/383#issuecomment-1265822697 to use jk or kj to switch to normal mode from insert mode. I was wondering whether one could amend it to also work when in replace mode but I wasn't sure how to set the equivalent of insert_mode <- radian$key_bindings$insert_mode for replace mode. Would you mind pointing me in the right direction? Thank you!