Olical / conjure

Interactive evaluation for Neovim (Clojure, Fennel, Janet, Racket, Hy, MIT Scheme, Guile, Python and more!)
https://conjure.fun
The Unlicense
1.68k stars 107 forks source link

Prompt buffer #98

Open theHamsta opened 4 years ago

theHamsta commented 4 years ago

Will conjure use prompt buffers at some point in future (:h bufype prompt)? At the moment it's not possible to use the log buffer as a repl and all the text is editable.

Olical commented 4 years ago

Oh interesting, I didn't know about this! I guess this is how terminal buffers work to some extent? I'll have a look, maybe it can be configurable :thinking: not sure if I'd want it to be the default but I'll give it some thought. Thanks for the idea!

Olical commented 4 years ago

You'd only be able to run one line at a time though, is that okay :thinking: so if you were writing a defn, it would all need to be on one line, kinda like an actual terminal.

theHamsta commented 4 years ago

I can implement this.

The terminals work differently. Prompt buffers are relatively new and merged as a vim patch and for that a bit broken (in the sense of how you use them correctly, vim-style, vim feature are always a bit broken by default)

Olical commented 4 years ago

Ah, is it not in stable nvim yet then? Because I'm only really relying on stable features right now. I suppose if it's configurable and off by default then that won't matter so much anyway.

theHamsta commented 4 years ago

You could implement multi-line commands. In that case you would detect that due to parenthesis imbalance the command is not finished and just wait for another line before eval.

Is there a function to directly eval a string? Is the private eval-str function for that?

theHamsta commented 4 years ago

It's in neovim master. One can just check whether the function activating it is available.

Olical commented 4 years ago

Hmm, I was thinking as you press enter it sends the last line to a function of your choice? Maybe that's not how it works. I think the conjure.eval module will just need the eval-str function exposed which is easy enough to do. I'm wondering if it should be wrapped up though so eval-str becomes eval-str* and remains private, then there's a public one other people can call that may have a different contract.

Olical commented 4 years ago
      prompt    buffer where only the last line can be edited, meant
            to be used by a plugin, see |prompt-buffer|

Sounds great to me! And it only supports one line I guess which solves the problem of multi line things. I wouldn't want to try to support multiline inputs with this.

hi-im-buggy commented 2 years ago

I was thinking of suggesting this just a little earlier. I think the REPL based workflow complements conjure's current paradigm, and bringing the two together would make for a really powerful dev environment.

Currently when using Conjure I find it a bit awkward to write a couple lines of code in the current buffer to evaluate and then delete. For example when the file is filled only with function definitions it feels unintuitive to write a function call in there, evaluate it to test the function and then delete it from the buffer.

The buf-prompt would make it so this would be unnecessary, and you could treat the log more or less like a normal REPL if you wanted to.

If no one else is currently working on this, I'd like to try and implement this.

Olical commented 2 years ago

I've since bumped the minimum required version to 0.5 too where I think this option exists? If you want to have a go at it just let me know if it's not what I think it is: Setting an option in the buffer and writing some function that helps Neovim know what to do with each input line. I think it's very little code, I just couldn't depend on it until I ruled out <0.5 really.

I was also planning on putting it behind an option / flag which is off by default buuuuut I'm not sure if on by default and documenting how to switch it off makes more sense or not 🤔

Olical commented 2 years ago

The prompt buffer prevents you from modifying any other line, you can only modify and submit the last one, like a real REPL. That sounds pretty annoying to me and I use edits across the entire log buffer ALL of the time, so probably not something we'd want on by default or all the time in general. Maybe switching that mode on in insert mode makes sense though... if you're on the last line?

hi-im-buggy commented 2 years ago

Hmm, it does indeed seem like it'd be better to have edit access across the entire log buffer, but I don't know if that lends itself to the prompt buffer type itself. Having it work that way on just the last line sounds great, but I don't know if that's something that can be done with the in-built prompt buf itself, sounds like something that would have to be hand-rolled.

I'll start playing around with it a little to see what works and what doesn't.

hi-im-buggy commented 2 years ago

After some poking around, I think buf-prompt is not suitable for Conjure. There is nothing to gain from making the buffer mostly uneditable, and I'm guessing the code overhead to introduce it won't be worth it.

:ConjureEval itself already supports giving code to it for evaluation, so for a REPL-like experience using that makes most sense, it can be bound by the user to some command or keybind according to their convenience.

One thing of note though, is that :ConjureEval does not currently check for the balancing of parentheses etc. in the code given to it (see #213). Normally the interrupt signal makes sense here, but this means that you can't have multi-line forms to pass to :ConjureEval. Now whether or not it's suitable to have :ConjureEval handle multi-line forms is debatable; it might make more sense for the user to type the definition in a buffer and eval it instead.

Solving the unbalanced paren issue would make it possible to both improve the solution

213 and have a more idiomatc REPL-like experience though.

helins commented 6 months ago

For what it's worth, I scripted a poor man's "multi-line prompt" for Clojure for wherever I am. A function that simply opens commented tags and when called a second time, evals what's inside, deleting everything. It feels a bit funny but it works surprisingly well. Even if you call the function right in the middle of some text, everything goes back to normal on eval (at least in my setup...)

Plus, it requires only one key mapping, which I even have in insert mode, making it all super snappy. It works well enough in the eval buffer as well. Of course, it does not provide any repeatable history though.

in case someone finds that useful:

function! ClojurePrompt(is_i_mode)

    let line_start = search(';;(((')

    if line_start == 0
        execute "normal! o\;;(((\<ENTER>\<ENTER>;;)))\<ESC>k"
        if a:is_i_mode != 1
            call feedkeys('i', 'n')
        endif
    else
        let line_end = search(';;)))')
        if line_end == 0
            echoerr 'No closing tag!'
        else
            execute 'normal! ' . line_end . 'G"edd'
            execute 'normal! ' . line_start . 'G"edd'
            execute 'normal! "ed' . (line_end - 2) . 'G'
            execute ':ConjureEval ' .getreg('e')
        endif
    endif
endfunction
Olical commented 6 months ago

Oh that's neat! I love it!

helins commented 6 months ago

You know what, after using it a little, I actually prefer it to a dedicated window or such popping up. It's mainly for quickly testing something about what I'm doing and this is very distraction free. I tweaked it so that cursor position is properly restored. Also, it supports wrapping the code string to eval in a "hook". I have key mappings for setting g:clojure_prompt_fn to things like tap>.

let g:clojure_prompt_hook = ''

function! ClojurePromptSetHook(hook)
    let g:clojure_prompt_hook = a:hook
    let pos_cursor = getpos('.')
    let line_start = search(';;(((')
    if line_start != 0
        execute "normal! " . line_start . "GddO;;((( " . g:clojure_prompt_hook
        call setpos('.', pos_cursor)
    endif
endfunction

function! ClojurePrompt()

    let line_start = search(';;(((')

    if line_start == 0
        let g:clojure_prompt_pos = getpos('.')
        execute "normal! o\;;((( ". g:clojure_prompt_hook ."\<ENTER>\<ENTER>;;)))\<ESC>k"
        startinsert!
    else
        let line_end = search(';;)))')
        if line_end == 0
            echoerr 'No closing tag!'
        else
            execute 'normal! ' . line_end . 'G"edd'
            execute 'normal! ' . line_start . 'G"edd'
            execute 'normal! "ed' . (line_end - 2) . 'G'
            call setpos('.', g:clojure_prompt_pos)
            let code = getreg('e')
            if g:clojure_prompt_hook != ''
                let code = '(' . g:clojure_prompt_hook . ' ' . code . ')'
            endif
            execute ':ConjureEval ' . code
        endif
    endif
endfunction

Disclaimer: I'm a VimL noob. It works though. The only part that doesn't quite is undoing if ClojurePrompt was called from insert mode for evaluation. Then u goes back to empty tags. Whereas if ClojurePrompt was called from normal mode, it undoes to the exact content prior to deleting and evaluating, which is what you would expect.