2

I am new to vim and tmux.

TL;DR

I want to have tmux set up so that I have a pane for my (python) code and a pane for the output of the code.

What I know

Now, from vim I can do a command like :!python % to run/interpret my code, but the problem is that it displays the results, on the same screen, instead of the code. I have to end the program to see the code again.

What I found

I can do something like tmux run-shell -t 2 "echo hello" to send the output to a different pane, but this still replaces the code with Press ENTER or type a command to continue.

What I'd like

I'd love to know how can I send the output of the code to a different pane without disturbing my code pane.

  • I think it should be possible to switch to the pane and run python file from there or am I missing anything? – Maxim Kim Nov 16 at 16:43
  • @MaximKim Yeah that would work but I'd like to bee able to map that into F5 or something so I could compile the code without needing to switch pane and type the command – Daniel Cohen Hillel Nov 16 at 16:53
2

If you have vim8, it's easy to set up a mapping to open a :terminal, to which you can then send commands (like build and run code).

My workflow is something like

  1. Edit python code
  2. Try some code in the python interpreter (<Leader>t)
  3. Copy or write in the code
  4. Either tmux split, :term, Ctrl-Z, or :quit to run my script (with sessions, I can then get a semblance of where I was with vim -S).

I have seen several answers on this site dedicated to opening a :term and sending commands to it. There are also tmux plugins for vim that might help you do this. My alternative (below) is to let the shell do its thing (run code), but provide a way to do that in vim. It also lets me play with interpreters in vim with a minimum of fuss.

At the end, I do provide a possibility of something more like "run code" then "run an interpreter." It is untested, however.


I have these mappings:

" Terms and interpreters
" set b:interpreter for filetypes to affect the term
nnoremap <Leader>t :call terminal#run('++close')<CR>
nnoremap <Leader>T :call terminal#run()<CR>

which use this code (I should just make this a plugin at this point):

" autoload/terminal.vim
" Functions for dealing with the terminal

" Global function for calling terminal with the appropriate interpreter
" b:interpreter controls the program run
function! terminal#run(...) abort
  let l:interpreter = get(b:, 'interpreter', &shell)
  let l:command = 'term'
  if a:0
    for l:opt in a:000
      let l:command .= printf(' %s', opt)
    endfor
  endif
  let l:command .= printf(' %s', l:interpreter)
  execute l:command
endfunction

Then, in after/ftplugin/python.vim, I have

let b:interpreter = 'python'

I have similar definitions for lisp (clisp), ruby (irb), and more.

You could probably even do something like let b:interpreter = 'python '.expand('%'), so that the "interpreter" is actually "run this file." Then you would prefer <Leader>T, which, in my mappings, does not close the terminal when it finishes.


I'm not hating on tmux. I actually use it anytime I'm in a terminal. But for running code, it can be nice to pop open an integrated terminal, particularly for interpreted languages.


If the program is not interactive, the Clam plugin is quite useful.

  • Thanks! This works out really well, It's nice that at the end it also gives all the prints as a vim file(looks like it's not exactly a file but a buffer or something I dunno, still nice). – Daniel Cohen Hillel Nov 18 at 21:21
  • Its a terminal buffer. While its executing you can get to “normal” mode too. Read :help :term @DanielCohenHillel – D. Ben Knoble Nov 18 at 21:24
5

The run-shell example you mention looks identical to that in my 'Unix & Linux' post on this topic so you may have read that. If so, at the very end I mention use of a mapping to prevent external commands (e.g. a 'make' command or equivalent) from interrupting the vim session in too disruptive a way. Admittedly, it's just a small update to the answer with a basic example.

A more substantive example might be

nnoremap <leader>X :!tmux run-shell -b -t 3 'cd mybuilddir && make arg1 arg2... 2>&1'<CR><C-L>

Unlike the original this shows how to quote the shell command and use 2>&1 to ensure that errors sent to stderr appear in the same pane as regular output.

I also included run-shell flag -b which, as mentioned in the U&L answer, tells tmux to start the command in the background. If the shell command does anything wonky (like start by sleeping) this is an additional safeguard against interrupting the vim session. I'd try your own command with and without -b and use whatever works best.

Finally, if you have a complex shell (Bash) command you might want to do something like:

nnoremap <leader>X :!tmux run-shell -b -t 3 '{ cd mybuilddir; do this; do that; ... ; make arg1 arg2...; } 2>&1'<CR><C-L>

This, for one, avoids having to specify 2>&1 for each command. (Important: Bash requires that the braces be separated from the shell commands by whitespace and the last command, before }, be followed by ;).

If you have further questions on this approach let me know.

  • Also, Dispatch (tpope) makes all of this fun and easy :P – D. Ben Knoble Nov 16 at 23:01
  • Well not exactly...it doesn't run commands in existing panes or call run-shell. :P (It appears it always creates a new, dedicated pane with new-window or variant. Those commands accept a shell-command to be run on initialization.) Being serious, though, yes that's a good solution if you want something more than a quick and dirty approach. – B Layer Nov 17 at 1:14
3

If you build tmux from Git master you can send the output to an empty pane, for example:

mybuildcommand|tmux splitw -dI -c/my/source/directory

I don't know how to stop vim hiding the code while this is running however.

  • Welcome to Vi and Vim! Why does this need the master branch source compiled? Is this feature not present in the most recent release? In vim, the typical "unhide" sequence involves the mapping pressing <CR> for you, or doing some kind of :redraw! – D. Ben Knoble Nov 16 at 19:43
2

OK so I found a bit of a work around that works. I first declare a new command that gets as an input a command and executes the given command and then redraws the screen

command! -nargs=1 Silent execute ':silent !'.<q-args> | execute ':redraw!'

And on the next line it uses that command to send the interpreter command to a different pane.

map <F6> :Silent tmux send-keys -t 2 C-z 'python %' Enter
  • You probably don't need the ! on command, and you almost certainly want nnoremap instead of map. – D. Ben Knoble Nov 16 at 19:30
1

I've had great success with Vim-Slime.

The plugin allows you to send selections of text (current line, highlighted entry, etc.) from Vim running in one tmux pane to another tmux pane. (It also supports GNU Screen.)

It doesn't try to do bi-directional communication, which could be seen as a negative, but it's extremely flexible and lets you send text from Vim to anything running in the other pane (bash, SQL, any language with a REPL, etc.).

1

Expanding on pdoherty926's suggestion of using vim-slime. I use the following functions in my vimrc combined with vim-slime to map the F7 key to send the current line to the python shell window, and F8 to send the current selection buffer (i.e. the previously yanked/copied lines)

nnoremap <F7> :call <SID>slime_runline()<CR>
nnoremap <F8> :call <SID>slime_runselection()<CR>

" send the line beneath the vim cursor to the tmux window
" bound to F7 key
function! s:slime_runline()
   if getline('.') =~ "^\s*$"
      call cursor(getpos('.')[1]+1, col('.'))
      return
   endif
   let cmd = "SlimeSend1 ".getline('.')
   if matchstr(cmd, ':') != ''
      let cmd = cmd."\n"
   endif
   exec cmd 
   call cursor(getpos('.')[1]+1, col('.'))
endfunction

" send yanked lines to the tmux window
" bound to F8 key
" note that the operation works on the vim selection buffer,
" regardless of whether or not yank was the last action
function! s:slime_runselection()
   if mode() =="v"
      return
   endif
   let start = getpos("'<")[1]
   let end = getpos("'>")[1]
   let cmd = start.",".end."SlimeSend"
   echo cmd 
   exec cmd 
endfunction

For simple configuration with tmux, i also include the following in my vimrc to send lines to the bottom tmux pane

let g:slime_default_config = {"socket_name": "default", "target_pane": "{bottom-left}"}
1

I use the following mapping

" Save current file and rerun previous command in previous tmux window.
map <leader>t :up<CR>:execute 'silent !tmux select-window -t :{last} && tmux send-keys Up Enter'<CR>

This assumes that you are currently editing a script and that you have executed it already once in another tmux window. I detailed this approach here.

0

I think a plugin usage will be better since you are not limiting yourself to plugin.

For now I think there are two plugins that is worth checking out

Or if you prefer to show the result in vim quickfix window, you can use the Dispatch plugin instead

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.