src/session.c \
src/sha256.c \
src/sign.c \
+ src/socketserver.c \
src/sound.c \
src/spell.c \
src/spell.h \
src/proto/session.pro \
src/proto/sha256.pro \
src/proto/sign.pro \
+ src/proto/socketserver.pro \
src/proto/sound.pro \
src/proto/spell.pro \
src/proto/spellfile.pro \
*--clientserver*
--clientserver {method} Use the specified method {method} as the
backend for clientserver functionality. Can
- either be "socket" or "x11".
+ either be "socket", "x11", or "mswin".
{only available when compiled with both |+X11|
- and |+socketserver| features}
+ and |+socketserver| features, or
+ |+socketserver| on MS-Windows}
Examples ~
<
==============================================================================
4. Socket server specific items *socketserver-clientserver*
- *E1563* *E1564* *E1565* *E1566* *E1567*
+ *E1564* *E1565* *E1566*
-The communication between client and server is done using Unix domain sockets.
-These sockets are either placed in these directories in the following order of
+NOTE: Vim version before patch 9.2.511 use a different socketserver backend
+which is incompatible with the new one, which is based on channels and JSON.
+
+The communication between client and server is done using channels internally.
+When using the traditional/generic clientserver naming (only available on
+Unix), sockets are placed in these directories in the following order of
availability:
- 1. "$XDG_RUTIME_DIR/vim" if $XDG_RUNTIME_DIR is set in the environment.
+ 1. "$XDG_RUNTIME_DIR/vim" if $XDG_RUNTIME_DIR is set in the environment.
2. "$TMPDIR/vim-[uid]", where "[uid]" is the uid of the user. This
directory will have the access permissions set to 700 so only the user
can read or write from/to it. If $TMPDIR is not set, "/tmp" is used.
Otherwise the server id will be the filename of the socket which will be
placed in the above common directories. Note that a server id/name can only
contain slashes "/" if it is taken as a path, so names such as "abc/dir" will
-be invalid.
+be invalid. This feature is only available on Unix.
+
+ *socketserver-address*
+In addition, the socketserver can also be created as a |channel-address|. To
+do this, prefix the address with "channel:" (which will be ignored). This is
+available on both Unix and MS-Windows, and is the only available naming for the
+socketserver backend on Windows. However, note that |ch_listen()|
+restrictions apply, meaning only port numbers can be used for TCP sockets.
+
+If the name is prefixed with "name:", then the legacy servername behaviour is
+used, so "name:VIM" is the same as "VIM".
-Socket server functionality is available in both GTK GUI and terminal versions
-of Vim. Unless Vim is compiled with |+autoservername| feature, the socket
-server will have to started explicitly, just like X11, even in the GUI.
+Unless Vim is compiled with |+autoservername| feature, the socket server will
+have to started explicitly, similar to X11. This is unless a X11 GUI Vim is
+being used.
If Vim crashes or does not exit cleanly, the socket server will not remove the
socket file and it will be left around. This is generally not a problem,
because if a socket name is taken, Vim checks if the socket in its place is
dead (not attached to any process), and can replace it instead of finding a
-new name.
-
-To send commands to a Vim socket server from another application, read the
-source file src/os_unix.c, there is detailed description of the protocol used.
+new name. This is only relevant for Unix.
*socketserver-differences*
-Most of the functionality is the same as X11, however unlike X11, where the
-client does not need to be a server in order to communicate with another
-server, the socket server requires the server to be running even as a client.
-The exception is |serverlist()| or the |--serverlist| argument, which does not
-require the server to be running.
+Most of the functionality is the same as the other clientserver backends.
-Additionally, the server id or client id will not be a number like X11 or
+However, the server id or client id will not be a number like X11 or
MS-Windows (shown in hex representation), instead it is the absolute path to
-the socket. This can be seen via the |v:servername| variable.
+the socket. This can be seen via the |v:servername| variable. If the name is
+a channel address, then it will be the address with the "a/" prefix as well.
+
+Note when using |--remote-wait|, the client id will look like "t/<some
+number>". This naming is specifically for this command, and attempting to use
+it as an address will fail.
-The |--serverlist| argument will act just like X11, however it only checks the
-given common directories above. If a custom path is used for a socket, it
-will not be detected, such as a path either not in $XDG_RUNTIME_DIR or
-<$TMPDIR or /tmp>/vim of the |--serverlist| Vim process.
+If on Unix, the |--serverlist| argument will act just like X11, however it
+only checks the given common directories above. If a custom path is used for
+a socket, it will not be detected, such as a path either not in
+$XDG_RUNTIME_DIR or <$TMPDIR or /tmp>/vim of the |--serverlist| Vim process.
+If on MS-Windows, then the |--serverlist| argument will output nothing.
-If you have both |+socketserver| and |+X11| compiled, you will need to add
-|--clientserver| set to "socket" in combination with |--serverlist| to list
-the available servers. You cannot list both types of backends in one command.
+The |+socketserver| feature is automatically enabled when on Unix or
+MS-Windows, and the |+channel| feature is enabled
*socketserver-x11*
-If Vim is compiled with both |+X11| and |+socketserver|, then deciding which
-backend to use is done at startup time, via the |--clientserver| argument. By
-default if it is not specified, then X11 will be used. A Vim instance using a
-socket server cannot communicate with one using X11.
+If Vim is compiled with |+socketserver| and another clientserver backend, then
+deciding which backend to use is done at startup time, via the
+|--clientserver| argument. By default if it is not specified, then the native
+backend (X11 or MS-Window messages) will be used, if available. A Vim
+instance using a socket server cannot communicate with one using another
+clientserver backend.
vim:tw=78:sw=4:ts=8:noet:ft=help:norl:
E1560 vim9.txt /*E1560*
E1561 vim9.txt /*E1561*
E1562 options.txt /*E1562*
-E1563 remote.txt /*E1563*
E1564 remote.txt /*E1564*
E1565 remote.txt /*E1565*
E1566 remote.txt /*E1566*
-E1567 remote.txt /*E1567*
E1568 options.txt /*E1568*
E1569 builtin.txt /*E1569*
E157 sign.txt /*E157*
slow-start starting.txt /*slow-start*
slow-terminal term.txt /*slow-terminal*
socket-interface channel.txt /*socket-interface*
+socketserver-address remote.txt /*socketserver-address*
socketserver-clientserver remote.txt /*socketserver-clientserver*
socketserver-differences remote.txt /*socketserver-differences*
socketserver-name remote.txt /*socketserver-name*
-*version9.txt* For Vim version 9.2. Last change: 2026 May 19
+*version9.txt* For Vim version 9.2. Last change: 2026 May 22
VIM REFERENCE MANUAL by Bram Moolenaar
if needed, see |wayland-primary-selection| for an example.
- On Unix, filename completion for single-file Ex commands now treats embedded
whitespace as part of the filename, like on other platforms.
+- Rewrite the clientserver socketserver backend to use channels and JSON.
*added-9.3*
it is taken as either an absolute, relative or relative path to the socket.
.TP
\-\-clientserver {backend}
-Use {backend} as the backend for clientserver functionality, either "socket" or
-"x11" respectively. Only available when compiled with both socketserver and X11
-features present
+Use {backend} as the backend for clientserver functionality, either "socket",
+"x11", or "mswin" respectively. Only available when compiled with both
+socketserver and X11 features present, or if compiled with socketserver on
+MS-Windows.
.TP
\-\-socketid {id}
GTK GUI only: Use the GtkPlug mechanism to run gVim in another window.
--clientserver {backend}
Use {backend} as the backend for clientserver functional‐
- ity, either "socket" or "x11" respectively. Only available
- when compiled with both socketserver and X11 features
- present
+ ity, either "socket", "x11", or "mswin" respectively. Only
+ available when compiled with both socketserver and X11 fea‐
+ tures present, or if compiled with socketserver on MS-Win‐
+ dows.
--socketid {id}
GTK GUI only: Use the GtkPlug mechanism to run gVim in an‐
$(OUTDIR)/session.o \
$(OUTDIR)/sha256.o \
$(OUTDIR)/sign.o \
+ $(OUTDIR)/socketserver.o \
$(OUTDIR)/spell.o \
$(OUTDIR)/spellfile.o \
$(OUTDIR)/spellsuggest.o \
$(OUTDIR)\session.obj \
$(OUTDIR)\sha256.obj \
$(OUTDIR)\sign.obj \
+ $(OUTDIR)\socketserver.obj \
$(OUTDIR)\spell.obj \
$(OUTDIR)\spellfile.obj \
$(OUTDIR)\spellsuggest.obj \
$(OUTDIR)/sign.obj: $(OUTDIR) sign.c $(INCL)
+$(OUTDIR)/socketserver.obj: $(OUTDIR) socketserver.c $(INCL)
+
$(OUTDIR)/spell.obj: $(OUTDIR) spell.c $(INCL)
$(OUTDIR)/spellfile.obj: $(OUTDIR) spellfile.c $(INCL)
proto/session.pro \
proto/sha256.pro \
proto/sign.pro \
+ proto/socketserver.pro \
proto/spell.pro \
proto/spellfile.pro \
proto/spellsuggest.pro \
session.c \
sha256.c \
sign.c \
+ socketserver.c \
sound.c \
spell.c \
spellfile.c \
objects/session.o \
objects/sha256.o \
objects/sign.o \
+ objects/socketserver.o \
objects/sound.o \
objects/spell.o \
objects/spellfile.o \
objects/sign.o: sign.c
$(CCC) -o $@ sign.c
+objects/socketserver.o: socketserver.c
+ $(CCC) -o $@ socketserver.c
+
objects/sound.o: sound.c
$(CCC) -o $@ sound.c
structs.h regexp.h gui.h libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
globals.h errors.h
+objects/socketserver.o: socketserver.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h \
+ beval.h structs.h regexp.h gui.h libvterm/include/vterm.h \
+ libvterm/include/vterm_keycodes.h xdiff/xdiff.h xdiff/../vim.h alloc.h \
+ ex_cmds.h spell.h proto.h globals.h errors.h
objects/sound.o: auto/osdef.h sound.c vim.h protodef.h auto/config.h feature.h os_unix.h \
ascii.h keymap.h termdefs.h macros.h option.h beval.h \
structs.h regexp.h gui.h libvterm/include/vterm.h \
proto/session.pro: session.c
proto/sha256.pro: sha256.c
proto/sign.pro: sign.c
+proto/socketserver.pro: socketserver.c
proto/sound.pro: sound.c
proto/spell.pro: spell.c
proto/spellfile.pro: spellfile.c
enable_channel
enable_terminal
enable_autoservername
-enable_socketserver
enable_multibyte
enable_rightleft
enable_arabic
--disable-channel Disable process communication support.
--enable-terminal Enable terminal emulation support.
--enable-autoservername Automatically define servername at vim startup.
- --enable-socketserver Use sockets for clientserver communication.
--enable-multibyte Include multibyte editing support.
--disable-rightleft Do not include Right-to-Left language support.
--disable-arabic Do not include Arabic language support.
fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking --enable-socketserver argument" >&5
-printf %s "checking --enable-socketserver argument... " >&6; }
-# Check whether --enable-socketserver was given.
-if test ${enable_socketserver+y}
-then :
- enableval=$enable_socketserver; enable_socketserver=$enableval
-else case e in #(
- e) if test "x$features" = xtiny
-then :
- enable_socketserver=no_msg
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cannot use socketserver with tiny features" >&5
-printf "%s\n" "cannot use socketserver with tiny features" >&6; }
-else case e in #(
- e) enable_socketserver=yes ;;
-esac
-fi ;;
-esac
-fi
-
-
-if test "$enable_socketserver" = "yes"; then
- printf "%s\n" "#define WANT_SOCKETSERVER 1" >>confdefs.h
-
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-printf "%s\n" "yes" >&6; }
-elif test "$enable_socketserver" = "no"; then
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf "%s\n" "no" >&6; }
-fi
-
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking --enable-multibyte argument" >&5
printf %s "checking --enable-multibyte argument... " >&6; }
# Check whether --enable-multibyte was given.
* Returns the channel for success.
* Returns NULL for failure.
*/
- static channel_T *
+ channel_T *
channel_open_unix(
const char *path,
void (*nb_close_cb)(void))
channel->ch_part[PART_IN].ch_io = opt->jo_io[PART_IN];
}
+/*
+ * Parse the address for socketserver and store port in "port", or the path in
+ * "unix_path" if it is a unix domain socket. Returns OK on success and FAIL on
+ * failure.
+ */
+ int
+channel_parse_socketserver_address(
+ char_u *address,
+ int *port,
+ char_u **unix_path,
+ bool quiet)
+{
+ char *rest;
+ long val;
+
+ if (*address == NUL)
+ goto fail;
+
+ if (!STRNCMP(address, "unix:", 5))
+ {
+ *unix_path = vim_strsave(address + 5);
+ return OK;
+ }
+
+ val = strtol((char *)(address), &rest, 10);
+ if (val < 0 || val >= 65536 || *rest != NUL)
+ {
+ if (!quiet)
+ semsg(_(e_invalid_argument_str), address);
+ return FAIL;
+ }
+ *port = (int)val;
+
+ return OK;
+fail:
+ if (!quiet)
+ semsg(_(e_invalid_argument_str), address);
+ return FAIL;
+}
+
/*
* Implements ch_open().
*/
}
if (is_unix)
- channel = channel_listen_unix((char *)arg, NULL);
+ channel = channel_listen_unix((char *)arg, NULL, true);
else
channel = channel_listen(port, NULL);
if (channel != NULL)
/*
* Listen to a Unix domain socket channel.
+ * If "replace" is true, then unlink the path first beforehand.
* Returns the channel for success.
* Returns NULL for failure.
*/
channel_T *
channel_listen_unix(
char *path,
- void (*nb_close_cb)(void))
+ void (*nb_close_cb)(void),
+ bool replace)
{
int sd = -1;
struct sockaddr_un server;
return NULL;
}
- // Unlink the socket in case it already exists
- unlink(server.sun_path);
+ if (replace)
+ // Unlink the socket in case it already exists
+ unlink(server.sun_path);
// Bind the socket to the path
server_len = offsetof(struct sockaddr_un, sun_path) + path_len + 1;
/*
* Write any lines waiting to be written to "channel".
*/
- static void
+ void
channel_write_input(channel_T *channel)
{
chanpart_T *in_part = &channel->ch_part[PART_IN];
* Try to fill the buffer of "reader".
* Returns FALSE when nothing was added.
*/
- static int
+ int
channel_fill(js_read_T *reader)
{
channel_T *channel = (channel_T *)reader->js_cookie;
/*
* Use the read buffer of "channel"/"part" and parse a JSON message that is
- * complete. The messages are added to the queue.
+ * complete. The messages are added to the queue. If "socketserver" is true,
+ * then ignore the channel mode.
* Return TRUE if there is more to read.
*/
- static int
-channel_parse_json(channel_T *channel, ch_part_T part)
+ int
+channel_parse_json(channel_T *channel, ch_part_T part, bool socketserver)
{
js_read_T reader;
typval_T listtv;
reader.js_cookie = channel;
reader.js_cookie_arg = part;
- if (chanpart->ch_mode == CH_MODE_LSP
- || chanpart->ch_mode == CH_MODE_DAP)
+ if (!socketserver && (chanpart->ch_mode == CH_MODE_LSP
+ || chanpart->ch_mode == CH_MODE_DAP))
status = channel_process_lspdap_http_hdr(&reader);
// When a message is incomplete we wait for a short while for more to
{
// Only accept the response when it is a list with at least two
// items.
- if ((chanpart->ch_mode == CH_MODE_LSP || chanpart->ch_mode == CH_MODE_DAP)
+ if (!socketserver && (chanpart->ch_mode == CH_MODE_LSP
+ || chanpart->ch_mode == CH_MODE_DAP)
&& listtv.v_type != VAR_DICT)
{
ch_error(channel, "Did not receive a LSP dict, discarding");
clear_tv(&listtv);
}
- else if (chanpart->ch_mode != CH_MODE_LSP && chanpart->ch_mode != CH_MODE_DAP
- && (listtv.v_type != VAR_LIST || listtv.vval.v_list->lv_len < 2))
+ else if (!socketserver && chanpart->ch_mode != CH_MODE_LSP
+ && chanpart->ch_mode != CH_MODE_DAP
+ && (listtv.v_type != VAR_LIST || listtv.vval.v_list->lv_len < 2))
{
if (listtv.v_type != VAR_LIST)
ch_error(channel, "Did not receive a list, discarding");
* Remove "node" from the queue that it is in and free it.
* Caller should have freed or used node->jq_value.
*/
- static void
+ void
remove_json_node(jsonq_T *head, jsonq_T *node)
{
if (node->jq_prev == NULL)
int called_otc; // one time callbackup
int raw_len = 0;
- if (channel->ch_nb_close_cb != NULL)
- // this channel is handled elsewhere (netbeans)
+ if (channel->ch_nb_close_cb != NULL
+#ifdef FEAT_SOCKETSERVER
+ || channel->ch_socketserver
+#endif
+ )
+ // this channel is handled elsewhere (netbeans or socketserver)
return FALSE;
// Use a message-specific callback, part callback or channel callback
(void)channel_collapse(channel, part, FALSE);
// Parse readahead, return when there is still no message.
- channel_parse_json(channel, part);
+ channel_parse_json(channel, part, false);
if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL)
return FALSE;
}
if (head->jq_next == NULL)
// Parse json from readahead, there might be a complete message to
// process.
- channel_parse_json(channel, part);
+ channel_parse_json(channel, part, false);
return head->jq_next;
}
}
channel->ch_nb_close_cb = NULL;
+#ifdef FEAT_SOCKETSERVER
+ channel->ch_socketserver = false;
+ channel->ch_ss_accept_cb = NULL;
+ channel->ch_ss_close_cb = NULL;
+#endif
#ifdef FEAT_TERMINAL
term_channel_closed(channel);
ch_log(channel, "Closing channel because all readable fds are closed");
if (channel->ch_nb_close_cb != NULL)
(*channel->ch_nb_close_cb)();
+#ifdef FEAT_SOCKETSERVER
+ if (channel->ch_ss_close_cb != NULL)
+ channel->ch_ss_close_cb(channel);
+#endif
channel_close(channel, TRUE);
}
channel_gui_register_one(newchannel, PART_SOCK);
#endif
+#ifdef FEAT_SOCKETSERVER
+ if (channel->ch_ss_accept_cb != NULL)
+ {
+ channel->ch_ss_accept_cb(newchannel);
+ return;
+ }
+#endif
+
if (client.ss_family == AF_INET)
{
#ifdef HAVE_INET_NTOP
#endif
}
+#ifdef FEAT_SOCKETSERVER
+
+ void
+channel_check(channel_T *channel, ch_part_T part)
+{
+ channel_read(channel, part, "channel_check");
+}
+
+#endif
+
/*
* Read from RAW or NL "channel"/"part". Blocks until there is something to
* read or the timeout expires.
// received messages.
(void)channel_collapse(channel, part, FALSE);
- more = channel_parse_json(channel, part);
+ more = channel_parse_json(channel, part, false);
// search for message "id"
if (channel_get_json(channel, part, id, TRUE, rettv) == OK)
return buf;
}
+/*
+ * Return the channel with the given ID. Returns NULL if not found.
+ */
+ channel_T *
+channel_find(int ch_id)
+{
+ channel_T *ch;
+ FOR_ALL_CHANNELS(ch)
+ if (ch->ch_id == ch_id)
+ return ch;
+ return NULL;
+}
+
#endif // FEAT_JOB_CHANNEL
#if defined(FEAT_CLIENTSERVER)
-# ifdef FEAT_SOCKETSERVER
-# include <sys/socket.h>
-# include "sys/un.h"
-# endif
-
static void cmdsrv_main(int *argc, char **argv, char_u *serverName_arg, char_u **serverStr);
static char_u *serverMakeName(char_u *arg, char *cmd);
serverInitMessaging();
# endif
-# ifdef FEAT_SOCKETSERVER
- // If servername is specified and we are using sockets, always init the
- // sockt server. We may need to receive replies back to us. If --serverlist
- // is passed, the socket server will be uninitialized before listing
- // sockets then initialized after. This is so we don't add our own socket
- // in the list. This does not happen in serverlist().
- if ((parmp->serverArg || parmp->serverName_arg != NULL) &&
- clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- {
- parmp->servername = serverMakeName(parmp->serverName_arg,
- parmp->argv[0]);
- if (socket_server_init(parmp->servername) == OK)
- TIME_MSG("initialize socket server");
- made_name = TRUE;
- }
-# endif
-
/*
* When a command server argument was found, execute it. This may
* exit Vim when it was successful. Otherwise it's executed further
parmp->servername = serverMakeName(parmp->serverName_arg,
parmp->argv[0]);
# ifdef MSWIN
- if (parmp->servername != NULL)
+ if (parmp->servername != NULL && clientserver_method == CLIENTSERVER_METHOD_MSWIN)
{
serverSetName(parmp->servername);
+# ifndef FEAT_SOCKETSERVER
vim_free(parmp->servername);
+# endif
}
# endif
}
* Only register nongui-vim's with an explicit --servername argument,
* or when compiling with autoservername.
* When running as root --servername is also required.
+ *
+ * If we are Windows and socketserver is being used, then don't use
+ * autoservername, since traditional naming isn't supported.
*/
if (
-# ifdef FEAT_X11
- X_DISPLAY != NULL &&
+# if defined(FEAT_X11) || (defined(FEAT_SOCKETSERVER) && !defined(MSWIN))
+ (
+# ifdef FEAT_X11
+ X_DISPLAY != NULL
+# endif
+# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER) && !defined(MSWIN)
+ ||
+# endif
+# if defined(FEAT_SOCKETSERVER) && !defined(MSWIN)
+ clientserver_method == CLIENTSERVER_METHOD_SOCKET
+# endif
+ ) &&
# endif
-
parmp->servername != NULL && (
# if defined(FEAT_AUTOSERVERNAME) || defined(FEAT_GUI)
(
+# if defined(FEAT_SOCKETSERVER) && defined(MSWIN)
+ clientserver_method != CLIENTSERVER_METHOD_SOCKET &&
+# endif
# if defined(FEAT_AUTOSERVERNAME)
1
# else
# endif
parmp->serverName_arg != NULL))
{
+
# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- {
- if (socket_server_init(parmp->servername) == OK)
- TIME_MSG("initialize socket server");
- }
+ socketserver_start(parmp->servername, false);
# endif
# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
# endif
vim_free(parmp->servername);
}
-# ifdef FEAT_X11
else
- serverDelayedStartName = parmp->servername;
+ {
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ serverDelayedStartName = parmp->servername;
+ else
# endif
+ vim_free(parmp->servername);
+ }
# endif
/*
# define ARGTYPE_SEND 3
int silent = FALSE;
int tabs = FALSE;
-# ifdef FEAT_SOCKETSERVER
- char_u *receiver;
-# endif
# ifdef MSWIN
HWND srv;
# elif defined(FEAT_X11)
setup_term_clip();
# endif
+# ifdef FEAT_SOCKETSERVER
+ channel_T *ch = NULL;
+# endif
sname = serverMakeName(serverName_arg, argv[0]);
if (sname == NULL)
# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- ret = socket_server_send(
- sname, *serverStr, NULL, &receiver,
- 0, -1, silent);
+ ret = socketserver_send(sname, *serverStr, NULL, false, -1,
+ silent, &ch);
# endif
# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
}
# endif
# ifdef MSWIN
- // Win32 always works?
- ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, 0, silent);
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ // Win32 always works?
+ ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, 0,
+ silent);
# endif
if (ret < 0)
{
char_u *p = NULL;
int j;
# ifdef MSWIN
- p = serverGetReply(srv, NULL, TRUE, TRUE, 0);
- if (p == NULL)
- break;
-# else
-# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ p = serverGetReply(srv, NULL, TRUE, TRUE, 0);
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET
- && socket_server_read_reply(receiver, &p, -1) == FAIL)
+ && (ch == NULL
+ || socketserver_read_reply(sname, &p, -1, true)
+ == FAIL))
break;
-# endif
-# ifdef FEAT_X11
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11
&& serverReadReply(xterm_dpy, srv, &p, TRUE, -1) < 0)
break;
-# endif
+# endif
if (p == NULL)
break;
-# endif
j = atoi((char *)p);
vim_free(p);
if (j >= 0 && j < numFiles)
done[j] = 1;
}
}
+# ifdef FEAT_SOCKETSERVER
+ if (ch != NULL)
+ {
+ channel_close(ch, false);
+ channel_clear(ch);
+ }
+# endif
# ifdef FEAT_GUI_MSWIN
Shell_NotifyIcon(NIM_DELETE, &ni);
# endif
}
else if (STRICMP(argv[i], "--remote-expr") == 0)
{
+ int status = OK;
+
if (i == *argc - 1)
mainerr_arg_missing((char_u *)argv[i]);
# ifdef MSWIN
// Win32 always works?
- if (serverSendToVim(sname, (char_u *)argv[i + 1],
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN
+ && serverSendToVim(sname, (char_u *)argv[i + 1],
&res, NULL, 1, 0, FALSE) < 0)
-# else
-# ifdef FEAT_SOCKETSERVER
- if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- {
- if (!socket_server_valid())
- mch_errmsg(_("Socket server not online:"
- "Send expression failed"));
- else if (socket_server_send(sname, (char_u *)argv[i + 1],
- &res, NULL, 1, 0, FALSE) < 0)
- goto expr_fail;
- }
-# endif
-# ifdef FEAT_X11
+ status = FAIL;
+# endif
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET
+ && socketserver_send(sname, (char_u *)argv[i + 1],
+ &res, 1, 0, false, NULL) < 0)
+ status = FAIL;
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
{
if (xterm_dpy == NULL)
else if (serverSendToVim(xterm_dpy, sname,
(char_u *)argv[i + 1], &res,
NULL, 1, 0, 1, FALSE) < 0)
- goto expr_fail;
+ status = FAIL;
}
-# endif
- if (FALSE)
# endif
+ if (status == FAIL)
{
-# if !defined(MSWIN)
-expr_fail:
-# endif
if (res != NULL && *res != NUL)
{
// Output error from remote
else if (STRICMP(argv[i], "--serverlist") == 0)
{
# ifdef MSWIN
- // Win32 always works?
- res = serverGetVimNames();
-# else
-# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ // Win32 always works?
+ res = serverGetVimNames();
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- {
- int was_init = socket_server_valid();
-
- // Don't want to add ourselves to the list. So shutdown the
- // server before listing then startup back again.
- socket_server_uninit();
- res = socket_server_list_sockets();
-
- if (was_init)
- socket_server_init(NULL);
- }
+# ifdef MSWIN
+ res = vim_strsave((char_u *)"");
+# else
+ res = socketserver_list();
# endif
-# ifdef FEAT_X11
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
xterm_dpy != NULL)
res = serverGetVimNames(xterm_dpy);
-# endif
# endif
if (did_emsg)
mch_errmsg("\n");
if (didone)
{
-# ifdef FEAT_SOCKETSERVER
- socket_server_uninit();
-# endif
display_errors(); // display any collected messages
exit(exiterr); // Mission accomplished - get out
}
if (arg != NULL && *arg != NUL)
{
# ifdef FEAT_SOCKETSERVER
- // If we are using a socket server, we want to preserve the original
- // name if it is a path, else uppercase it if its just a generic name.
+ // When using socketserver backend, do not change the name if path or
+ // channel address.
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
{
- if (arg[0] == '/' || STRNCMP(arg, "./", 2) == 0 ||
- STRNCMP(arg, "../", 3) == 0)
+ if (STRNICMP(arg, "channel:", 8) == 0 || arg[0] == '/' ||
+ STRNCMP(arg, "./", 2) == 0 || STRNCMP(arg, "../", 3) == 0)
p = vim_strsave(arg);
else
p = vim_strsave_up(arg);
static int
check_connection(void)
{
+ if (clientserver_method != CLIENTSERVER_METHOD_X11)
+ return OK;
make_connection();
if (X_DISPLAY == NULL)
{
# ifdef FEAT_X11
Window w;
# endif
-# ifdef FEAT_SOCKETSERVER
- char_u *client = NULL;
-# endif
# endif
+ int ret = OK;
if (check_restricted() || check_secure())
return;
return; // type error; errmsg already given
keys = tv_get_string_buf(&argvars[1], buf);
# ifdef MSWIN
- if (serverSendToVim(server_name, keys, &r, &w, expr, timeout, TRUE) < 0)
-# else
-# ifdef FEAT_SOCKETSERVER
- if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- if (socket_server_send(server_name, keys, &r, &client, expr,
- timeout * 1000, TRUE) < 0)
- goto fail;
-# endif
-# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN
+ && serverSendToVim(server_name, keys, &r, &w, expr,
+ timeout, TRUE) < 0)
+ ret = FAIL;
+# endif
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET
+ && socketserver_send(server_name, keys, &r, expr,
+ timeout * 1000, true, NULL) < 0)
+ ret = FAIL;
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout,
0, TRUE) < 0)
- goto fail;
-# endif
+ ret = FAIL;
# endif
-# if !defined(MSWIN)
- if (FALSE)
- {
-fail:
-# else
+ if (ret == FAIL)
{
-# endif
if (r != NULL)
{
emsg((char *)r); // sending worked but evaluation failed
vim_free(r);
-# ifdef FEAT_SOCKETSERVER
- vim_free(client);
-# endif
}
else
semsg(_(e_unable_to_send_to_str), server_name);
if (argvars[2].v_type != VAR_UNKNOWN)
{
dictitem_T v;
-# if defined(FEAT_SOCKETSERVER)
- struct sockaddr_un addr;
- char_u str[sizeof(addr.sun_path)];
-# else
- char_u str[30];
+# if defined(MSWIN) || defined(FEAT_X11)
+ char_u sbuf[30];
# endif
+ char_u *str = NULL;
char_u *idvar;
idvar = tv_get_string_chk(&argvars[2]);
if (idvar != NULL && *idvar != NUL)
{
- str[0] = NUL;
# ifdef MSWIN
- sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
-# else
-# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ {
+ sprintf((char *)sbuf, PRINTF_HEX_LONG_U, (long_u)w);
+ str = sbuf;
+ }
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
- sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
-# endif
-# ifdef FEAT_SOCKETSERVER
+ {
+ sprintf((char *)sbuf, PRINTF_HEX_LONG_U, (long_u)w);
+ str = sbuf;
+ }
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- vim_snprintf((char *)str, sizeof(addr.sun_path),
- "%s", client);
-# endif
+ str = server_name;
# endif
+ if (str == NULL)
+ str = (char_u *)"";
+
v.di_tv.v_type = VAR_STRING;
v.di_tv.vval.v_string = vim_strsave(str);
set_var(idvar, &v.di_tv, FALSE);
vim_free(v.di_tv.vval.v_string);
}
}
-# ifdef FEAT_SOCKETSERVER
- vim_free(client);
-# endif
}
#endif
if (serverid == NULL)
return; // type error; errmsg already given
# ifdef MSWIN
- sscanf((const char *)serverid, SCANF_HEX_LONG_U, &n);
- if (n == 0)
- rettv->vval.v_number = -1;
- else
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
{
- s = serverGetReply((HWND)n, FALSE, FALSE, FALSE, 0);
- rettv->vval.v_number = (s != NULL);
+ sscanf((const char *)serverid, SCANF_HEX_LONG_U, &n);
+ if (n == 0)
+ rettv->vval.v_number = -1;
+ else
+ {
+ s = serverGetReply((HWND)n, FALSE, FALSE, FALSE, 0);
+ rettv->vval.v_number = (s != NULL);
+ }
}
-# else
-# ifdef FEAT_SOCKETSERVER
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- rettv->vval.v_number = socket_server_peek_reply(serverid, &s);
-# endif
-# ifdef FEAT_X11
+ rettv->vval.v_number = socketserver_peek_reply(serverid, &s);
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
{
if (check_connection() == FAIL)
rettv->vval.v_number = serverPeekReply(X_DISPLAY,
serverStrToWin(serverid), &s);
}
-# endif
# endif
if (argvars[1].v_type != VAR_UNKNOWN && rettv->vval.v_number > 0)
timeout = tv_get_number(&argvars[1]);
# ifdef MSWIN
- sscanf((char *)serverid, SCANF_HEX_LONG_U, &n);
- if (n != 0)
- r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout);
- if (r == NULL)
- emsg(_(e_unable_to_read_server_reply));
-# else
-# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ {
+ sscanf((char *)serverid, SCANF_HEX_LONG_U, &n);
+ if (n != 0)
+ r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout);
+ if (r == NULL)
+ emsg(_(e_unable_to_read_server_reply));
+ }
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
- socket_server_read_reply(serverid, &r, timeout * 1000) == FAIL)
+ socketserver_read_reply(serverid, &r, timeout * 1000, false)
+ == FAIL)
emsg(_(e_unable_to_read_server_reply));
-# endif
-# ifdef FEAT_X11
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
(check_connection() == FAIL
|| serverReadReply(X_DISPLAY, serverStrToWin(serverid),
&r, FALSE, timeout) < 0))
emsg(_(e_unable_to_read_server_reply));
-# endif
# endif
}
# endif
char_u *server = tv_get_string_chk(&argvars[0]);
# ifdef MSWIN
- serverSetName(server);
-# else
-# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ serverSetName(server);
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- socket_server_init(server);
-# endif
-# ifdef FEAT_X11
+ socketserver_start(server, true);
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
check_connection() == OK)
serverRegisterName(X_DISPLAY, server);
-# endif
# endif
# else
char_u buf[NUMBUFLEN];
char_u *server;
char_u *reply;
+ int ret = OK;
rettv->vval.v_number = -1;
if (check_restricted() || check_secure())
# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
- socket_server_send_reply(server, reply) == FAIL)
- goto fail;
+ socketserver_send_reply(server, reply) == FAIL)
+ ret = FAIL;
# endif
-
# ifdef FEAT_X11
- if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
- check_connection() == FAIL)
- return;
-
if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
serverSendReply(server, reply) < 0)
+ ret = FAIL;
# endif
# ifdef MSWIN
- if (serverSendReply(server, reply) < 0)
-# endif
-# if defined(FEAT_SOCKETSERVER) && !defined(FEAT_X11) && !defined(MSWIN)
- if (FALSE)
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN
+ && serverSendReply(server, reply) < 0)
+ ret = FAIL;
# endif
+
+ if (ret == FAIL)
{
-# ifdef FEAT_SOCKETSERVER
-fail:
-# endif
emsg(_(e_unable_to_send_to_client));
return;
}
# ifdef FEAT_CLIENTSERVER
# ifdef MSWIN
- r = serverGetVimNames();
-# else
-# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
+ r = serverGetVimNames();
+# endif
+# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
- r = socket_server_list_sockets();
+# ifdef MSWIN
+ r = vim_strsave((char_u *)"");
+# else
+ r = socketserver_list();
# endif
-# ifdef FEAT_X11
+# endif
+# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
{
make_connection();
if (X_DISPLAY != NULL)
r = serverGetVimNames(X_DISPLAY);
}
-# endif
# endif
# endif
rettv->v_type = VAR_STRING;
AC_DEFINE(FEAT_AUTOSERVERNAME)
fi
-AC_MSG_CHECKING(--enable-socketserver argument)
-AC_ARG_ENABLE(socketserver,
- [ --enable-socketserver Use sockets for clientserver communication.],
- [enable_socketserver=$enableval],
- AS_IF([test "x$features" = xtiny],
- [enable_socketserver=no_msg
- AC_MSG_RESULT([cannot use socketserver with tiny features])],
- [enable_socketserver=yes]))
-
-if test "$enable_socketserver" = "yes"; then
- AC_DEFINE(WANT_SOCKETSERVER)
- AC_MSG_RESULT([yes])
-elif test "$enable_socketserver" = "no"; then
- AC_MSG_RESULT([no])
-fi
-
AC_MSG_CHECKING(--enable-multibyte argument)
AC_ARG_ENABLE(multibyte,
[ --enable-multibyte Include multibyte editing support.], ,
INIT(= N_("E457: Can't read PostScript resource file \"%s\""));
#endif
// E458 unused
-#if defined(UNIX) || defined(FEAT_SESSION)
+#if defined(UNIX) || defined(FEAT_SESSION) || defined(FEAT_SOCKETSERVER)
EXTERN char e_cannot_go_back_to_previous_directory[]
INIT(= N_("E459: Cannot go back to previous directory"));
#endif
INIT(= N_("E1562: Diff anchors cannot be used with hidden diff windows"));
#endif
#ifdef FEAT_SOCKETSERVER
-EXTERN char e_socket_path_too_big[]
- INIT(= N_("E1563: Socket path is too big"));
EXTERN char e_socket_name_no_slashes[]
- INIT(= N_("E1564: Socket name cannot have slashes in it without being a path"));
+ INIT(= N_("E1564: Socket name '%s' cannot have slashes in it without being a path"));
EXTERN char e_socket_server_not_online[]
INIT(= N_("E1565: Socket server is not online, call remote_startserver() first"));
EXTERN char e_socket_server_failed_connecting[]
- INIT(= N_("E1566: Failed connecting to socket %s: %s"));
-EXTERN char e_socket_server_unavailable[]
- INIT(= N_("E1567: Cannot start socket server, socket path is unavailable"));
+ INIT(= N_("E1566: Failed connecting to socket '%s'"));
+EXTERN char e_socket_server_version_mismatch[]
+ INIT(= N_("E1567: Socket server protocol version mismatch, check what Vim version you are using"));
#endif
EXTERN char e_osc_response_timed_out[]
INIT(= N_("E1568: OSC command response timed out: %.*s"));
#endif
/*
- * +socketserver Use UNIX domain sockets for clientserver communication
+ * The +channel feature requires +eval.
+ */
+#if !defined(FEAT_EVAL) && defined(FEAT_JOB_CHANNEL)
+# undef FEAT_JOB_CHANNEL
+#endif
+
+/*
+ * +socketserver Use channels for clientserver communication
*/
-#if defined(UNIX) && defined(WANT_SOCKETSERVER)
+#if (defined(UNIX) || defined(MSWIN)) && defined(FEAT_JOB_CHANNEL)
# define FEAT_SOCKETSERVER
#endif
#if (defined(MSWIN) || defined(FEAT_XCLIPBOARD) || defined(FEAT_SOCKETSERVER)) \
&& defined(FEAT_EVAL)
# define FEAT_CLIENTSERVER
+# if defined(FEAT_SOCKETSERVER) && (defined(FEAT_XCLIPBOARD) || defined(MSWIN))
+# define FEAT_CLIENTSERVER_BACKENDS
+# endif
#endif
/*
* +tgetent
*/
-
-/*
- * The +channel feature requires +eval.
- */
-#if !defined(FEAT_EVAL) && defined(FEAT_JOB_CHANNEL)
-# undef FEAT_JOB_CHANNEL
-#endif
-
/*
* The Netbeans feature requires +eval and +job_channel
*/
#ifdef FEAT_NETBEANS_INTG
abort = abort || set_ref_in_nb_channel(copyID);
#endif
+#ifdef FEAT_SOCKETSERVER
+ abort = abort || set_ref_in_socketserver_channel(copyID);
+#endif
#ifdef FEAT_TIMERS
abort = abort || set_ref_in_timer(copyID);
// Process the queued netbeans messages.
netbeans_parse_messages();
# endif
+# ifdef FEAT_SOCKETSERVER
+ socketserver_parse_messages();
+# endif
# ifdef FEAT_JOB_CHANNEL
// Write any buffer lines still to be written.
channel_write_any_lines();
#endif
-#if defined(FEAT_CLIENTSERVER) && !defined(MSWIN)
+#if defined(FEAT_CLIENTSERVER)
// Backend for clientserver functionality
typedef enum {
CLIENTSERVER_METHOD_NONE,
+# ifdef FEAT_X11
CLIENTSERVER_METHOD_X11,
+# endif
+# ifdef MSWIN
+ CLIENTSERVER_METHOD_MSWIN,
+# endif
+# ifdef FEAT_SOCKETSERVER
CLIENTSERVER_METHOD_SOCKET
+# endif
} clientserver_method_T;
-// Default to X11 if compiled with support for it, else use socket server.
-# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
EXTERN clientserver_method_T clientserver_method
-# else
-// Since we aren't going to be changing clientserver_method, make it constant to
-// allow compiler optimizations.
-EXTERN const clientserver_method_T clientserver_method
-# endif
+
# ifdef FEAT_X11
INIT(= CLIENTSERVER_METHOD_X11);
+# elif defined(MSWIN)
+INIT(= CLIENTSERVER_METHOD_MSWIN);
# elif defined(FEAT_SOCKETSERVER)
INIT(= CLIENTSERVER_METHOD_SOCKET);
# else
choose_clipmethod();
#endif
-#if defined(FEAT_SOCKETSERVER) && defined(FEAT_GUI_GTK)
- // Install socket server listening socket if we are running it
- if (socket_server_valid())
- gui_gtk_init_socket_server();
-#endif
-
#ifdef FEAT_GUI_MSWIN
// Enable fullscreen mode
if (vim_strchr(p_go, GO_FULLSCREEN) != NULL)
# include <X11/Sunkeysym.h>
#endif
-#ifdef FEAT_SOCKETSERVER
-# include <glib-unix.h>
-
-// Used to track the source for the listening socket
-static uint socket_server_source_id = 0;
-#endif
-
/*
* Easy-to-use macro for multihead support.
*/
}
#endif // !USE_GNOME_SESSION
-#if defined(FEAT_SOCKETSERVER)
-
-/*
- * Callback for new events from the socket server listening socket
- */
- static int
-socket_server_poll_in(int fd UNUSED, GIOCondition cond, void *user_data UNUSED)
-{
- if (cond & G_IO_IN)
- socket_server_accept_client();
- else if (cond & (G_IO_ERR | G_IO_HUP))
- {
- socket_server_uninit();
- return FALSE;
- }
-
- return TRUE;
-}
-
-/*
- * Initialize socket server for use in the GUI (does not actually initialize the
- * socket server, only attaches a source).
- */
- void
-gui_gtk_init_socket_server(void)
-{
- if (socket_server_source_id > 0)
- return;
- // Register source for file descriptor to global default context
- socket_server_source_id = g_unix_fd_add(socket_server_get_fd(),
- G_IO_IN | G_IO_ERR | G_IO_HUP, socket_server_poll_in, NULL);
-}
-
-/*
- * Remove the source for the socket server listening socket.
- */
- void
-gui_gtk_uninit_socket_server(void)
-{
- if (socket_server_source_id > 0)
- {
- g_source_remove(socket_server_source_id);
- socket_server_source_id = 0;
- }
-}
-
-#endif
-
static GdkPixbuf *
pixbuf_new_from_png_data(const unsigned char *data, unsigned int len)
{
// Store bytes in the growarray.
ga_init2(&ga, 1, 4000);
json_encode_gap(&ga, val, options);
+ if (options & JSON_NL)
+ ga_append(&ga, NL);
ga_append(&ga, NUL);
return ga.ga_data;
}
#ifdef FEAT_NETBEANS_INTG
netbeans_end();
#endif
+#ifdef FEAT_SOCKETSERVER
+ socketserver_stop();
+#endif
#ifdef FEAT_CSCOPE
cs_end();
#endif
gui.dofork = false;
# endif
}
-# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+# ifdef FEAT_CLIENTSERVER_BACKENDS
else if (STRNICMP(argv[i], "--clientserver", 14) == 0)
{
char_u *arg;
if (STRICMP(arg, "socket") == 0)
clientserver_method = CLIENTSERVER_METHOD_SOCKET;
+# ifdef FEAT_X11
else if (STRICMP(arg, "x11") == 0)
clientserver_method = CLIENTSERVER_METHOD_X11;
+# endif
+# ifdef MSWIN
+ else if (STRICMP(arg, "mswin") == 0)
+ clientserver_method = CLIENTSERVER_METHOD_MSWIN;
+# endif
else
mainerr(ME_UNKNOWN_OPTION, arg);
}
; // already processed -- no arg
else if (STRNICMP(argv[0] + argv_idx, "servername", 10) == 0
|| STRNICMP(argv[0] + argv_idx, "serversend", 10) == 0
-# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+ // Don't put this under FEAT_CLIENTSERVER_BACKENDS, just
+ // let it be ignored. Makes tests less complicated
|| STRNICMP(argv[0] + argv_idx, "clientserver", 12) == 0
-# endif
)
{
// already processed -- snatch the following arg
main_msg(_("-Y\t\t\tDo not connect to Wayland compositor"));
# endif
# ifdef FEAT_CLIENTSERVER
-# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
- main_msg(_("--clientserver <socket|x11> Backend for clientserver communication"));
+# ifdef FEAT_CLIENTSERVER_BACKENDS
+ main_msg(_("--clientserver <socket|x11|mswin> Backend for clientserver communication"));
# endif
main_msg(_("--remote <files>\tEdit <files> in a Vim server if possible"));
main_msg(_("--remote-silent <files> Same, don't complain if there is no server"));
Display *x11_display = NULL;
#endif
-#if defined(FEAT_SOCKETSERVER)
-# include <sys/socket.h>
-# include <sys/un.h>
-
-# define SOCKET_SERVER_MAX_BACKLOG 5
-# define SOCKET_SERVER_MAX_CMD_SIZE 16384
-# define SOCKET_SERVER_MAX_MSG 6
-
-static int socket_server_fd = -1;
-static char_u *socket_server_path = NULL;
-
-typedef enum {
- SS_MSG_TYPE_ENCODING = 'e', // Encoding of message.
- SS_MSG_TYPE_STRING = 'c', // Script to execute or reply string.
- SS_MSG_TYPE_SERIAL = 's', // Serial of pending command
- SS_MSG_TYPE_CODE = 'r', // Result code for an expression sent
- SS_MSG_TYPE_SENDER = 'd' // Location of socket for the client that
- // sent the command.
-} ss_msg_type_T;
-
-typedef enum {
- SS_CMD_TYPE_EXPR = 'E', // An expression
- SS_CMD_TYPE_KEYSTROKES = 'K', // Series of keystrokes
- SS_CMD_TYPE_REPLY = 'R', // Reply from an expression
- SS_CMD_TYPE_NOTIFY = 'N', // A notification
- SS_CMD_TYPE_ALIVE = 'A', // Check if server is still responsive
-} ss_cmd_type_T;
-
-// Represents a message in a command. A command can contain multiple messages.
-// Each message starts with a single byte representing the type, then a uint32
-// representing the length of the contents, and then the actual contents.
-// Everything is in native byte order.
-//
-// While contents may contain NULL characters, such as when it is a number, it
-// is always NULL terminated. Note that the NULL terminator does not count in
-// the length.
-typedef struct {
- char_u msg_type; // Type of message
- uint32_t msg_len; // Total length of contents
- char_u *msg_contents; // Actual contents of message
-} ss_msg_T;
-
-// Represents a command sent over a socket. Each socket starts with a byte
-// representing the type, then a uint32 representing the number of messages,
-// then a uint32 representing the total size of the messages in bytes, and then
-// the actual messages. Everything is in native byte order.
-typedef struct {
- char_u cmd_type; // Type of command
- uint32_t cmd_num; // Number of messages
- uint32_t cmd_len; // Combined size of all
- // messages
- ss_msg_T cmd_msgs[SOCKET_SERVER_MAX_MSG]; // Array of messages
-} ss_cmd_T;
-
-# define SS_CMD_INFO_SIZE (sizeof(char_u) + (sizeof(uint32_t) * 2))
-# define SS_MSG_INFO_SIZE (sizeof(char_u) + sizeof(uint32_t))
-
-// Represents a pending reply from a command sent to a Vim server. When a
-// command is sent out, we generate unique serial number with it. When we
-// receive any reply, we check which pending command has a matching serial
-// number, and is therefore the reply for that pending command.
-//
-// The reason we just don't use the existing fd created by the connect() call,
-// and communicate using that, is that it can't handle recursive calls, ex:
-// call remote_expr('B', 'remote_expr("A", "<expr>")')
-//
-// This idea is taken from the existing X server functionality
-typedef struct ss_pending_cmd_S {
- uint32_t serial; // Serial number expected in result
- char_u code; // Result code, can be 0 or -1.
- char_u *result; // Result of command
-
- struct ss_pending_cmd_S *next; // Next in list
-} ss_pending_cmd_T;
-
-static ss_pending_cmd_T *ss_pending_cmds;
-
-// Serial is always greater than zero
-static uint32_t ss_serial = 0;
-
-// Represents a reply from a server2client call. Each client that calls a
-// server2client call to us has its own ss_reply_T. Each time a client sends
-// data using server2client, Vim creates a ss_reply_T if it doesn't exist and
-// adds the string to the array. When remote_read is called, the server id is
-// used to find the specific ss_reply_T, and a single string is popped from the
-// array.
-//
-// This idea is taken from the existing X server functionality
-typedef struct {
- char_u *sender;
- garray_T strings;
-} ss_reply_T;
-
-static garray_T ss_replies;
-
-static char_u *socket_server_get_path_from_name(char_u *name);
-static int socket_server_connect(char_u *name, char_u **path, int silent);
-static void socket_server_init_pending_cmd(ss_pending_cmd_T *pending);
-static void socket_server_pop_pending_cmd(ss_pending_cmd_T *pending);
-static void socket_server_init_cmd(ss_cmd_T *cmd, ss_cmd_type_T type);
-static int socket_server_append_msg(ss_cmd_T *cmd, char_u type,
- char_u *contents, int len);
-static void socket_server_free_cmd(ss_cmd_T *cmd);
-static char_u *socket_server_encode_cmd(ss_cmd_T *cmd, size_t *sz);
-static int socket_server_decode_cmd(ss_cmd_T *cmd, int socket_fd, int timeout);
-static int socket_server_write(int sock_fd, char_u *data, size_t sz,
- int timeout);
-static ss_reply_T *socket_server_get_reply(char_u *sender, int *index);
-static ss_reply_T *socket_server_add_reply(char_u *sender);
-static void socket_server_remove_reply(char_u *sender);
-static void socket_server_exec_cmd(ss_cmd_T *cmd, int fd);
-static int socket_server_dispatch(int timeout);
-static int socket_server_check_alive(char_u *name);
-static int socket_server_name_is_valid(char_u *name);
-
-#endif // FEAT_SOCKETSERVER
-
static int ignore_sigtstp = FALSE;
static int get_x11_title(int);
x11_export_final_selection();
#endif
-#ifdef FEAT_SOCKETSERVER
- socket_server_uninit();
-#endif
-
#ifdef FEAT_GUI
if (!gui.in_use)
#endif
// each channel may use in, out and err
struct pollfd fds[7 + 3 * MAX_OPEN_CHANNELS];
int nfd;
-# ifdef FEAT_SOCKETSERVER
- int socket_server_idx = -1;
-# endif
# ifdef FEAT_WAYLAND_CLIPBOARD
int wayland_idx = -1;
# endif
fds[0].events = POLLIN;
nfd = 1;
-# ifdef FEAT_SOCKETSERVER
- if (socket_server_fd != -1)
- {
- socket_server_idx = nfd;
- fds[nfd].fd = socket_server_fd;
- fds[nfd].events = POLLIN;
- nfd++;
- }
-# endif
-
# ifdef FEAT_WAYLAND
if ((wayland_fd = wayland_prepare_read()) >= 0)
{
finished = FALSE;
# endif
-# ifdef FEAT_SOCKETSERVER
- if (socket_server_idx >= 0)
- {
- if (fds[socket_server_idx].revents & POLLIN)
- {
- if (socket_server_accept_client() == FAIL)
- socket_server_uninit();
- }
- else if (fds[socket_server_idx].revents & (POLLHUP | POLLERR))
- socket_server_uninit();
- }
-# endif
-
# ifdef FEAT_WAYLAND
if (wayland_idx >= 0)
wayland_poll_check(fds[wayland_idx].revents);
# endif
maxfd = fd;
-# ifdef FEAT_SOCKETSERVER
- if (socket_server_fd != -1)
- {
- FD_SET(socket_server_fd, &rfds);
-
- if (maxfd < socket_server_fd)
- maxfd = socket_server_fd;
- }
-# endif
-
# ifdef FEAT_WAYLAND
if ((wayland_fd = wayland_prepare_read()) >= 0)
{
finished = FALSE;
# endif
-# ifdef FEAT_SOCKETSERVER
- if (ret > 0 && socket_server_fd != -1
- && FD_ISSET(socket_server_fd, &rfds)
- && socket_server_accept_client() == FAIL)
- socket_server_uninit();
-# endif
-
# ifdef FEAT_WAYLAND
if (wayland_fd != -1)
wayland_select_check(ret > 0 && FD_ISSET(wayland_fd, &rfds));
if (finished || msec == 0)
break;
-# if defined(FEAT_CLIENTSERVER)
-# ifdef FEAT_X11
+# if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11)
if (clientserver_method == CLIENTSERVER_METHOD_X11 && server_waiting())
break;
-# endif
-# ifdef FEAT_SOCKETSERVER
- if (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
- socket_server_waiting_accept())
- break;
-# endif
# endif
// We're going to loop around again, find out for how long
}
return fd;
}
-
-#if defined(FEAT_SOCKETSERVER)
-
-/*
- * Initialize socket server called "name" (the socket filename). If "name" is a
- * path (starts with a '/', './', or '../'), it is assumed to be the path to
- * the desired socket. If the socket path is already taken, append an
- * incrementing number to the path until we find a socket filename that can be
- * used. If NULL is passed as the name, the previous socket path is used (only
- * if not NULL). Returns OK on success and FAIL on failure.
- */
- int
-socket_server_init(char_u *name)
-{
- struct sockaddr_un addr;
- char_u *path;
- int num_printed;
- int fd;
- int i = 1;
-
- if (socket_server_valid() || (name == NULL && socket_server_path == NULL))
- return FAIL;
- if (name == NULL)
- name = socket_server_path;
-
- path = alloc(sizeof(addr.sun_path));
-
- if (path == NULL)
- return FAIL;
-
- fd = socket(AF_UNIX, SOCK_STREAM, 0);
-
- if (fd == -1)
- {
- vim_free(path);
- return FAIL;
- }
-
- addr.sun_family = AF_UNIX;
-
- // If name is not a path, find a common directory to place the
- // socket.
- if (name[0] == '/' || STRNCMP(name, "./", 2) == 0 ||
- STRNCMP(name, "../", 3) == 0)
- num_printed =
- vim_snprintf((char *)path, sizeof(addr.sun_path), "%s", name);
- else
- {
- const char_u *dir;
- char_u *buf;
-
- // Check if there are slashes in the name
- if (vim_strchr(name, '/') != NULL)
- {
- emsg(_(e_socket_name_no_slashes));
- goto fail;
- }
-
- dir = mch_getenv("XDG_RUNTIME_DIR");
-
- if (dir == NULL)
- {
- // Use $TMPDIR or /tmp if $XDG_RUNTIME_DIR is not set.
- const char_u *tmpdir = mch_getenv("TMPDIR");
- size_t sz;
-
- if (tmpdir != NULL)
- dir = tmpdir;
- else
- dir = (char_u *)"/tmp";
-
- sz = STRLEN(dir) + 25;
- buf = alloc(sz);
-
- if (buf == NULL)
- goto fail;
-
- vim_snprintf((char *)buf, sz, "%s/vim-%lu", dir,
- (unsigned long int)getuid());
- }
- else
- {
- buf = alloc(STRLEN(dir) + STRLEN("vim") + 2);
-
- if (buf == NULL)
- goto fail;
-
- sprintf((char *)buf, "%s/vim", dir);
- }
-
- // Always set directory permissions to 0700 for security
- if (vim_mkdir(buf, 0700) == -1 && errno != EEXIST)
- {
- semsg(_("Failed creating socket directory: %s"), strerror(errno));
- vim_free(buf);
- goto fail;
- }
-
- num_printed = vim_snprintf((char *)path, sizeof(addr.sun_path),
- "%s/%s", buf, name);
-
- vim_free(buf);
- }
-
- // Check if path was too big
- if ((size_t)num_printed >= sizeof(addr.sun_path))
- {
- emsg(_(e_socket_path_too_big));
- goto fail;
- }
-
- vim_snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
-
- // Bind to a suitable path/address
- while (i < 1000)
- {
- if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))
- == -1)
- {
- int fd2;
-
- if (errno != EADDRINUSE)
- {
- emsg(_(e_socket_server_unavailable));
- goto fail;
- }
-
- // If the socket is dead, remove it and try again
- fd2 = socket_server_connect((char_u *)addr.sun_path, NULL, TRUE);
-
- if (fd2 == -1)
- {
- mch_remove(addr.sun_path);
- continue;
- }
- else
- close(fd2);
- }
- else
- break;
-
- num_printed = vim_snprintf(addr.sun_path, sizeof(addr.sun_path),
- "%s%d", path, i);
-
- if ((size_t)num_printed >= sizeof(addr.sun_path))
- {
- // Address too big
- emsg(_(e_socket_path_too_big));
- goto fail;
- }
-
- i++;
- }
-
- if (i >= 1000)
- {
- emsg(_(e_socket_server_unavailable));
- goto fail;
- }
-
- // Start listening for connections
- if (listen(fd, SOCKET_SERVER_MAX_BACKLOG) == -1)
- goto fail;
-
- // Set global path and vvar to the absolute path
- if ((socket_server_path = alloc(MAXPATHL)) == NULL)
- goto fail;
-
- socket_server_path[0] = NUL;
-
- if (mch_FullName((char_u *)addr.sun_path, socket_server_path,
- MAXPATHL, FALSE) == FAIL)
- {
- vim_free(socket_server_path);
- goto fail;
- }
-
- serverName = vim_strsave(socket_server_path);
-# ifdef FEAT_EVAL
- set_vim_var_string(VV_SEND_SERVER, serverName, -1);
-# endif
-
- socket_server_fd = fd;
-
-# ifdef FEAT_GUI_GTK
- if (gui.in_use)
- // Initialize source for GUI if we are using it
- gui_gtk_init_socket_server();
-# endif
-
- vim_free(path);
- return OK;
-fail:
- close(fd);
- vim_free(path);
- socket_server_uninit();
- return FAIL;
-}
-
- void
-socket_server_uninit(void)
-{
- if (socket_server_fd != -1)
- {
- close(socket_server_fd);
- socket_server_fd = -1;
- }
-
- if (socket_server_path != NULL)
- {
- mch_remove(socket_server_path);
- vim_free(socket_server_path);
- socket_server_path = NULL;
- }
-# ifdef FEAT_GUI_GTK
- if (gui.in_use)
- gui_gtk_uninit_socket_server();
-# endif
-}
-
-/*
- * List available sockets that can be connected to, only in common directories
- * that Vim knows about. Vim instances with custom socket paths will not be
- * detected. Returns a newline separated string on success and NULL on failure.
- */
- char_u *
-socket_server_list_sockets(void)
-{
- garray_T str;
- string_T buf;
- string_T path;
- DIR *dirp;
- struct dirent *dp;
- struct sockaddr_un addr;
- char_u *known_dirs[] = {
- mch_getenv("XDG_RUNTIME_DIR"),
- mch_getenv("TMPDIR"),
- (char_u *)"/tmp"
- };
-
- if ((buf.string = alloc(sizeof(addr.sun_path))) == NULL)
- return NULL;
- if ((path.string = alloc(sizeof(addr.sun_path))) == NULL)
- {
- vim_free(buf.string);
- return NULL;
- }
- buf.length = 0;
- path.length = 0;
-
- ga_init2(&str, 1, 100);
-
- for (size_t i = 0 ; i < ARRAY_LENGTH(known_dirs); i++)
- {
- char_u *dir = known_dirs[i];
-
- if (dir == NULL)
- continue;
-
- if (STRCMP(dir, "/tmp") == 0 ||
- (known_dirs[1] != NULL && STRCMP(dir, known_dirs[1]) == 0))
- path.length = vim_snprintf_safelen((char *)path.string, sizeof(addr.sun_path),
- "%s/vim-%lu", dir, (unsigned long int)getuid());
- else
- path.length = vim_snprintf_safelen((char *)path.string, sizeof(addr.sun_path),
- "%s/vim", dir);
-
- dirp = opendir((char *)path.string);
- if (dirp == NULL)
- continue;
-
- // Loop through directory
- while ((dp = readdir(dirp)) != NULL)
- {
- if (STRCMP(dp->d_name, ".") == 0 || STRCMP(dp->d_name, "..") == 0)
- continue;
-
- buf.length = vim_snprintf_safelen((char *)buf.string, sizeof(addr.sun_path),
- "%s/%s", path.string, dp->d_name);
-
- // Don't want to send to ourselves, but we do want to list our
- // server name (if we are a server).
- if (socket_server_path == NULL
- || STRCMP(socket_server_path, buf.string) != 0)
- {
- // Try sending an ALIVE command. This is more assuring than a
- // simple connect, and *also seems to make tests less flaky*.
- //
- // We could also use a lock file which may be better, but this
- // has worked fine so far... - 64bitman
- if (!socket_server_check_alive(buf.string))
- continue;
- }
-
- ga_concat_len(&str, (char_u *)dp->d_name, buf.length - (path.length + 1));
- ga_append(&str, '\n');
- }
-
- closedir(dirp);
-
- break;
- }
-
- vim_free(path.string);
- vim_free(buf.string);
-
- ga_append(&str, NUL);
-
- return str.ga_data;
-}
-
-/*
- * Called when the server has received a new command. If so, parse it and do the
- * stuff it says, and possibly send back a reply. Returns OK if client was
- * accepted, else FAIL.
- */
- int
-socket_server_accept_client(void)
-{
- int fd = accept(socket_server_fd, NULL, NULL);
- ss_cmd_T cmd;
-
- if (fd == -1)
- return FAIL;
-
- if (socket_server_decode_cmd(&cmd, fd, 1000) == FAIL)
- goto exit;
-
-# ifdef FEAT_EVAL
- ch_log(NULL, "accepted new client on socket %s", socket_server_path);
-# endif
-
- socket_server_exec_cmd(&cmd, fd);
- socket_server_free_cmd(&cmd);
-
-exit:
- close(fd);
- return OK;
-}
-
-/*
- * Check if socket server is able to be used
- */
- int
-socket_server_valid(void)
-{
- return socket_server_fd != -1 && socket_server_path != NULL;
-}
-
-/*
- * If "name" is a pathless name such as "VIM", search known directories for the
- * socket named "name", and return the alloc'ed path to it. If "name" starts
- * with a '/', './' or '../', then a copy of "name" is returned. Returns NULL
- * on failure or if no socket was found.
- */
- static char_u *
-socket_server_get_path_from_name(char_u *name)
-{
- char_u *buf;
- stat_T s;
- const char_u *known_dirs[] = {
- mch_getenv("XDG_RUNTIME_DIR"),
- mch_getenv("TMPDIR"),
- (char_u *)"/tmp"
- };
-
- if (name == NULL)
- return NULL;
-
- // Ignore if name is a path
- if (name[0] == '/' || STRNCMP(name, "./", 2) == 0 ||
- STRNCMP(name, "../", 3) == 0)
- return vim_strsave(name);
-
- buf = alloc(MAXPATHL);
-
- if (buf == NULL)
- return NULL;
-
- for (size_t i = 0; i < ARRAY_LENGTH(known_dirs); i++)
- {
- const char_u *dir = known_dirs[i];
-
- if (dir == NULL)
- continue;
- else if (STRCMP(dir, "/tmp") == 0 ||
- (known_dirs[1] != NULL && STRCMP(dir, known_dirs[1]) == 0))
- vim_snprintf((char *)buf, MAXPATHL, "%s/vim-%lu/%s", dir,
- (unsigned long int)getuid(), name);
- else
- vim_snprintf((char *)buf, MAXPATHL, "%s/vim/%s", dir, name);
-
- if (mch_stat((char *)buf,&s) == 0 && S_ISSOCK(s.st_mode))
- {
- if (STRCMP(buf, socket_server_path) == 0)
- // Can't connect to itself
- break;
- return buf;
- }
- }
-
- vim_free(buf);
- return NULL;
-}
-
-/*
- * Send command to socket named "name". Returns 0 for OK, -1 on error.
- */
- int
-socket_server_send(
- char_u *name, // Socket path or a general name
- char_u *str, // What to send
- char_u **result, // Set to result of expr
- char_u **receiver, // Full path of "name"
- int is_expr, // Is it an expression or keystrokes?
- int timeout, // In milliseconds
- int silent) // Don't complain if socket doesn't exist
-{
- ss_cmd_T cmd;
- int socket_fd;
- size_t sz;
- char_u *final;
- char_u *path;
-# ifdef ELAPSED_FUNC
- elapsed_T start_tv;
-# endif
-
- if (!socket_server_valid())
- {
- emsg(_(e_socket_server_not_online));
- return -1;
- }
-
- socket_fd = socket_server_connect(name, &path, silent);
-
- if (socket_fd == -1)
- return -1;
-
-# ifdef FEAT_EVAL
- ch_log(NULL, "socket_server_send(%s, %s)", path, str);
-# endif
-
- // Execute locally if target is ourselves
- if (serverName != NULL && STRICMP(path, serverName) == 0)
- {
- vim_free(path);
- close(socket_fd);
- return sendToLocalVim(str, is_expr, result);
- }
-
- socket_server_init_cmd(&cmd,
- is_expr ? SS_CMD_TYPE_EXPR : SS_CMD_TYPE_KEYSTROKES);
-
- socket_server_append_msg(&cmd, SS_MSG_TYPE_ENCODING, p_enc, STRLEN(p_enc));
-
- // Add +1 in case of empty string
- socket_server_append_msg(&cmd, SS_MSG_TYPE_STRING, str, STRLEN(str) + 1);
-
- // Tell server who we are so it can save our socket path internally for
- // later use with server2client
- socket_server_append_msg(&cmd, SS_MSG_TYPE_SENDER, socket_server_path,
- STRLEN(socket_server_path));
-
- if (is_expr)
- {
- ss_serial++;
- socket_server_append_msg(&cmd, SS_MSG_TYPE_SERIAL,
- (char_u *)&ss_serial, sizeof(ss_serial));
- }
-
- final = socket_server_encode_cmd(&cmd, &sz);
-
- if (final == NULL ||
- socket_server_write(socket_fd, final, sz, 1000) == FAIL)
- {
- if (final != NULL)
- emsg(_(e_failed_to_send_command_to_destination_program));
-
- vim_free(path);
- socket_server_free_cmd(&cmd);
- close(socket_fd);
- vim_free(final);
- return -1;
- }
- socket_server_free_cmd(&cmd);
- vim_free(final);
-
-
- close(socket_fd);
- if (!is_expr)
- {
- if (receiver != NULL)
- *receiver = path;
- else
- vim_free(path);
-
- // Exit, we aren't waiting for a response
- return 0;
- }
-
- ss_pending_cmd_T pending;
-
- socket_server_init_pending_cmd(&pending);
-
-# ifdef ELAPSED_FUNC
- ELAPSED_INIT(start_tv);
-# endif
-
- // Wait for server to send back result
- while (socket_server_dispatch(500) >= 0)
- {
- if (pending.result != NULL)
- break;
-
-# ifdef ELAPSED_FUNC
- if (ELAPSED_FUNC(start_tv) >= (timeout > 0 ? timeout : 1000))
- break;
-# endif
- }
-
- if (pending.result == NULL)
- {
- socket_server_pop_pending_cmd(&pending);
- vim_free(path);
- return -1;
- }
-
- if (result != NULL)
- *result = pending.result;
- else
- vim_free(pending.result);
-
- if (receiver != NULL)
- *receiver = path;
- else
- vim_free(path);
-
- socket_server_pop_pending_cmd(&pending);
-
- return pending.code == 0 ? 0 : -1;
-}
-
-/*
- * Wait for replies from "client" and place result in "str". Returns OK on
- * success and FAIL on failure. Timeout is in milliseconds
- */
- int
-socket_server_read_reply(char_u *client, char_u **str, int timeout UNUSED)
-{
- ss_reply_T *reply = NULL;
-# ifdef ELAPSED_FUNC
- elapsed_T start_tv;
-# endif
-
- if (!socket_server_name_is_valid(client))
- return -1;
-
- if (!socket_server_valid())
- return -1;
-
-# ifdef ELAPSED_FUNC
- if (timeout > 0)
- ELAPSED_INIT(start_tv);
-# endif
-
- // Try seeing if there already is a reply in the queue
- goto get_reply;
-
- while (socket_server_dispatch(500) >= 0)
- {
- int fd;
-
-# ifdef ELAPSED_FUNC
- if (timeout > 0 && ELAPSED_FUNC(start_tv) >= timeout)
- break;
-# endif
-
-get_reply:
- reply = socket_server_get_reply(client, NULL);
-
- if (reply != NULL)
- break;
-
- // Check if sender is down by connecting to it as a test. A simple
- // connect will do.
- fd = socket_server_connect(client, NULL, TRUE);
-
- if (fd == -1)
- return FAIL;
- else
- close(fd);
- }
-
- if (reply == NULL || reply->strings.ga_data == NULL ||
- reply->strings.ga_len <= 0)
- {
- return FAIL;
- }
-
- // Consume the string
- *str = ((char_u **)reply->strings.ga_data)[0];
-
- for (int i = 1; i < reply->strings.ga_len; i++)
- {
- ((char_u **)reply->strings.ga_data)[i - 1] =
- ((char_u **)reply->strings.ga_data)[i];
- }
- reply->strings.ga_len--;
-
- if (reply->strings.ga_len < 1)
- // Last string removed, remove the reply
- socket_server_remove_reply(client);
-
-
- return OK;
-}
-
-/*
- * Check for any replies for "sender". Returns 1 if there is and places the
- * reply in "str" without consuming it. Returns 0 if otherwise and -1 on
- * error.
- */
- int
-socket_server_peek_reply(char_u *sender, char_u **str)
-{
- ss_reply_T *reply;
-
- if (!socket_server_name_is_valid(sender))
- return -1;
-
- if (!socket_server_valid())
- return 0;
-
- reply = socket_server_get_reply(sender, NULL);
-
- if (reply != NULL && reply->strings.ga_len > 0)
- {
- if (str != NULL)
- *str = ((char_u **)reply->strings.ga_data)[0];
- return 1;
- }
-
- return 0;
-}
-
-/*
- * Send a string to "client" as a reply (notification). Returns OK on success
- * and FAIL on failure.
- */
- int
-socket_server_send_reply(char_u *client, char_u *str)
-{
- int socket_fd;
- ss_cmd_T cmd;
- size_t sz;
- char_u *final;
-
- if (!socket_server_name_is_valid(client))
- return FAIL;
-
- if (!socket_server_valid())
- {
- emsg(_(e_socket_server_not_online));
- return FAIL;
- }
-
- socket_fd = socket_server_connect(client, NULL, TRUE);
-
- if (socket_fd == -1)
- return FAIL;
-
- socket_server_init_cmd(&cmd, SS_CMD_TYPE_NOTIFY);
-
- socket_server_append_msg(&cmd, SS_MSG_TYPE_ENCODING, p_enc, STRLEN(p_enc));
- socket_server_append_msg(&cmd, SS_MSG_TYPE_STRING, str, STRLEN(str));
- socket_server_append_msg(&cmd, SS_MSG_TYPE_SENDER,
- socket_server_path, STRLEN(socket_server_path));
-
- final = socket_server_encode_cmd(&cmd, &sz);
-
- if (final == NULL ||
- socket_server_write(socket_fd, final, sz, 1000) == FAIL)
- {
- socket_server_free_cmd(&cmd);
- vim_free(final);
- close(socket_fd);
- return FAIL;
- }
-
- socket_server_free_cmd(&cmd);
- vim_free(final);
- close(socket_fd);
-
- return OK;
-}
-
-/*
- * Connect to a socket using "name". "path" is set to the full path of "name"
- * used to create the socket, only if its not NULL. Returns fd on success and -1
- * on failure.
- */
- static int
-socket_server_connect(char_u *name, char_u **path, int silent)
-{
- int socket_fd;
- int res;
- struct sockaddr_un addr;
-
- char_u *socket_path = socket_server_get_path_from_name(name);
-
- if (socket_path == NULL)
- {
- if (!silent)
- semsg(_(e_no_registered_server_named_str), name);
- return -1;
- }
- if (STRLEN(socket_path) >= sizeof(addr.sun_path))
- {
- // Path too big
- vim_free(socket_path);
- return -1;
- }
-
- socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
-
- if (socket_fd == -1)
- {
- vim_free(socket_path);
- return -1;
- }
-
- addr.sun_family = AF_UNIX;
- vim_snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
-
- res = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr));
-
- if (res == -1)
- {
- if (!silent)
- semsg(_(e_socket_server_failed_connecting), socket_path,
- strerror(errno));
- close(socket_fd);
- vim_free(socket_path);
- return -1;
- }
-
- if (path != NULL)
- *path = socket_path;
- else
- vim_free(socket_path);
-
- return socket_fd;
-
-}
-
-/*
- * Add a new pending command to the list of pending commands. Returns OK on
- * success and FAIL on failure
- */
- static void
-socket_server_init_pending_cmd(ss_pending_cmd_T *pending)
-{
- pending->code = 0;
- pending->result = NULL;
- pending->serial = ss_serial;
- pending->next = ss_pending_cmds;
- ss_pending_cmds = pending;
-}
-
-/*
- * Remove pending command from the list, does not free the result string.
- */
- static void
-socket_server_pop_pending_cmd(ss_pending_cmd_T *pending)
-{
- if (ss_pending_cmds == pending)
- {
- ss_pending_cmds = pending->next;
- return;
- }
-
- for (ss_pending_cmd_T *cmd = ss_pending_cmds; cmd != NULL; cmd = cmd->next)
- {
- if (cmd->next == pending)
- {
- cmd->next = pending->next;
- return;
- }
- }
-}
-
-/*
- * Initialize command structure to empty state
- */
- static void
-socket_server_init_cmd(ss_cmd_T *cmd, ss_cmd_type_T type)
-{
- cmd->cmd_len = 0;
- cmd->cmd_num = 0;
- cmd->cmd_type = type;
-}
-
-/*
- * Append a message to a command. Note that "len" is the length of contents.
- * Returns OK on success and FAIL on failure
- */
- static int
-socket_server_append_msg(ss_cmd_T *cmd, char_u type, char_u *contents, int len)
-{
- ss_msg_T *msg = cmd->cmd_msgs + cmd->cmd_num;
-
- if (cmd->cmd_num >= SOCKET_SERVER_MAX_MSG)
- return FAIL;
-
- // Check if command will be too big.
- if (SS_CMD_INFO_SIZE + cmd->cmd_len + SS_MSG_INFO_SIZE + len
- > SOCKET_SERVER_MAX_CMD_SIZE)
- return FAIL;
-
- msg->msg_contents = alloc(len);
-
- if (msg->msg_contents == NULL)
- return FAIL;
-
- msg->msg_type = type;
- msg->msg_len = len;
- memcpy(msg->msg_contents, contents, len);
-
- cmd->cmd_len += SS_MSG_INFO_SIZE + len;
- cmd->cmd_num++;
-
- return OK;
-}
-
-/*
- * Free all resources associated with a command object.
- */
- static void
-socket_server_free_cmd(ss_cmd_T *cmd)
-{
- for (uint32_t i = 0; i < cmd->cmd_num; i++)
- {
- ss_msg_T *msg = cmd->cmd_msgs + i;
-
- vim_free(msg->msg_contents);
- }
-}
-
-/*
- * Encode command struct and return the final message to send. Returns NULL on
- * failure.
- */
- static char_u *
-socket_server_encode_cmd(ss_cmd_T *cmd, size_t *sz)
-{
- size_t size;
- char_u *buf;
- char_u *start;
-
- size = SS_CMD_INFO_SIZE + cmd->cmd_len;
- buf = alloc(size);
-
- if (buf == NULL)
- return NULL;
-
- start = buf;
- memcpy(start, &cmd->cmd_type, sizeof(cmd->cmd_type));
- start += sizeof(cmd->cmd_type);
- memcpy(start, &cmd->cmd_num, sizeof(cmd->cmd_num));
- start += sizeof(cmd->cmd_num);
- memcpy(start, &cmd->cmd_len, sizeof(cmd->cmd_len));
- start += sizeof(cmd->cmd_len);
-
- // Append messages to buffer
- for (uint32_t i = 0; i < cmd->cmd_num; i++)
- {
- ss_msg_T *msg = cmd->cmd_msgs + i;
-
- memcpy(start, &msg->msg_type, sizeof(msg->msg_type));
- start += sizeof(msg->msg_type);
- memcpy(start, &msg->msg_len, sizeof(msg->msg_len));
- start += sizeof(msg->msg_len);
-
- memcpy(start, msg->msg_contents, msg->msg_len);
- start += msg->msg_len;
- }
-
- *sz = size;
-
- return buf;
-}
-
-/*
- * Read from "socket_fd" an entire command and return the result in "cmd". The
- * socket fd should be at the start of the command. Returns OK on success and
- * FAIL on failure.
- */
- static int
-socket_server_decode_cmd(ss_cmd_T *cmd, int socket_fd, int timeout)
-{
- int got_cmd_info = FALSE; // Consists of type, num, and len
- size_t total_r = 0;
- char_u *buf;
- char_u *cur;
-# ifdef ELAPSED_FUNC
- elapsed_T start_tv;
-# endif
-
- // We also poll the socket server listening file descriptor to handle
- // recursive remote calls between Vim instances, such as when one Vim
- // instance calls remote_expr for an expression that calls remote_expr to
- // itself again.
-# ifndef HAVE_SELECT
- struct pollfd pfd;
-
- pfd.fd = socket_fd;
- pfd.events = POLLIN;
-# else
- fd_set rfds;
- struct timeval tv;
-
- FD_ZERO(&rfds);
- FD_SET(socket_fd, &rfds);
-# endif
-
- buf = alloc(SS_CMD_INFO_SIZE);
-
- if (buf == NULL)
- return FAIL;
-
- // We may exit in the middle of the loop and free the messages, we don't
- // want to free an uninitialized pointer.
- memset(cmd, 0, sizeof(*cmd));
-
-# ifdef ELAPSED_FUNC
- ELAPSED_INIT(start_tv);
-# endif
-
- while (TRUE)
- {
- int ret;
- ssize_t r = 0;
-
-# ifndef HAVE_SELECT
- ret = poll(&pfd, 1, timeout);
-# else
- tv.tv_sec = 0;
- tv.tv_usec = 500 * 1000;
- ret = select(socket_fd + 1, &rfds, NULL, NULL, &tv);
-# endif
- if (ret < 0)
- goto fail;
- if (ret == 0)
- goto continue_loop;
-
- // Get cmd info first so we know the total size of all messages, and
- // can read it all in one go.
- if (!got_cmd_info)
- {
- r = read(socket_fd, buf + total_r, SS_CMD_INFO_SIZE - total_r);
-
- if ((size_t)r >= SS_CMD_INFO_SIZE - total_r)
- {
- char_u *tmp;
-
- got_cmd_info = TRUE;
-
- memcpy(&cmd->cmd_type, buf, sizeof(cmd->cmd_type));
- memcpy(&cmd->cmd_num, buf + sizeof(cmd->cmd_type),
- sizeof(cmd->cmd_num));
- memcpy(&cmd->cmd_len,
- buf + sizeof(cmd->cmd_type) + sizeof(cmd->cmd_num),
- sizeof(cmd->cmd_len));
-
- if (cmd->cmd_num > SOCKET_SERVER_MAX_MSG)
- // Too many messages to handle or invalid number
- goto fail;
-
- if (cmd->cmd_num == 0)
- // No messages to read
- goto exit;
-
- // Now that we now the total size of messages, we can realloc
- // the buffer to contain all data
- tmp = vim_realloc(buf, SS_CMD_INFO_SIZE + cmd->cmd_len);
-
- if (tmp == NULL)
- goto fail;
-
- buf = tmp;
- cur = buf + SS_CMD_INFO_SIZE;
-
- continue;
- }
- }
- else
- {
- // Read message data
- r = read(socket_fd, cur + total_r, cmd->cmd_len - total_r);
-
- if ((size_t)r >= cmd->cmd_len - total_r)
- break;
- }
-
- if (r == -1 || r == 0)
- goto fail;
-
- total_r += r;
-
-continue_loop:
-# ifdef ELAPSED_FUNC
- if (ELAPSED_FUNC(start_tv) >= timeout)
- goto fail;
-# endif
- }
-
- // Parse message data
- for (uint32_t i = 0; i < cmd->cmd_num; i++)
- {
- ss_msg_T *msg = cmd->cmd_msgs + i;
-
- memcpy(&msg->msg_type, cur, sizeof(msg->msg_type));
- cur += sizeof(msg->msg_type);
- memcpy(&msg->msg_len, cur, sizeof(msg->msg_len));
- cur += sizeof(msg->msg_len);
-
- msg->msg_contents = alloc(msg->msg_len + 1);
-
- if (msg->msg_contents == NULL)
- goto fail;
-
- memcpy(msg->msg_contents, cur, msg->msg_len);
- msg->msg_contents[msg->msg_len] = 0; // NULL terminate it
-
- // Move pointer to start of next message
- cur += msg->msg_len;
- }
-
-exit:
- vim_free(buf);
- return OK;
-fail:
- socket_server_free_cmd(cmd);
- vim_free(buf);
- return FAIL;
-}
-
-/*
- * Low level function that writes to a socket with a timeout in milliseconds.
- * Returns OK on success and FAIL on failure.
- */
- static int
-socket_server_write(int socket_fd, char_u *data, size_t sz, int timeout)
-{
- char_u *cur = data;
- size_t total_w = 0;
-# ifdef ELAPSED_FUNC
- elapsed_T start_tv;
-# endif
-# ifndef HAVE_SELECT
- struct pollfd pfd;
-
- pfd.fd = socket_fd;
- pfd.events = POLLOUT;
-# else
- fd_set wfds;
- struct timeval tv;
-
- FD_ZERO(&wfds);
- FD_SET(socket_fd, &wfds);
-# endif
-
-# ifdef ELAPSED_FUNC
- ELAPSED_INIT(start_tv);
-# endif
-
- while (total_w < sz)
- {
- int ret;
- ssize_t written;
-
- errno = 0;
-# ifndef HAVE_SELECT
- ret = poll(&pfd, 1, timeout);
-# else
- tv.tv_sec = 0;
- tv.tv_usec = 500 * 1000;
- ret = select(socket_fd + 1, NULL, &wfds, NULL, &tv);
-# endif
- if (ret < 0)
- return FAIL;
- else if (ret == 0)
- goto continue_loop;
-
- written = write(socket_fd, cur, sz - total_w);
-
- if (written == -1)
- return FAIL;
-
- total_w += written;
-
-continue_loop:
-# ifdef ELAPSED_FUNC
- if (ELAPSED_FUNC(start_tv) >= timeout)
- return FAIL;
-# endif
- }
-
- return OK;
-}
-
- static ss_reply_T *
-socket_server_get_reply(char_u *sender, int *index)
-{
- for (int i = 0; i < ss_replies.ga_len; i++)
- {
- ss_reply_T *reply = ((ss_reply_T *)ss_replies.ga_data) + i;
-
- if (STRCMP(reply->sender, sender) == 0)
- {
- if (index != NULL)
- *index = i;
- return reply;
- }
- }
- return NULL;
-}
-
-/*
- * Add reply to list of replies. Returns a pointer to the ss_reply_T that was
- * initialized or was found.
- */
- static ss_reply_T *
-socket_server_add_reply(char_u *sender)
-{
- ss_reply_T *reply;
-
- if (ss_replies.ga_growsize == 0)
- ga_init2(&ss_replies, sizeof(ss_reply_T), 1);
-
- reply = socket_server_get_reply(sender, NULL);
-
- if (reply == NULL && ga_grow(&ss_replies, 1) == OK)
- {
- reply = ((ss_reply_T *)ss_replies.ga_data) + ss_replies.ga_len++;
-
- reply->sender = vim_strsave(sender);
-
- if (reply->sender == NULL)
- return NULL;
-
- ga_init2(&reply->strings, sizeof(char_u *), 5);
- }
-
- return reply;
-}
-
- static void
-socket_server_remove_reply(char_u *sender)
-{
- int index;
- ss_reply_T *reply = socket_server_get_reply(sender, &index);
-
- if (reply != NULL)
- {
- ss_reply_T *arr = ss_replies.ga_data;
-
- // Free strings
- vim_free(reply->sender);
- ga_clear_strings(&reply->strings);
-
- // Move all elements after the removed reply forward by one
- for (int i = index + 1; i < ss_replies.ga_len; i++)
- arr[i - 1] = arr[i];
- ss_replies.ga_len--;
- }
-}
-
-/*
- * Execute the actions given by command. "fd" is the socket of the client that
- * sent the command.
- */
- static void
-socket_server_exec_cmd(ss_cmd_T *cmd, int fd)
-{
- char_u *str = NULL;
- char_u *enc = NULL;
- char_u *sender = NULL;
- uint32_t serial = 0;
- char_u rcode = 0;
- char_u *to_free;
- char_u *to_free2;
-
- for (uint32_t i = 0; i < cmd->cmd_num; i++)
- {
- ss_msg_T *msg = cmd->cmd_msgs + i;
-
- if (msg->msg_type == SS_MSG_TYPE_STRING)
- str = msg->msg_contents;
- if (msg->msg_type == SS_MSG_TYPE_ENCODING)
- enc = msg->msg_contents;
- if (msg->msg_type == SS_MSG_TYPE_SERIAL)
- memcpy(&serial, msg->msg_contents, sizeof(serial));
- if (msg->msg_type == SS_MSG_TYPE_CODE)
- memcpy(&rcode, msg->msg_contents, sizeof(rcode));
- else if (msg->msg_type == SS_MSG_TYPE_SENDER)
- {
- sender = msg->msg_contents;
-
- // Save in global
- vim_free(client_socket);
- client_socket = vim_strsave(sender);
- }
- }
-
-# ifdef FEAT_EVAL
- ch_log(NULL, "socket_server_exec_cmd(): encoding: %s, result: %s",
- enc == NULL ? (char_u *)"(null)" : enc,
- str == NULL ? (char_u *)"(null)" : str);
-# endif
-
- if (cmd->cmd_type == SS_CMD_TYPE_EXPR ||
- cmd->cmd_type == SS_CMD_TYPE_KEYSTROKES)
- {
- // Either an expression or keystrokes.
- if (socket_server_valid() && enc != NULL && str != NULL)
- {
- str = serverConvert(enc, str, &to_free);
-
- if (cmd->cmd_type == SS_CMD_TYPE_KEYSTROKES)
- server_to_input_buf(str);
- else if (sender != NULL)
- {
- // Evaluate expression and send reply containing result
- char_u *result;
- size_t sz;
- char_u *buf;
- char_u code;
-
- result = eval_client_expr_to_string(str);
-
- code = result == NULL ? -1 : 0;
-
- // Send reply
- ss_cmd_T rcmd;
-
- socket_server_init_cmd(&rcmd, SS_CMD_TYPE_REPLY);
-
- // Don't care about errors, server will just ignore command if
- // its missing something.
- if (result != NULL)
- socket_server_append_msg(&rcmd, SS_MSG_TYPE_STRING, result,
- STRLEN(result) + 1); // We add +1 in case "result"
- // is an empty string.
- else
- // An error occurred, return an error msg instead
- socket_server_append_msg(&rcmd, SS_MSG_TYPE_STRING,
- (char_u *)_(e_invalid_expression_received),
- STRLEN(e_invalid_expression_received));
-
- socket_server_append_msg(&rcmd, SS_MSG_TYPE_CODE,
- &code, sizeof(code));
-
- socket_server_append_msg(&rcmd, SS_MSG_TYPE_ENCODING, p_enc,
- STRLEN(p_enc));
-
- socket_server_append_msg(&rcmd, SS_MSG_TYPE_SERIAL,
- (char_u *)&serial, sizeof(serial));
-
- buf = socket_server_encode_cmd(&rcmd, &sz);
-
- if (buf != NULL)
- {
- int fd2 = socket_server_connect(sender, NULL, TRUE);
-
- if (fd2 >= 0)
- socket_server_write(fd2, buf, sz, 1000);
- vim_free(buf);
- close(fd2);
- }
-
- socket_server_free_cmd(&rcmd);
- vim_free(result);
- }
- vim_free(to_free);
- }
- return;
- }
- else if (cmd->cmd_type == SS_CMD_TYPE_REPLY)
- {
- // A reply from a previous command we set up, update the corresponding
- // pending command.
- if (serial > 0 && str != NULL)
- {
- for (ss_pending_cmd_T *pending = ss_pending_cmds; pending != NULL;
- pending = pending->next)
- {
- if (serial == pending->serial && pending->result == NULL)
- {
- str = serverConvert(enc, str, &to_free);
-
- pending->code = rcode;
-
- if (to_free == NULL)
- pending->result = vim_strsave(str);
- else
- pending->result = str;
- break;
- }
- }
- }
- return;
- }
- else if (cmd->cmd_type == SS_CMD_TYPE_NOTIFY)
- {
- // Notification, execute autocommands and save the reply for later use
- if (sender != NULL && str != NULL && enc != NULL)
- {
- ss_reply_T *reply;
-
- str = serverConvert(enc, str, &to_free);
- sender = serverConvert(enc, sender, &to_free2);
-
- reply = socket_server_add_reply(sender);
-
- if (reply != NULL)
- ga_copy_string(&reply->strings, str);
-
- apply_autocmds(EVENT_REMOTEREPLY, sender, str, TRUE, curbuf);
-
- vim_free(to_free);
- vim_free(to_free2);
- }
- return;
- }
- else if (cmd->cmd_type == SS_CMD_TYPE_ALIVE)
- {
- // Client wants to check if we are still responsive, send back a single
- // byte as a YES.
- char_u buf[1] = {1};
-# ifndef HAVE_SELECT
- struct pollfd pfd;
-
- pfd.fd = fd;
- pfd.events = POLLIN;
-# else
- fd_set rfds;
- struct timeval tv;
-
- FD_ZERO(&rfds);
- FD_SET(fd, &rfds);
-# endif
-
- if (write(fd, buf, 1) == -1)
- return;
-
- // Poll until client closes their end
-
-# ifndef HAVE_SELECT
- poll(&pfd, 1, 1000);
-# else
- tv.tv_sec = 1;
- tv.tv_usec = 0;
- select(fd + 1, &rfds, NULL, NULL, &tv);
-# endif
- return;
- }
-
- // Command type is invalid, do nothing
- return;
-}
-
-/*
- * Poll the socket server fd until a new connection is accepted. Returns 0 on
- * success, 1 if it timed out or if poll returned empty, and -1 on error.
- */
- static int
-socket_server_dispatch(int timeout)
-{
- int ret;
-# ifndef HAVE_SELECT
- struct pollfd pfd;
-
- pfd.fd = socket_server_fd;
- pfd.events = POLLIN;
-# else
- fd_set rfds;
- fd_set efds;
- struct timeval tv;
-
- FD_ZERO(&rfds);
- FD_ZERO(&efds);
- FD_SET(socket_server_fd, &rfds);
- FD_SET(socket_server_fd, &efds);
-# endif
-
-# ifndef HAVE_SELECT
- ret = poll(&pfd, 1, timeout);
-# else
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
- ret = select(socket_server_fd + 1, &rfds, NULL, &efds, &tv);
-# endif
-
- if (ret < 0)
- return -1;
- else if (ret == 0)
- return 1;
-
-# ifndef HAVE_SELECT
- if (pfd.revents & POLLIN)
-# else
- if (FD_ISSET(socket_server_fd, &rfds))
-# endif
- {
- socket_server_accept_client();
- return 0;
- }
-# ifndef HAVE_SELECT
- else if (pfd.revents & (POLLHUP | POLLERR))
-# else
- else if (FD_ISSET(socket_server_fd, &efds))
-# endif
- // Connection was closed
- return -1;
- else
- return 1;
-
- return -1;
-}
-
-/*
- * Check if socket "name" is responsive by sending an ALIVE command. This does
- * not require the socket server to be active.
- */
- static int
-socket_server_check_alive(char_u *name)
-{
- int socket_fd;
- int ret;
- size_t sz;
- char_u *final;
- char_u buf[1] = {0};
-# ifndef HAVE_SELECT
- struct pollfd pfd;
-# else
- fd_set rfds;
- struct timeval tv;
-# endif
-
- socket_fd = socket_server_connect(name, NULL, TRUE);
-
- if (socket_fd == -1)
- return FALSE;
-
-# ifndef HAVE_SELECT
- pfd.fd = socket_fd;
- pfd.events = POLLIN;
-# else
- FD_ZERO(&rfds);
- FD_SET(socket_fd, &rfds);
-# endif
-
- ss_cmd_T cmd;
-
- socket_server_init_cmd(&cmd, SS_CMD_TYPE_ALIVE);
-
- final = socket_server_encode_cmd(&cmd, &sz);
-
- if (final == NULL ||
- socket_server_write(socket_fd, final, sz, 1000) == FAIL)
- {
- vim_free(final);
- close(socket_fd);
- return FALSE;
- }
- vim_free(final);
-
- // Poll for response
-# ifndef HAVE_SELECT
- ret = poll(&pfd, 1, 1000);
-# else
- tv.tv_sec = 1;
- tv.tv_usec = 0;
- ret = select(socket_fd + 1, &rfds, NULL, NULL, &tv);
-# endif
-
- if (ret > 0)
- if (read(socket_fd, buf, 1) == -1)
- {
- close(socket_fd);
- return FALSE;
- }
-
- close(socket_fd);
- return buf[0] == 1;
-}
-
-/*
- * Get file descriptor of listening socket
- */
- int
-socket_server_get_fd(void)
-{
- return socket_server_fd;
-}
-
-
-/*
- * Check if socket name is a valid name
- */
- static int
-socket_server_name_is_valid(char_u *name)
-{
- if (STRLEN(name) == 0 || (name[0] != '/' && vim_strchr(name, '/') != NULL))
- {
- semsg(_(e_invalid_server_id_used_str), name);
- return FALSE;
- }
- return TRUE;
-}
-
-/*
- * Returns TRUE if there are clients queued in the listening socket waiting to
- * be accepted
- */
- int
-socket_server_waiting_accept(void)
-{
- int ret;
-# ifndef HAVE_SELECT
- struct pollfd pfd;
-
- pfd.fd = socket_server_fd;
- pfd.events = POLLIN;
-
- ret = poll(&pfd, 1, 0);
-
- if (ret > 0 && pfd.revents & POLLIN)
- return TRUE;
-# else
- fd_set rfds;
- struct timeval tv;
-
- if (socket_server_fd == -1)
- return FALSE;
-
- FD_ZERO(&rfds);
- FD_SET(socket_server_fd, &rfds);
-
- tv.tv_sec = 0;
- tv.tv_usec = 0;
- ret = select(socket_server_fd + 1, &rfds, NULL, NULL, &tv);
-
- if (ret > 0 && FD_ISSET(socket_server_fd, &rfds))
- return TRUE;
-# endif
-
- return FALSE;
-}
-
-#endif // FEAT_SOCKETSERVER
msgid "%d of %d edited"
msgstr ""
-msgid "Socket server not online:Send expression failed"
-msgstr ""
-
msgid "No display: Send expression failed.\n"
msgstr ""
msgid "-Y\t\t\tDo not connect to Wayland compositor"
msgstr ""
-msgid "--clientserver <socket|x11> Backend for clientserver communication"
+msgid ""
+"--clientserver <socket|x11|mswin> Backend for clientserver communication"
msgstr ""
msgid "--remote <files>\tEdit <files> in a Vim server if possible"
msgid "XSMP SmcOpenConnection failed: %s"
msgstr ""
-#, c-format
-msgid "Failed creating socket directory: %s"
-msgstr ""
-
msgid "At line"
msgstr ""
msgid " (not supported)"
msgstr ""
+#, c-format
+msgid "Failed creating socket directory: %s"
+msgstr ""
+
#, c-format
msgid "Warning: Cannot find word list \"%s_%s.spl\" or \"%s_ascii.spl\""
msgstr ""
msgid "E1562: Diff anchors cannot be used with hidden diff windows"
msgstr ""
-msgid "E1563: Socket path is too big"
-msgstr ""
-
-msgid "E1564: Socket name cannot have slashes in it without being a path"
+#, c-format
+msgid "E1564: Socket name '%s' cannot have slashes in it without being a path"
msgstr ""
msgid "E1565: Socket server is not online, call remote_startserver() first"
msgstr ""
#, c-format
-msgid "E1566: Failed connecting to socket %s: %s"
+msgid "E1566: Failed connecting to socket '%s'"
msgstr ""
-msgid "E1567: Cannot start socket server, socket path is unavailable"
+msgid ""
+"E1567: Socket server protocol version mismatch, check what Vim version you "
+"are using"
msgstr ""
#, c-format
# include "job.pro"
# include "channel.pro"
# endif
+# ifdef FEAT_SOCKETSERVER
+# include "socketserver.pro"
+# endif
# ifdef FEAT_EVAL
// Not generated automatically so that we can add an extra attribute.
int free_unused_channels_contents(int copyID, int mask);
void free_unused_channels(int copyID, int mask);
void channel_gui_register_all(void);
+channel_T *channel_open_unix(const char *path, void (*nb_close_cb)(void));
channel_T *channel_open(const char *hostname, int port, int waittime, void (*nb_close_cb)(void));
+int channel_parse_socketserver_address(char_u *address, int *port, char_u **unix_path, bool quiet);
channel_T *channel_listen_func(typval_T *argvars);
channel_T *channel_listen(int port_in, void (*nb_close_cb)(void));
-channel_T *channel_listen_unix(char *path, void (*nb_close_cb)(void));
+channel_T *channel_listen_unix(char *path, void (*nb_close_cb)(void), bool replace);
void ch_close_part(channel_T *channel, ch_part_T part);
void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options);
void channel_write_in(channel_T *channel);
void channel_buffer_free(buf_T *buf);
+void channel_write_input(channel_T *channel);
void channel_write_any_lines(void);
void channel_write_new_lines(buf_T *buf);
readq_T *channel_peek(channel_T *channel, ch_part_T part);
char_u *channel_get(channel_T *channel, ch_part_T part, int *outlen);
void channel_consume(channel_T *channel, ch_part_T part, int len);
int channel_collapse(channel_T *channel, ch_part_T part, int want_nl);
+int channel_fill(js_read_T *reader);
+int channel_parse_json(channel_T *channel, ch_part_T part, bool socketserver);
+void remove_json_node(jsonq_T *head, jsonq_T *node);
int channel_can_write_to(channel_T *channel);
int channel_is_open(channel_T *channel);
void channel_close(channel_T *channel, int invoke_close_cb);
void channel_clear(channel_T *channel);
void channel_free_all(void);
+void channel_check(channel_T *channel, ch_part_T part);
int channel_in_blocking_wait(void);
channel_T *get_channel_arg(typval_T *tv, int check_open, int reading, ch_part_T part);
void channel_handle_events(int only_keep_open);
void f_ch_setoptions(typval_T *argvars, typval_T *rettv);
void f_ch_status(typval_T *argvars, typval_T *rettv);
char_u *channel_to_string_buf(typval_T *varp, char_u *buf);
+channel_T *channel_find(int ch_id);
/* vim: set ft=c : */
void gui_mch_start_blink(void);
int gui_mch_early_init_check(int give_message);
int gui_mch_init_check(void);
-void gui_gtk_init_socket_server(void);
-void gui_gtk_uninit_socket_server(void);
void gui_mch_set_dark_theme(int dark);
void gui_mch_show_tabline(int showit);
int gui_mch_showing_tabline(void);
volatile sig_atomic_t *start_timeout(long msec);
void delete_timer(void);
int mch_create_anon_file(void);
-int socket_server_init(char_u *name);
-void socket_server_uninit(void);
-char_u *socket_server_list_sockets(void);
-int socket_server_accept_client(void);
-int socket_server_valid(void);
-int socket_server_send(char_u *name, char_u *str, char_u **result, char_u **receiver, int is_expr, int timeout, int silent);
-int socket_server_read_reply(char_u *client, char_u **str, int timeout);
-int socket_server_peek_reply(char_u *sender, char_u **str);
-int socket_server_send_reply(char_u *client, char_u *str);
-int socket_server_get_fd(void);
-int socket_server_waiting_accept(void);
/* vim: set ft=c : */
--- /dev/null
+/* socketserver.c */
+int socketserver_start(char_u *name, bool quiet);
+void socketserver_stop(void);
+char_u *socketserver_list(void);
+int set_ref_in_socketserver_channel(int copyID);
+void socketserver_parse_messages(void);
+int socketserver_send(char_u *name, char_u *str, char_u **result, bool is_expr, int timeout, bool silent, channel_T **ch);
+int socketserver_send_reply(char_u *client, char_u *str);
+int socketserver_read_reply(char_u *client, char_u **str, int timeout, bool remotewait);
+int socketserver_peek_reply(char_u *sender, char_u **str);
+/* vim: set ft=c : */
--- /dev/null
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ */
+
+/*
+ * socketserver.c: Socketserver clientserver functionality
+ */
+
+#include "vim.h"
+
+#ifdef FEAT_SOCKETSERVER
+
+// Always greater than one.
+# define PROTOCOL_VER 1
+
+/*
+ * Any message sent to a Vim server or received from a Vim server is a simple
+ * JSON object with the following fields:
+ * {
+ * "version": PROTOCOL_VER -- Protocol version, if different, then ignore.
+ *
+ * "type": "expr"|"keystrokes"|"notify"|"reply"|"ver" -- The type of message
+ *
+ * "str": string -- What to execute for the command or contents of result
+ *
+ * ?"code": number -- Return code for expression
+ *
+ * ?"sender": string -- Address of client that sent command if it is a server.
+ *
+ * ?"wait": bool -- Used by --remote-wait since the client is not a server.
+ * }
+ */
+
+/*
+ * Represents a reply from a server2client call. Each client that calls a
+ * server2client call to us has its own ss_reply_T. Each time a client sends
+ * data using server2client, Vim creates a ss_reply_T if it doesn't exist and
+ * adds the string to the array. When remote_read is called, the server id is
+ * used to find the specific ss_reply_T, and a single string is popped from the
+ * array.
+ */
+typedef struct
+{
+ char_u *sender; // Includes "type:" prefix if any
+ garray_T strings;
+} ss_reply_T;
+
+static channel_T *server_channel = NULL;
+static char_u *server_address = NULL; // Includes "type:" prefix if any
+static bool server_is_unix = false;
+static char_u *server_addr_cwd = NULL; // CWD when server was started, used
+ // to handle relative file paths.
+static garray_T server_replies;
+
+static channel_T *client_channels = NULL;
+
+# define FOR_ALL_CLIENTS(ch) \
+ for (ch = client_channels; ch != NULL; ch = ch->ch_ss_next)
+
+static void socketserver_cleanup(void);
+static char_u *socketserver_create_path(char_u *name, bool quiet);
+static char_u *socketserver_get_path(char_u *name, bool new, bool quiet, bool *fatal);
+static void socketserver_accept(channel_T *channel);
+static void socketserver_close(channel_T *channel);
+static ss_reply_T *socketserver_add_reply(char_u *sender);
+
+/*
+ * Start the socketserver using the given name. Returns OK on success and FAIL
+ * on failure.
+ */
+ int
+socketserver_start(char_u *name, bool quiet)
+{
+ char_u *address = NULL;
+ int port;
+ bool is_unix = false;
+ channel_T *channel;
+ char_u *buf;
+ char_u dirbuf[MAXPATHL];
+
+ if (server_channel != NULL)
+ return OK;
+
+ if (STRNICMP(name, "channel:", 8) == 0)
+ {
+ if (channel_parse_socketserver_address(name + 8, &port, &address, quiet)
+ == FAIL)
+ return FAIL;
+ if (address != NULL)
+ is_unix = true;
+ }
+ else
+ {
+ address = socketserver_create_path(name, quiet);
+ if (address == NULL)
+ return FAIL;
+ is_unix = true;
+ }
+
+ if (is_unix)
+ channel = channel_listen_unix((char *)address, NULL, false);
+ else
+ channel = channel_listen(port, NULL);
+
+ if (channel == NULL)
+ {
+ vim_free(address);
+ return FAIL;
+ }
+
+ channel->ch_socketserver = true;
+ channel->ch_ss_accept_cb = socketserver_accept;
+ channel->ch_ss_close_cb = socketserver_close;
+
+ server_channel = channel;
+ server_is_unix = is_unix;
+
+ VIM_CLEAR(serverName);
+
+ if (STRNICMP(name, "channel:", 8) == 0)
+ buf = vim_strsave(name);
+ else
+ {
+ buf = alloc(MAXPATHL + 1);
+ if (buf != NULL)
+ {
+ buf[0] = NUL;
+ mch_FullName(address, buf, MAXPATHL, false);
+ }
+ }
+ vim_free(address);
+
+ if (buf != NULL)
+ {
+ server_address = vim_strsave(buf);
+ serverName = buf;
+ set_vim_var_string(VV_SEND_SERVER, serverName, -1);
+ }
+
+ vim_free(server_addr_cwd);
+ if (mch_dirname(dirbuf, sizeof(dirbuf)) == OK)
+ server_addr_cwd = vim_strsave(dirbuf);
+ else
+ server_addr_cwd = NULL;
+
+ ga_init2(&server_replies, sizeof(ss_reply_T), 2);
+
+ ch_log(NULL, "socketserver: started server at %s", name);
+
+ return OK;
+}
+
+/*
+ * Stop running the socketserver if it is. Note that this does not stop Vim from
+ * becoming a client.
+ */
+ void
+socketserver_stop(void)
+{
+ if (server_channel == NULL)
+ return;
+
+ channel_close(server_channel, false);
+ channel_clear(server_channel);
+
+ socketserver_cleanup();
+
+ ch_log(NULL, "socketserver: shutting down server");
+}
+
+/*
+ * Cleanup server stuff. Do not close client channels, as those are not part of
+ * the actual server.
+ */
+ static void
+socketserver_cleanup(void)
+{
+# ifdef UNIX
+ if (server_is_unix && server_address != NULL)
+ {
+ char_u *path = server_address;
+ char_u dirbuf[MAXPATHL];
+
+ if (STRNICMP(path, "channel:unix:", 13) == 0)
+ path += 13;
+ else if (STRNICMP(path, "name:", 5) == 0)
+ path += 5;
+
+ if (*path == '/')
+ mch_remove(path);
+ // Go to the directory where the server was started. This is to handle
+ // when Vim changes directories and the servername is a relative path.
+ else if (server_addr_cwd != NULL
+ && mch_dirname(dirbuf, sizeof(dirbuf)) == OK)
+ {
+ if (mch_chdir((char *)server_addr_cwd) == 0)
+ {
+ mch_remove(path);
+ if (mch_chdir((char *)dirbuf) < 0)
+ emsg(_(e_cannot_go_back_to_previous_directory));
+ }
+ }
+ }
+# endif
+
+ server_channel = NULL;
+ VIM_CLEAR(server_address);
+ VIM_CLEAR(server_addr_cwd);
+
+ // Free all replies
+ for (int i = 0; i < server_replies.ga_len; i++)
+ {
+ ss_reply_T *reply = (ss_reply_T *)server_replies.ga_data + i;
+
+ vim_free(reply->sender);
+ ga_clear_strings(&reply->strings);
+ }
+ ga_clear(&server_replies);
+}
+
+/*
+ * List available sockets that can be connected to, only in common directories
+ * that Vim knows about. Vim instances with custom socket paths will not be
+ * detected. Returns a newline separated string on success and NULL on failure.
+ */
+ char_u *
+socketserver_list(void)
+{
+# ifdef MSWIN
+ // Only support addresses on Windows
+ return vim_strsave((char_u *)"");
+# else
+ garray_T str;
+ string_T buf;
+ string_T path;
+ DIR *dirp;
+ struct dirent *dp;
+ const char_u *known_dirs[] = {
+ mch_getenv("XDG_RUNTIME_DIR"),
+ mch_getenv("TMPDIR"),
+ (char_u *)"/tmp"
+ };
+
+ if ((buf.string = alloc(MAXPATHL)) == NULL)
+ return NULL;
+ if ((path.string = alloc(MAXPATHL)) == NULL)
+ {
+ vim_free(buf.string);
+ return NULL;
+ }
+ buf.length = 0;
+ path.length = 0;
+
+ ga_init2(&str, 1, 100);
+
+ for (size_t i = 0 ; i < ARRAY_LENGTH(known_dirs); i++)
+ {
+ const char_u *dir = known_dirs[i];
+
+ if (dir == NULL)
+ continue;
+
+ if (STRCMP(dir, "/tmp") == 0 ||
+ (known_dirs[1] != NULL && STRCMP(dir, known_dirs[1]) == 0))
+ path.length = vim_snprintf_safelen((char *)path.string, MAXPATHL,
+ "%s/vim-%lu", dir, (unsigned long int)getuid());
+ else
+ path.length = vim_snprintf_safelen((char *)path.string, MAXPATHL,
+ "%s/vim", dir);
+
+ dirp = opendir((char *)path.string);
+ if (dirp == NULL)
+ continue;
+
+ // Loop through directory
+ while ((dp = readdir(dirp)) != NULL)
+ {
+ if (STRCMP(dp->d_name, ".") == 0 || STRCMP(dp->d_name, "..") == 0)
+ continue;
+
+ buf.length = vim_snprintf_safelen((char *)buf.string, MAXPATHL,
+ "%s/%s", path.string, dp->d_name);
+
+ ga_concat_len(&str, (char_u *)dp->d_name,
+ buf.length - (path.length + 1));
+ ga_append(&str, '\n');
+ }
+
+ closedir(dirp);
+
+ break;
+ }
+
+ vim_free(path.string);
+ vim_free(buf.string);
+
+ ga_append(&str, NUL);
+
+ return str.ga_data;
+# endif
+}
+
+/*
+ * If "name" is a path (starts with a '/', './', or '../'), it is assumed to be
+ * the path to the desired socket. If the socket path is already taken, append
+ * an incrementing number to the path until we find a socket filename that can
+ * be used. Returns alloced string or NULL on failure.
+ */
+ static char_u *
+socketserver_create_path(char_u *name, bool quiet)
+{
+# ifdef MSWIN
+ // Only support channel addresses on Windows
+ if (STRNICMP(name, "channel:", 8) == 0 && STRLEN(name) > 8)
+ return vim_strsave(name + 8);
+ else
+ {
+ if (!quiet)
+ semsg(_(e_invalid_argument_str), name);
+ return NULL;
+ }
+# else
+ char_u *buf = NULL;
+ int buflen = STRLEN(name) + NUMBUFLEN;
+ char_u *path = NULL;
+ bool fatal = false;
+
+ if (STRNICMP(name, "channel:", 8) == 0 && STRLEN(name) > 8)
+ return vim_strsave(name + 8);
+ if (STRNICMP(name, "name:", 5) == 0)
+ name += 5;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ if (buf != NULL)
+ {
+ vim_snprintf((char *)buf, buflen, "%s%d", name, i);
+ path = socketserver_get_path(buf, true, quiet, &fatal);
+ }
+ else
+ path = socketserver_get_path(name, true, quiet, &fatal);
+
+ if (fatal)
+ break;
+
+ if (path == NULL)
+ {
+ if (buf == NULL)
+ {
+ buf = alloc(buflen);
+ if (buf == NULL)
+ {
+ semsg(_(e_out_of_memory_allocating_nr_bytes), buflen);
+ return NULL;
+ }
+ }
+ continue;
+ }
+ break;
+ }
+ vim_free(buf);
+
+ return path;
+# endif
+}
+
+/*
+ * If "name" is a pathless name such as "VIM", search known directories for the
+ * socket named "name", and return the alloc'ed path to it. If "name" starts
+ * with a '/', './' or '../', then a copy of "name" is returned.
+ *
+ * If "name" starts with "channel:", then return the address part
+ *
+ * If "new" is true, then return a path if the name does not exist in the known
+ * location.
+ *
+ * If "fatal" is not NULL, then it is set to true if error is fatal.
+ *
+ * Returns path on success and NULL on failure.
+ */
+ static char_u *
+socketserver_get_path(char_u *name, bool new UNUSED, bool quiet, bool *fatal)
+{
+# ifdef MSWIN
+ // Only support channel addresses on Windows
+ if (STRNICMP(name, "channel:", 8) == 0 && STRLEN(name) > 8)
+ return vim_strsave(name + 8);
+ else
+ {
+ if (!quiet)
+ semsg(_(e_invalid_argument_str), name);
+ if (fatal != NULL)
+ *fatal = true;
+ return NULL;
+ }
+# else
+ char_u *buf;
+ bool res = false;
+ channel_T *channel;
+ const char_u *known_dirs[] = {
+ mch_getenv("XDG_RUNTIME_DIR"),
+ mch_getenv("TMPDIR"),
+ (char_u *)"/tmp"
+ };
+
+ if (name == NULL)
+ {
+ if (fatal != NULL)
+ *fatal = true;
+ return NULL;
+ }
+
+ // Ignore if name is a path
+ if (name[0] == '/' || STRNCMP(name, "./", 2) == 0 ||
+ STRNCMP(name, "../", 3) == 0)
+ return vim_strsave(name);
+
+ if (STRNICMP(name, "channel:", 8) == 0 && STRLEN(name) > 8)
+ return vim_strsave(name + 8);
+
+ if (STRNICMP(name, "name:", 5) == 0)
+ name += 5;
+
+ if (vim_strchr(name, '/') != NULL)
+ {
+ if (!quiet)
+ semsg(_(e_socket_name_no_slashes), name);
+ if (fatal != NULL)
+ *fatal = true;
+ return NULL;
+ }
+
+ buf = alloc(MAXPATHL);
+
+ if (buf == NULL)
+ {
+ if (fatal != NULL)
+ *fatal = true;
+ semsg(_(e_out_of_memory_allocating_nr_bytes), MAXPATHL);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < ARRAY_LENGTH(known_dirs); i++)
+ {
+ const char_u *dir = known_dirs[i];
+ bool got = false;
+
+ if (dir == NULL)
+ continue;
+ else if (STRCMP(dir, "/tmp") == 0 ||
+ (known_dirs[1] != NULL && STRCMP(dir, known_dirs[1]) == 0))
+ {
+ // "/tmp" or $TMPDIR, must suffix dir with uid
+ vim_snprintf((char *)buf, MAXPATHL, "%s/vim-%lu", dir,
+ (unsigned long int)getuid());
+ if (vim_mkdir(buf, 0700) == -1 && errno != EEXIST)
+ continue;
+
+ vim_snprintf((char *)buf, MAXPATHL, "%s/vim-%lu/%s", dir,
+ (unsigned long int)getuid(), name);
+ }
+ else
+ {
+ vim_snprintf((char *)buf, MAXPATHL, "%s/vim", dir);
+ if (vim_mkdir(buf, 0700) == -1 && errno != EEXIST)
+ continue;
+
+ vim_snprintf((char *)buf, MAXPATHL, "%s/vim/%s", dir, name);
+ }
+
+ // If looking for a new socket path, and "buf" currently exists, check
+ // if it is a dead socket, if it is then remove it.
+ if (new)
+ {
+ emsg_silent++;
+ channel = channel_open_unix((char *)buf, NULL);
+ emsg_silent--;
+
+ if (channel != NULL)
+ {
+ channel_close(channel, false);
+ channel_clear(channel);
+ }
+ else
+ {
+ mch_remove(buf);
+ got = true;
+ }
+ }
+
+ res = true;
+ if (got || (!new && mch_access((char *)buf, F_OK) == 0))
+ {
+ if (server_address != NULL && STRCMP(buf, server_address) == 0)
+ // Can't connect to itself
+ break;
+ return buf;
+ }
+ break;
+ }
+
+ if (!quiet)
+ {
+ if (!res)
+ semsg(_("Failed creating socket directory: %s"), strerror(errno));
+ else
+ semsg(_(e_invalid_server_id_used_str), name);
+ }
+ if (!res && fatal != NULL)
+ *fatal = true;
+
+ vim_free(buf);
+ return NULL;
+# endif
+}
+
+/*
+ * Callback for when client channel is closed
+ */
+ static void
+socketserver_client_close(channel_T *channel)
+{
+ if (channel == client_channels)
+ client_channels = channel->ch_ss_next;
+ if (channel->ch_ss_prev != NULL)
+ channel->ch_ss_prev->ch_ss_next = channel->ch_ss_next;
+ if (channel->ch_ss_next != NULL)
+ channel->ch_ss_next->ch_ss_prev = channel->ch_ss_prev;
+
+ ch_log(NULL, "socketserver: client channel closed");
+}
+
+/*
+ * Callback for when server channel accepted new client.
+ */
+ static void
+socketserver_accept(channel_T *channel)
+{
+ channel->ch_socketserver = true;
+ channel->ch_ss_close_cb = socketserver_client_close;
+
+ channel->ch_ss_next = client_channels;
+ channel->ch_ss_prev = NULL;
+ if (client_channels != NULL)
+ client_channels->ch_ss_prev = channel;
+ client_channels = channel;
+
+ // We will read the command from the client later in the input loop.
+ ch_log(NULL, "socketserver: accepted new client");
+}
+
+/*
+ * Callback for when server channel is closed
+ */
+ static void
+socketserver_close(channel_T *channel UNUSED)
+{
+ socketserver_cleanup();
+
+ ch_log(NULL, "socketserver: server channel closed");
+}
+
+/*
+ * Mark references to socketserver channels
+ */
+ int
+set_ref_in_socketserver_channel(int copyID)
+{
+ bool abort = false;
+ channel_T *channel;
+ typval_T tv;
+
+ tv.v_type = VAR_CHANNEL;
+ tv.vval.v_channel = server_channel;
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
+
+ FOR_ALL_CLIENTS(channel)
+ {
+ if (abort)
+ break;
+
+ tv.v_type = VAR_CHANNEL;
+ tv.vval.v_channel = channel;
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
+ }
+ return abort;
+}
+
+/*
+ * Serialize "msg" into a string and send it over the given channel "ch". Also
+ * adds version header to "msg", if "ver" is true. Returns OK on success and
+ * FAIL on failure.
+ */
+ static int
+socketserver_send_message(channel_T *ch, dict_T *msg, char *func, bool ver)
+{
+ typval_T tv;
+ char_u *buf;
+ int ret;
+
+ if (ver)
+ dict_add_number(msg, "version", PROTOCOL_VER);
+
+ tv.v_type = VAR_DICT;
+ tv.vval.v_dict = msg;
+ buf = json_encode(&tv, JSON_NL);
+
+ if (buf != NULL)
+ {
+ emsg_silent++;
+ ret = channel_send(ch, PART_SOCK, buf, (int)STRLEN(buf), func);
+ emsg_silent--;
+ }
+ else
+ return FAIL;
+ vim_free(buf);
+ return ret;
+}
+
+/*
+ * Execute the JSON message represented by "dict".
+ */
+ static void
+socketserver_exec(channel_T *channel, dict_T *message)
+{
+ dictitem_T *di;
+ char_u *type;
+ char_u *str = NULL;
+ char_u *sender = NULL;
+ char_u idbuf[11 + NUMBUFLEN];
+
+ di = dict_find(message, (char_u *)"type", -1);
+ if (di == NULL || di->di_tv.v_type != VAR_STRING)
+ return;
+ else
+ type = di->di_tv.vval.v_string;
+
+ di = dict_find(message, (char_u *)"str", -1);
+ if (di != NULL && di->di_tv.v_type == VAR_STRING)
+ str = di->di_tv.vval.v_string;
+
+ di = dict_find(message, (char_u *)"sender", -1);
+ if (di != NULL && di->di_tv.v_type == VAR_STRING)
+ {
+ sender = di->di_tv.vval.v_string;
+
+ // Save in global
+ vim_free(client_socket);
+ client_socket = vim_strsave(sender);
+ }
+
+ di = dict_find(message, (char_u *)"wait", -1);
+ if (di != NULL && di->di_tv.v_type == VAR_BOOL && di->di_tv.vval.v_number)
+ {
+ // Client is not a server, but still wants a response later. Save the
+ // ID of the channel connection that we will use to send back a response,
+ vim_snprintf((char *)idbuf, sizeof(idbuf), "remotewait:%d",
+ channel->ch_id);
+
+ sender = idbuf;
+ vim_free(client_socket);
+ client_socket = vim_strsave(sender);
+ }
+
+ ch_log(NULL, "socketserver_exec(): result: %s",
+ str == NULL ? (char_u *)"(null)" : str);
+
+ if (STRCMP(type, "expr") == 0 && str != NULL)
+ {
+ // Evaluate expression and send back reply
+ dict_T *dict;
+ char_u *result;
+ int code;
+
+ dict = dict_alloc();
+ if (dict == NULL)
+ return;
+
+ result = eval_client_expr_to_string(str);
+ code = result == NULL ? -1 : 0;
+
+ dict_add_string(dict, "type", (char_u *)"reply");
+ if (result != NULL)
+ {
+ dict_add_string(dict, "str", result);
+ vim_free(result);
+ }
+ else
+ // Error occured, return error message
+ dict_add_string(dict, "str",
+ (char_u *)_(e_invalid_expression_received));
+
+ dict_add_number(dict, "code", code);
+
+ socketserver_send_message(channel, dict, "socketserver_exec", true);
+ dict_unref(dict);
+ }
+ else if (STRCMP(type, "keystrokes") == 0 && str != NULL)
+ {
+ // Execute keystrokes
+ server_to_input_buf(str);
+ }
+ else if (STRCMP(type, "notify") == 0)
+ {
+ // Notification, execute autocommands and save the reply for later use
+ if (sender != NULL && str != NULL)
+ {
+ ss_reply_T *reply;
+
+ reply = socketserver_add_reply(sender);
+
+ if (reply != NULL)
+ ga_copy_string(&reply->strings, str);
+
+ apply_autocmds(EVENT_REMOTEREPLY, sender, str, TRUE, curbuf);
+ }
+ }
+ else if (STRCMP(type, "ver") == 0)
+ {
+ // A message we sent previously had the wrong version. Emit an error
+ // message to alert the user.
+ emsg(_(e_socket_server_version_mismatch));
+ }
+ else
+ ch_error(NULL, "socketserver: unknown command type '%s'", type);
+}
+
+ static int
+socketserver_get_message(channel_T *channel, typval_T **tv)
+{
+ jsonq_T *head = &channel->ch_part[PART_SOCK].ch_json_head;
+ jsonq_T *json_msg = head->jq_next;
+ int ver;
+
+ if (json_msg == NULL)
+ {
+ // Check the readahead buffer
+ channel_parse_json(channel, PART_SOCK, true);
+ json_msg = head->jq_next;
+ }
+ if (json_msg == NULL)
+ return FAIL;
+ *tv = json_msg->jq_value;
+ remove_json_node(head, json_msg);
+
+ if ((*tv)->v_type != VAR_DICT)
+ {
+ ch_error(NULL, "socketserver: message is not a JSON object");
+ free_tv(*tv);
+ return FAIL;
+ }
+
+ ver = dict_get_number((*tv)->vval.v_dict, "version");
+
+ if (ver != 0 && ver != PROTOCOL_VER)
+ {
+ dict_T *dict;
+
+ ch_error(NULL, "socketserver: message has different version %d", ver);
+ free_tv(*tv);
+
+ // Send back a special error
+ dict = dict_alloc();
+ if (dict != NULL)
+ {
+ dict_add_string(dict, "type", (char_u *)"ver");
+ socketserver_send_message(channel, dict,
+ "socketserver_get_message", false);
+ dict_unref(dict);
+ }
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/*
+ * Parse any commands in the queue and execute them.
+ */
+ void
+socketserver_parse_messages(void)
+{
+ typval_T *tv;
+
+ for (channel_T *ch = client_channels; ch != NULL;)
+ {
+ // Make sure to save next channel in case "ch" is freed. Not sure if
+ // this can actually happen but be safe.
+ channel_T *next = ch->ch_ss_next;
+
+ // Get the JSON message if there is any from the queue for this channel.
+ if (socketserver_get_message(ch, &tv) == FAIL)
+ {
+ ch = next;
+ continue;
+ }
+
+ socketserver_exec(ch, tv->vval.v_dict);
+ free_tv(tv);
+ ch = next;
+ }
+}
+
+
+/*
+ * Poll until there is something to read on "channel". Also handle other
+ * socketserver channels in the meantime. If "channel" is NULL, then poll all
+ * channels once then exit. If "timeout" is -1, then wait forever unless
+ * interrupted.
+ *
+ * Return OK on success and FAIL on failure or timeout.
+ */
+ static int
+socketserver_wait(channel_T *channel, int timeout)
+{
+ while (true)
+ {
+ int ret;
+ channel_T *ch;
+# ifdef HAVE_SELECT
+ fd_set rfds;
+ struct timeval tv;
+ int maxfd = -1;
+
+ if (timeout != -1)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+
+ FD_ZERO(&rfds);
+
+ if (channel != NULL)
+ {
+ if (channel->CH_SOCK_FD == INVALID_FD)
+ // Shouldn't happen
+ return FAIL;
+
+ maxfd = channel->CH_SOCK_FD;
+ FD_SET(channel->CH_SOCK_FD, &rfds);
+ }
+ if (server_channel != NULL && server_channel->CH_SOCK_FD != INVALID_FD)
+ {
+ FD_SET(server_channel->CH_SOCK_FD, &rfds);
+ if (server_channel->CH_SOCK_FD > maxfd)
+ maxfd = server_channel->CH_SOCK_FD;
+ }
+
+ FOR_ALL_CLIENTS(ch)
+ {
+ if (ch->CH_SOCK_FD != INVALID_FD)
+ {
+ FD_SET(ch->CH_SOCK_FD, &rfds);
+ if (maxfd < (int)ch->CH_SOCK_FD)
+ maxfd = (int)ch->CH_SOCK_FD;
+ }
+ }
+
+ if (maxfd == -1)
+ return FAIL;
+
+ ret = select(maxfd + 1, &rfds, NULL, NULL, timeout == -1 ? NULL : &tv);
+
+# ifdef EINTR
+ if (ret == -1 && errno == EINTR)
+ {
+ if (got_int)
+ break;
+ continue;
+ }
+# endif
+
+ if (ret > 0)
+ {
+ if (server_channel != NULL
+ && server_channel->CH_SOCK_FD != INVALID_FD
+ && FD_ISSET(server_channel->CH_SOCK_FD, &rfds))
+ channel_check(server_channel, PART_SOCK);
+
+ FOR_ALL_CLIENTS(ch)
+ if (ch->CH_SOCK_FD != INVALID_FD
+ && FD_ISSET(ch->CH_SOCK_FD, &rfds))
+ channel_check(ch, PART_SOCK);
+
+ socketserver_parse_messages();
+
+ if (channel == NULL)
+ return OK;
+
+ if (channel->CH_SOCK_FD != INVALID_FD
+ && FD_ISSET(channel->CH_SOCK_FD, &rfds))
+ {
+ channel_check(channel, PART_SOCK);
+ return OK;
+ }
+ continue;
+ }
+# else
+ struct pollfd fds[MAX_OPEN_CHANNELS + 1];
+ int nfd = 0;
+ int channel_idx = -1;
+ int server_idx = -1;
+
+ if (channel != NULL)
+ {
+ if (channel->CH_SOCK_FD == INVALID_FD)
+ // Shouldn't happen
+ return FAIL;
+
+ channel_idx = nfd;
+ fds[nfd].fd = channel->CH_SOCK_FD;
+ fds[nfd++].events = POLLIN;
+ }
+ if (server_channel != NULL && server_channel->CH_SOCK_FD != INVALID_FD)
+ {
+ server_idx = nfd;
+ fds[nfd].fd = server_channel->CH_SOCK_FD;
+ fds[nfd++].events = POLLIN;
+ }
+
+ FOR_ALL_CLIENTS(ch)
+ if (ch->CH_SOCK_FD != INVALID_FD)
+ {
+ fds[nfd].fd = ch->CH_SOCK_FD;
+ fds[nfd].events = POLLIN;
+ ch->ch_part[PART_SOCK].ch_poll_idx = nfd;
+ nfd++;
+ }
+
+ ret = poll(fds, nfd, timeout);
+
+# ifdef EINTR
+ if (ret == -1 && errno == EINTR)
+ {
+ if (got_int)
+ break;
+ continue;
+ }
+# endif
+
+ if (ret > 0)
+ {
+ if (server_channel != NULL
+ && server_channel->CH_SOCK_FD != INVALID_FD
+ && fds[server_idx].revents & POLLIN)
+ channel_check(server_channel, PART_SOCK);
+
+ FOR_ALL_CLIENTS(ch)
+ if (ch->CH_SOCK_FD != INVALID_FD
+ && fds[ch->ch_part[PART_SOCK].ch_poll_idx]
+ .revents & POLLIN)
+ channel_check(ch, PART_SOCK);
+
+ socketserver_parse_messages();
+
+ if (channel == NULL)
+ return OK;
+
+ if (channel->CH_SOCK_FD != INVALID_FD
+ && fds[channel_idx].revents & POLLIN)
+ {
+ channel_check(channel, PART_SOCK);
+ return OK;
+ }
+ continue;
+ }
+# endif
+ break;
+ }
+ return FAIL;
+}
+
+/*
+ * Parse "name" and create or get the channel connection for it. If "wait" is
+ * not NULL, then if the client name is a channel ID, then it will be set to
+ * true. Returns NULL on
+ * failure.
+ */
+ static channel_T *
+socketserver_get_channel(char_u *name, bool quiet, bool *wait)
+{
+ char_u *address = NULL;
+ int port;
+ bool is_unix = false;
+ channel_T *channel;
+
+ if (STRNICMP(name, "channel:", 8) == 0)
+ {
+ if (channel_parse_socketserver_address(name + 8, &port, &address, true)
+ == FAIL)
+ return NULL;
+ if (address != NULL)
+ is_unix = true;
+ }
+ else if (STRNICMP(name, "remotewait:", 11) == 0)
+ {
+ // Channel ID name, find channel with that ID.
+ int id;
+
+ if (name[11] == NUL)
+ return NULL;
+ id = strtol((char *)name + 11, NULL, 10);
+ if (wait != NULL)
+ *wait = true;
+
+ return channel_find(id);
+ }
+ else
+ {
+ address = socketserver_get_path(name, false, quiet, NULL);
+ if (address == NULL)
+ return NULL;
+ is_unix = true;
+ }
+
+ if (is_unix)
+ channel = channel_open_unix((char *)address, NULL);
+ else
+ channel = channel_open("localhost", port, 1000, NULL);
+
+ if (channel == NULL && !quiet)
+ semsg(_(e_socket_server_failed_connecting), name);
+
+ vim_free(address);
+
+ return channel;
+}
+
+/*
+ * Send command to address "name". If "ch" is not NULL, it is set to the channel
+ * for the connection between us and the server, and the channel will not just
+ * be closed immediately, this is used for --remote-wait. Returns 0 for OK, -1
+ * on error.
+ */
+ int
+socketserver_send(
+ char_u *name,
+ char_u *str,
+ char_u **result,
+ bool is_expr,
+ int timeout,
+ bool silent,
+ channel_T **ch)
+{
+ int rcode = -1;
+ channel_T *channel;
+ dict_T *dict;
+ dictitem_T *di;
+ typval_T *resp_tv = NULL;
+
+ if (*name == NUL)
+ {
+ semsg(_(e_unable_to_send_to_str), name);
+ return -1;
+ }
+
+ // Execute locally if target is ourselves
+ if (serverName != NULL && STRICMP(name, serverName) == 0)
+ return sendToLocalVim(str, is_expr, result);
+
+ channel = socketserver_get_channel(name, silent, NULL);
+ if (channel == NULL)
+ return -1;
+
+ dict = dict_alloc();
+ if (dict == NULL)
+ goto exit;
+
+ dict_add_string(dict, "type", (char_u *)(is_expr ? "expr" : "keystrokes"));
+ dict_add_string(dict, "str", str);
+
+ // Tell server who we are so it can save our socket path internally for
+ // later use with server2client. Only do this if we are actually a server.
+ //
+ // If we are not a server, then --remote-wait will not work. To handle this
+ // case, we add "wait" to the JSON message set to true, so that the server
+ // will create an internal address for our connection to it. This is
+ // only used for --remote-wait, not exposed to user.
+ if (server_address != NULL)
+ dict_add_string(dict, "sender", server_address);
+ else if (ch != NULL)
+ dict_add_bool(dict, "wait", true);
+
+ if (socketserver_send_message(channel, dict, "socketserver_send", true)
+ == FAIL)
+ {
+ semsg(_(e_unable_to_send_to_str), name);
+ dict_unref(dict);
+ goto exit;
+ }
+ dict_unref(dict);
+
+ if (!is_expr)
+ {
+ // Exit, we aren't waiting for a response
+ rcode = 0;
+
+ if (ch != NULL)
+ {
+ *ch = channel;
+
+ channel->ch_ss_next = client_channels;
+ channel->ch_ss_prev = NULL;
+ channel->ch_ss_close_cb = socketserver_client_close;
+ client_channels = channel;
+
+ return rcode;
+ }
+ goto exit;
+ }
+
+ if (timeout == 0)
+ timeout = 1000;
+
+ // To handle recursive calls, we must handle any socketserver channels as
+ // well.
+ while (socketserver_wait(channel, timeout) == OK)
+ if (socketserver_get_message(channel, &resp_tv) == OK)
+ break;
+
+ if (resp_tv == NULL)
+ {
+ // Channel closed, don't make this an error, because this may be the
+ // result of the expression (e.g. --remote-expr 'execute("qa!")')
+ rcode = 0;
+ goto exit;
+ }
+
+ dict = resp_tv->vval.v_dict;
+
+ di = dict_find(dict, (char_u *)"type", -1);
+ if (di == NULL || di->di_tv.v_type != VAR_STRING ||
+ (STRCMP(di->di_tv.vval.v_string, "reply") != 0
+ && STRCMP(di->di_tv.vval.v_string, "ver") != 0))
+ {
+ ch_error(NULL, "socketserver: unknown reply type");
+ free_tv(resp_tv);
+ goto exit;
+ }
+
+ if (STRCMP(di->di_tv.vval.v_string, "ver") == 0)
+ {
+ emsg(_(e_socket_server_version_mismatch));
+ free_tv(resp_tv);
+ goto exit;
+ }
+
+ if (result != NULL)
+ {
+ di = dict_find(dict, (char_u *)"str", -1);
+ if (di != NULL && di->di_tv.v_type == VAR_STRING)
+ *result = vim_strsave(di->di_tv.vval.v_string);
+ else
+ {
+ free_tv(resp_tv);
+ goto exit;
+ }
+ }
+
+ di = dict_find(dict, (char_u *)"code", -1);
+ if (di != NULL && di->di_tv.v_type == VAR_NUMBER)
+ rcode = di->di_tv.vval.v_number;
+
+ free_tv(resp_tv);
+
+exit:
+ channel_close(channel, false);
+ channel_clear(channel);
+ return rcode;
+}
+
+ static ss_reply_T *
+socketserver_get_reply(char_u *sender, int *index)
+{
+ for (int i = 0; i < server_replies.ga_len; i++)
+ {
+ ss_reply_T *reply = ((ss_reply_T *)server_replies.ga_data) + i;
+
+ if (STRCMP(reply->sender, sender) == 0)
+ {
+ if (index != NULL)
+ *index = i;
+ return reply;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Add reply to list of replies. Returns a pointer to the ss_reply_T that was
+ * initialized or was found.
+ */
+ static ss_reply_T *
+socketserver_add_reply(char_u *sender)
+{
+ ss_reply_T *reply;
+
+ if (server_replies.ga_growsize == 0)
+ ga_init2(&server_replies, sizeof(ss_reply_T), 1);
+
+ reply = socketserver_get_reply(sender, NULL);
+
+ if (reply == NULL && ga_grow(&server_replies, 1) == OK)
+ {
+ reply = ((ss_reply_T *)server_replies.ga_data) + server_replies.ga_len++;
+
+ reply->sender = vim_strsave(sender);
+
+ if (reply->sender == NULL)
+ return NULL;
+
+ ga_init2(&reply->strings, sizeof(char_u *), 5);
+ }
+
+ return reply;
+}
+
+ static void
+socketserver_remove_reply(char_u *sender)
+{
+ int index;
+ ss_reply_T *reply = socketserver_get_reply(sender, &index);
+
+ if (reply != NULL)
+ {
+ ss_reply_T *arr = server_replies.ga_data;
+ int len = server_replies.ga_len;
+
+ // Free strings
+ vim_free(reply->sender);
+ ga_clear_strings(&reply->strings);
+
+ // Move all elements after the removed reply forward by one
+ if (len > 1)
+ mch_memmove(arr + index, arr + index + 1,
+ sizeof(ss_reply_T) * (len - index - 1));
+ server_replies.ga_len--;
+ }
+}
+
+/*
+ * Send a string to "client" as a reply (notification). Returns OK on success
+ * and FAIL on failure.
+ */
+ int
+socketserver_send_reply(char_u *client, char_u *str)
+{
+ dict_T *dict;
+ channel_T *channel;
+ int ret = OK;
+ bool wait = false;
+
+ if (*client == NUL)
+ {
+ semsg(_(e_invalid_server_id_used_str), client);
+ return FAIL;
+ }
+
+ if (server_channel == NULL || server_address == NULL)
+ {
+ emsg(_(e_socket_server_not_online));
+ return FAIL;
+ }
+
+ channel = socketserver_get_channel(client, false, &wait);
+ if (channel == NULL)
+ return FAIL;
+
+ dict = dict_alloc();
+ if (dict == NULL)
+ {
+ ret = FAIL;
+ goto exit;
+ }
+
+ dict_add_string(dict, "type", (char_u *)"notify");
+ dict_add_string(dict, "str", str);
+ if (server_address != NULL)
+ dict_add_string(dict, "sender", server_address);
+
+ ret = socketserver_send_message(channel, dict,
+ "socketserver_send_reply", true);
+ dict_unref(dict);
+
+exit:
+ // Don't want to close the channel if client is referenced by channel ID.
+ // This allows --remote-wait to work with multiple files.
+ if (!wait)
+ {
+ channel_close(channel, false);
+ channel_clear(channel);
+ }
+
+ return ret;
+}
+
+/*
+ * Wait for reply from "client" and place result in "str". Returns OK on success
+ * and FAIL on failure. Timeout is in milliseconds
+ */
+ int
+socketserver_read_reply(
+ char_u *client,
+ char_u **str,
+ int timeout,
+ bool remotewait)
+{
+ ss_reply_T *reply = NULL;
+ char_u *actual;
+
+ if (*client == NUL)
+ {
+ semsg(_(e_invalid_server_id_used_str), client);
+ return FAIL;
+ }
+
+ if (!remotewait && (server_channel == NULL || server_address == NULL))
+ {
+ emsg(_(e_socket_server_not_online));
+ return FAIL;
+ }
+
+ actual = socketserver_get_path(client, false, false, NULL);
+ if (actual == NULL)
+ return FAIL;
+
+ while (true)
+ {
+ reply = socketserver_get_reply(actual, NULL);
+ if (reply != NULL)
+ break;
+ if (socketserver_wait(NULL, timeout) == FAIL)
+ break;
+ }
+
+ if (reply == NULL || reply->strings.ga_len == 0)
+ {
+ vim_free(actual);
+ return FAIL;
+ }
+
+ // Consume the string
+ *str = ((char_u **)reply->strings.ga_data)[0];
+
+ if (reply->strings.ga_len > 1)
+ mch_memmove((char_u **)reply->strings.ga_data,
+ ((char_u **)reply->strings.ga_data) + 1,
+ sizeof(char_u *) * (reply->strings.ga_len - 1));
+ reply->strings.ga_len--;
+
+ if (reply->strings.ga_len < 1)
+ // Last string removed, remove the reply
+ socketserver_remove_reply(actual);
+
+ vim_free(actual);
+
+ return OK;
+}
+
+/*
+ * Check for any replies for "sender". Returns 1 if there is and places the
+ * reply in "str" without consuming it (note that a copy is not created).
+ * Returns 0 if otherwise and -1 on
+ * error.
+ */
+ int
+socketserver_peek_reply(char_u *sender, char_u **str)
+{
+ ss_reply_T *reply;
+ char_u *actual;
+
+ if (*sender == NUL)
+ {
+ semsg(_(e_invalid_server_id_used_str), sender);
+ return FAIL;
+ }
+
+ if (server_channel == NULL || server_address == NULL)
+ {
+ emsg(_(e_socket_server_not_online));
+ return FAIL;
+ }
+
+ actual = socketserver_get_path(sender, false, false, NULL);
+ if (actual == NULL)
+ return FAIL;
+
+ reply = socketserver_get_reply(actual, NULL);
+ vim_free(actual);
+
+ if (reply != NULL && reply->strings.ga_len > 0)
+ {
+ if (str != NULL)
+ *str = ((char_u **)reply->strings.ga_data)[0];
+ return 1;
+ }
+ return 0;
+}
+
+#endif // FEAT_SOCKETSERVER
void (*ch_nb_close_cb)(void);
// callback for Netbeans when channel is
// closed
+#ifdef FEAT_SOCKETSERVER
+ bool ch_socketserver; // If channel is used by socketserver
+ void (*ch_ss_close_cb)(channel_T *);
+ void (*ch_ss_accept_cb)(channel_T *);
+ channel_T *ch_ss_next;
+ channel_T *ch_ss_prev;
+#endif
#ifdef MSWIN
int ch_named_pipe; // using named pipe instead of pty
--- /dev/null
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1|5|6|7|:| |S|o|c|k|e|t| |s|e|r|v|e|r| |p|r|o|t|o|c|o|l| |v|e|r|s|i|o|n| |m|i|s|m|a|t|c|h|,| |c|h|e|c|k| |w|h|a|t| |V|i|m| |v|e|r|s|i|o|n| |y|o|u|
+|a|r|e| |u|s|i|n|g| +0#0000000#ffffff0@65
+|E+0#ffffff16#e000002|2|4|1|:| |U|n|a|b|l|e| |t|o| |s|e|n|d| |t|o| |c|h|a|n@1|e|l|:|2|0@2| +0#0000000#ffffff0@38
+@75
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35
" Tests for the +clientserver feature.
+source util/screendump.vim
CheckFeature job
if !has('clientserver')
source util/shared.vim
-" Unlike X11, we need the socket server running if we want to send commands to
-" a server via sockets.
-RunSocketServer
-
func Check_X11_Connection()
if has('x11')
CheckX11
" When using socket server, server id is not a number, but the path to the
" socket.
- if has('socketserver') && !has('X11')
- call assert_fails("let x = remote_read('vim/10')", ['E573:.*vim/10'])
- call assert_fails("call server2client('a/b/c', 'xyz')", ['E573:.*a/b/c'])
+ if (has('socketserver') && !has('X11') && !has('win32')) || index(v:argv, "socket") != -1
+ call assert_fails("let x = remote_read('vim/10')", ['E1564:'])
+ call assert_fails("call server2client('x/b/c', 'xyz')", ['E1564:'])
else
call assert_fails("let x = remote_read('vim10')",
\ has('unix') ? ['E573:.*vim10'] : 'E277:')
endtry
endfunc
-" Test if socket server and X11 backends can be chosen and work properly.
-func Test_client_server_x11_and_socket_server()
- CheckNotMSWindows
+" Test if socket server, X11, and mswin backends can be chosen and work properly.
+func Test_client_server_multiple_backends()
+ CheckFeature socketserver
+
+ let g:test_is_flaky = 1
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+ call Check_X11_Connection()
+
+ let types = [
+ \ ['socket', "channel:2000"],
+ \ ['x11', "TEST"],
+ \ ['mswin', "TEST"],
+ \ ]
+
+ for [type, expected] in types
+ if (type == 'x11' && (!has('x11') || !empty($WAYLAND_DISPLAY) || empty($DISPLAY))
+ \ || (type == 'mswin' && !has('win32')))
+ continue
+ endif
+ if has('win32') && has('gui_running')
+ " Windows gVim --remote-expr shows a dialog window, which blocks tests
+ " from running. Using --gui-dialog-file does not seem to work either.
+ continue
+ endif
+
+ let actual_cmd = cmd .. ' --clientserver ' .. type
+ let actual_cmd ..= ' --servername ' .. expected
+ let job = job_start(actual_cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+ call assert_match(expected, system(actual_cmd .. ' --remote-expr "v:servername"'))
+
+ " On Windows using --remote-expr causes E282, possibly due to some shell
+ " escaping quirk? When gtk gui is running, using system() seems to cause a
+ " deadlock when using the x11 backend only... don't use it for now...
+ if has('win32') || has('gui_running')
+ call job_stop(job, 'kill')
+ else
+ call system(actual_cmd .. " --remote-expr 'execute(\"qa!\")'")
+ endif
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+ endfor
+ endfunc
+
+" Test if custom paths work for socketserver
+func Test_client_server_socketserver_custom_path()
CheckFeature socketserver
- CheckFeature x11
+ CheckNotMSWindows
let g:test_is_flaky = 1
let cmd = GetVimCommand()
if cmd == ''
throw 'GetVimCommand() failed'
endif
- call Check_X11_Connection()
- let types = ['socket', 'x11']
+ let name = 'VIMTESTSOCKET2'
+
+ let paths = ['./' .. name, '../testdir/' .. name, getcwd(-1) .. '/' .. name]
- for type in types
- let name = 'VIMTEST_' .. toupper(type)
- let actual_cmd = cmd .. ' --clientserver ' .. type
- let actual_cmd .= ' --servername ' .. name
- let job = job_start(actual_cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+ for path in paths
+ let actual = cmd .. ' --clientserver socket --servername ' .. path
- call WaitForAssert({-> assert_equal("run", job_status(job))})
- call WaitForAssert({-> assert_match(name, system(cmd .. ' --clientserver ' .. type .. ' --serverlist'))})
+ let job = job_start(actual, {'stoponexit': 'kill', 'out_io': 'null'})
- call assert_match(name, system(actual_cmd .. ' --remote-expr "v:servername"'))
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+ call WaitForAssert({-> assert_equal(path, glob(path))})
- call system(actual_cmd .. " --remote-expr 'execute(\"qa!\")'")
+ call system(actual .. " --remote-expr 'execute(\"qa!\")'")
try
call WaitForAssert({-> assert_equal("dead", job_status(job))})
finally
endfor
endfunc
-" Test if socket server works in the GUI
-func Test_client_server_socket_server_gui()
- CheckNotMSWindows
+" Test if "channel:" prefix works correctly to use channel address for
+" socketserver.
+func Test_client_server_socketserver_address()
CheckFeature socketserver
- CheckFeature gui_gtk
let g:test_is_flaky = 1
let cmd = GetVimCommand()
if cmd == ''
throw 'GetVimCommand() failed'
endif
- call Check_X11_Connection()
- let name = 'VIMTESTSOCKET'
- let cmd .= ' --clientserver socket'
- let cmd .= ' --servername ' .. name
+ let actual = cmd .. ' --clientserver socket --servername channel:2000'
- let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+ let job = job_start(actual, {'stoponexit': 'kill', 'out_io': 'null'})
call WaitForAssert({-> assert_equal("run", job_status(job))})
- call WaitForAssert({-> assert_match(name, system(cmd .. ' --serverlist'))})
-
- call system(cmd .. " --remote-expr 'execute(\"gui\")'")
- call assert_match('1', system(cmd .. " --remote-expr 'has(\"gui_running\")'"))
- call assert_match(name, system(cmd .. ' --remote-expr "v:servername"'))
+ if !has('win32') || !has('gui_running')
+ " Does not work with gVim on Windows because it shows an OK dialog box which
+ " blocks tests from running
+ call assert_match('channel:2000', system(actual .. ' --remote-expr "v:servername"'))
+ endif
- call system(cmd .. " --remote-expr 'execute(\"qa!\")'")
+ if has('win32')
+ call job_stop(job, 'kill')
+ else
+ call system(actual .. " --remote-expr 'execute(\"qa!\")'")
+ endif
try
call WaitForAssert({-> assert_equal("dead", job_status(job))})
finally
endtry
endfunc
-" Test if custom paths work for socketserver
-func Test_client_socket_server_custom_path()
+" Test if --remote-wait works properly with multiple files
+func Test_client_server_multiple_remote_wait()
+ CheckRunVimInTerminal
+
+ call Check_X11_Connection()
+
+ let buf = RunVimInTerminal('--servername TEST', {'rows': 8})
+ call TermWait(buf)
+
+ call writefile(["1"], 'XRemoteOne', 'D')
+ call writefile(["2"], 'XRemoteTwo', 'D')
+
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+
+ let actual = cmd .. ' -n --servername TEST --remote-wait ./XRemoteOne ./XRemoteTwo'
+ let job = job_start(actual, {'stoponexit': 'kill', 'out_io': 'null'})
+
+ sleep 500m " Wait for server to receive request
+ call assert_equal("run", job_status(job))
+
+ call term_sendkeys(buf, "\<Esc>:next\<CR>")
+ call TermWait(buf)
+ call assert_equal("run", job_status(job))
+
+ call term_sendkeys(buf, "\<Esc>:q!\<CR>") " Don't use qa! because we only want to quit one file
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+endfunc
+
+" Check if socket is removed even if Vim changes directory
+func Test_client_server_socketserver_chdir()
+ CheckFeature socketserver
+ CheckRunVimInTerminal
CheckNotMSWindows
+
+ let buf = RunVimInTerminal('--clientserver socket --servername ./TEST',
+ \ {'rows': 8})
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "\<Esc>:cd ../\<CR>")
+ call StopVimInTerminal(buf)
+
+ call assert_equal("", glob("./TEST"))
+endfunc
+
+func DummyServerCallback(ch, addr)
+ let msg = json_encode(#{type: "ver"}) .. "\n"
+ call ch_sendraw(a:ch, msg)
+endfunc
+
+" Test if commands with different version are ignored and handled properly.
+func Test_client_server_socketserver_version_mismatch()
CheckFeature socketserver
- CheckNotFeature x11
+ CheckRunVimInTerminal
+ CheckScreendump
- let g:test_is_flaky = 1
let cmd = GetVimCommand()
if cmd == ''
throw 'GetVimCommand() failed'
endif
- let name = 'VIMTESTSOCKET2'
+ let buf = RunVimInTerminal('--clientserver socket --servername
+ \ channel:2000', {'rows': 8})
+ call TermWait(buf)
- let paths = ['./' .. name, '../testdir/' .. name, getcwd(-1) .. '/' .. name]
+ let ch = ch_open('localhost:2000', #{mode: 'nl'})
+ let msg = json_encode(#{
+ \ type: "expr",
+ \ str: "v:servername",
+ \ version: 999999999
+ \ }) .. "\n"
- for path in paths
- let actual = cmd .. ' --servername ' .. path
+ call ch_sendraw(ch, msg)
+ let resp = ch_readraw(ch)
- let job = job_start(actual, {'stoponexit': 'kill', 'out_io': 'null'})
+ call assert_equal(#{type: "ver"}, json_decode(resp))
- call WaitForAssert({-> assert_equal("run", job_status(job))})
- call WaitForAssert({-> assert_equal(path, glob(path))})
+ call StopVimInTerminal(buf)
- call system(actual .. " --remote-expr 'execute(\"qa!\")'")
- try
- call WaitForAssert({-> assert_equal("dead", job_status(job))})
- finally
- if job_status(job) != 'dead'
- call assert_report('Server did not exit')
- call job_stop(job, 'kill')
- endif
- endtry
- endfor
+ let ch = ch_listen("2000", #{
+ \ mode: 'nl',
+ \ callback: function('DummyServerCallback')
+ \ })
+
+ let buf = RunVimInTerminal('--clientserver socket --servername channel:3000', {'rows': 8})
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "\<Esc>:echo remote_expr('channel:2000', 'v:servername', '', 1)\<CR>")
+ call TermWait(buf)
+
+ call VerifyScreenDump(buf, 'Test_clientserver_1', #{wait: 3000})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test if invalid messages do not crash Vim socketserver.
+func Test_clientserver_socketserver_invalid_msg()
+ CheckFeature socketserver
+ CheckRunVimInTerminal
+
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+
+ let buf = RunVimInTerminal('--clientserver socket --servername
+ \ channel:3000', {'rows': 8})
+ call TermWait(buf)
+
+ let ch = ch_open('localhost:3000', #{mode: 'nl'})
+
+ call ch_sendraw(ch, "wjdaljdsjalsj\n")
+ call ch_sendraw(ch, "{\"type\": \"unknown\"}\n")
+
+ call assert_match("channel:3000", system(cmd .. " --clientserver socket --servername channel:3000 --remote-expr 'v:servername'"))
+ call assert_equal("running", term_getstatus(buf))
+
+ call StopVimInTerminal(buf)
endfunc
" Uncomment this line to get a debugging log
let buf = RunVimInTerminal('--servername XVIMTEST', {'rows': 8})
call TermWait(buf)
- " For some reason when the socket server is being used, the terminal Vim never
- " receives the `:w! XVimRemoteTest.txt` command from term_sendkeys.
- if has('socketserver') && !has('X11')
- if match(serverlist(), "XVIMTEST") == -1
- call StopVimInTerminal(buf)
- throw s:skip
- endif
-
- let s:remote = 1
- return
- endif
-
let cmd = GetVimCommandCleanTerm() .. '--serverlist'
call term_sendkeys(buf, ":r! " .. cmd .. "\<CR>")
call TermWait(buf)
" open XTEST.txt, if wildignore setting is not ignored, the server
" will continue with the Xdummy.log file
let buf2 = RunVimInTerminal('--servername XVIMTEST --remote-silent XTEST.txt', {'rows': 5, 'wait_for_ruler': 0})
+
+ " It may take a while for the command to be sent to XVIMTEST and be executed.
+ " Do a blocking --remote-expr so that is assured the command was executed.
+ botright new
+ let buf3 = RunVimInTerminal('--servername XVIMTEST --remote-expr "v:servername"', {'rows': 5, 'wait_for_ruler': 0})
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(buf3))})
+ exe buf3 .. 'bw!'
+
" job should be no-longer running, so we can just close it
exe buf2 .. 'bw!'
call term_sendkeys(buf, ":sil :3,$d\<CR>")
exe 'delcommand ' .. cmd
endfor
delcommand MissingFeature
- delcommand RunSocketServer
command! DoCmd1 :
command! DoCmd2 :
" Test using builtin functions in the Vim9 script language.
source util/screendump.vim
-source util/socketserver.vim
import './util/vim9.vim' as v9
" Test for passing too many or too few arguments to builtin functions
def Test_remote_expr()
CheckFeature clientserver
- TrySocketServer
+ CheckNotMSWindows
- if !g:socketserver_only
+ if has("x11")
CheckEnv DISPLAY
endif
+
v9.CheckSourceDefAndScriptFailure(['remote_expr(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['remote_expr("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
v9.CheckSourceDefAndScriptFailure(['remote_expr("a", "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
def Test_remote_peek()
CheckFeature clientserver
- TrySocketServer
- if !g:socketserver_only
+ CheckNotMSWindows
+
+ if has("x11")
CheckEnv DISPLAY
endif
+
v9.CheckSourceDefAndScriptFailure(['remote_peek(0z10)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1174: String required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['remote_peek("a5b6c7", [1])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
v9.CheckSourceDefExecAndScriptFailure(['remote_peek("")'], 'E573: Invalid server id used')
def Test_remote_read()
CheckFeature clientserver
- CheckEnv DISPLAY
+ CheckNotMSWindows
+
+ if has("x11")
+ CheckEnv DISPLAY
+ endif
+
v9.CheckSourceDefAndScriptFailure(['remote_read(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['remote_read("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
v9.CheckSourceDefExecAndScriptFailure(['remote_read("")'], 'E573: Invalid server id used')
def Test_remote_send()
CheckFeature clientserver
- TrySocketServer
- if !g:socketserver_only
+ CheckNotMSWindows
+
+ if has("x11")
CheckEnv DISPLAY
endif
+
v9.CheckSourceDefAndScriptFailure(['remote_send(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['remote_send("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
v9.CheckSourceDefAndScriptFailure(['remote_send("a", "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
def Test_remote_startserver()
CheckFeature clientserver
- TrySocketServer
- if !g:socketserver_only
+ CheckNotMSWindows
+
+ if has("x11")
CheckEnv DISPLAY
endif
+
v9.CheckSourceDefAndScriptFailure(['remote_startserver({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<any>', 'E1174: String required for argument 1'])
enddef
endif
endfunc
-command RunSocketServer call RunSocketServer()
-func RunSocketServer()
- if has("socketserver") && v:servername == ""
- try
- call remote_startserver('VIMSOCKETSERVERTEST')
- catch " not possible to start a remote server
- throw 'Skipped: Cannot start remote server'
- endtry
- endif
-endfunc
-
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: shiftwidth=2 sts=2 expandtab
+++ /dev/null
-" Check if only the socketserver backend is available for clientserver (only on
-" Unix), and set g:socketserver_only to v:true along with starting the
-" socketserver.
-command TrySocketServer call TrySocketServer()
-func TrySocketServer()
- if has("socketserver") && !has("x11")
- let g:socketserver_only = v:true
-
- if v:servername == ""
- call remote_startserver('VIMSOCKETSERVERTEST')
- endif
- else
- let g:socketserver_only = v:false
- endif
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
# endif
if ((resize_func != NULL && resize_func(TRUE))
-# if defined(FEAT_CLIENTSERVER) && defined(UNIX)
- || (
-# ifdef FEAT_X11
- (clientserver_method == CLIENTSERVER_METHOD_X11 &&
- server_waiting())
-# endif
-# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
- ||
-# endif
-# ifdef FEAT_SOCKETSERVER
- (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
- socket_server_waiting_accept())
-# endif
- )
+# if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11)
+ ||
+ (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ server_waiting())
# endif
# ifdef MESSAGE_QUEUE
|| interrupted
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 512,
/**/
511,
/**/