liquidz / vim-iced

Clojure Interactive Development Environment for Vim8/Neovim
https://liquidz.github.io/vim-iced/
MIT License
518 stars 35 forks source link

Referring to evaluation ranges in :IcedEval #407

Open sheluchin opened 2 years ago

sheluchin commented 2 years ago

Not an issue, but a question.

Is it possible to refer to some evaluation range within the code passed to :IcedEval? For example, if I want to do something like :IcedEval '(time <outer list>)', is there some way to write that without literally copying the outer list?

Thank you for the amazing plugin!

liquidz commented 2 years ago

@sheluchin Currently there is no way to do that. But it is interesting idea! It would be great if we could generalize iced_eval_and_tap. cf. https://github.com/liquidz/vim-iced/blob/0382d4ca34e5993b121f882e68b951c16a8fbc99/autoload/iced/operation.vim#L59-L61

sheluchin commented 2 years ago

@liquidz Thank you for your consideration.

A generalized iced_eval_and_tap sounds like a good approach.

Reveal has a similar feature which it implements by using *v to mean the "currently selected value" in the given expression. For example, (prn *v) would print the currently selected value in the Reveal window.

Maybe vim-iced could implement it similarly by using something like *v or just %s in the expression passed to :IcedEval? Something like :IcedEval '(time %s)'?

My knowledge of vimscript is unfortunately not very strong, but I hacked around the iced_eval_and_tap function and think that I could figure out a rough implementation at some point. Do you happen to have another example in the codebase similar to using both selected_value and input_code rather than just the code?

sheluchin commented 2 years ago

So I tried this:

function! iced#operation#eval_and_tap_wrapped(type, wrapper) abort
  return s:eval({code -> iced#repl#execute(
        \ 'eval_code',
        \ printf('(clojure.core/tap> %s)', substitute(a:wrapper, '*v', code, 'g')))})
endfunction

And then with the cursor positioned at |:

(iden|tity (do (Thread/sleep 5000) (str 123)))

I used :call iced#operation#eval_and_tap_wrapped("x", "(time *v)"), which gave me this in my Reveal window:

"Elapsed time: 5001.441812 msecs"
; I think this line is a Reveal artifact you can probably ignore
(clojure.core/tap> (time (identity (do (Thread/sleep 5000) (str 123)))))
=> true
tap> "123"

I think that looks pretty good, but I'm sure it could be improved. What do you think, @liquidz?

I like this idea because it allows for a tighter integration between the editor and the interactive environment. For example, it would make it easy to create an editor mapping that would pass a datalog query directly to your query function without having to manually wrap the query in your code.

liquidz commented 2 years ago

@sheluchin Nice work! Thanks! I'll have a look.

liquidz commented 2 years ago

@sheluchin How about to define a function like below?

function! s:eval_wrapped(wrapper, type) abort
  return s:eval({code -> iced#repl#execute('eval_code',
        \ substitute(a:wrapper, '*v', code, 'g')
        \ )})
endfunction

function! iced#operation#setup_wrapper(wrapper) abort
  let &operatorfunc = funcref('s:eval_wrapped', [a:wrapper])
  let s:register = v:register
endfunction

Then, we can define the following mappings.

nnoremap <silent> <Plug>(iced_eval_and_time) <Cmd>call iced#operation#setup_wrapper('(clojure.core/time *v)')<CR>g@

aug Fixme
  au!
  au FileType clojure nmap <buffer> <Leader>eat <Plug>(iced_eval_and_time)<Plug>(sexp_outer_list)``
aug END

It may be a little difficult to define, but this feature will allow us to freely define operations wchich wraps some code.

sheluchin commented 2 years ago

So by separating the evaluation and wrapper setup into two functions it makes it possible to use the setup generically to define bindings. Looks like a good idea.

It looks like there is a bug in your snippet because when I try to use it:

Error detected while processing function iced#operation#setup_wrapper:
line    1:
E703: Using a Funcref as a Number
E729: using Funcref as a String

I don't know vim script, but it looks like an operatorfunc cannot receive an argument. I think it requires a different workaround. I can try find a solution sometime soon.

I'll note that this direction is slightly different from the initial idea of issue - using the evaluation range in :IcedEval - but it does achieve exactly what I was looking for anyway.

liquidz commented 2 years ago

@sheluchin Oh, it seems a problem in neovim. (works in vim) I'll fix.

liquidz commented 2 years ago

Ah, neovim has not been applied patch 8.2.3619 yet. https://github.com/vim/vim/commit/777175b0df8c5ec3cd30d19a2e887e661ac209c8 Hmm, we cannot realize with this method in neovim..

I'll reconsider.

sheluchin commented 2 years ago

@liquidz thanks for giving this some thought. I think it's reasonable to wait for the patch to merged to Neovim if there's no easy workaround now. I can try just using my above snippet with :call iced#operation#eval_and_tap_wrapped("x", "(time *v)") for the time being.

liquidz commented 2 years ago

@sheluchin Thanks! I agree with you. I'll check neovim repository occasionally.

sheluchin commented 2 years ago

https://github.com/neovim/neovim/pull/19846 :tada:

liquidz commented 2 years ago

Nice catch!

sheluchin commented 1 year ago

One more idea about this... if it can also be possible to include the cursor position as an argument to the receiving Clojure function, it would make a powerful way to extend vim-iced functionality using Clojure code.

For example, given a form like:

(let [k :foo]
 (x k)
 (y |k)
 (z k))

if the sending the outer top list can look like (my-fn *v 3), where *v is the whole thing and 3 is the cursor position (let = 0, [...] = 1, (x k) = 2, (y k) = 3), it would make it possible to use Clojure to completely re-write the evaluation logic before evaluating. You could write Clojure code to only eval the y call and ignore its siblings or re-arrange in any other way.

Just an idea :man_shrugging: