4

So I mainly use Vim for work in python at the moment, and I've recently discovered 2 great strategies for running code externally and bringing it back into Vim. The first is the use of a function called Shell from the Vim pages:

function! s:ExecuteInShell(command)
  let command = join(map(split(a:command), 'expand(v:val)'))
  let winnr = bufwinnr('^' . command . '$')
  silent! execute  winnr < 0 ? 'botright new ' . fnameescape(command) : winnr . 'wincmd w'
  setlocal buftype=nowrite bufhidden=wipe nobuflisted noswapfile nowrap number
  echo 'Execute ' . command . '...'
  silent! execute 'silent %!'. command
  silent! execute 'resize ' . line('$')
  silent! redraw
  silent! execute 'au BufUnload <buffer> execute bufwinnr(' . bufnr('#') . ') . ''wincmd w'''
  silent! execute 'nnoremap <silent> <buffer> <LocalLeader>r :call <SID>ExecuteInShell(''' . command . ''')<CR>'
  echo 'Shell command ' . command . ' executed.'
endfunction
command! -complete=shellcmd -nargs=+ Shell call s:ExecuteInShell(<q-args>)

which allows one to run something like :Shell nosetests and see the results in a new window that is:

  1. Persistent (no "hit enter" to make it go away)
  2. Uses a temporary buffer (not a temp file)
  3. And most importantly, running the command again just refreshes the current window, it doesn't open a new one every time.

Then I use this little gem as well:

:'<,'>:w !python

which let's me use a selection from my current buffer, but which goes away after hitting enter.

What I can't figure out how to do is combine the two. What I want is:

  1. All the window properties of the Shell command, but
  2. The ability to run it from a selection on the screen.
  3. EDIT: do this for selected python code, not bash code. The function already does regular shell commands. Instead of using Shell to run $ python script.py I want it to run code directly like :'<,'>:w !python would.

I don't know enough Vimscript to modify Shell to include a selection, and I can't for the life of me figure out how to at least put :'<,'>:w !python into it's own window without the use of a temporary file, which seems unnecessary to me. Any ideas? Tips?

Community
  • 1
  • 1
brianclements
  • 879
  • 2
  • 8
  • 14
  • i don't know vimscript nearly well enough to fix this either, but since this code already uses `%!` from within the temp buffer, it'll probably work if you can get your selection copied into the temp buffer first. maybe user-defined commands can accept ranges? good luck :) – Eevee Oct 30 '13 at 03:39

2 Answers2

2

You can make the function accept ranges and test if a range was passed by:

function! s:ExecuteInShell(command) range
  let lines = []
  if (a:firstline != a:lastline)
    let lines=getline(a:firstline, a:lastline)
  endif
  let command = join(map(split(a:command), 'expand(v:val)'))
  let winnr = bufwinnr('^' . command . '$')
  silent! execute  winnr < 0 ? 'botright new ' . fnameescape(command) : winnr . 'wincmd w'
  setlocal buftype=nowrite bufhidden=wipe nobuflisted noswapfile nowrap number
  echo 'Execute ' . command . '...'
  if (len(lines))
    silent! call append(line('.'), lines)
    silent! 1d
    silent! redir => results
    silent! execute '1,$w !' . command
    silent! redir end
    silent! %d
    let lines = split(results, '\r')
    for line in lines[:1]
        call append(line('$'), line[1:])
    endfor
    silent! 1d
  else
    silent! execute 'silent %!'. command
  endif
  silent! execute 'resize ' . line('$')
  silent! redraw
  silent! execute 'au BufUnload <buffer> execute bufwinnr(' . bufnr('#') . ') . ''wincmd w'''
  silent! execute 'nnoremap <silent> <buffer> <LocalLeader>r :call <SID>ExecuteInShell(''' . command . ''')<CR>'
  echo 'Shell command ' . command . ' executed.'
endfunction
command! -range -complete=shellcmd -nargs=+ Shell <line1>,<line2>call s:ExecuteInShell(<q-args>)

To use with a command:

:Shell echo 'no range supplied, but a command (echo) is.'

To use while lines are selected (you don't type the "'<,'>" part as press ":" will put that there for you (command supplied as the lines selected are interpreted by the command):

:'<,'>Shell python
cforbish
  • 8,567
  • 3
  • 28
  • 32
  • Using that code verbatim, a new window pops up but nothing gets run. I typed `ls -hal` in an empty buffer, then I visually selected the line and ran it like: `:Shell '<,'>`. Am I using it wrong? – brianclements Oct 30 '13 at 08:56
  • Two things. First, this script needs more than one line passed in as the only way I found to determine if a range was selected was by a:firstline being different than a:lastline. Second, this script needs to be passed a command to use (i.e. python). You might be able to edit it to use the lack of a command passed to know that the lines are things to run. – cforbish Oct 30 '13 at 12:04
0

This is a version that uses lack of a command to process a range of lines:

function! s:ExecuteInShell(command) range
  let lines = []
  if (!len(a:command))
    let lines=getline(a:firstline, a:lastline)
  endif
  let command = join(map(split(a:command), 'expand(v:val)'))
  let winnr = bufwinnr('^' . command . '$')
  silent! execute  winnr < 0 ? 'botright new ' . fnameescape(command) : winnr . 'wincmd w'
  setlocal buftype=nowrite bufhidden=wipe nobuflisted noswapfile nowrap number
  echo 'Execute ' . command . '...'
  if (len(lines))
    for line in lines
        silent! execute 'silent r! '. line
    endfor
    silent! 1d
  else
    silent! execute 'silent %!'. command
  endif
  silent! execute 'resize ' . line('$')
  silent! redraw
  silent! execute 'au BufUnload <buffer> execute bufwinnr(' . bufnr('#') . ') . ''wincmd w'''
  silent! execute 'nnoremap <silent> <buffer> <LocalLeader>r :call <SID>ExecuteInShell(''' . command . ''')<CR>'
  echo 'Shell command ' . command . ' executed.'
endfunction
command! -range -complete=shellcmd -nargs=* Shell <line1>,<line2>call s:ExecuteInShell(<q-args>)

To use with a command:

:Shell echo 'no range supplied, but a command (echo) is.'

To use while lines are selected (you don't type the "'<,'>" part as press ":" will put that there for you (no command supplied as the lines selected supply the command):

:'<,'>Shell
cforbish
  • 8,567
  • 3
  • 28
  • 32
  • this is great, thanks! The other half of the question though was how to do this for running python code as well. I wasn't clear in my original questions so I'll update it now. I tried some things myself thinking it was trivial, but simply adding `python` before/after the `!` didn't work as I thought it would. Is that an addition you think you could make @cforbish ? – brianclements Oct 31 '13 at 06:32
  • This is what I understood with my original answer. I have updated my original answer with details on how to use. – cforbish Oct 31 '13 at 10:06
  • Unfortunately, trying `:'<,'>Shell python` with the script in this answer returns an empty window. – brianclements Nov 05 '13 at 04:08