Skip to content

Commit

Permalink
Move xolox#shell#execute() -> xolox#misc#os#exec()
Browse files Browse the repository at this point in the history
Most of the Vim plug-ins I write have to execute external commands;
providing them input, intercepting stdout/stderr, verifying the exit
status, etc. Naturally I'd like to implement this in one place where
I can easily call it from all my plug-ins, even when vim-shell is not
installed. This is why I'm moving most of this code into the
miscellaneous script (which will be merged after this commit).
  • Loading branch information
xolox committed May 12, 2013
1 parent e82a7f6 commit 6539185
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 103 deletions.
17 changes: 11 additions & 6 deletions README.md
Expand Up @@ -8,7 +8,7 @@ This plug-in aims to improve the integration between [Vim][vim] and its environm

* The `:Open` command and `<F6>` mapping know how to open file and directory names, URLs and e-mail addresses in your favorite programs (file manager, web browser, e-mail client, etc). To invoke this functionality without using the `:Open` command see my [open.vim](http://peterodding.com/code/vim/open-associated-programs/) plug-in, which was split off from `shell.vim` so that other Vim plug-ins can bundle it without bringing in all the other crap :-).

* The `xolox#shell#execute()` function enables other Vim plug-ins (like my [easytags.vim] [easytags] plug-in) to execute external commands in the background (i.e. asynchronously) *without opening a command prompt window on Windows*.
* The `xolox#misc#os#exec()` function enables other Vim plug-ins (like my [easytags.vim] [easytags] plug-in) to execute external commands in the background (i.e. asynchronously) *without opening a command prompt window on Windows*.

Two [Windows DLL files][dll] are included to perform these functions on Windows, while on UNIX external commands are used.

Expand Down Expand Up @@ -40,19 +40,24 @@ Note that on UNIX if the environment variable `$DISPLAY` is empty the plug-in wi

This command is a very simple replacement for the [:make][make] command that does not pop up a console window on Windows. It doesn't come with all of the bells and whistles that Vim's built-in make command does but it should work.

### The `xolox#shell#execute()` function
### The `xolox#misc#os#exec()` function

This function enables other Vim plug-ins to execute external commands in the background (i.e. asynchronously) *without opening a command prompt window on Windows*. For example try to execute the following command on Windows ([vimrun.exe][vimrun] is only included with Vim for Windows because it isn't needed on other platforms):

:call xolox#shell#execute('vimrun', 0)
:call xolox#misc#os#exec({'command': 'vimrun', 'async': 1})

Immediately after executing this command Vim will respond to input again because `xolox#shell#execute()` doesn't wait for the external command to finish when the second argument is false (0). In addition no command prompt window will be shown which means [vimrun.exe][vimrun] is running completely invisible in the background. When the second argument is true (1) the output of the command will be returned as a list of lines, otherwise true (1) is returned unless an error occurred, in which case false (0) is returned.
Immediately after executing this command Vim will respond to input again because `xolox#misc#os#exec()` doesn't wait for the external command to finish when the 'async' argument is true (1). In addition no command prompt window will be shown which means [vimrun.exe][vimrun] is running completely invisible in the background.

If you want to verify that this function works as described, open the Windows task manager by pressing `Control-Shift-Escape` and check that the process `vimrun.exe` is listed in the processes tab. If you don't see the problem this is solving, try executing [vimrun.exe][vimrun] using Vim's built-in [system()][system] function instead:
The function returns a dictionary of return values. In asynchronous mode the dictionary is empty. In synchronous mode it contains the following key/value pairs:

:echo xolox#misc#os#exec({'command': 'echo "this is stdout" && echo "this is stderr" >&2 && exit 42'})
{'exit_code': 42, 'stdout': ['this is stdout'], 'stderr': ['this is stderr']}

If you want to verify that this function works as described, execute the command mentioning `vimrun` above, open the Windows task manager by pressing `Control-Shift-Escape` and check that the process `vimrun.exe` is listed in the processes tab. If you don't see the problem this is solving, try executing [vimrun.exe][vimrun] using Vim's built-in [system()][system] function instead:

:call system('vimrun')

Vim will be completely unresponsive until you "press any key to continue" in the command prompt window that's running [vimrun.exe][vimrun]. Now of course the [system()][system] function should only be used with non-interactive programs (the documentation says as much) but my point was to simulate an external command that takes a while to finish and blocks Vim while doing so.
Vim will be completely unresponsive until you "press any key to continue" in the command prompt window that's running [vimrun.exe][vimrun]. Of course the [system()][system] function should only be used with non-interactive programs (the documentation says as much) but the point is to simulate an external command that takes a while to finish and blocks Vim while doing so.

Note that on Windows this function uses Vim's ['shell'][sh_opt] and ['shellcmdflag'][shcf_opt] options to compose the command line passed to the DLL.

Expand Down
103 changes: 35 additions & 68 deletions autoload/xolox/shell.vim
@@ -1,9 +1,9 @@
" Vim auto-load script
" Author: Peter Odding <peter@peterodding.com>
" Last Change: May 6, 2013
" Last Change: May 13, 2013
" URL: http://peterodding.com/code/vim/shell/

let g:xolox#shell#version = '0.11.3'
let g:xolox#shell#version = '0.12'

call xolox#misc#compat#check('shell', 2)

Expand Down Expand Up @@ -89,7 +89,7 @@ function! s:open_at_cursor()
endfunction

function! xolox#shell#open_with_windows_shell(location)
if xolox#misc#os#is_win() && s:has_dll()
if xolox#shell#can_use_dll()
let error = s:library_call('openurl', a:location)
if error != ''
let msg = "shell.vim %s: Failed to open '%s' with Windows shell! (error: %s)"
Expand Down Expand Up @@ -117,51 +117,19 @@ function! xolox#shell#highlight_urls() " -- highlight URLs and e-mail addresses
endif
endfunction

function! xolox#shell#execute(command, synchronous, ...) " -- execute external commands asynchronously {{{1
try
let cmd = a:command
let has_input = a:0 > 0
if has_input
let tempin = tempname()
call writefile(type(a:1) == type([]) ? a:1 : split(a:1, "\n"), tempin)
let cmd .= ' < ' . xolox#misc#escape#shell(tempin)
endif
if a:synchronous
let tempout = tempname()
let cmd .= ' > ' . xolox#misc#escape#shell(tempout) . ' 2>&1'
endif
if xolox#misc#os#is_win() && s:has_dll()
let fn = 'execute_' . (a:synchronous ? '' : 'a') . 'synchronous'
let cmd = &shell . ' ' . &shellcmdflag . ' ' . cmd
call xolox#misc#msg#debug("shell.vim %s: Executing %s using compiled DLL.", g:xolox#shell#version, cmd)
let error = s:library_call(fn, cmd)
if error != ''
let msg = '%s(%s) failed! (error: %s)'
throw printf(msg, fn, strtrans(cmd), strtrans(error))
endif
else
if has('unix') && !a:synchronous
let cmd = '(' . cmd . ') &'
endif
call xolox#misc#msg#debug("shell.vim %s: Executing %s using system().", g:xolox#shell#version, cmd)
call s:handle_error(cmd, system(cmd))
function! xolox#shell#execute_with_dll(cmd, async) " -- execute external commands on Windows using the compiled DLL {{{1
let fn = 'execute_' . (a:async ? 'a' : '') . 'synchronous'
let cmd = &shell . ' ' . &shellcmdflag . ' ' . a:cmd
let result = s:library_call(fn, cmd)
if result =~ '^exit_code=\d\+$'
return matchstr(result, '^exit_code=\zs\d\+$') + 0
elseif result =~ '\S'
let msg = printf('%s(%s) failed!', fn, string(cmd))
if result =~ '\S'
let msg .= ' (output: ' . xolox#misc#str#trim(result) . ')'
endif
if a:synchronous
try
return readfile(tempout)
catch
let msg = 'Failed to get output of command "%s"!'
throw printf(msg, strtrans(cmd))
endtry
else
return 1
endif
catch
call xolox#misc#msg#warn("shell.vim %s: %s at %s", g:xolox#shell#version, v:exception, v:throwpoint)
finally
if exists('tempin') | call delete(tempin) | endif
if exists('tempout') | call delete(tempout) | endif
endtry
throw msg
endif
endfunction

function! xolox#shell#make(bang, args) " -- run :make silent (without a console window) {{{1
Expand Down Expand Up @@ -225,7 +193,7 @@ function! xolox#shell#fullscreen() " -- toggle Vim between normal and full-scree
" Now try to toggle the real full-screen status of Vim's GUI window using a
" custom dynamic link library on Windows or the "wmctrl" program on UNIX.
try
if xolox#misc#os#is_win() && s:has_dll()
if xolox#shell#can_use_dll()
let options = s:fullscreen_enabled ? 'disable' : 'enable'
if g:shell_fullscreen_always_on_top
let options .= ', always on top'
Expand All @@ -240,7 +208,17 @@ function! xolox#shell#fullscreen() " -- toggle Vim between normal and full-scree
throw msg . " On Debian/Ubuntu you can install it by executing `sudo apt-get install wmctrl'."
endif
let command = 'wmctrl -r :ACTIVE: -b toggle,fullscreen 2>&1'
call s:handle_error(command, system(command))
let output = system(command)
if v:shell_error
let msg = "Command %s failed!"
if a:output =~ '^\_s*$'
throw printf(msg, string(a:cmd))
else
let msg .= ' (output: %s)'
let output = strtrans(xolox#misc#str#trim(a:output))
throw printf(msg, string(a:cmd), output)
endif
endif
else
throw printf(s:enoimpl, 'fullscreen', s:contact)
endif
Expand Down Expand Up @@ -346,27 +324,16 @@ if xolox#misc#os#is_win()
return result
endfunction

function! s:has_dll() " {{{2
try
return s:library_call('libversion', '') == '0.5'
catch
return 0
endtry
function! xolox#shell#can_use_dll() " {{{2
if xolox#misc#os#is_win()
try
return s:library_call('libversion', '') == '0.5'
catch
return 0
endtry
endif
endfunction

endif

function! s:handle_error(cmd, output) " {{{2
if v:shell_error
let msg = "Command %s failed!"
if a:output =~ '^\_s*$'
throw printf(msg, string(a:cmd))
else
let msg .= ' (output: %s)'
let output = strtrans(xolox#misc#str#trim(a:output))
throw printf(msg, string(a:cmd), output)
endif
endif
endfunction

" vim: ts=2 sw=2 et fdm=marker
41 changes: 23 additions & 18 deletions doc/shell.txt
Expand Up @@ -10,7 +10,7 @@ Contents ~
2. The |:Fullscreen| command
3. The |:Open| command
4. The |:MakeWithShell| command
5. The |xolox#shell#execute()| function
5. The |xolox#misc#os#exec()| function
6. The |xolox#shell#fullscreen()| function
7. The |xolox#shell#is_fullscreen()| function
8. The |g:shell_fullscreen_items| option
Expand Down Expand Up @@ -45,7 +45,7 @@ This plug-in aims to improve the integration between Vim and its environment
from 'shell.vim' so that other Vim plug-ins can bundle it without bringing
in all the other crap :-).

- The 'xolox#shell#execute()' function enables other Vim plug-ins (like my
- The 'xolox#misc#os#exec()' function enables other Vim plug-ins (like my
easytags.vim [3] plug-in) to execute external commands in the background
(i.e. asynchronously) without opening a command prompt window on Windows.

Expand Down Expand Up @@ -108,36 +108,41 @@ pop up a console window on Windows. It doesn't come with all of the bells and
whistles that Vim's built-in make command does but it should work.

-------------------------------------------------------------------------------
The *xolox#shell#execute()* function
The *xolox#misc#os#exec()* function

This function enables other Vim plug-ins to execute external commands in the
background (i.e. asynchronously) without opening a command prompt window on
Windows. For example try to execute the following command on Windows
(vimrun.exe (see |win32-vimrun|) is only included with Vim for Windows because
it isn't needed on other platforms):
>
:call xolox#shell#execute('vimrun', 0)
:call xolox#misc#os#exec({'command': 'vimrun', 'async': 1})
Immediately after executing this command Vim will respond to input again
because 'xolox#shell#execute()' doesn't wait for the external command to
finish when the second argument is false (0). In addition no command prompt
window will be shown which means vimrun.exe (see |win32-vimrun|) is running
completely invisible in the background. When the second argument is true (1)
the output of the command will be returned as a list of lines, otherwise true
(1) is returned unless an error occurred, in which case false (0) is returned.

If you want to verify that this function works as described, open the Windows
task manager by pressing 'Control-Shift-Escape' and check that the process
'vimrun.exe' is listed in the processes tab. If you don't see the problem this
is solving, try executing vimrun.exe (see |win32-vimrun|) using Vim's built-in
|system()| function instead:
because 'xolox#misc#os#exec()' doesn't wait for the external command to finish
when the 'async' argument is true (1). In addition no command prompt window
will be shown which means vimrun.exe (see |win32-vimrun|) is running completely
invisible in the background.

The function returns a dictionary of return values. In asynchronous mode the
dictionary is empty. In synchronous mode it contains the following key/value
pairs:
>
:echo xolox#misc#os#exec({'command': 'echo "this is stdout" && echo "this is stderr" >&2 && exit 42'})
{'exit_code': 42, 'stdout': ['this is stdout'], 'stderr': ['this is stderr']}
If you want to verify that this function works as described, execute the
command mentioning 'vimrun' above, open the Windows task manager by pressing
'Control-Shift-Escape' and check that the process 'vimrun.exe' is listed in
the processes tab. If you don't see the problem this is solving, try executing
vimrun.exe (see |win32-vimrun|) using Vim's built-in |system()| function instead:
>
:call system('vimrun')
Vim will be completely unresponsive until you "press any key to continue" in
the command prompt window that's running vimrun.exe (see |win32-vimrun|). Now of
the command prompt window that's running vimrun.exe (see |win32-vimrun|). Of
course the |system()| function should only be used with non-interactive programs
(the documentation says as much) but my point was to simulate an external
(the documentation says as much) but the point is to simulate an external
command that takes a while to finish and blocks Vim while doing so.

Note that on Windows this function uses Vim's |'shell'| and |'shellcmdflag'|
Expand Down
4 changes: 2 additions & 2 deletions misc/shell/make.cmd
Expand Up @@ -3,10 +3,10 @@

:: Build shell-x86.dll.
CALL SETENV /Release /x86 /xp
CL /nologo /Wall /LD shell.c /link /out:shell-x86.dll shell32.lib user32.lib
CL /nologo /Wall /LD /D_CRT_SECURE_NO_WARNINGS shell.c /link /out:shell-x86.dll shell32.lib user32.lib
DEL shell.exp shell.lib shell.obj

:: Build shell-x64.dll.
CALL SETENV /Release /x64 /xp
CL /nologo /Wall /LD shell.c /link /out:shell-x64.dll shell32.lib user32.lib
CL /nologo /Wall /LD /D_CRT_SECURE_NO_WARNINGS shell.c /link /out:shell-x64.dll shell32.lib user32.lib
DEL shell.exp shell.lib shell.obj
Binary file modified misc/shell/shell-x64.dll
Binary file not shown.
Binary file modified misc/shell/shell-x86.dll
Binary file not shown.
23 changes: 14 additions & 9 deletions misc/shell/shell.c
Expand Up @@ -30,6 +30,7 @@
#include <windows.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <shellapi.h> /* ShellExecute? */

/* Dynamic strings are returned using a static buffer to avoid memory leaks */
Expand Down Expand Up @@ -72,17 +73,21 @@ static const char *execute(char *command, int wait) /* {{{1 */
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
if (CreateProcess(0, command, 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &si, &pi)) {
if (wait) {
WaitForSingleObject(pi.hProcess, INFINITE);
/* long exit_code; */
/* TODO: GetExitCodeProcess( pi.hProcess, &exit_code); */
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (!wait) {
return Success(NULL);
} else {
char rv[500];
DWORD exit_code;
if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED
&& GetExitCodeProcess(pi.hProcess, &exit_code)
&& CloseHandle(pi.hProcess)
&& CloseHandle(pi.hThread)
&& sprintf(rv, "exit_code=%li", exit_code)) {
return Success(rv);
}
}
return Success(NULL);
} else {
return Failure(GetError());
}
return Failure(GetError());
}

__declspec(dllexport)
Expand Down

0 comments on commit 6539185

Please sign in to comment.