jalvesaq / Nvim-R

Vim plugin to work with R
GNU General Public License v2.0
963 stars 125 forks source link

Is it possible to define/use Nvim-R operators/nouns? #491

Closed seb-mueller closed 8 months ago

seb-mueller commented 4 years ago

First of all, absolutely fantastic tool, I'm alomost using it daily and it is saving me a lot of time and head-aches!

I was wondering if it can be used even more reflecting the vim way?

Is it possible to define a "send" verb:

At the moment it is possible to "send" various entities such as files, chunks, functions, lines, selections etc which is already great.

Say I wanted to send something else, for example the content of a bracket: e.g. given the following code:

library(datasets)
test <- function(iris[1:3,])

With the cursor in iris, I'd now like to send the content of the round bracket for inspection. At the moment I'd select it with vi( and than run "send selection" \ss. This works, but it would feel more natural to have a send verb, say "gs", in which gsi( would send the content of the bracket iris[1:3,] directly.

This SO thread is also touching on this topic. They point out that there is something similar done in Tim Pope's commentary plugin, he has defined a verb gc that works in that way.

Is it possible to defining extra R object such as chunks, blocks, functions etc.

The above objects are already somewhat defined. For example \bb, \be etc are already used to send the current block. As far as I can tell "send" is all that can be done with a block. However say I wanted to apply a different verbs with on a block, for example visual mode or delete/change. A new defined noun, say "b" or so would enable this. If I wanted to visualize the current block, having defined "b", this would work the vim way out of the box: vib

I'm relatively new to this concept, but I suppose the above can be achived with "Operator-Pending Mappings" https://learnvimscriptthehardway.stevelosh.com/chapters/15.html

Are those things already possible? If not this might be a possible considertion.

P.S. happy to contribute, even with a donation etc.

jalvesaq commented 4 years ago

If you could write a pull request, I would try it to see how it would work. Unfortunately, I don't have spare time to develop new features that I don't expect to use.

seb-mueller commented 4 years ago

I see. I take from this that this is not possible yet (I might have overlooked this functionality)? Would you in general say that this is a sensible idea? Anyway I'd have to upper my vimscript game for this, so this might take some time, but thanks for the feedback!

jalvesaq commented 4 years ago

I'm sorry, but I did not read all the documentation that you indicated.

I didn't like the idea of pressing the key "g" in normal mode for sending commands to R because "g" to me means "go", and not "send". The easiest way of trying to convince me that this change would be good is if I could try it.

seb-mueller commented 4 years ago

The "g" was just a random example which I didn't put thought into, my focus was more on the general concept, so of course this can be chosen more wisely.

Having done some more reading, I've managed to come up with something crude that is not perfect but should exemplify my first point, i.e. defining a new verb. Note, a verb in vim is also referred as operator in vim (I might change this above to avoid confusion). The main inspiration was drawn from here and :h :map-operator, I've therefore used the F4 as in the help as the operator, but again this just example and subject to change!

Note, I'm new to vimscript, so didn't understand everything reading your code, so might have overseen important features. Also, the example is only tested in neovim. Going through your code, I've identified chansend(g:rplugin.jobs["R"], a:str) as the workhorse function to send text to the R console. Using the the boilerplate code in :h :map-operator, I've basically just replaced one line by chansend

silent call chansend(g:rplugin.jobs["R"], @@)

The full code:

function! SendNvimR(type, ...)
  let sel_save = &selection
  let &selection = "inclusive"
  let reg_save = @@

  if a:0  " Invoked from Visual mode, use '< and '> marks.
    silent exe "normal! `<" . a:type . "`>y"
  elseif a:type == 'line'
    silent exe "normal! '[V']y"
  elseif a:type == 'block'
    silent exe "normal! `[\<C-V>`]y"
  else
    silent exe "normal! `[v`]y"
    " character wise operator needs to have a trailing "\n" to avoid having to press enter manually
    let @@ = @@."\n"
  endif

  silent call chansend(g:rplugin.jobs["R"], @@)

  let &selection = sel_save
  let @@ = reg_save
  call winrestview(g:restore_position)
endfunction

fun! SetOpFunc()
  let g:restore_position=winsaveview()
  set opfunc=SendNvimR
  return 'g@'
endfun
nnoremap <expr> <localleader><localleader> SetOpFunc()
vnoremap <localleader><localleader> :<C-U>let g:restore_position=winsaveview()<Bar>call SendNvimR(visualmode(), 1)<CR>

Edit: <localleader><localleader> used to be F4.

The above can be sourced into (neo)vim. In an open R vim session (having stared with <localleader>rf), one can send for example use F4 the limitless the same way as yank or any other operators. For example given the following R-code with the cursor in iris:

library(datasets)
iris[1][1,]

for (a in 1:3) {
  print(a)
}

for (b in 3:1) {
  print(b)
}

Of course this is only scratching the surface. The point is this intuitive since the other operators work that way as well, one has just to remember F4 since the rest is applied the same as for the other operators..

The real power than comes when combining with custom R-objects such as functions or chunks to combine with this, but I'd have to look into this some other time, just curious if this all makes sense? I might be completely mistaken still ;)

jalvesaq commented 4 years ago

Please, talk with @BlueDrink9 who has proposed a pull request to change the way that key bindings are set: https://github.com/jalvesaq/Nvim-R/pull/472

I'm highly resistant to any changes in the key bindings because the current ones are in use for many years and I simply can't implement any and all ideas that people present.

seb-mueller commented 4 years ago

Ok, I didn't really suggest a change in the mapping. Again, above were just examples, key-bindings could be done such that they are staying the same. Such as send selection could be done the way it already is. nnoremap <localleader>ss :SetOpFunc()<CR> I was much more interested in your opinion about the general concept to determine if I should spend time exploring it with it them being discarded because I was missing something. I'll have a look at the PR you linked. Thanks for you response anyway!

BlueDrink9 commented 4 years ago

I totally agree that this plugin should define the objects it currently only exposed via mappings. The vim way is not well-adhered to.

In terms of a "send" verb - it already exists, although it is well-hidden in the documentation. It is mapped as <plug>RSend. See my PR for a caveat

seb-mueller commented 4 years ago

I've in fact spotted RSend in the documentation, but couldn't make it to work as an operator. How could you for example achieve 2<F4>ap, i.e. send 2 paragraphs?

BlueDrink9 commented 4 years ago

Try checking out my fork and see if it works as expected then. Are you using nmap <buffer> <F4> <plug>RSend?

seb-mueller commented 4 years ago

Just checked tried your PR. I have no idea how to get <plug>RSend to work as an operator, but the obove nmap <buffer> <F4> <plug>RSend does not seem to work, hitting i.e. 2<F4>ap doesn't do the desired.. However nno <expr> <F4> SetOpFunc() as in the code above makes F4 work as an operator.

BlueDrink9 commented 4 years ago

Yeah, sorry this is actually what I'm using, at least while the plug issue is sorted. This is what <plug>RSendMotion actually maps to, iirc.

noremap <silent><buffer> <space>s :set opfunc=SendMotionToR<CR>g@'

I haven't set it up to use a range yet though (doesn't work in visual).

seb-mueller commented 4 years ago

That's certainly going in the right direction. However it seems only to work for lines paragraphs. I.e. <space>sip sends correctly a paragraph, but <space>siw sends the whole line rather than a word, so does <space>si( etc. I've further improved the code (I've updated the inital code block), note that I've changed the operator to <localleader><localleader> which is ,, for me. the new code also prevents the cursor from moving after an operation, as well as adding certain R objects in operator pending mode:

onoremap am :<c-u>normal! [(%v%b<cr>
onoremap iif :<c-u>execute "normal! ?function(\rv^of{%\r"<cr>

iif creates a "function" object. ,,iif will sends the a function to R. Needless to say that diif will delete the function etc.

im creates a "outer match" object. I need this all the time, e.g. res <- read.table(file.path(mydir, "file.txt")), having the cursor on the y, I want to quickly inspect the result of the first enclosing, i.e. file.path(mydir, "file.txt"). Hitting ,,im does the job nicely.

Needless to say that a lot more objects can be created and work seamlessly with such an operator. @BlueDrink9 , do you think this can be done having the plug issue sorted? Otherwise I'm happy to submit a PR so people can have a try.

BlueDrink9 commented 4 years ago

Very nice, I haven't created custom objects before. I think it would be best to create a PR that exposes the objects nvim-R uses, possibly as functions (<plug> mappings for omap would be good here). I think the PR should let the users choose the abbreviation.

Since nvim-R must already define them for the default mappings, it would be good to make them more usable.

yeah, only sending the whole line is a nuisance. I'm assuming the actual plugRSend mapping doesn't do that, although if it does then a PR to fix it would surely be useful

BlueDrink9 commented 4 years ago

We should keep things unopinionated to reduce the burden on jalvesaq when deciding to merge

seb-mueller commented 4 years ago

Update. Since there hasn't been more feedback, I've found a more integrated solution using neoterm at least for neovim/vim at newer versions (i.e. support :terminal). One of the things that it solved and bothered me is that the mappings are R specific, I'd have to momorize "submit" commands seperatly for for this plugin. Neoterm has a more general approach which works for many other REPLs, so one can use the same for python, shell, Julia, you name it. At its core, it defines a Operator which is pretty much all you need!:

<Plug>(neoterm-repl-send): sends with text-objects or motions, or sends the selection to a REPL in a terminal. 

For instance nmap s <Plug>(neoterm-repl-send) defines the "submit" verb s which works the vim way as described above, i.e. sip submitts the current paragraph. But it works with all motions/objects! So much more control!

It turns out it works alongside Nvim-R. It first needs to be installed. Also, a started NvimR buffer needs to be made known to neoterm. I've written a chunk which can go into the vimrc

" start Nvim-R and make buffer also work for neoterm
function Rmy(command)
  " start nvim-R manually:
  call StartR(a:command)
  " get nvim-R buffer name
  let mywin=bufwinnr(g:rplugin["R_bufname"])
  " got to terminal window
  exe mywin . "wincmd w" 
  " make nivm-R buffer neoterm compatible
  call neoterm#new({'from_event': 1})
  " go back to R code buffer
  exe thiswin . "wincmd w" 
endfunct
nnoremap <silent> <localleader>R :call Rmy("R")<CR>

In a neovim R file, you can just hit R which starts NvimR and makes the buffer known to neoterm, which combines the nice features of both.

Of course there is much more one can do with this approach, I just wanted make give an easy workflow for others to try.

On the long run I could see this replacing how NvimR submitts to the R-REPL, which would lower the maintenance cost considerably (and makes a start with Nvim-R it more natural along the vim philosopy) since this is been taking care of by neoterm. Happy to give more use cases what perks this has if anybody is interessted.

BlueDrink9 commented 4 years ago

Yeah, I use neoterm for other things. I like this approach. If code is run this way, are the R objects still visible to nvim-R?

seb-mueller commented 4 years ago

Yes, it basically just adds neoterm functionality without loosing any Nvim-R functionality. You just have to watch out for mapping conflicts. I've been using this approach for a bit now, and it makes things more efficient.. It's little things such as sometimes I just want to send only the 2 next lines, which is just hitting sj or the next 2 paragraphs ('s2ap'), it also works nicely with the . to repeat the last action..

jalvesaq commented 8 months ago

Nvim-R is being superseded by R.nvim, a new project that will be inaugurated in a week at the R-nvim organization. Please, check out tmp-Nvim-R and help us to find the last remaining bugs before R.nvim inauguration. When the project is officially started, you will be able to open issues there and request new features.

Nvim-R will remain alive as a feature-frozen project for Vim users. If you want to know the reason, please, see this discussion.