Mercurial > vim
changeset 2935:bf283e37792b v7.3.240
updated for version 7.3.240
Problem: External commands can't use pipes on MS-Windows.
Solution: Implement pipes and use them when 'shelltemp' isn't set. (Vincent
Berthoux)
author | Bram Moolenaar <bram@vim.org> |
---|---|
date | Thu, 07 Jul 2011 16:20:52 +0200 |
parents | ba9df996e47c |
children | 73c9479ccf57 |
files | src/eval.c src/ex_cmds.c src/misc2.c src/os_unix.c src/os_win32.c src/proto/misc2.pro src/ui.c src/version.c |
diffstat | 8 files changed, 540 insertions(+), 29 deletions(-) [+] |
line wrap: on
line diff
--- a/src/eval.c +++ b/src/eval.c @@ -11931,7 +11931,7 @@ f_has(argvars, rettv) #ifdef FEAT_SEARCHPATH "file_in_path", #endif -#if defined(UNIX) && !defined(USE_SYSTEM) +#if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(WIN3264) "filterpipe", #endif #ifdef FEAT_FIND_ID
--- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -1107,7 +1107,7 @@ do_filter(line1, line2, eap, cmd, do_in, if (do_out) shell_flags |= SHELL_DOOUT; -#if !defined(USE_SYSTEM) && defined(UNIX) +#if (!defined(USE_SYSTEM) && defined(UNIX)) || defined(WIN3264) if (!do_in && do_out && !p_stmp) { /* Use a pipe to fetch stdout of the command, do not use a temp file. */
--- a/src/misc2.c +++ b/src/misc2.c @@ -2146,6 +2146,25 @@ ga_append(gap, c) } } +#if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(WIN3264) +/* + * Append the text in "gap" below the cursor line and clear "gap". + */ + void +append_ga_line(gap) + garray_T *gap; +{ + /* Remove trailing CR. */ + if (gap->ga_len > 0 + && !curbuf->b_p_bin + && ((char_u *)gap->ga_data)[gap->ga_len - 1] == CAR) + --gap->ga_len; + ga_append(gap, NUL); + ml_append(curwin->w_cursor.lnum++, gap->ga_data, 0, FALSE); + gap->ga_len = 0; +} +#endif + /************************************************************************ * functions that use lookup tables for various things, generally to do with * special key codes.
--- a/src/os_unix.c +++ b/src/os_unix.c @@ -3660,27 +3660,6 @@ mch_new_shellsize() /* Nothing to do. */ } -#ifndef USE_SYSTEM -static void append_ga_line __ARGS((garray_T *gap)); - -/* - * Append the text in "gap" below the cursor line and clear "gap". - */ - static void -append_ga_line(gap) - garray_T *gap; -{ - /* Remove trailing CR. */ - if (gap->ga_len > 0 - && !curbuf->b_p_bin - && ((char_u *)gap->ga_data)[gap->ga_len - 1] == CAR) - --gap->ga_len; - ga_append(gap, NUL); - ml_append(curwin->w_cursor.lnum++, gap->ga_data, 0, FALSE); - gap->ga_len = 0; -} -#endif - int mch_call_shell(cmd, options) char_u *cmd;
--- a/src/os_win32.c +++ b/src/os_win32.c @@ -417,6 +417,11 @@ static PSNSECINFO pSetNamedSecurityInfo; static PGNSECINFO pGetNamedSecurityInfo; #endif +typedef BOOL (WINAPI *PSETHANDLEINFORMATION)(HANDLE, DWORD, DWORD); + +static BOOL allowPiping = FALSE; +static PSETHANDLEINFORMATION pSetHandleInformation; + /* * Set g_PlatformId to VER_PLATFORM_WIN32_NT (NT) or * VER_PLATFORM_WIN32_WINDOWS (Win95). @@ -467,6 +472,18 @@ PlatformId(void) } } #endif + /* + * If we are on windows NT, try to load the pipe functions, only + * available from Win2K. + */ + if (g_PlatformId == VER_PLATFORM_WIN32_NT) + { + HANDLE kernel32 = GetModuleHandle("kernel32"); + pSetHandleInformation = (PSETHANDLEINFORMATION)GetProcAddress( + kernel32, "SetHandleInformation"); + + allowPiping = pSetHandleInformation != NULL; + } done = TRUE; } } @@ -1635,7 +1652,7 @@ executable_exists(char *name) } #if ((defined(__MINGW32__) || defined (__CYGWIN32__)) && \ - __MSVCRT_VERSION__ >= 0x800) || (defined(_MSC_VER) && _MSC_VER >= 1400) + __MSVCRT_VERSION__ >= 0x800) || (defined(_MSC_VER) && _MSC_VER >= 1400) /* * Bad parameter handler. * @@ -3210,7 +3227,7 @@ mch_set_winsize_now(void) * 4. Prompt the user to press a key to close the console window */ static int -mch_system(char *cmd, int options) +mch_system_classic(char *cmd, int options) { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -3315,6 +3332,498 @@ mch_system(char *cmd, int options) return ret; } + +/* + * Thread launched by the gui to send the current buffer data to the + * process. This way avoid to hang up vim totally if the children + * process take a long time to process the lines. + */ + static DWORD WINAPI +sub_process_writer(LPVOID param) +{ + HANDLE g_hChildStd_IN_Wr = param; + linenr_T lnum = curbuf->b_op_start.lnum; + DWORD len = 0; + DWORD l; + char_u *lp = ml_get(lnum); + char_u *s; + int written = 0; + + for (;;) + { + l = (DWORD)STRLEN(lp + written); + if (l == 0) + len = 0; + else if (lp[written] == NL) + { + /* NL -> NUL translation */ + WriteFile(g_hChildStd_IN_Wr, "", 1, &len, NULL); + } + else + { + s = vim_strchr(lp + written, NL); + WriteFile(g_hChildStd_IN_Wr, (char *)lp + written, + s == NULL ? l : (DWORD)(s - (lp + written)), + &len, NULL); + } + if (len == (int)l) + { + /* Finished a line, add a NL, unless this line should not have + * one. */ + if (lnum != curbuf->b_op_end.lnum + || !curbuf->b_p_bin + || (lnum != curbuf->b_no_eol_lnum + && (lnum != curbuf->b_ml.ml_line_count + || curbuf->b_p_eol))) + { + WriteFile(g_hChildStd_IN_Wr, "\n", 1, &ignored, NULL); + } + + ++lnum; + if (lnum > curbuf->b_op_end.lnum) + break; + + lp = ml_get(lnum); + written = 0; + } + else if (len > 0) + written += len; + } + + /* finished all the lines, close pipe */ + CloseHandle(g_hChildStd_IN_Wr); + ExitThread(0); +} + + +# define BUFLEN 100 /* length for buffer, stolen from unix version */ + +/* + * This function read from the children's stdout and write the + * data on screen or in the buffer accordingly. + */ + static void +dump_pipe(int options, + HANDLE g_hChildStd_OUT_Rd, + garray_T *ga, + char_u buffer[], + DWORD *buffer_off) +{ + DWORD availableBytes = 0; + DWORD i; + int c; + char_u *p; + int ret; + DWORD len; + DWORD toRead; + int repeatCount; + + /* we query the pipe to see if there is any data to read + * to avoid to perform a blocking read */ + ret = PeekNamedPipe(g_hChildStd_OUT_Rd, /* pipe to query */ + NULL, /* optional buffer */ + 0, /* buffe size */ + NULL, /* number of read bytes */ + &availableBytes, /* available bytes total */ + NULL); /* byteLeft */ + + repeatCount = 0; + /* We got real data in the pipe, read it */ + while (ret != 0 && availableBytes > 0 && availableBytes > 0) + { + repeatCount++; + toRead = +# ifdef FEAT_MBYTE + (DWORD)(BUFLEN - *buffer_off); +# else + (DWORD)BUFLEN; +# endif + toRead = availableBytes < toRead ? availableBytes : toRead; + ReadFile(g_hChildStd_OUT_Rd, buffer +# ifdef FEAT_MBYTE + + *buffer_off, toRead +# else + , toRead +# endif + , &len, NULL); + + /* If we haven't read anything, there is a problem */ + if (len == 0) + break; + + availableBytes -= len; + + if (options & SHELL_READ) + { + /* Do NUL -> NL translation, append NL separated + * lines to the current buffer. */ + for (i = 0; i < len; ++i) + { + if (buffer[i] == NL) + append_ga_line(ga); + else if (buffer[i] == NUL) + ga_append(ga, NL); + else + ga_append(ga, buffer[i]); + } + } +# ifdef FEAT_MBYTE + else if (has_mbyte) + { + int l; + + len += *buffer_off; + buffer[len] = NUL; + + /* Check if the last character in buffer[] is + * incomplete, keep these bytes for the next + * round. */ + for (p = buffer; p < buffer + len; p += l) + { + l = mb_cptr2len(p); + if (l == 0) + l = 1; /* NUL byte? */ + else if (MB_BYTE2LEN(*p) != l) + break; + } + if (p == buffer) /* no complete character */ + { + /* avoid getting stuck at an illegal byte */ + if (len >= 12) + ++p; + else + { + *buffer_off = len; + return; + } + } + c = *p; + *p = NUL; + msg_puts(buffer); + if (p < buffer + len) + { + *p = c; + *buffer_off = (DWORD)((buffer + len) - p); + mch_memmove(buffer, p, *buffer_off); + return; + } + *buffer_off = 0; + } +# endif /* FEAT_MBYTE */ + else + { + buffer[len] = NUL; + msg_puts(buffer); + } + + windgoto(msg_row, msg_col); + cursor_on(); + out_flush(); + } +} + +/* + * Version of system to use for windows NT > 5.0 (Win2K), use pipe + * for communication and doesn't open any new window. + */ + static int +mch_system_piped(char *cmd, int options) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + DWORD ret = 0; + + HANDLE g_hChildStd_IN_Rd = NULL; + HANDLE g_hChildStd_IN_Wr = NULL; + HANDLE g_hChildStd_OUT_Rd = NULL; + HANDLE g_hChildStd_OUT_Wr = NULL; + + char_u buffer[BUFLEN + 1]; /* reading buffer + size */ + DWORD len; + + /* buffer used to receive keys */ + char_u ta_buf[BUFLEN + 1]; /* TypeAHead */ + int ta_len = 0; /* valid bytes in ta_buf[] */ + + DWORD i; + int c; + int noread_cnt = 0; + garray_T ga; + int delay = 1; +# ifdef FEAT_MBYTE + DWORD buffer_off = 0; /* valid bytes in buffer[] */ +# endif + + SECURITY_ATTRIBUTES saAttr; + + /* Set the bInheritHandle flag so pipe handles are inherited. */ + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) + /* Ensure the read handle to the pipe for STDOUT is not inherited. */ + || ! pSetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) + /* Create a pipe for the child process's STDIN. */ + || ! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0) + /* Ensure the write handle to the pipe for STDIN is not inherited. */ + || ! pSetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) ) + { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + CloseHandle(g_hChildStd_OUT_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + MSG_PUTS(_("\nCannot create pipes\n")); + } + + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + + /* set-up our file redirection */ + si.hStdError = g_hChildStd_OUT_Wr; + si.hStdOutput = g_hChildStd_OUT_Wr; + si.hStdInput = g_hChildStd_IN_Rd; + si.wShowWindow = SW_HIDE; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + + if (options & SHELL_READ) + ga_init2(&ga, 1, BUFLEN); + + /* Now, run the command */ + CreateProcess(NULL, /* Executable name */ + cmd, /* Command to execute */ + NULL, /* Process security attributes */ + NULL, /* Thread security attributes */ + + // this command can be litigeous, handle inheritence was + // deactivated for pending temp file, but, if we deactivate + // it, the pipes don't work for some reason. + TRUE, /* Inherit handles, first deactivated, + * but needed */ + CREATE_DEFAULT_ERROR_MODE, /* Creation flags */ + NULL, /* Environment */ + NULL, /* Current directory */ + &si, /* Startup information */ + &pi); /* Process information */ + + + /* Close our unused side of the pipes */ + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + + if (options & SHELL_WRITE) + { + HANDLE thread = + CreateThread(NULL, /* security attributes */ + 0, /* default stack size */ + sub_process_writer, /* function to be executed */ + g_hChildStd_IN_Wr, /* parameter */ + 0, /* creation flag, start immediately */ + NULL); /* we don't care about thread id */ + CloseHandle(thread); + g_hChildStd_IN_Wr = NULL; + } + + /* Keep updating the window while waiting for the shell to finish. */ + for (;;) + { + MSG msg; + + if (PeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* write pipe information in the window */ + if ((options & (SHELL_READ|SHELL_WRITE)) +# ifdef FEAT_GUI + || gui.in_use +# endif + ) + { + len = 0; + if (!(options & SHELL_EXPAND) + && ((options & + (SHELL_READ|SHELL_WRITE|SHELL_COOKED)) + != (SHELL_READ|SHELL_WRITE|SHELL_COOKED) +# ifdef FEAT_GUI + || gui.in_use +# endif + ) + && (ta_len > 0 || noread_cnt > 4)) + { + if (ta_len == 0) + { + /* Get extra characters when we don't have any. Reset the + * counter and timer. */ + noread_cnt = 0; +# if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) + gettimeofday(&start_tv, NULL); +# endif + len = ui_inchar(ta_buf, BUFLEN, 10L, 0); + } + if (ta_len > 0 || len > 0) + { + /* + * For pipes: Check for CTRL-C: send interrupt signal to + * child. Check for CTRL-D: EOF, close pipe to child. + */ + if (len == 1 && cmd != NULL) + { + if (ta_buf[ta_len] == Ctrl_C) + { + /* Learn what exit code is expected, for + * now put 9 as SIGKILL */ + TerminateProcess(pi.hProcess, 9); + } + if (ta_buf[ta_len] == Ctrl_D) + { + CloseHandle(g_hChildStd_IN_Wr); + g_hChildStd_IN_Wr = NULL; + } + } + + /* replace K_BS by <BS> and K_DEL by <DEL> */ + for (i = ta_len; i < ta_len + len; ++i) + { + if (ta_buf[i] == CSI && len - i > 2) + { + c = TERMCAP2KEY(ta_buf[i + 1], ta_buf[i + 2]); + if (c == K_DEL || c == K_KDEL || c == K_BS) + { + mch_memmove(ta_buf + i + 1, ta_buf + i + 3, + (size_t)(len - i - 2)); + if (c == K_DEL || c == K_KDEL) + ta_buf[i] = DEL; + else + ta_buf[i] = Ctrl_H; + len -= 2; + } + } + else if (ta_buf[i] == '\r') + ta_buf[i] = '\n'; +# ifdef FEAT_MBYTE + if (has_mbyte) + i += (*mb_ptr2len_len)(ta_buf + i, + ta_len + len - i) - 1; +# endif + } + + /* + * For pipes: echo the typed characters. For a pty this + * does not seem to work. + */ + for (i = ta_len; i < ta_len + len; ++i) + { + if (ta_buf[i] == '\n' || ta_buf[i] == '\b') + msg_putchar(ta_buf[i]); +# ifdef FEAT_MBYTE + else if (has_mbyte) + { + int l = (*mb_ptr2len)(ta_buf + i); + + msg_outtrans_len(ta_buf + i, l); + i += l - 1; + } +# endif + else + msg_outtrans_len(ta_buf + i, 1); + } + windgoto(msg_row, msg_col); + out_flush(); + + ta_len += len; + + /* + * Write the characters to the child, unless EOF has been + * typed for pipes. Write one character at a time, to + * avoid losing too much typeahead. When writing buffer + * lines, drop the typed characters (only check for + * CTRL-C). + */ + if (options & SHELL_WRITE) + ta_len = 0; + else if (g_hChildStd_IN_Wr != NULL) + { + WriteFile(g_hChildStd_IN_Wr, (char*)ta_buf, + 1, &len, NULL); + // if we are typing in, we want to keep things reactive + delay = 1; + if (len > 0) + { + ta_len -= len; + mch_memmove(ta_buf, ta_buf + len, ta_len); + } + } + } + } + } + + if (ta_len) + ui_inchar_undo(ta_buf, ta_len); + + if (WaitForSingleObject(pi.hProcess, delay) != WAIT_TIMEOUT) + { + dump_pipe(options, g_hChildStd_OUT_Rd, + &ga, buffer, &buffer_off); + break; + } + + ++noread_cnt; + dump_pipe(options, g_hChildStd_OUT_Rd, + &ga, buffer, &buffer_off); + + /* We start waiting for a very short time and then increase it, so + * that we respond quickly when the process is quick, and don't + * consume too much overhead when it's slow. */ + if (delay < 50) + delay += 10; + } + + /* Close the pipe */ + CloseHandle(g_hChildStd_OUT_Rd); + if (g_hChildStd_IN_Wr != NULL) + CloseHandle(g_hChildStd_IN_Wr); + + WaitForSingleObject(pi.hProcess, INFINITE); + + /* Get the command exit code */ + GetExitCodeProcess(pi.hProcess, &ret); + + if (options & SHELL_READ) + { + if (ga.ga_len > 0) + { + append_ga_line(&ga); + /* remember that the NL was missing */ + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; + } + else + curbuf->b_no_eol_lnum = 0; + ga_clear(&ga); + } + + /* Close the handles to the subprocess, so that it goes away */ + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return ret; +} + + static int +mch_system(char *cmd, int options) +{ + /* if we can pipe and the shelltemp option is off */ + if (allowPiping && !p_stmp) + return mch_system_piped(cmd, options); + else + return mch_system_classic(cmd, options); +} #else # define mch_system(c, o) system(c) @@ -3388,7 +3897,7 @@ mch_call_shell( char_u *newcmd; long_u cmdlen = ( #ifdef FEAT_GUI_W32 - STRLEN(vimrun_path) + + (allowPiping && !p_stmp ? 0 : STRLEN(vimrun_path)) + #endif STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10); @@ -3497,7 +4006,7 @@ mch_call_shell( MB_ICONWARNING); need_vimrun_warning = FALSE; } - if (!s_dont_use_vimrun) + if (!s_dont_use_vimrun && (!allowPiping || p_stmp)) /* Use vimrun to execute the command. It opens a console * window, which can be closed without killing Vim. */ vim_snprintf((char *)newcmd, cmdlen, "%s%s%s %s %s", @@ -3521,7 +4030,8 @@ mch_call_shell( /* Print the return value, unless "vimrun" was used. */ if (x != 0 && !(options & SHELL_SILENT) && !emsg_silent #if defined(FEAT_GUI_W32) - && ((options & SHELL_DOOUT) || s_dont_use_vimrun) + && ((options & SHELL_DOOUT) || s_dont_use_vimrun + || (allowPiping && !p_stmp)) #endif ) {
--- a/src/proto/misc2.pro +++ b/src/proto/misc2.pro @@ -58,6 +58,7 @@ int ga_grow __ARGS((garray_T *gap, int n char_u *ga_concat_strings __ARGS((garray_T *gap)); void ga_concat __ARGS((garray_T *gap, char_u *s)); void ga_append __ARGS((garray_T *gap, int c)); +void append_ga_line __ARGS((garray_T *gap)); int name_to_mod_mask __ARGS((int c)); int simplify_key __ARGS((int key, int *modifiers)); int handle_x_keys __ARGS((int key));
--- a/src/ui.c +++ b/src/ui.c @@ -58,7 +58,7 @@ ui_write(s, len) #endif } -#if defined(UNIX) || defined(VMS) || defined(PROTO) +#if defined(UNIX) || defined(VMS) || defined(PROTO) || defined(WIN3264) /* * When executing an external program, there may be some typed characters that * are not consumed by it. Give them back to ui_inchar() and they are stored