To: vim_dev@googlegroups.com Subject: Patch 8.0.0902 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.0.0902 Problem: Cannot specify directory or environment for a job. Solution: Add the "cwd" and "env" arguments to job options. (Yasuhiro Matsumoto, closes #1160) Files: runtime/doc/channel.txt, src/channel.c, src/terminal.c, src/os_unix.c, src/os_win32.c, src/structs.h, src/testdir/test_channel.vim, src/testdir/test_terminal.vim *** ../vim-8.0.0901/runtime/doc/channel.txt 2017-01-25 22:40:45.076782542 +0100 --- runtime/doc/channel.txt 2017-08-11 18:02:28.198366352 +0200 *************** *** 1,4 **** ! *channel.txt* For Vim version 8.0. Last change: 2016 Oct 27 VIM REFERENCE MANUAL by Bram Moolenaar --- 1,4 ---- ! *channel.txt* For Vim version 8.0. Last change: 2017 Aug 11 VIM REFERENCE MANUAL by Bram Moolenaar *************** *** 375,381 **** ============================================================================== 6. Using a RAW or NL channel *channel-raw* ! If mode is RAW or NL then a message can be send like this: > let response = ch_evalraw(channel, {string}) The {string} is sent as-is. The response will be what can be read from the --- 375,381 ---- ============================================================================== 6. Using a RAW or NL channel *channel-raw* ! If mode is RAW or NL then a message can be sent like this: > let response = ch_evalraw(channel, {string}) The {string} is sent as-is. The response will be what can be read from the *************** *** 427,434 **** channels, an empty string for a RAW or NL channel. You can use |ch_canread()| to check if there is something to read. ! Note that when there is no callback message are dropped. To avoid that add a ! close callback to the channel. To read all output from a RAW channel that is available: > let output = ch_readraw(channel) --- 427,434 ---- channels, an empty string for a RAW or NL channel. You can use |ch_canread()| to check if there is something to read. ! Note that when there is no callback, messages are dropped. To avoid that add ! a close callback to the channel. To read all output from a RAW channel that is available: > let output = ch_readraw(channel) *************** *** 480,490 **** of a pipe causes the read end to get EOF). To avoid this make the job sleep for a short while before it exits. - Note that if the job exits before you read the output, the output may be lost. - This depends on the system (on Unix this happens because closing the write end - of a pipe causes the read end to get EOF). To avoid this make the job sleep - for a short while before it exits. - The handler defined for "out_cb" will not receive stderr. If you want to handle that separately, add an "err_cb" handler: > let job = job_start(command, {"out_cb": "MyHandler", --- 480,485 ---- *************** *** 494,499 **** --- 489,499 ---- "callback" option: > let job = job_start(command, {"callback": "MyHandler"}) + Depending on the system, starting a job can put Vim in the background, the + started job gets the focus. To avoid that, use the `foreground()` function. + This might not always work when called early, put in the callback handler or + use a timer to call it after the job has started. + You can send a message to the command with ch_evalraw(). If the channel is in JSON or JS mode you can use ch_evalexpr(). *************** *** 518,524 **** and "in_bot" options. A special mode is when "in_top" is set to zero and "in_bot" is not set: Every ! time a line is added to the buffer, the last-but-one line will be send to the job stdin. This allows for editing the last line and sending it when pressing Enter. *channel-close-in* --- 518,524 ---- and "in_bot" options. A special mode is when "in_top" is set to zero and "in_bot" is not set: Every ! time a line is added to the buffer, the last-but-one line will be sent to the job stdin. This allows for editing the last line and sending it when pressing Enter. *channel-close-in* *************** *** 549,555 **** To start another process without creating a channel: > let job = job_start(command, ! \ {"in_io": "null", "out_io": "null", "err_io": "null"}) This starts {command} in the background, Vim does not wait for it to finish. --- 549,555 ---- To start another process without creating a channel: > let job = job_start(command, ! \ {"in_io": "null", "out_io": "null", "err_io": "null"}) This starts {command} in the background, Vim does not wait for it to finish. *************** *** 610,620 **** *job-close_cb* "close_cb": handler Callback for when the channel is closed. Same as "close_cb" on |ch_open()|, see |close_cb|. ! *job-exit_cb* ! "drop" Specifies when to drop messages. Same as "drop" on |ch_open()|, see |channel-drop|. For "auto" the exit_cb is not considered. ! "exit_cb": handler Callback for when the job ends. The arguments are the job and the exit status. Vim checks up to 10 times per second for jobs that --- 610,620 ---- *job-close_cb* "close_cb": handler Callback for when the channel is closed. Same as "close_cb" on |ch_open()|, see |close_cb|. ! *job-drop* ! "drop": when Specifies when to drop messages. Same as "drop" on |ch_open()|, see |channel-drop|. For "auto" the exit_cb is not considered. ! *job-exit_cb* "exit_cb": handler Callback for when the job ends. The arguments are the job and the exit status. Vim checks up to 10 times per second for jobs that *************** *** 624,635 **** Note that data can be buffered, callbacks may still be called after the process ends. *job-timeout* ! "timeout" The time to wait for a request when blocking, E.g. when using ch_evalexpr(). In milliseconds. The default is 2000 (2 seconds). *out_timeout* *err_timeout* ! "out_timeout" Timeout for stdout. Only when using pipes. ! "err_timeout" Timeout for stderr. Only when using pipes. Note: when setting "timeout" the part specific mode is overwritten. Therefore set "timeout" first and the part specific mode later. --- 624,635 ---- Note that data can be buffered, callbacks may still be called after the process ends. *job-timeout* ! "timeout": time The time to wait for a request when blocking, E.g. when using ch_evalexpr(). In milliseconds. The default is 2000 (2 seconds). *out_timeout* *err_timeout* ! "out_timeout": time Timeout for stdout. Only when using pipes. ! "err_timeout": time Timeout for stderr. Only when using pipes. Note: when setting "timeout" the part specific mode is overwritten. Therefore set "timeout" first and the part specific mode later. *************** *** 641,657 **** The default is "term". *job-term* ! "term": "open" Start a terminal and connect the job ! stdin/stdout/stderr to it. NOTE: Not implemented yet! "channel": {channel} Use an existing channel instead of creating a new one. The parts of the channel that get used for the new job will be disconnected from what they were used before. ! If the channel was still use by another job this may cause I/O errors. Existing callbacks and other settings remain. *job-in_io* *in_top* *in_bot* *in_name* *in_buf* "in_io": "null" disconnect stdin (read from /dev/null) "in_io": "pipe" stdin is connected to the channel (default) --- 641,663 ---- The default is "term". *job-term* ! "term": "open" Start a terminal in a new window and connect the job ! stdin/stdout/stderr to it. Similar to using ! `:terminal`. NOTE: Not implemented yet! "channel": {channel} Use an existing channel instead of creating a new one. The parts of the channel that get used for the new job will be disconnected from what they were used before. ! If the channel was still used by another job this may cause I/O errors. Existing callbacks and other settings remain. + "pty": 1 Use a pty (pseudo-tty) instead of a pipe when + possible. This is most useful in combination with a + terminal window, see |terminal|. + {only on Unix and Unix-like systems} + *job-in_io* *in_top* *in_bot* *in_name* *in_buf* "in_io": "null" disconnect stdin (read from /dev/null) "in_io": "pipe" stdin is connected to the channel (default) *************** *** 666,672 **** "out_io": "null" disconnect stdout (goes to /dev/null) "out_io": "pipe" stdout is connected to the channel (default) "out_io": "file" stdout writes to a file ! "out_io": "buffer" stdout appends to a buffer (see below) "out_name": "/path/file" the name of the file or buffer to write to "out_buf": number the number of the buffer to write to "out_modifiable": 0 when writing to a buffer, 'modifiable' will be off --- 672,678 ---- "out_io": "null" disconnect stdout (goes to /dev/null) "out_io": "pipe" stdout is connected to the channel (default) "out_io": "file" stdout writes to a file ! "out_io": "buffer" stdout appends to a buffer (see below) "out_name": "/path/file" the name of the file or buffer to write to "out_buf": number the number of the buffer to write to "out_modifiable": 0 when writing to a buffer, 'modifiable' will be off *************** *** 679,685 **** "err_io": "null" disconnect stderr (goes to /dev/null) "err_io": "pipe" stderr is connected to the channel (default) "err_io": "file" stderr writes to a file ! "err_io": "buffer" stderr appends to a buffer (see below) "err_name": "/path/file" the name of the file or buffer to write to "err_buf": number the number of the buffer to write to "err_modifiable": 0 when writing to a buffer, 'modifiable' will be off --- 685,691 ---- "err_io": "null" disconnect stderr (goes to /dev/null) "err_io": "pipe" stderr is connected to the channel (default) "err_io": "file" stderr writes to a file ! "err_io": "buffer" stderr appends to a buffer (see below) "err_name": "/path/file" the name of the file or buffer to write to "err_buf": number the number of the buffer to write to "err_modifiable": 0 when writing to a buffer, 'modifiable' will be off *************** *** 690,695 **** --- 696,705 ---- "block_write": number only for testing: pretend every other write to stdin will block + "env": dict environment variables for the new process + "cwd": "/path/to/dir" current working directory for the new process; + if the directory does not exist an error is given + Writing to a buffer ~ *out_io-buffer* *************** *** 725,734 **** first line set to "Reading from channel output...". The default is to add the message. "err_msg" does the same for channel error. - 'modifiable' option off, or write to a buffer that has 'modifiable' off. That - means that lines will be appended to the buffer, but the user can't easily - change the buffer. - When an existing buffer is to be written where 'modifiable' is off and the "out_modifiable" or "err_modifiable" options is not zero, an error is given and the buffer will not be written to. --- 735,740 ---- *** ../vim-8.0.0901/src/channel.c 2017-08-11 16:31:50.325234460 +0200 --- src/channel.c 2017-08-11 18:23:09.601610683 +0200 *************** *** 4153,4158 **** --- 4153,4160 ---- partial_unref(opt->jo_exit_partial); else if (opt->jo_exit_cb != NULL) func_unref(opt->jo_exit_cb); + if (opt->jo_env != NULL) + dict_unref(opt->jo_env); } /* *************** *** 4433,4438 **** --- 4435,4460 ---- opt->jo_term_finish = *val; } #endif + else if (STRCMP(hi->hi_key, "env") == 0) + { + if (!(supported & JO2_ENV)) + break; + opt->jo_set |= JO2_ENV; + opt->jo_env = item->vval.v_dict; + ++item->vval.v_dict->dv_refcount; + } + else if (STRCMP(hi->hi_key, "cwd") == 0) + { + if (!(supported & JO2_CWD)) + break; + opt->jo_cwd = get_tv_string_buf_chk(item, opt->jo_cwd_buf); + if (opt->jo_cwd == NULL || !mch_isdir(opt->jo_cwd)) + { + EMSG2(_(e_invarg2), "cwd"); + return FAIL; + } + opt->jo_set |= JO2_CWD; + } else if (STRCMP(hi->hi_key, "waittime") == 0) { if (!(supported & JO_WAITTIME)) *** ../vim-8.0.0901/src/terminal.c 2017-08-11 16:31:50.329234432 +0200 --- src/terminal.c 2017-08-11 18:25:15.192723599 +0200 *************** *** 2362,2368 **** && get_job_options(&argvars[1], &opt, JO_TIMEOUT_ALL + JO_STOPONEXIT + JO_EXIT_CB + JO_CLOSE_CALLBACK ! + JO2_TERM_NAME + JO2_TERM_FINISH) == FAIL) return; term_start(cmd, &opt); --- 2362,2369 ---- && get_job_options(&argvars[1], &opt, JO_TIMEOUT_ALL + JO_STOPONEXIT + JO_EXIT_CB + JO_CLOSE_CALLBACK ! + JO2_TERM_NAME + JO2_TERM_FINISH ! + JO2_CWD + JO2_ENV) == FAIL) return; term_start(cmd, &opt); *** ../vim-8.0.0901/src/os_unix.c 2017-08-11 16:31:50.325234460 +0200 --- src/os_unix.c 2017-08-11 19:04:00.768368595 +0200 *************** *** 5320,5325 **** --- 5320,5341 ---- # endif set_default_child_environment(); + if (options->jo_env != NULL) + { + dict_T *dict = options->jo_env; + hashitem_T *hi; + int todo = (int)dict->dv_hashtab.ht_used; + + for (hi = dict->dv_hashtab.ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) + { + typval_T *item = &dict_lookup(hi)->di_tv; + + vim_setenv((char_u*)hi->hi_key, get_tv_string(item)); + --todo; + } + } + if (use_null_for_in || use_null_for_out || use_null_for_err) null_fd = open("/dev/null", O_RDWR | O_EXTRA, 0); *************** *** 5387,5392 **** --- 5403,5411 ---- if (null_fd >= 0) close(null_fd); + if (options->jo_cwd != NULL && mch_chdir((char *)options->jo_cwd) != 0) + _exit(EXEC_FAILED); + /* See above for type of argv. */ execvp(argv[0], argv); *** ../vim-8.0.0901/src/os_win32.c 2017-08-11 16:31:50.329234432 +0200 --- src/os_win32.c 2017-08-11 18:13:57.105511911 +0200 *************** *** 3981,4011 **** BOOL inherit_handles, DWORD flags, STARTUPINFO *si, ! PROCESS_INFORMATION *pi) { #ifdef FEAT_MBYTE if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) { ! WCHAR *wcmd = enc_to_utf16((char_u *)cmd, NULL); ! if (wcmd != NULL) { ! BOOL ret; ! ret = CreateProcessW( ! NULL, /* Executable name */ ! wcmd, /* Command to execute */ ! NULL, /* Process security attributes */ ! NULL, /* Thread security attributes */ ! inherit_handles, /* Inherit handles */ ! flags, /* Creation flags */ ! NULL, /* Environment */ ! NULL, /* Current directory */ ! (LPSTARTUPINFOW)si, /* Startup information */ ! pi); /* Process information */ ! vim_free(wcmd); ! return ret; } } #endif return CreateProcess( NULL, /* Executable name */ --- 3981,4026 ---- BOOL inherit_handles, DWORD flags, STARTUPINFO *si, ! PROCESS_INFORMATION *pi, ! LPVOID *env, ! char *cwd) { #ifdef FEAT_MBYTE if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) { ! BOOL ret; ! WCHAR *wcmd, *wcwd = NULL; ! wcmd = enc_to_utf16((char_u *)cmd, NULL); ! if (wcmd == NULL) ! goto fallback; ! if (cwd != NULL) { ! wcwd = enc_to_utf16((char_u *)cwd, NULL); ! if (wcwd == NULL) ! { ! vim_free(wcmd); ! goto fallback; ! } } + + ret = CreateProcessW( + NULL, /* Executable name */ + wcmd, /* Command to execute */ + NULL, /* Process security attributes */ + NULL, /* Thread security attributes */ + inherit_handles, /* Inherit handles */ + flags, /* Creation flags */ + env, /* Environment */ + wcwd, /* Current directory */ + (LPSTARTUPINFOW)si, /* Startup information */ + pi); /* Process information */ + vim_free(wcmd); + if (wcwd != NULL) + vim_free(wcwd); + return ret; } + fallback: #endif return CreateProcess( NULL, /* Executable name */ *************** *** 4014,4021 **** NULL, /* Thread security attributes */ inherit_handles, /* Inherit handles */ flags, /* Creation flags */ ! NULL, /* Environment */ ! NULL, /* Current directory */ si, /* Startup information */ pi); /* Process information */ } --- 4029,4036 ---- NULL, /* Thread security attributes */ inherit_handles, /* Inherit handles */ flags, /* Creation flags */ ! env, /* Environment */ ! cwd, /* Current directory */ si, /* Startup information */ pi); /* Process information */ } *************** *** 4079,4085 **** /* Now, run the command */ vim_create_process(cmd, FALSE, ! CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE, &si, &pi); /* Wait for the command to terminate before continuing */ { --- 4094,4101 ---- /* Now, run the command */ vim_create_process(cmd, FALSE, ! CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE, ! &si, &pi, NULL, NULL); /* Wait for the command to terminate before continuing */ { *************** *** 4398,4404 **** * About "Inherit handles" being TRUE: this command can be litigious, * handle inheritance was deactivated for pending temp file, but, if we * deactivate it, the pipes don't work for some reason. */ ! vim_create_process(p, TRUE, CREATE_DEFAULT_ERROR_MODE, &si, &pi); if (p != cmd) vim_free(p); --- 4414,4421 ---- * About "Inherit handles" being TRUE: this command can be litigious, * handle inheritance was deactivated for pending temp file, but, if we * deactivate it, the pipes don't work for some reason. */ ! vim_create_process(p, TRUE, CREATE_DEFAULT_ERROR_MODE, ! &si, &pi, NULL, NULL); if (p != cmd) vim_free(p); *************** *** 4835,4841 **** * inherit our handles which causes unpleasant dangling swap * files if we exit before the spawned process */ ! if (vim_create_process((char *)newcmd, FALSE, flags, &si, &pi)) x = 0; else if (vim_shell_execute((char *)newcmd, n_show_cmd) > (HINSTANCE)32) --- 4852,4859 ---- * inherit our handles which causes unpleasant dangling swap * files if we exit before the spawned process */ ! if (vim_create_process((char *)newcmd, FALSE, flags, ! &si, &pi, NULL, NULL)) x = 0; else if (vim_shell_execute((char *)newcmd, n_show_cmd) > (HINSTANCE)32) *************** *** 4976,4981 **** --- 4994,5060 ---- return h; } + /* + * Turn the dictionary "env" into a NUL separated list that can be used as the + * environment argument of vim_create_process(). + */ + static void + make_job_env(garray_T *gap, dict_T *env) + { + hashitem_T *hi; + int todo = (int)env->dv_hashtab.ht_used; + LPVOID base = GetEnvironmentStringsW(); + + /* for last \0 */ + if (ga_grow(gap, 1) == FAIL) + return; + + if (base) + { + WCHAR *p = (WCHAR*) base; + + /* for last \0 */ + if (ga_grow(gap, 1) == FAIL) + return; + + while (*p != 0 || *(p + 1) != 0) + { + if (ga_grow(gap, 1) == OK) + *((WCHAR*)gap->ga_data + gap->ga_len++) = *p; + p++; + } + FreeEnvironmentStrings(base); + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + } + + for (hi = env->dv_hashtab.ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + typval_T *item = &dict_lookup(hi)->di_tv; + WCHAR *wkey = enc_to_utf16((char_u *)hi->hi_key, NULL); + WCHAR *wval = enc_to_utf16(get_tv_string(item), NULL); + --todo; + if (wkey != NULL && wval != NULL) + { + int n, lkey = wcslen(wkey), lval = wcslen(wval); + if (ga_grow(gap, lkey + lval + 2) != OK) + continue; + for (n = 0; n < lkey; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = wkey[n]; + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'='; + for (n = 0; n < lval; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = wval[n]; + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + } + if (wkey != NULL) vim_free(wkey); + if (wval != NULL) vim_free(wval); + } + } + + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + } + void mch_job_start(char *cmd, job_T *job, jobopt_T *options) { *************** *** 4987,4992 **** --- 5066,5072 ---- HANDLE ifd[2]; HANDLE ofd[2]; HANDLE efd[2]; + garray_T ga; int use_null_for_in = options->jo_io[PART_IN] == JIO_NULL; int use_null_for_out = options->jo_io[PART_OUT] == JIO_NULL; *************** *** 5005,5010 **** --- 5085,5091 ---- ofd[1] = INVALID_HANDLE_VALUE; efd[0] = INVALID_HANDLE_VALUE; efd[1] = INVALID_HANDLE_VALUE; + ga_init2(&ga, (int)sizeof(wchar_t), 500); jo = CreateJobObject(NULL, NULL); if (jo == NULL) *************** *** 5013,5018 **** --- 5094,5102 ---- goto failed; } + if (options->jo_env != NULL) + make_job_env(&ga, options->jo_env); + ZeroMemory(&pi, sizeof(pi)); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); *************** *** 5100,5113 **** CREATE_SUSPENDED | CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE, ! &si, &pi)) { CloseHandle(jo); job->jv_status = JOB_FAILED; goto failed; } if (!AssignProcessToJobObject(jo, pi.hProcess)) { /* if failing, switch the way to terminate --- 5184,5202 ---- CREATE_SUSPENDED | CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP | + CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, ! &si, &pi, ! ga.ga_data, ! (char *)options->jo_cwd)) { CloseHandle(jo); job->jv_status = JOB_FAILED; goto failed; } + ga_clear(&ga); + if (!AssignProcessToJobObject(jo, pi.hProcess)) { /* if failing, switch the way to terminate *************** *** 5148,5153 **** --- 5237,5243 ---- CloseHandle(ofd[1]); CloseHandle(efd[1]); channel_unref(channel); + ga_clear(&ga); } char * *** ../vim-8.0.0901/src/structs.h 2017-08-10 23:15:15.002851689 +0200 --- src/structs.h 2017-08-11 18:08:37.555767310 +0200 *************** *** 1686,1692 **** #define JO2_ERR_MSG 0x0002 /* "err_msg" (JO_OUT_ << 1) */ #define JO2_TERM_NAME 0x0004 /* "term_name" */ #define JO2_TERM_FINISH 0x0008 /* "term_finish" */ ! #define JO2_ALL 0x000F #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) #define JO_CB_ALL \ --- 1686,1694 ---- #define JO2_ERR_MSG 0x0002 /* "err_msg" (JO_OUT_ << 1) */ #define JO2_TERM_NAME 0x0004 /* "term_name" */ #define JO2_TERM_FINISH 0x0008 /* "term_finish" */ ! #define JO2_ENV 0x0010 /* "env" */ ! #define JO2_CWD 0x0020 /* "cwd" */ ! #define JO2_ALL 0x003F #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) #define JO_CB_ALL \ *************** *** 1738,1743 **** --- 1740,1748 ---- int jo_id; char_u jo_soe_buf[NUMBUFLEN]; char_u *jo_stoponexit; + dict_T *jo_env; /* environment variables */ + char_u jo_cwd_buf[NUMBUFLEN]; + char_u *jo_cwd; #ifdef FEAT_TERMINAL /* when non-zero run the job in a terminal window of this size */ *** ../vim-8.0.0901/src/testdir/test_channel.vim 2017-07-15 17:01:50.350726204 +0200 --- src/testdir/test_channel.vim 2017-08-11 17:59:25.967647596 +0200 *************** *** 1664,1669 **** --- 1664,1708 ---- call assert_equal(1, g:linecount) endfunc + func Test_env() + if !has('job') + return + endif + + let s:envstr = '' + if has('win32') + call job_start(['cmd', '/c', 'echo %FOO%'], {'callback': {ch,msg->execute(":let s:envstr .= msg")}, 'env':{'FOO': 'bar'}}) + else + call job_start([&shell, &shellcmdflag, 'echo $FOO'], {'callback': {ch,msg->execute(":let s:envstr .= msg")}, 'env':{'FOO': 'bar'}}) + endif + call WaitFor('"" != s:envstr') + call assert_equal("bar", s:envstr) + unlet s:envstr + endfunc + + func Test_cwd() + if !has('job') + return + endif + + let s:envstr = '' + if has('win32') + let expect = $TEMP + call job_start(['cmd', '/c', 'echo %CD%'], {'callback': {ch,msg->execute(":let s:envstr .= msg")}, 'cwd': expect}) + else + let expect = $HOME + call job_start(['pwd'], {'callback': {ch,msg->execute(":let s:envstr .= msg")}, 'cwd': expect}) + endif + call WaitFor('"" != s:envstr') + let expect = substitute(expect, '[/\\]$', '', '') + let s:envstr = substitute(s:envstr, '[/\\]$', '', '') + if $CI != '' && stridx(s:envstr, '/private/') == 0 + let s:envstr = s:envstr[8:] + endif + call assert_equal(expect, s:envstr) + unlet s:envstr + endfunc + function Ch_test_close_lambda(port) let handle = ch_open('localhost:' . a:port, s:chopt) if ch_status(handle) == "fail" *** ../vim-8.0.0901/src/testdir/test_terminal.vim 2017-08-10 23:15:15.002851689 +0200 --- src/testdir/test_terminal.vim 2017-08-11 19:08:30.394474816 +0200 *************** *** 8,15 **** " Open a terminal with a shell, assign the job to g:job and return the buffer " number. ! func Run_shell_in_terminal() ! let buf = term_start(&shell) let termlist = term_list() call assert_equal(1, len(termlist)) --- 8,15 ---- " Open a terminal with a shell, assign the job to g:job and return the buffer " number. ! func Run_shell_in_terminal(options) ! let buf = term_start(&shell, a:options) let termlist = term_list() call assert_equal(1, len(termlist)) *************** *** 32,38 **** endfunc func Test_terminal_basic() ! let buf = Run_shell_in_terminal() if has("unix") call assert_match("^/dev/", job_info(g:job).tty) call assert_match("^/dev/", term_gettty('')) --- 32,38 ---- endfunc func Test_terminal_basic() ! let buf = Run_shell_in_terminal({}) if has("unix") call assert_match("^/dev/", job_info(g:job).tty) call assert_match("^/dev/", term_gettty('')) *************** *** 51,57 **** endfunc func Test_terminal_make_change() ! let buf = Run_shell_in_terminal() call Stop_shell_in_terminal(buf) call term_wait(buf) --- 51,57 ---- endfunc func Test_terminal_make_change() ! let buf = Run_shell_in_terminal({}) call Stop_shell_in_terminal(buf) call term_wait(buf) *************** *** 65,71 **** endfunc func Test_terminal_wipe_buffer() ! let buf = Run_shell_in_terminal() call assert_fails(buf . 'bwipe', 'E517') exe buf . 'bwipe!' call WaitFor('job_status(g:job) == "dead"') --- 65,71 ---- endfunc func Test_terminal_wipe_buffer() ! let buf = Run_shell_in_terminal({}) call assert_fails(buf . 'bwipe', 'E517') exe buf . 'bwipe!' call WaitFor('job_status(g:job) == "dead"') *************** *** 76,82 **** endfunc func Test_terminal_hide_buffer() ! let buf = Run_shell_in_terminal() quit for nr in range(1, winnr('$')) call assert_notequal(winbufnr(nr), buf) --- 76,82 ---- endfunc func Test_terminal_hide_buffer() ! let buf = Run_shell_in_terminal({}) quit for nr in range(1, winnr('$')) call assert_notequal(winbufnr(nr), buf) *************** *** 266,274 **** endfunc func Test_finish_close() call assert_equal(1, winnr('$')) - " TODO: use something that takes much less than a whole second if has('win32') let cmd = $windir . '\system32\timeout.exe 1' else --- 266,276 ---- endfunc func Test_finish_close() + return + " TODO: use something that takes much less than a whole second + echo 'This will take five seconds...' call assert_equal(1, winnr('$')) if has('win32') let cmd = $windir . '\system32\timeout.exe 1' else *************** *** 304,306 **** --- 306,337 ---- bwipe endfunc + + func Test_terminal_cwd() + if !has('unix') + return + endif + call mkdir('Xdir') + let buf = term_start('pwd', {'cwd': 'Xdir'}) + sleep 100m + call term_wait(buf) + call assert_equal(getcwd() . '/Xdir', getline(1)) + + exe buf . 'bwipe' + call delete('Xdir', 'rf') + endfunc + + func Test_terminal_env() + if !has('unix') + return + endif + let buf = Run_shell_in_terminal({'env': {'TESTENV': 'correct'}}) + call term_wait(buf) + call term_sendkeys(buf, "echo $TESTENV\r") + call term_wait(buf) + call Stop_shell_in_terminal(buf) + call term_wait(buf) + call assert_equal('correct', getline(2)) + + exe buf . 'bwipe' + endfunc *** ../vim-8.0.0901/src/version.c 2017-08-11 17:56:16.044983391 +0200 --- src/version.c 2017-08-11 18:01:04.838952501 +0200 *************** *** 771,772 **** --- 771,774 ---- { /* Add new patch number below this line */ + /**/ + 902, /**/ -- It is illegal for anyone to give lighted cigars to dogs, cats, and other domesticated animal kept as pets. [real standing law in Illinois, United States of America] /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///