nikvdp/neomux

Sending commands to terminal from a Vim buffer (neoterm flow?)

Closed this issue · 3 comments

Thank you so much for this plugin!! The idea is so clever, and the implementation seems smooth.

Is there a way to send commands to the terminal from Vim?

I found that this works if I first start Neomux with :Neomux. Then I open another terminal using Neoterm (:Topen). From then on, the Neoterm buffer understands both Neomux stuff and Neoterm stuff (2-way communication between vim and the terminal!). But the first Neomux terminal I opened is invisible to Neoterm.

I got some of the Neomux functionality by sourcing funcs.sh from a Neoterm buffer, but vw is missing.

I guess what I'm looking for is advice on how to initialize Neomux without opening a terminal, so I can have Neoterm open the terminal. Thanks for your thoughts on this, and sorry if this workflow was already covered in the docs, I couldn't find it.

Hey @mheiber, glad you're enjoying it!

Is there a way to send commands to the terminal from Vim?

For my needs doing <Ctrl>-s and then just pasting things into the term with p has been enough. If you're looking for something fancier there's some limited support for this in the partially-documented NeomuxSend function that kind of does this. It won't work as is for your use case because it assumes the terminal window is the currently active window, but it's a one line func. If you can find a way to get the b:terminal_job_id (:echo b:terminal_job_id while the window is active should do it) for the window you want to send data to you can just use chansend in the same way NeomuxSend does, replacing a:keys with the data you want to send to the terminal:

function! NeomuxSend(keys)
    call chansend(b:terminal_job_id, a:keys)
endfunction

I guess what I'm looking for is advice on how to initialize Neomux without opening a terminal, so I can have Neoterm open the terminal

As for initializing a neomux term without using :Neomux, take a look at these lines in the NeomuxTerm function. Essentially, for a shell to become a "neomux" shell, all you need to do is add the neomux bin folders to the PATH env var, and optionally also set the EDITOR env var. (It technically also requires NVIM_LISTEN_ADDRESS to be set to the correct value, but this should happen automatically for any :term opened inside neovim.)

tl;dr if you're not in the mood to look at vim script (eminently understandable), then first figure out where your plugin manager installed neomux to (I'll refer to it as $PATH_TO_NEOMUX_FOLDER below, replace with the actual path on your machine) and put something like the following in your ~/.bashrc or ~/.zshrc, or paste it into a new term after doing :Topen

if [[ -n "$NVIM_LISTEN_ADDRESS" ]]; then
    export PATH="$PATH:$PATH_TO_NEOMUX_FOLDER/plugin/$(uname).x86_64.bin:$PATH_TO_NEOMUX_FOLDER/plugin/bin"
    export EDITOR="$PATH_TO_NEOMUX_FOLDER/plugin/nmux"
fi

With those two env vars exported you should have a fully functional neomux shell, setting those two vars is basically all the NeomuxTerm() function does.

Sending commands to the term from vim is a cool idea, let's leave this issue open and I'll look into adding support for sending text to a term window and/or integrating with Neoterm (I haven't tried neoterm yet).

Thank you!

@mheiber you bet! Actually in the time since you originally opened this I'd cobbled together some send-to-term functionality that I use as a REPL for clojure development. Have been meaning to clean it up and add it into neomux but haven't yet gotten around to it.

That said, if you want to try it out, you can add these functions to your init.vim and get a neoterm-ish experience:

function! GetVisualSelection()
    " from https://stackoverflow.com/a/47051271
    if mode()=="v"
        let [l:line_start, l:column_start] = getpos("v")[1:2]
        let [l:line_end, l:column_end] = getpos(".")[1:2]
    else
        let [l:line_start, l:column_start] = getpos("'<")[1:2]
        let [l:line_end, l:column_end] = getpos("'>")[1:2]
    end
    if (line2byte(l:line_start)+l:column_start) > (line2byte(l:line_end)+l:column_end)
        let [l:line_start, l:column_start, l:line_end, l:column_end] =
        \   [l:line_end, l:column_end, l:line_start, l:column_start]
    end
    let lines = getline(l:line_start, l:line_end)
    if len(lines) == 0
            return ''
    endif
    let lines[-1] = lines[-1][: l:column_end - 1]
    let lines[0] = lines[0][l:column_start - 1:]
    return join(lines, "\n")

function! SendToReplTerm(repl_term_chan_id)
    let l:to_send = GetVisualSelection() . "\n"
    call chansend(a:repl_term_chan_id, l:to_send)
endfunction!

With the above functions in place you can set up some mappings to send the visual selection to the repl (and append a carriage return to simulate pressing enter).

I use this mapping to store the id of the terminal to which I want to send stuff in a global var:

" set active repl. Marks currently focused terminal as target for repl commands
noremap <Leader>srp <cmd>let g:repl_term_id = b:terminal_job_id<CR><cmd>echo "g:repl_term_id is now " . g:repl_term_id<CR>

and then have a few other mappings based around 's' (for send) to visually select something and then send it to the repl/term in one keybinding:

" send visual selection to the repl via SendToReplTerm 
vnoremap s :<C-u>call SendToReplTerm(g:repl_term_id)<CR>  

" send current line to repl
map <LocalLeader>sl Vs