feat(server): set $NVIM, unset $NVIM_LISTEN_ADDRESS #11009

PROBLEM
------------------------------------------------------------------------
$NVIM_LISTEN_ADDRESS has conflicting purposes as both a parameter ("the
current process should listen on this address") and a descriptor ("the
current process is a child of this address").

This contradiction means the presence of NVIM_LISTEN_ADDRESS is
ambiguous, so child Nvim always tries to listen on its _parent's_
socket. This is the cause of lots of  "Failed to start server" spam in
our test/CI logs:

    WARN  2022-04-30… server_start:154: Failed to start server: address already in use: \\.\pipe\nvim-4480-0
    WARN  2022-04-30… server_start:154: Failed to start server: address already in use: \\.\pipe\nvim-2168-0

SOLUTION
------------------------------------------------------------------------

1. Set $NVIM to the parent v:servername, *only* in child processes.
   - Now the correct way to detect a "parent" Nvim is to check for $NVIM.
2. Do NOT set $NVIM_LISTEN_ADDRESS in child processes.
3. On startup if $NVIM_LISTEN_ADDRESS exists, unset it immediately after
   server init.
4. Open a channel to parent automatically, expose it as v:parent.

Fixes #3118
Fixes #6764
Fixes #9336
Ref https://github.com/neovim/neovim/pull/8247#issuecomment-380275696
Ref #8696
This commit is contained in:
Justin M. Keyes 2022-05-03 15:08:35 +02:00 committed by GitHub
parent b5c15ba7e5
commit 4fb48c5654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 157 additions and 245 deletions

View File

@ -59,7 +59,7 @@ Nvim instance:
# trailing '&' which is required since Nvim won't process events while # trailing '&' which is required since Nvim won't process events while
# running a blocking command): # running a blocking command):
# #
# :!./hello.rb & # :!./hello.rb &
# #
# Or from another shell by setting NVIM_LISTEN_ADDRESS: # Or from another shell by setting NVIM_LISTEN_ADDRESS:
# $ NVIM_LISTEN_ADDRESS=[address] ./hello.rb # $ NVIM_LISTEN_ADDRESS=[address] ./hello.rb

View File

@ -158,7 +158,6 @@ foldclosedend({lnum}) Number last line of fold at {lnum} if closed
foldlevel({lnum}) Number fold level at {lnum} foldlevel({lnum}) Number fold level at {lnum}
foldtext() String line displayed for closed fold foldtext() String line displayed for closed fold
foldtextresult({lnum}) String text for closed fold at {lnum} foldtextresult({lnum}) String text for closed fold at {lnum}
foreground() Number bring the Vim window to the foreground
fullcommand({name}) String get full command from {name} fullcommand({name}) String get full command from {name}
funcref({name} [, {arglist}] [, {dict}]) funcref({name} [, {arglist}] [, {dict}])
Funcref reference to function {name} Funcref reference to function {name}
@ -352,16 +351,6 @@ reg_recording() String get the recording register name
reltime([{start} [, {end}]]) List get time value reltime([{start} [, {end}]]) List get time value
reltimefloat({time}) Float turn the time value into a Float reltimefloat({time}) Float turn the time value into a Float
reltimestr({time}) String turn time value into a String reltimestr({time}) String turn time value into a String
remote_expr({server}, {string} [, {idvar} [, {timeout}]])
String send expression
remote_foreground({server}) Number bring Vim server to the foreground
remote_peek({serverid} [, {retvar}])
Number check for reply string
remote_read({serverid} [, {timeout}])
String read reply string
remote_send({server}, {string} [, {idvar}])
String send key sequence
remote_startserver({name}) none become server {name}
remove({list}, {idx} [, {end}]) any/List remove({list}, {idx} [, {end}]) any/List
remove items {idx}-{end} from {list} remove items {idx}-{end} from {list}
remove({blob}, {idx} [, {end}]) Number/Blob remove({blob}, {idx} [, {end}]) Number/Blob
@ -395,8 +384,6 @@ searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
List search for other end of start/end pair List search for other end of start/end pair
searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
List search for {pattern} List search for {pattern}
server2client({clientid}, {string})
Number send reply string
serverlist() String get a list of available servers serverlist() String get a list of available servers
setbufline({expr}, {lnum}, {text}) setbufline({expr}, {lnum}, {text})
Number set line {lnum} to {text} in buffer Number set line {lnum} to {text} in buffer
@ -1974,7 +1961,7 @@ expand({string} [, {nosuf} [, {list}]]) *expand()*
<cword> word under the cursor <cword> word under the cursor
<cWORD> WORD under the cursor <cWORD> WORD under the cursor
<client> the {clientid} of the last received <client> the {clientid} of the last received
message |server2client()| message
Modifiers: Modifiers:
:p expand to full path :p expand to full path
:h head (last path component removed) :h head (last path component removed)
@ -2409,14 +2396,6 @@ foldtextresult({lnum}) *foldtextresult()*
Can also be used as a |method|: > Can also be used as a |method|: >
GetLnum()->foldtextresult() GetLnum()->foldtextresult()
< <
*foreground()*
foreground() Move the Vim window to the foreground. Useful when sent from
a client to a Vim server. |remote_send()|
On Win32 systems this might not work, the OS does not always
allow a window to bring itself to the foreground. Use
|remote_foreground()| instead.
{only in the Win32 GUI and console version}
fullcommand({name}) *fullcommand()* fullcommand({name}) *fullcommand()*
Get the full command name from a short abbreviated command Get the full command name from a short abbreviated command
name; see |20.2| for details on command abbreviations. name; see |20.2| for details on command abbreviations.
@ -4280,6 +4259,15 @@ jobstart({cmd} [, {opts}]) *jobstart()*
by CommandLineToArgvW https://msdn.microsoft.com/bb776391 by CommandLineToArgvW https://msdn.microsoft.com/bb776391
unless cmd[0] is some form of "cmd.exe". unless cmd[0] is some form of "cmd.exe".
*jobstart-env*
The job environment is initialized as follows:
$NVIM is set to |v:servername| of the parent Nvim
$NVIM_LISTEN_ADDRESS is unset
$NVIM_LOG_FILE is unset
$VIM is unset
$VIMRUNTIME is unset
You can set these with the `env` option.
*jobstart-options* *jobstart-options*
{opts} is a dictionary with these keys: {opts} is a dictionary with these keys:
clear_env: (boolean) `env` defines the job environment clear_env: (boolean) `env` defines the job environment
@ -4290,8 +4278,8 @@ jobstart({cmd} [, {opts}]) *jobstart()*
killed when Nvim exits. If the process exits killed when Nvim exits. If the process exits
before Nvim, `on_exit` will be invoked. before Nvim, `on_exit` will be invoked.
env: (dict) Map of environment variable name:value env: (dict) Map of environment variable name:value
pairs extending (or replacing if |clear_env|) pairs extending (or replacing with |clear_env|)
the current environment. the current environment. |jobstart-env|
height: (number) Height of the `pty` terminal. height: (number) Height of the `pty` terminal.
|on_exit|: (function) Callback invoked when the job exits. |on_exit|: (function) Callback invoked when the job exits.
|on_stdout|: (function) Callback invoked when the job emits |on_stdout|: (function) Callback invoked when the job emits
@ -4305,8 +4293,8 @@ jobstart({cmd} [, {opts}]) *jobstart()*
platforms, this option is silently ignored.) platforms, this option is silently ignored.)
pty: (boolean) Connect the job to a new pseudo pty: (boolean) Connect the job to a new pseudo
terminal, and its streams to the master file terminal, and its streams to the master file
descriptor. Then `on_stderr` is ignored, descriptor. `on_stdout` receives all output,
`on_stdout` receives all output. `on_stderr` is ignored. |terminal-start|
rpc: (boolean) Use |msgpack-rpc| to communicate with rpc: (boolean) Use |msgpack-rpc| to communicate with
the job over stdio. Then `on_stdout` is ignored, the job over stdio. Then `on_stdout` is ignored,
but `on_stderr` can still be used. but `on_stderr` can still be used.
@ -5242,9 +5230,8 @@ mode([expr]) Return a string that indicates the current mode.
! Shell or external command is executing ! Shell or external command is executing
t Terminal mode: keys go to the job t Terminal mode: keys go to the job
This is useful in the 'statusline' option or when used This is useful in the 'statusline' option or RPC calls. In
with |remote_expr()| In most other places it always returns most other places it always returns "c" or "n".
"c" or "n".
Note that in the future more modes and more specific modes may Note that in the future more modes and more specific modes may
be added. It's better not to compare the whole string but only be added. It's better not to compare the whole string but only
the leading character(s). the leading character(s).
@ -5964,107 +5951,6 @@ reltimestr({time}) *reltimestr()*
Can also be used as a |method|: > Can also be used as a |method|: >
reltime(start)->reltimestr() reltime(start)->reltimestr()
< <
*remote_expr()* *E449*
remote_expr({server}, {string} [, {idvar} [, {timeout}]])
Send the {string} to {server}. The {server} argument is a
string, also see |{server}|.
The string is sent as an expression and the result is returned
after evaluation. The result must be a String or a |List|. A
|List| is turned into a String by joining the items with a
line break in between (not at the end), like with join(expr,
"\n").
If {idvar} is present and not empty, it is taken as the name
of a variable and a {serverid} for later use with
|remote_read()| is stored there.
If {timeout} is given the read times out after this many
seconds. Otherwise a timeout of 600 seconds is used.
See also |clientserver| |RemoteReply|.
This function is not available in the |sandbox|.
Note: Any errors will cause a local error message to be issued
and the result will be the empty string.
Variables will be evaluated in the global namespace,
independent of a function currently being active. Except
when in debug mode, then local function variables and
arguments can be evaluated.
Examples: >
:echo remote_expr("gvim", "2+2")
:echo remote_expr("gvim1", "b:current_syntax")
<
remote_foreground({server}) *remote_foreground()*
Move the Vim server with the name {server} to the foreground.
The {server} argument is a string, also see |{server}|.
This works like: >
remote_expr({server}, "foreground()")
< Except that on Win32 systems the client does the work, to work
around the problem that the OS doesn't always allow the server
to bring itself to the foreground.
Note: This does not restore the window if it was minimized,
like foreground() does.
This function is not available in the |sandbox|.
{only in the Win32 GUI and the Win32 console version}
remote_peek({serverid} [, {retvar}]) *remote_peek()*
Returns a positive number if there are available strings
from {serverid}. Copies any reply string into the variable
{retvar} if specified. {retvar} must be a string with the
name of a variable.
Returns zero if none are available.
Returns -1 if something is wrong.
See also |clientserver|.
This function is not available in the |sandbox|.
Examples: >
:let repl = ""
:echo "PEEK: " .. remote_peek(id, "repl") .. ": " .. repl
remote_read({serverid}, [{timeout}]) *remote_read()*
Return the oldest available reply from {serverid} and consume
it. Unless a {timeout} in seconds is given, it blocks until a
reply is available.
See also |clientserver|.
This function is not available in the |sandbox|.
Example: >
:echo remote_read(id)
<
*remote_send()* *E241*
remote_send({server}, {string} [, {idvar}])
Send the {string} to {server}. The {server} argument is a
string, also see |{server}|.
The string is sent as input keys and the function returns
immediately. At the Vim server the keys are not mapped
|:map|.
If {idvar} is present, it is taken as the name of a variable
and a {serverid} for later use with remote_read() is stored
there.
See also |clientserver| |RemoteReply|.
This function is not available in the |sandbox|.
Note: Any errors will be reported in the server and may mess
up the display.
Examples: >
:echo remote_send("gvim", ":DropAndReply " .. file, "serverid") ..
\ remote_read(serverid)
:autocmd NONE RemoteReply *
\ echo remote_read(expand("<amatch>"))
:echo remote_send("gvim", ":sleep 10 | echo " ..
\ 'server2client(expand("<client>"), "HELLO")<CR>')
<
*remote_startserver()* *E941* *E942*
remote_startserver({name})
Become the server {name}. This fails if already running as a
server, when |v:servername| is not empty.
remove({list}, {idx} [, {end}]) *remove()* remove({list}, {idx} [, {end}]) *remove()*
Without {end}: Remove the item at {idx} from |List| {list} and Without {end}: Remove the item at {idx} from |List| {list} and
return the item. return the item.
@ -6648,21 +6534,6 @@ searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Can also be used as a |method|: > Can also be used as a |method|: >
GetPattern()->searchpos() GetPattern()->searchpos()
server2client({clientid}, {string}) *server2client()*
Send a reply string to {clientid}. The most recent {clientid}
that sent a string can be retrieved with expand("<client>").
Note:
Returns zero for success, -1 for failure.
This id has to be stored before the next command can be
received. I.e. before returning from the received command and
before calling any commands that waits for input.
See also |clientserver|.
Example: >
:echo server2client(expand("<client>"), "HELLO")
< Can also be used as a |method|: >
GetClientId()->server2client(string)
<
serverlist() *serverlist()* serverlist() *serverlist()*
Returns a list of server addresses, or empty if all servers Returns a list of server addresses, or empty if all servers
were stopped. |serverstart()| |serverstop()| were stopped. |serverstart()| |serverstop()|
@ -6672,7 +6543,9 @@ serverlist() *serverlist()*
serverstart([{address}]) *serverstart()* serverstart([{address}]) *serverstart()*
Opens a socket or named pipe at {address} and listens for Opens a socket or named pipe at {address} and listens for
|RPC| messages. Clients can send |API| commands to the address |RPC| messages. Clients can send |API| commands to the address
to control Nvim. Returns the address string. to control Nvim.
Returns the address string.
If {address} does not contain a colon ":" it is interpreted as If {address} does not contain a colon ":" it is interpreted as
a named pipe or Unix domain socket path. a named pipe or Unix domain socket path.
@ -6694,14 +6567,11 @@ serverstart([{address}]) *serverstart()*
If no address is given, it is equivalent to: > If no address is given, it is equivalent to: >
:call serverstart(tempname()) :call serverstart(tempname())
< |$NVIM_LISTEN_ADDRESS| is set to {address} if not already set.
serverstop({address}) *serverstop()* serverstop({address}) *serverstop()*
Closes the pipe or socket at {address}. Closes the pipe or socket at {address}.
Returns TRUE if {address} is valid, else FALSE. Returns TRUE if {address} is valid, else FALSE.
If |$NVIM_LISTEN_ADDRESS| is stopped it is unset.
If |v:servername| is stopped it is set to the next available If |v:servername| is stopped it is set to the next available
address returned by |serverlist()|. address in |serverlist()|.
setbufline({buf}, {lnum}, {text}) *setbufline()* setbufline({buf}, {lnum}, {text}) *setbufline()*
Set line {lnum} to {text} in buffer {buf}. This works like Set line {lnum} to {text} in buffer {buf}. This works like
@ -8281,19 +8151,15 @@ tempname() *tempname()* *temp-file-name*
termopen({cmd} [, {opts}]) *termopen()* termopen({cmd} [, {opts}]) *termopen()*
Spawns {cmd} in a new pseudo-terminal session connected Spawns {cmd} in a new pseudo-terminal session connected
to the current buffer. {cmd} is the same as the one passed to to the current (unmodified) buffer. Parameters and behavior
|jobstart()|. This function fails if the current buffer is are the same as |jobstart()| except "pty", "width", "height",
modified (all buffer contents are destroyed). and "TERM" are ignored: "height" and "width" are taken from
the current window.
The {opts} dict is similar to the one passed to |jobstart()|,
but the `pty`, `width`, `height`, and `TERM` fields are
ignored: `height`/`width` are taken from the current window
and `$TERM` is set to "xterm-256color".
Returns the same values as |jobstart()|. Returns the same values as |jobstart()|.
See |terminal| for more information. Terminal environment is initialized as in ||jobstart-env|,
except $TERM is set to "xterm-256color". Full behavior is
test_ functions are documented here: |test-functions-details| described in |terminal|.
tan({expr}) *tan()* tan({expr}) *tan()*
Return the tangent of {expr}, measured in radians, as a |Float| Return the tangent of {expr}, measured in radians, as a |Float|

View File

@ -6,9 +6,8 @@
Nvim *deprecated* Nvim *deprecated*
The items listed below are "deprecated". This means they will be removed in The items listed below are deprecated: they will be removed in the future.
the future. They should not be used in new scripts, and old scripts should be They should not be used in new scripts, and old scripts should be updated.
updated.
============================================================================== ==============================================================================
@ -25,8 +24,12 @@ Commands ~
*:wviminfo* Deprecated alias to |:wshada| command. *:wviminfo* Deprecated alias to |:wshada| command.
Environment Variables ~ Environment Variables ~
*$NVIM_LISTEN_ADDRESS* Deprecated in favor of |--listen|. If both are given, *$NVIM_LISTEN_ADDRESS* $NVIM_LISTEN_ADDRESS is a deprecated way to set the
$NVIM_LISTEN_ADDRESS is ignored. |--listen| address of Nvim, and also had a conflicting
purpose as a way to detect a parent Nvim (use |$NVIM|
for that). It is unset by |terminal| and |jobstart()|
(unless explicitly given by the "env" option).
Ignored if --listen is given.
Events ~ Events ~
*BufCreate* Use |BufAdd| instead. *BufCreate* Use |BufAdd| instead.

View File

@ -2138,9 +2138,19 @@ v:scrollstart String describing the script or function that caused the
*v:servername* *servername-variable* *v:servername* *servername-variable*
v:servername Primary listen-address of the current Nvim instance, the first v:servername Primary listen-address of the current Nvim instance, the first
item returned by |serverlist()|. Can be set by |--listen| or item returned by |serverlist()|. Can be set by |--listen| or
|$NVIM_LISTEN_ADDRESS| at startup. |serverstart()| |serverstop()| |$NVIM_LISTEN_ADDRESS| (deprecated) at startup.
See also |serverstart()| |serverstop()|.
Read-only. Read-only.
*$NVIM*
$NVIM is set by |terminal| and |jobstart()|, and is thus
a hint that the current environment is a subprocess of Nvim.
Example: >
if $NVIM
echo nvim_get_chan_info(v:parent)
endif
< Note the contents of $NVIM may change in the future.
v:searchforward *v:searchforward* *searchforward-variable* v:searchforward *v:searchforward* *searchforward-variable*
Search direction: 1 after a forward search, 0 after a Search direction: 1 after a forward search, 0 after a

View File

@ -25,23 +25,23 @@ Start *terminal-start*
There are several ways to create a terminal buffer: There are several ways to create a terminal buffer:
- Invoke the |:terminal| command. - Run the |:terminal| command.
- Call the |termopen()| function. - Call the |nvim_open_term()| or |termopen()| function.
- Edit a file with a name matching `term://(.{-}//(\d+:)?)?\zs.*`. - Edit a "term://" buffer. Examples: >
For example:
>
:edit term://bash :edit term://bash
:vsplit term://top :vsplit term://top
<
Note: The "term://" pattern is handled by a BufReadCmd handler, so the < Note: To open a "term://" buffer from an autocmd, the |autocmd-nested|
|autocmd-nested| modifier is required to use it in an autocmd. > modifier is required. >
autocmd VimEnter * ++nested split term://sh autocmd VimEnter * ++nested split term://sh
< This is only mentioned for reference; use |:terminal| instead. < (This is only mentioned for reference; use |:terminal| instead.)
When the terminal starts, the buffer contents are updated and the buffer is When the terminal starts, the buffer contents are updated and the buffer is
named in the form of `term://{cwd}//{pid}:{cmd}`. This naming scheme is used named in the form of `term://{cwd}//{pid}:{cmd}`. This naming scheme is used
by |:mksession| to restore a terminal buffer (by restarting the {cmd}). by |:mksession| to restore a terminal buffer (by restarting the {cmd}).
The terminal environment is initialized as in |jobstart-env|.
============================================================================== ==============================================================================
Input *terminal-input* Input *terminal-input*

View File

@ -164,7 +164,7 @@ typedef enum {
VV_ARGV, VV_ARGV,
VV_COLLATE, VV_COLLATE,
VV_EXITING, VV_EXITING,
// Neovim // Nvim
VV_STDERR, VV_STDERR,
VV_MSGPACK_TYPES, VV_MSGPACK_TYPES,
VV__NULL_STRING, // String with NULL value. For test purposes only. VV__NULL_STRING, // String with NULL value. For test purposes only.

View File

@ -5088,6 +5088,16 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
tv_dict_add_str(env, S_LEN("TERM"), pty_term_name); tv_dict_add_str(env, S_LEN("TERM"), pty_term_name);
} }
// Set $NVIM (in the child process) to v:servername. #3118
char *nvim_addr = (char *)get_vim_var_str(VV_SEND_SERVER);
if (nvim_addr[0] != '\0') {
dictitem_T *dv = tv_dict_find(env, S_LEN("NVIM"));
if (dv) {
tv_dict_item_remove(env, dv);
}
tv_dict_add_str(env, S_LEN("NVIM"), nvim_addr);
}
if (job_env) { if (job_env) {
#ifdef WIN32 #ifdef WIN32
TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {

View File

@ -1588,8 +1588,6 @@ varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key)
} }
/// Converts a dict to an environment /// Converts a dict to an environment
///
///
char **tv_dict_to_env(dict_T *denv) char **tv_dict_to_env(dict_T *denv)
{ {
size_t env_size = (size_t)tv_dict_len(denv); size_t env_size = (size_t)tv_dict_len(denv);

View File

@ -22,7 +22,8 @@
#include "nvim/vim.h" #include "nvim/vim.h"
#define MAX_CONNECTIONS 32 #define MAX_CONNECTIONS 32
#define LISTEN_ADDRESS_ENV_VAR "NVIM_LISTEN_ADDRESS" #define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated
#define ENV_NVIM "NVIM"
static garray_T watchers = GA_EMPTY_INIT_VALUE; static garray_T watchers = GA_EMPTY_INIT_VALUE;
@ -35,20 +36,24 @@ bool server_init(const char *listen_addr)
{ {
ga_init(&watchers, sizeof(SocketWatcher *), 1); ga_init(&watchers, sizeof(SocketWatcher *), 1);
// $NVIM_LISTEN_ADDRESS // $NVIM_LISTEN_ADDRESS (deprecated)
const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); if (!listen_addr && os_env_exists(ENV_LISTEN)) {
int rv = listen_addr == NULL ? 1 : server_start(listen_addr); listen_addr = os_getenv(ENV_LISTEN);
}
int rv = listen_addr ? server_start(listen_addr) : 1;
if (0 != rv) { if (0 != rv) {
rv = env_addr == NULL ? 1 : server_start(env_addr); listen_addr = server_address_new();
if (0 != rv) { if (!listen_addr) {
listen_addr = server_address_new(); return false;
if (listen_addr == NULL) {
return false;
}
rv = server_start(listen_addr);
xfree((char *)listen_addr);
} }
rv = server_start(listen_addr);
xfree((char *)listen_addr);
}
if (os_env_exists(ENV_LISTEN)) {
// Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter.
os_unsetenv(ENV_LISTEN);
} }
return rv == 0; return rv == 0;
@ -60,8 +65,8 @@ static void close_socket_watcher(SocketWatcher **watcher)
socket_watcher_close(*watcher, free_server); socket_watcher_close(*watcher, free_server);
} }
/// Set v:servername to the first server in the server list, or unset it if no /// Sets the "primary address" (v:servername and $NVIM) to the first server in
/// servers are known. /// the server list, or unsets if no servers are known.
static void set_vservername(garray_T *srvs) static void set_vservername(garray_T *srvs)
{ {
char *default_server = (srvs->ga_len > 0) char *default_server = (srvs->ga_len > 0)
@ -156,12 +161,6 @@ int server_start(const char *endpoint)
return result; return result;
} }
// Update $NVIM_LISTEN_ADDRESS, if not set.
const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR);
if (listen_address == NULL) {
os_setenv(LISTEN_ADDRESS_ENV_VAR, watcher->addr, 1);
}
// Add the watcher to the list. // Add the watcher to the list.
ga_grow(&watchers, 1); ga_grow(&watchers, 1);
((SocketWatcher **)watchers.ga_data)[watchers.ga_len++] = watcher; ((SocketWatcher **)watchers.ga_data)[watchers.ga_len++] = watcher;
@ -200,12 +199,6 @@ bool server_stop(char *endpoint)
return false; return false;
} }
// Unset $NVIM_LISTEN_ADDRESS if it is the stopped address.
const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR);
if (listen_address && STRCMP(addr, listen_address) == 0) {
os_unsetenv(LISTEN_ADDRESS_ENV_VAR);
}
socket_watcher_close(watcher, free_server); socket_watcher_close(watcher, free_server);
// Remove this server from the list by swapping it with the last item. // Remove this server from the list by swapping it with the last item.
@ -215,8 +208,8 @@ bool server_stop(char *endpoint)
} }
watchers.ga_len--; watchers.ga_len--;
// If v:servername is the stopped address, re-initialize it. // Bump v:servername to the next available server, if any.
if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { if (strequal(addr, (char *)get_vim_var_str(VV_SEND_SERVER))) {
set_vservername(&watchers); set_vservername(&watchers);
} }

View File

@ -179,6 +179,13 @@ static void term_output_callback(const char *s, size_t len, void *user_data)
// public API {{{ // public API {{{
/// Initializes terminal properties, and triggers TermOpen.
///
/// The PTY process (TerminalOptions.data) was already started by termopen(),
/// via ex_terminal() or the term:// BufReadCmd.
///
/// @param buf Buffer used for presentation of the terminal.
/// @param opts PTY process channel, various terminal properties and callbacks.
Terminal *terminal_open(buf_T *buf, TerminalOptions opts) Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
{ {
// Create a new terminal instance and configure it // Create a new terminal instance and configure it
@ -374,6 +381,7 @@ void terminal_check_size(Terminal *term)
invalidate_terminal(term, -1, -1); invalidate_terminal(term, -1, -1);
} }
/// Implements TERM_FOCUS mode. :help Terminal-mode
void terminal_enter(void) void terminal_enter(void)
{ {
buf_T *buf = curbuf; buf_T *buf = curbuf;
@ -502,6 +510,7 @@ static int terminal_check(VimState *state)
return 1; return 1;
} }
/// Processes one char of terminal-mode input.
static int terminal_execute(VimState *state, int key) static int terminal_execute(VimState *state, int key)
{ {
TerminalState *s = (TerminalState *)state; TerminalState *s = (TerminalState *)state;
@ -1448,7 +1457,8 @@ static void refresh_terminal(Terminal *term)
long ml_added = buf->b_ml.ml_line_count - ml_before; long ml_added = buf->b_ml.ml_line_count - ml_before;
adjust_topline(term, buf, ml_added); adjust_topline(term, buf, ml_added);
} }
// Calls refresh_terminal() on all invalidated_terminals.
/// Calls refresh_terminal() on all invalidated_terminals.
static void refresh_timer_cb(TimeWatcher *watcher, void *data) static void refresh_timer_cb(TimeWatcher *watcher, void *data)
{ {
refresh_pending = false; refresh_pending = false;

View File

@ -13,7 +13,7 @@ typedef void (*terminal_close_cb)(void *data);
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
typedef struct { typedef struct {
void *data; void *data; // PTY process channel
uint16_t width, height; uint16_t width, height;
terminal_write_cb write_cb; terminal_write_cb write_cb;
terminal_resize_cb resize_cb; terminal_resize_cb resize_cb;

View File

@ -16,6 +16,7 @@ local poke_eventloop = helpers.poke_eventloop
local iswin = helpers.iswin local iswin = helpers.iswin
local get_pathsep = helpers.get_pathsep local get_pathsep = helpers.get_pathsep
local pathroot = helpers.pathroot local pathroot = helpers.pathroot
local exec_lua = helpers.exec_lua
local nvim_set = helpers.nvim_set local nvim_set = helpers.nvim_set
local expect_twostreams = helpers.expect_twostreams local expect_twostreams = helpers.expect_twostreams
local expect_msg_seq = helpers.expect_msg_seq local expect_msg_seq = helpers.expect_msg_seq
@ -208,7 +209,7 @@ describe('jobs', function()
ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil) ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil)
end) end)
it('produces error when using non-executable `cwd`', function() it('error on non-executable `cwd`', function()
if iswin() then return end -- N/A for Windows if iswin() then return end -- N/A for Windows
local dir = 'Xtest_not_executable_dir' local dir = 'Xtest_not_executable_dir'
@ -249,7 +250,7 @@ describe('jobs', function()
eq({'notification', 'exit', {0, 0}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg())
end) end)
it('allows interactive commands', function() it('interactive commands', function()
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
neq(0, eval('j')) neq(0, eval('j'))
nvim('command', 'call jobsend(j, "abc\\n")') nvim('command', 'call jobsend(j, "abc\\n")')
@ -295,7 +296,7 @@ describe('jobs', function()
nvim('command', "call jobstop(j)") nvim('command', "call jobstop(j)")
end) end)
it("will not buffer data if it doesn't end in newlines", function() it("emits partial lines (does NOT buffer data lacking newlines)", function()
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
nvim('command', 'call jobsend(j, "abc\\nxyz")') nvim('command', 'call jobsend(j, "abc\\nxyz")')
eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg()) eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg())
@ -378,7 +379,7 @@ describe('jobs', function()
eq(NIL, meths.get_proc(pid)) eq(NIL, meths.get_proc(pid))
end) end)
it("do not survive the exit of nvim", function() it("disposed on Nvim exit", function()
-- use sleep, which doesn't die on stdin close -- use sleep, which doesn't die on stdin close
nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)")
local pid = eval('jobpid(g:j)') local pid = eval('jobpid(g:j)')
@ -646,6 +647,43 @@ describe('jobs', function()
) )
end) end)
it('jobstart() environment: $NVIM, $NVIM_LISTEN_ADDRESS #11009', function()
local function get_env_in_child_job(envname, env)
return exec_lua([[
local envname, env = ...
local join = function(s) return vim.fn.join(s, '') end
local stdout = {}
local stderr = {}
local opt = {
env = env,
stdout_buffered = true,
stderr_buffered = true,
on_stderr = function(chan, data, name) stderr = data end,
on_stdout = function(chan, data, name) stdout = data end,
}
local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1',( '+echo "%s="..getenv("%s")'):format(envname, envname), '+qa!' }, opt)
vim.fn.jobwait({ j1 }, 10000)
return join({ join(stdout), join(stderr) })
]],
envname,
env)
end
local addr = eval('v:servername')
ok((addr):len() > 0)
-- $NVIM is _not_ defined in the top-level Nvim process.
eq('', eval('$NVIM'))
-- jobstart() shares its v:servername with the child via $NVIM.
eq('NVIM='..addr, get_env_in_child_job('NVIM'))
-- $NVIM_LISTEN_ADDRESS is unset by server_init in the child.
eq('NVIM_LISTEN_ADDRESS=null', get_env_in_child_job('NVIM_LISTEN_ADDRESS'))
eq('NVIM_LISTEN_ADDRESS=null', get_env_in_child_job('NVIM_LISTEN_ADDRESS',
{ NVIM_LISTEN_ADDRESS='Xtest_jobstart_env' }))
-- User can explicitly set $NVIM_LOG_FILE, $VIM, $VIMRUNTIME.
eq('NVIM_LOG_FILE=Xtest_jobstart_env',
get_env_in_child_job('NVIM_LOG_FILE', { NVIM_LOG_FILE='Xtest_jobstart_env' }))
end)
describe('jobwait', function() describe('jobwait', function()
before_each(function() before_each(function()
if iswin() then if iswin() then

View File

@ -782,7 +782,7 @@ function module.pathroot()
return iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/' return iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/'
end end
-- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS. -- Returns a valid, platform-independent Nvim listen address.
-- Useful for communicating with child instances. -- Useful for communicating with child instances.
function module.new_pipename() function module.new_pipename()
-- HACK: Start a server temporarily, get the name, then stop it. -- HACK: Start a server temporarily, get the name, then stop it.

View File

@ -29,7 +29,7 @@ describe('nodejs host', function()
local fname = 'Xtest-nodejs-hello.js' local fname = 'Xtest-nodejs-hello.js'
write_file(fname, [[ write_file(fname, [[
const neovim = require('neovim'); const neovim = require('neovim');
const nvim = neovim.attach({socket: process.env.NVIM_LISTEN_ADDRESS}); const nvim = neovim.attach({socket: process.env.NVIM});
nvim.command('let g:job_out = "hello"'); nvim.command('let g:job_out = "hello"');
]]) ]])
command('let g:job_id = jobstart(["node", "'..fname..'"])') command('let g:job_id = jobstart(["node", "'..fname..'"])')
@ -39,7 +39,7 @@ describe('nodejs host', function()
local fname = 'Xtest-nodejs-hello-plugin.js' local fname = 'Xtest-nodejs-hello-plugin.js'
write_file(fname, [[ write_file(fname, [[
const neovim = require('neovim'); const neovim = require('neovim');
const nvim = neovim.attach({socket: process.env.NVIM_LISTEN_ADDRESS}); const nvim = neovim.attach({socket: process.env.NVIM});
class TestPlugin { class TestPlugin {
hello() { hello() {

View File

@ -83,7 +83,7 @@ describe('perl provider', function()
use Neovim::Ext; use Neovim::Ext;
use Neovim::Ext::MsgPack::RPC; use Neovim::Ext::MsgPack::RPC;
my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS}); my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM});
my $nvim = Neovim::Ext::from_session($session); my $nvim = Neovim::Ext::from_session($session);
$nvim->command('let g:job_out = "hello"'); $nvim->command('let g:job_out = "hello"');
1; 1;
@ -116,7 +116,7 @@ describe('perl provider', function()
use Neovim::Ext; use Neovim::Ext;
use Neovim::Ext::MsgPack::RPC; use Neovim::Ext::MsgPack::RPC;
my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS}); my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM});
my $nvim = Neovim::Ext::from_session($session); my $nvim = Neovim::Ext::from_session($session);
my $plugin = TestPlugin->new($nvim); my $plugin = TestPlugin->new($nvim);
$plugin->test_command(); $plugin->test_command();

View File

@ -75,7 +75,7 @@ local busted = require('busted')
local deepcopy = helpers.deepcopy local deepcopy = helpers.deepcopy
local shallowcopy = helpers.shallowcopy local shallowcopy = helpers.shallowcopy
local concat_tables = helpers.concat_tables local concat_tables = helpers.concat_tables
local request, run_session = helpers.request, helpers.run_session local run_session = helpers.run_session
local eq = helpers.eq local eq = helpers.eq
local dedent = helpers.dedent local dedent = helpers.dedent
local get_session = helpers.get_session local get_session = helpers.get_session
@ -90,8 +90,6 @@ end
local Screen = {} local Screen = {}
Screen.__index = Screen Screen.__index = Screen
local debug_screen
local default_timeout_factor = 1 local default_timeout_factor = 1
if os.getenv('VALGRIND') then if os.getenv('VALGRIND') then
default_timeout_factor = default_timeout_factor * 3 default_timeout_factor = default_timeout_factor * 3
@ -123,18 +121,6 @@ do
Screen.colornames = colornames Screen.colornames = colornames
end end
function Screen.debug(command)
if not command then
command = 'pynvim -n -c '
end
command = command .. request('vim_eval', '$NVIM_LISTEN_ADDRESS')
if debug_screen then
debug_screen:close()
end
debug_screen = io.popen(command, 'r')
debug_screen:read()
end
function Screen.new(width, height) function Screen.new(width, height)
if not width then if not width then
width = 53 width = 53

View File

@ -1,6 +1,5 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval
local command = helpers.command
local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths
local iswin = helpers.iswin local iswin = helpers.iswin
local ok = helpers.ok local ok = helpers.ok
@ -16,27 +15,25 @@ end
describe('server', function() describe('server', function()
before_each(clear) before_each(clear)
it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function() it('serverstart(), serverstop() does not set $NVIM', function()
-- Unset $NVIM_LISTEN_ADDRESS
command('let $NVIM_LISTEN_ADDRESS = ""')
local s = eval('serverstart()') local s = eval('serverstart()')
assert(s ~= nil and s:len() > 0, "serverstart() returned empty") assert(s ~= nil and s:len() > 0, "serverstart() returned empty")
eq(s, eval('$NVIM_LISTEN_ADDRESS')) eq('', eval('$NVIM'))
eq('', eval('$NVIM_LISTEN_ADDRESS'))
eq(1, eval("serverstop('"..s.."')")) eq(1, eval("serverstop('"..s.."')"))
eq('', eval('$NVIM_LISTEN_ADDRESS')) eq('', eval('$NVIM_LISTEN_ADDRESS'))
end) end)
it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function()
clear({env={NVIM_LISTEN_ADDRESS='.'}}) clear({env={NVIM_LISTEN_ADDRESS='.'}})
eq('.', eval('$NVIM_LISTEN_ADDRESS')) -- Cleared on startup.
eq('', eval('$NVIM_LISTEN_ADDRESS'))
local servers = funcs.serverlist() local servers = funcs.serverlist()
eq(1, #servers) eq(1, #servers)
ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\…
end) end)
it('sets v:servername at startup or if all servers were stopped', it('sets v:servername at startup or if all servers were stopped', function()
function()
local initial_server = meths.get_vvar('servername') local initial_server = meths.get_vvar('servername')
assert(initial_server ~= nil and initial_server:len() > 0, assert(initial_server ~= nil and initial_server:len() > 0,
'v:servername was not initialized') 'v:servername was not initialized')
@ -55,11 +52,13 @@ describe('server', function()
eq(1, funcs.serverstop(funcs.serverlist()[1])) eq(1, funcs.serverstop(funcs.serverlist()[1]))
eq('', meths.get_vvar('servername')) eq('', meths.get_vvar('servername'))
-- v:servername will take the next available server. -- v:servername and $NVIM take the next available server.
local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]]
or 'Xtest-functional-server-socket') or 'Xtest-functional-server-socket')
funcs.serverstart(servername) funcs.serverstart(servername)
eq(servername, meths.get_vvar('servername')) eq(servername, meths.get_vvar('servername'))
-- Not set in the current process, only in children.
eq('', eval('$NVIM'))
end) end)
it('serverstop() returns false for invalid input', function() it('serverstop() returns false for invalid input', function()
@ -136,7 +135,6 @@ end)
describe('startup --listen', function() describe('startup --listen', function()
it('validates', function() it('validates', function()
clear() clear()
local cmd = { unpack(helpers.nvim_argv) } local cmd = { unpack(helpers.nvim_argv) }
table.insert(cmd, '--listen') table.insert(cmd, '--listen')
matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd)) matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd))