aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/os')
-rw-r--r--src/nvim/os/env.c62
-rw-r--r--src/nvim/os/fileio.c318
-rw-r--r--src/nvim/os/fileio.h72
-rw-r--r--src/nvim/os/fs.c511
-rw-r--r--src/nvim/os/input.c79
-rw-r--r--src/nvim/os/os_defs.h8
-rw-r--r--src/nvim/os/pty_process.h9
-rw-r--r--src/nvim/os/pty_process_unix.c245
-rw-r--r--src/nvim/os/pty_process_unix.h31
-rw-r--r--src/nvim/os/pty_process_win.h28
-rw-r--r--src/nvim/os/shell.c284
-rw-r--r--src/nvim/os/signal.c25
-rw-r--r--src/nvim/os/stdpaths.c54
-rw-r--r--src/nvim/os/time.c42
-rw-r--r--src/nvim/os/users.c1
15 files changed, 1584 insertions, 185 deletions
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index edc430410c..747a34d8ce 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -12,7 +12,6 @@
#include "nvim/os/os.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/misc2.h"
#include "nvim/path.h"
#include "nvim/strings.h"
#include "nvim/eval.h"
@@ -113,11 +112,11 @@ int64_t os_get_pid(void)
#endif
}
-/// Get the hostname of the machine running Neovim.
+/// Gets the hostname of the current machine.
///
-/// @param hostname Buffer to store the hostname.
-/// @param len Length of `hostname`.
-void os_get_hostname(char *hostname, size_t len)
+/// @param hostname Buffer to store the hostname.
+/// @param size Size of `hostname`.
+void os_get_hostname(char *hostname, size_t size)
{
#ifdef HAVE_SYS_UTSNAME_H
struct utsname vutsname;
@@ -125,8 +124,7 @@ void os_get_hostname(char *hostname, size_t len)
if (uname(&vutsname) < 0) {
*hostname = '\0';
} else {
- strncpy(hostname, vutsname.nodename, len - 1);
- hostname[len - 1] = '\0';
+ xstrlcpy(hostname, vutsname.nodename, size);
}
#else
// TODO(unknown): Implement this for windows.
@@ -227,25 +225,24 @@ char_u *expand_env_save_opt(char_u *src, bool one)
/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
/// If anything fails no expansion is done and dst equals src.
-/// @param src Input string e.g. "$HOME/vim.hlp"
-/// @param dst Where to put the result
-/// @param dstlen Maximum length of the result
+///
+/// @param src Input string e.g. "$HOME/vim.hlp"
+/// @param dst[out] Where to put the result
+/// @param dstlen Maximum length of the result
void expand_env(char_u *src, char_u *dst, int dstlen)
{
expand_env_esc(src, dst, dstlen, false, false, NULL);
}
/// Expand environment variable with path name and escaping.
-/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
-/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
-/// If anything fails no expansion is done and dst equals src.
-/// prefix recognize the start of a new name, for '~' expansion.
-/// @param srcp Input string e.g. "$HOME/vim.hlp"
-/// @param dst Where to put the result
-/// @param dstlen Maximum length of the result
-/// @param esc Should we escape spaces in expanded variables?
-/// @param one Should we expand more than one '~'?
-/// @param prefix Common prefix for paths, can be NULL
+/// @see expand_env
+///
+/// @param srcp Input string e.g. "$HOME/vim.hlp"
+/// @param dst[out] Where to put the result
+/// @param dstlen Maximum length of the result
+/// @param esc Escape spaces in expanded variables
+/// @param one `srcp` is a single filename
+/// @param prefix Start again after this (can be NULL)
void expand_env_esc(char_u *restrict srcp,
char_u *restrict dst,
int dstlen,
@@ -561,8 +558,8 @@ const void *vim_colon_env_iter_rev(const char *const val,
/// Vim's version of getenv().
/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to
/// override the vim runtime directory at runtime. Also does ACP to 'enc'
-/// conversion for Win32. Results must be freed by the calling function.
-/// @param name Name of environment variable to expand
+/// conversion for Win32. Result must be freed by the caller.
+/// @param name Environment variable to expand
char *vim_getenv(const char *name)
{
const char *kos_env_path = os_getenv(name);
@@ -599,6 +596,27 @@ char *vim_getenv(const char *name)
if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) {
vim_path = (char *)p_hf;
}
+
+#ifdef WIN32
+ // Find runtime path relative to the nvim binary i.e. ../share/runtime
+ if (vim_path == NULL) {
+ char exe_name[MAXPATHL];
+ size_t exe_name_len = MAXPATHL;
+ if (os_exepath(exe_name, &exe_name_len) == 0) {
+ char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "nvim.exe"
+ path_end = (char *)path_tail((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "bin/"
+ if (append_path(
+ exe_name,
+ "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
+ MAXPATHL) == OK) {
+ vim_path = exe_name;
+ }
+ }
+ }
+#endif
+
if (vim_path != NULL) {
// remove the file name
char *vim_path_end = (char *)path_tail((char_u *)vim_path);
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
new file mode 100644
index 0000000000..cf5bfd60ae
--- /dev/null
+++ b/src/nvim/os/fileio.c
@@ -0,0 +1,318 @@
+/// @file fileio.c
+///
+/// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with
+/// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite
+/// replacement.
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <fcntl.h>
+
+#include "auto/config.h"
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
+
+#include <uv.h>
+
+#include "nvim/os/fileio.h"
+#include "nvim/memory.h"
+#include "nvim/os/os.h"
+#include "nvim/globals.h"
+#include "nvim/rbuffer.h"
+#include "nvim/macros.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/fileio.c.generated.h"
+#endif
+
+/// Open file
+///
+/// @param[out] ret_fp Address where information needed for reading from or
+/// writing to a file is saved
+/// @param[in] fname File name to open.
+/// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and
+/// writing to the file at once is not supported, so either
+/// FILE_WRITE_ONLY or FILE_READ_ONLY is required.
+/// @param[in] mode Permissions for the newly created file (ignored if flags
+/// does not have FILE_CREATE\*).
+///
+/// @return Error code (@see os_strerror()) or 0.
+int file_open(FileDescriptor *const ret_fp, const char *const fname,
+ const int flags, const int mode)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int os_open_flags = 0;
+ int fd;
+ TriState wr = kNone;
+#define FLAG(flags, flag, fcntl_flags, wrval, cond) \
+ do { \
+ if (flags & flag) { \
+ os_open_flags |= fcntl_flags; \
+ assert(cond); \
+ if (wrval != kNone) { \
+ wr = wrval; \
+ } \
+ } \
+ } while (0)
+ FLAG(flags, kFileWriteOnly, O_WRONLY, kTrue, true);
+ FLAG(flags, kFileCreateOnly, O_CREAT|O_EXCL|O_WRONLY, kTrue, true);
+ FLAG(flags, kFileCreate, O_CREAT|O_WRONLY, kTrue, !(flags & kFileCreateOnly));
+ FLAG(flags, kFileTruncate, O_TRUNC|O_WRONLY, kTrue,
+ !(flags & kFileCreateOnly));
+ FLAG(flags, kFileReadOnly, O_RDONLY, kFalse, wr != kTrue);
+#ifdef O_NOFOLLOW
+ FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true);
+#endif
+#undef FLAG
+
+ fd = os_open(fname, os_open_flags, mode);
+
+ if (fd < 0) {
+ return fd;
+ }
+
+ ret_fp->wr = (wr == kTrue);
+ ret_fp->fd = fd;
+ ret_fp->eof = false;
+ ret_fp->rv = rbuffer_new(kRWBufferSize);
+ ret_fp->_error = 0;
+ if (ret_fp->wr) {
+ ret_fp->rv->data = ret_fp;
+ ret_fp->rv->full_cb = (rbuffer_callback)&file_rb_write_full_cb;
+ }
+ return 0;
+}
+
+/// Like file_open(), but allocate and return ret_fp
+///
+/// @param[out] error Error code, @see os_strerror(). Is set to zero on
+/// success.
+/// @param[in] fname File name to open.
+/// @param[in] flags Flags, @see FileOpenFlags.
+/// @param[in] mode Permissions for the newly created file (ignored if flags
+/// does not have FILE_CREATE\*).
+///
+/// @return [allocated] Opened file or NULL in case of error.
+FileDescriptor *file_open_new(int *const error, const char *const fname,
+ const int flags, const int mode)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FileDescriptor *const fp = xmalloc(sizeof(*fp));
+ if ((*error = file_open(fp, fname, flags, mode)) != 0) {
+ xfree(fp);
+ return NULL;
+ }
+ return fp;
+}
+
+/// Close file and free its buffer
+///
+/// @param[in,out] fp File to close.
+///
+/// @return 0 or error code.
+int file_close(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
+{
+ const int error = file_fsync(fp);
+ const int error2 = os_close(fp->fd);
+ rbuffer_free(fp->rv);
+ if (error2 != 0) {
+ return error2;
+ }
+ return error;
+}
+
+/// Close and free file obtained using file_open_new()
+///
+/// @param[in,out] fp File to close.
+///
+/// @return 0 or error code.
+int file_free(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
+{
+ const int ret = file_close(fp);
+ xfree(fp);
+ return ret;
+}
+
+/// Flush file modifications to disk
+///
+/// @param[in,out] fp File to work with.
+///
+/// @return 0 or error code.
+int file_fsync(FileDescriptor *const fp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (!fp->wr) {
+ return 0;
+ }
+ file_rb_write_full_cb(fp->rv, fp);
+ if (fp->_error != 0) {
+ const int error = fp->_error;
+ fp->_error = 0;
+ return error;
+ }
+ return os_fsync(fp->fd);
+}
+
+/// Buffer used for writing
+///
+/// Like IObuff, but allows file_\* callers not to care about spoiling it.
+static char writebuf[kRWBufferSize];
+
+/// Function run when RBuffer is full when writing to a file
+///
+/// Actually does writing to the file, may also be invoked directly.
+///
+/// @param[in,out] rv RBuffer instance used.
+/// @param[in,out] fp File to work with.
+static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(fp->wr);
+ assert(rv->data == (void *)fp);
+ if (rbuffer_size(rv) == 0) {
+ return;
+ }
+ const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize);
+ const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes);
+ if (wres != (ptrdiff_t)read_bytes) {
+ if (wres >= 0) {
+ fp->_error = UV_EIO;
+ } else {
+ fp->_error = (int)wres;
+ }
+ }
+}
+
+/// Read from file
+///
+/// @param[in,out] fp File to work with.
+/// @param[out] ret_buf Buffer to read to. Must not be NULL.
+/// @param[in] size Number of bytes to read. Buffer must have at least ret_buf
+/// bytes.
+///
+/// @return error_code (< 0) or number of bytes read.
+ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ assert(!fp->wr);
+ char *buf = ret_buf;
+ size_t read_remaining = size;
+ RBuffer *const rv = fp->rv;
+ while (read_remaining) {
+ const size_t rv_size = rbuffer_size(rv);
+ if (rv_size > 0) {
+ const size_t rsize = rbuffer_read(rv, buf, MIN(rv_size, read_remaining));
+ buf += rsize;
+ read_remaining -= rsize;
+ }
+ if (fp->eof) {
+ break;
+ }
+ if (read_remaining) {
+ assert(rbuffer_size(rv) == 0);
+ rbuffer_reset(rv);
+#ifdef HAVE_READV
+ // If there is readv() syscall, then take an opportunity to populate
+ // both target buffer and RBuffer at once, …
+ size_t write_count;
+ struct iovec iov[] = {
+ { .iov_base = buf, .iov_len = read_remaining },
+ { .iov_base = rbuffer_write_ptr(rv, &write_count),
+ .iov_len = kRWBufferSize },
+ };
+ assert(write_count == kRWBufferSize);
+ const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov,
+ ARRAY_SIZE(iov));
+ if (r_ret > 0) {
+ if (r_ret > (ptrdiff_t)read_remaining) {
+ rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining));
+ read_remaining = 0;
+ } else {
+ buf += (size_t)r_ret;
+ read_remaining -= (size_t)r_ret;
+ }
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+#else
+ if (read_remaining >= kRWBufferSize) {
+ // …otherwise leave RBuffer empty and populate only target buffer,
+ // because filtering information through rbuffer will be more syscalls.
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining);
+ if (r_ret >= 0) {
+ read_remaining -= (size_t)r_ret;
+ return (ptrdiff_t)(size - read_remaining);
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+ } else {
+ size_t write_count;
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,
+ rbuffer_write_ptr(rv, &write_count),
+ kRWBufferSize);
+ assert(write_count == kRWBufferSize);
+ if (r_ret > 0) {
+ rbuffer_produced(rv, (size_t)r_ret);
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+ }
+#endif
+ }
+ }
+ return (ptrdiff_t)(size - read_remaining);
+}
+
+/// Write to a file
+///
+/// @param[in] fd File descriptor to write to.
+/// @param[in] buf Data to write. May be NULL if size is zero.
+/// @param[in] size Amount of bytes to write.
+///
+/// @return Number of bytes written or libuv error code (< 0).
+ptrdiff_t file_write(FileDescriptor *const fp, const char *const buf,
+ const size_t size)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ assert(fp->wr);
+ const size_t written = rbuffer_write(fp->rv, buf, size);
+ if (fp->_error != 0) {
+ const int error = fp->_error;
+ fp->_error = 0;
+ return error;
+ } else if (written != size) {
+ return UV_EIO;
+ }
+ return (ptrdiff_t)written;
+}
+
+/// Buffer used for skipping. Its contents is undefined and should never be
+/// used.
+static char skipbuf[kRWBufferSize];
+
+/// Skip some bytes
+///
+/// This is like `fseek(fp, size, SEEK_CUR)`, but actual implementation simply
+/// reads to a buffer and discards the result.
+ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(!fp->wr);
+ size_t read_bytes = 0;
+ do {
+ const ptrdiff_t new_read_bytes = file_read(
+ fp, skipbuf, MIN(size - read_bytes, sizeof(skipbuf)));
+ if (new_read_bytes < 0) {
+ return new_read_bytes;
+ } else if (new_read_bytes == 0) {
+ break;
+ }
+ read_bytes += (size_t)new_read_bytes;
+ } while (read_bytes < size && !file_eof(fp));
+
+ return (ptrdiff_t)read_bytes;
+}
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
new file mode 100644
index 0000000000..2cffd5c851
--- /dev/null
+++ b/src/nvim/os/fileio.h
@@ -0,0 +1,72 @@
+#ifndef NVIM_OS_FILEIO_H
+#define NVIM_OS_FILEIO_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/func_attr.h"
+#include "nvim/rbuffer.h"
+
+/// Structure used to read from/write to file
+typedef struct {
+ int fd; ///< File descriptor.
+ int _error; ///< Error code for use with RBuffer callbacks or zero.
+ RBuffer *rv; ///< Read or write buffer.
+ bool wr; ///< True if file is in write mode.
+ bool eof; ///< True if end of file was encountered.
+} FileDescriptor;
+
+/// file_open() flags
+typedef enum {
+ kFileReadOnly = 1, ///< Open file read-only. Default.
+ kFileCreate = 2, ///< Create file if it does not exist yet.
+ ///< Implies kFileWriteOnly.
+ kFileWriteOnly = 4, ///< Open file for writing only.
+ ///< Cannot be used with kFileReadOnly.
+ kFileNoSymlink = 8, ///< Do not allow symbolic links.
+ kFileCreateOnly = 16, ///< Only create the file, failing if it already
+ ///< exists. Implies kFileWriteOnly. Cannot be used
+ ///< with kFileCreate.
+ kFileTruncate = 32, ///< Truncate the file if it exists.
+ ///< Implies kFileWriteOnly. Cannot be used with
+ ///< kFileCreateOnly.
+} FileOpenFlags;
+
+static inline bool file_eof(const FileDescriptor *const fp)
+ REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
+
+/// Check whether end of file was encountered
+///
+/// @param[in] fp File to check.
+///
+/// @return true if it was, false if it was not or read operation was never
+/// performed.
+static inline bool file_eof(const FileDescriptor *const fp)
+{
+ return fp->eof && rbuffer_size(fp->rv) == 0;
+}
+
+static inline int file_fd(const FileDescriptor *const fp)
+ REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
+
+/// Return the file descriptor associated with the FileDescriptor structure
+///
+/// @param[in] fp File to check.
+///
+/// @return File descriptor.
+static inline int file_fd(const FileDescriptor *const fp)
+{
+ return fp->fd;
+}
+
+enum {
+ /// Read or write buffer size
+ ///
+ /// Currently equal to (IOSIZE - 1), but they do not need to be connected.
+ kRWBufferSize = 1024
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/fileio.h.generated.h"
+#endif
+#endif // NVIM_OS_FILEIO_H
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 143a7160b0..30e08ac129 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1,23 +1,51 @@
// fs.c -- filesystem access
#include <stdbool.h>
-
+#include <stddef.h>
#include <assert.h>
+#include <limits.h>
#include <fcntl.h>
+#include <errno.h>
+
+#include "auto/config.h"
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
+
+#include <uv.h>
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/ascii.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/assert.h"
#include "nvim/misc1.h"
-#include "nvim/misc2.h"
#include "nvim/path.h"
#include "nvim/strings.h"
+#ifdef WIN32
+#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+#endif
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fs.c.generated.h"
#endif
+#define RUN_UV_FS_FUNC(ret, func, ...) \
+ do { \
+ bool did_try_to_free = false; \
+uv_call_start: {} \
+ uv_fs_t req; \
+ ret = func(&fs_loop, &req, __VA_ARGS__); \
+ uv_fs_req_cleanup(&req); \
+ if (ret == UV_ENOMEM && !did_try_to_free) { \
+ try_to_free_memory(); \
+ did_try_to_free = true; \
+ goto uv_call_start; \
+ } \
+ } while (0)
+
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
static uv_loop_t fs_loop;
@@ -135,8 +163,12 @@ int os_nodetype(const char *name)
// saves us the hassle.
int nodetype = NODE_WRITABLE;
- int fd = os_open(name, O_RDONLY, 0);
- switch(uv_guess_handle(fd)) {
+ int fd = os_open(name, O_RDONLY
+#ifdef O_NONBLOCK
+ | O_NONBLOCK
+#endif
+ , 0);
+ switch (uv_guess_handle(fd)) {
case UV_TTY: // FILE_TYPE_CHAR
nodetype = NODE_WRITABLE;
break;
@@ -160,6 +192,18 @@ int os_nodetype(const char *name)
return nodetype;
}
+/// Gets the absolute path of the currently running executable.
+///
+/// @param[out] buffer Returns the path string.
+/// @param[in] size Size of `buffer`.
+///
+/// @return `0` on success, or libuv error code on failure.
+int os_exepath(char *buffer, size_t *size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return uv_exepath(buffer, size);
+}
+
/// Checks if the given path represents an executable file.
///
/// @param[in] name Name of the executable.
@@ -325,13 +369,168 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
int os_open(const char* path, int flags, int mode)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t open_req;
- int r = uv_fs_open(&fs_loop, &open_req, path, flags, mode, NULL);
- uv_fs_req_cleanup(&open_req);
- // r is the same as open_req.result (except for OOM: then only r is set).
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_open, path, flags, mode, NULL);
return r;
}
+/// Close a file
+///
+/// @return 0 or libuv error code on failure.
+int os_close(const int fd)
+{
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_close, fd, NULL);
+ return r;
+}
+
+/// Read from a file
+///
+/// Handles EINTR and ENOMEM, but not other errors.
+///
+/// @param[in] fd File descriptor to read from.
+/// @param[out] ret_eof Is set to true if EOF was encountered, otherwise set
+/// to false. Initial value is ignored.
+/// @param[out] ret_buf Buffer to write to. May be NULL if size is zero.
+/// @param[in] size Amount of bytes to read.
+///
+/// @return Number of bytes read or libuv error code (< 0).
+ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,
+ const size_t size)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ *ret_eof = false;
+ if (ret_buf == NULL) {
+ assert(size == 0);
+ return 0;
+ }
+ size_t read_bytes = 0;
+ bool did_try_to_free = false;
+ while (read_bytes != size) {
+ const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes,
+ size - read_bytes);
+ if (cur_read_bytes > 0) {
+ read_bytes += (size_t)cur_read_bytes;
+ assert(read_bytes <= size);
+ }
+ if (cur_read_bytes < 0) {
+ const int error = os_translate_sys_error(errno);
+ errno = 0;
+ if (error == UV_EINTR || error == UV_EAGAIN) {
+ continue;
+ } else if (error == UV_ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ continue;
+ } else {
+ return (ptrdiff_t)error;
+ }
+ }
+ if (cur_read_bytes == 0) {
+ *ret_eof = true;
+ break;
+ }
+ }
+ return (ptrdiff_t)read_bytes;
+}
+
+#ifdef HAVE_READV
+/// Read from a file to multiple buffers at once
+///
+/// Wrapper for readv().
+///
+/// @param[in] fd File descriptor to read from.
+/// @param[out] ret_eof Is set to true if EOF was encountered, otherwise set
+/// to false. Initial value is ignored.
+/// @param[out] iov Description of buffers to write to. Note: this description
+/// may change, it is incorrect to use data it points to after
+/// os_readv().
+/// @param[in] iov_size Number of buffers in iov.
+ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *ret_eof = false;
+ size_t read_bytes = 0;
+ bool did_try_to_free = false;
+ size_t toread = 0;
+ for (size_t i = 0; i < iov_size; i++) {
+ // Overflow, trying to read too much data
+ assert(toread <= SIZE_MAX - iov[i].iov_len);
+ toread += iov[i].iov_len;
+ }
+ while (read_bytes < toread && iov_size && !*ret_eof) {
+ ptrdiff_t cur_read_bytes = readv(fd, iov, (int)iov_size);
+ if (toread && cur_read_bytes == 0) {
+ *ret_eof = true;
+ }
+ if (cur_read_bytes > 0) {
+ read_bytes += (size_t)cur_read_bytes;
+ while (iov_size && cur_read_bytes) {
+ if (cur_read_bytes < (ptrdiff_t)iov->iov_len) {
+ iov->iov_len -= (size_t)cur_read_bytes;
+ iov->iov_base = (char *)iov->iov_base + cur_read_bytes;
+ cur_read_bytes = 0;
+ } else {
+ cur_read_bytes -= (ptrdiff_t)iov->iov_len;
+ iov_size--;
+ iov++;
+ }
+ }
+ } else if (cur_read_bytes < 0) {
+ const int error = os_translate_sys_error(errno);
+ errno = 0;
+ if (error == UV_EINTR || error == UV_EAGAIN) {
+ continue;
+ } else if (error == UV_ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ continue;
+ } else {
+ return (ptrdiff_t)error;
+ }
+ }
+ }
+ return (ptrdiff_t)read_bytes;
+}
+#endif // HAVE_READV
+
+/// Write to a file
+///
+/// @param[in] fd File descriptor to write to.
+/// @param[in] buf Data to write. May be NULL if size is zero.
+/// @param[in] size Amount of bytes to write.
+///
+/// @return Number of bytes written or libuv error code (< 0).
+ptrdiff_t os_write(const int fd, const char *const buf, const size_t size)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (buf == NULL) {
+ assert(size == 0);
+ return 0;
+ }
+ size_t written_bytes = 0;
+ while (written_bytes != size) {
+ const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes,
+ size - written_bytes);
+ if (cur_written_bytes > 0) {
+ written_bytes += (size_t)cur_written_bytes;
+ }
+ if (cur_written_bytes < 0) {
+ const int error = os_translate_sys_error(errno);
+ errno = 0;
+ if (error == UV_EINTR || error == UV_EAGAIN) {
+ continue;
+ } else {
+ return error;
+ }
+ }
+ if (cur_written_bytes == 0) {
+ return UV_UNKNOWN;
+ }
+ }
+ return (ptrdiff_t)written_bytes;
+}
+
/// Flushes file modifications to disk.
///
/// @param fd the file descriptor of the file to flush to disk.
@@ -339,9 +538,8 @@ int os_open(const char* path, int flags, int mode)
/// @return `0` on success, a libuv error code on failure.
int os_fsync(int fd)
{
- uv_fs_t fsync_req;
- int r = uv_fs_fsync(&fs_loop, &fsync_req, fd, NULL);
- uv_fs_req_cleanup(&fsync_req);
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL);
return r;
}
@@ -379,16 +577,9 @@ int32_t os_getperm(const char_u *name)
int os_setperm(const char_u *name, int perm)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_chmod(&fs_loop, &request,
- (const char*)name, perm, NULL);
- uv_fs_req_cleanup(&request);
-
- if (result == kLibuvSuccess) {
- return OK;
- }
-
- return FAIL;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_chmod, (const char *)name, perm, NULL);
+ return (r == kLibuvSuccess ? OK : FAIL);
}
/// Changes the ownership of the file referred to by the open file descriptor.
@@ -397,24 +588,21 @@ int os_setperm(const char_u *name, int perm)
///
/// @note If the `owner` or `group` is specified as `-1`, then that ID is not
/// changed.
-int os_fchown(int file_descriptor, uv_uid_t owner, uv_gid_t group)
- FUNC_ATTR_NONNULL_ALL
+int os_fchown(int fd, uv_uid_t owner, uv_gid_t group)
{
- uv_fs_t request;
- int result = uv_fs_fchown(&fs_loop, &request, file_descriptor,
- owner, group, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_fchown, fd, owner, group, NULL);
+ return r;
}
-/// Check if a file exists.
+/// Check if a path exists.
///
-/// @return `true` if `name` exists.
-bool os_file_exists(const char_u *name)
+/// @return `true` if `path` exists
+bool os_path_exists(const char_u *path)
FUNC_ATTR_NONNULL_ALL
{
uv_stat_t statbuf;
- return os_stat((char *)name, &statbuf) == kLibuvSuccess;
+ return os_stat((char *)path, &statbuf) == kLibuvSuccess;
}
/// Check if a file is readable.
@@ -423,9 +611,8 @@ bool os_file_exists(const char_u *name)
bool os_file_is_readable(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- uv_fs_t req;
- int r = uv_fs_access(&fs_loop, &req, name, R_OK, NULL);
- uv_fs_req_cleanup(&req);
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_access, name, R_OK, NULL);
return (r == 0);
}
@@ -437,9 +624,8 @@ bool os_file_is_readable(const char *name)
int os_file_is_writable(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- uv_fs_t req;
- int r = uv_fs_access(&fs_loop, &req, name, W_OK, NULL);
- uv_fs_req_cleanup(&req);
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_access, name, W_OK, NULL);
if (r == 0) {
return os_isdir((char_u *)name) ? 2 : 1;
}
@@ -452,16 +638,10 @@ int os_file_is_writable(const char *name)
int os_rename(const char_u *path, const char_u *new_path)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_rename(&fs_loop, &request,
- (const char *)path, (const char *)new_path, NULL);
- uv_fs_req_cleanup(&request);
-
- if (result == kLibuvSuccess) {
- return OK;
- }
-
- return FAIL;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_rename, (const char *)path, (const char *)new_path,
+ NULL);
+ return (r == kLibuvSuccess ? OK : FAIL);
}
/// Make a directory.
@@ -470,10 +650,9 @@ int os_rename(const char_u *path, const char_u *new_path)
int os_mkdir(const char *path, int32_t mode)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_mkdir(&fs_loop, &request, path, mode, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_mkdir, path, mode, NULL);
+ return r;
}
/// Make a directory, with higher levels when needed
@@ -555,10 +734,9 @@ int os_mkdtemp(const char *template, char *path)
int os_rmdir(const char *path)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_rmdir(&fs_loop, &request, path, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_rmdir, path, NULL);
+ return r;
}
/// Opens a directory.
@@ -600,10 +778,9 @@ void os_closedir(Directory *dir)
int os_remove(const char *path)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_unlink(&fs_loop, &request, path, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_unlink, path, NULL);
+ return r;
}
/// Get the file information for a given path
@@ -742,10 +919,222 @@ bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2)
/// @param file_info Pointer to a `FileInfo`
/// @return `true` if the `FileID` and the `FileInfo` represent te same file.
bool os_fileid_equal_fileinfo(const FileID *file_id,
- const FileInfo *file_info)
+ const FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
return file_id->inode == file_info->stat.st_ino
&& file_id->device_id == file_info->stat.st_dev;
}
+#ifdef WIN32
+# include <shlobj.h>
+
+/// When "fname" is the name of a shortcut (*.lnk) resolve the file it points
+/// to and return that name in allocated memory.
+/// Otherwise NULL is returned.
+char_u * os_resolve_shortcut(char_u *fname)
+{
+ HRESULT hr;
+ IPersistFile *ppf = NULL;
+ OLECHAR wsz[MAX_PATH];
+ char_u *rfname = NULL;
+ int len;
+ IShellLinkW *pslw = NULL;
+ WIN32_FIND_DATAW ffdw;
+
+ // Check if the file name ends in ".lnk". Avoid calling CoCreateInstance(),
+ // it's quite slow.
+ if (fname == NULL) {
+ return rfname;
+ }
+ len = (int)STRLEN(fname);
+ if (len <= 4 || STRNICMP(fname + len - 4, ".lnk", 4) != 0) {
+ return rfname;
+ }
+
+ CoInitialize(NULL);
+
+ // create a link manager object and request its interface
+ hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+ &IID_IShellLinkW, (void **)&pslw);
+ if (hr == S_OK) {
+ WCHAR *p;
+ int conversion_result = utf8_to_utf16((char *)fname, &p);
+ if (conversion_result != 0) {
+ EMSG2("utf8_to_utf16 failed: %s", uv_strerror(conversion_result));
+ }
+
+ if (p != NULL) {
+ // Get a pointer to the IPersistFile interface.
+ hr = pslw->lpVtbl->QueryInterface(
+ pslw, &IID_IPersistFile, (void **)&ppf);
+ if (hr != S_OK) {
+ goto shortcut_errorw;
+ }
+
+ // "load" the name and resolve the link
+ hr = ppf->lpVtbl->Load(ppf, p, STGM_READ);
+ if (hr != S_OK) {
+ goto shortcut_errorw;
+ }
+
+# if 0 // This makes Vim wait a long time if the target does not exist.
+ hr = pslw->lpVtbl->Resolve(pslw, NULL, SLR_NO_UI);
+ if (hr != S_OK) {
+ goto shortcut_errorw;
+ }
+# endif
+
+ // Get the path to the link target.
+ ZeroMemory(wsz, MAX_PATH * sizeof(WCHAR));
+ hr = pslw->lpVtbl->GetPath(pslw, wsz, MAX_PATH, &ffdw, 0);
+ if (hr == S_OK && wsz[0] != NUL) {
+ int conversion_result = utf16_to_utf8(wsz, &rfname);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %s", uv_strerror(conversion_result));
+ }
+ }
+
+shortcut_errorw:
+ xfree(p);
+ goto shortcut_end;
+ }
+ }
+
+shortcut_end:
+ // Release all interface pointers (both belong to the same object)
+ if (ppf != NULL) {
+ ppf->lpVtbl->Release(ppf);
+ }
+ if (pslw != NULL) {
+ pslw->lpVtbl->Release(pslw);
+ }
+
+ CoUninitialize();
+ return rfname;
+}
+
+#endif
+
+int os_translate_sys_error(int sys_errno) {
+#ifdef HAVE_UV_TRANSLATE_SYS_ERROR
+ return uv_translate_sys_error(sys_errno);
+#elif WIN32
+ // TODO(equalsraf): libuv does not yet expose uv_translate_sys_error()
+ // in its public API, include a version here until it can be used.
+ // See https://github.com/libuv/libuv/issues/79
+# ifndef ERROR_SYMLINK_NOT_SUPPORTED
+# define ERROR_SYMLINK_NOT_SUPPORTED 1464
+# endif
+
+ if (sys_errno <= 0) {
+ return sys_errno; // If < 0 then it's already a libuv error
+ }
+
+ switch (sys_errno) {
+ case ERROR_NOACCESS: return UV_EACCES;
+ case WSAEACCES: return UV_EACCES;
+ case ERROR_ADDRESS_ALREADY_ASSOCIATED: return UV_EADDRINUSE;
+ case WSAEADDRINUSE: return UV_EADDRINUSE;
+ case WSAEADDRNOTAVAIL: return UV_EADDRNOTAVAIL;
+ case WSAEAFNOSUPPORT: return UV_EAFNOSUPPORT;
+ case WSAEWOULDBLOCK: return UV_EAGAIN;
+ case WSAEALREADY: return UV_EALREADY;
+ case ERROR_INVALID_FLAGS: return UV_EBADF;
+ case ERROR_INVALID_HANDLE: return UV_EBADF;
+ case ERROR_LOCK_VIOLATION: return UV_EBUSY;
+ case ERROR_PIPE_BUSY: return UV_EBUSY;
+ case ERROR_SHARING_VIOLATION: return UV_EBUSY;
+ case ERROR_OPERATION_ABORTED: return UV_ECANCELED;
+ case WSAEINTR: return UV_ECANCELED;
+ case ERROR_NO_UNICODE_TRANSLATION: return UV_ECHARSET;
+ case ERROR_CONNECTION_ABORTED: return UV_ECONNABORTED;
+ case WSAECONNABORTED: return UV_ECONNABORTED;
+ case ERROR_CONNECTION_REFUSED: return UV_ECONNREFUSED;
+ case WSAECONNREFUSED: return UV_ECONNREFUSED;
+ case ERROR_NETNAME_DELETED: return UV_ECONNRESET;
+ case WSAECONNRESET: return UV_ECONNRESET;
+ case ERROR_ALREADY_EXISTS: return UV_EEXIST;
+ case ERROR_FILE_EXISTS: return UV_EEXIST;
+ case ERROR_BUFFER_OVERFLOW: return UV_EFAULT;
+ case WSAEFAULT: return UV_EFAULT;
+ case ERROR_HOST_UNREACHABLE: return UV_EHOSTUNREACH;
+ case WSAEHOSTUNREACH: return UV_EHOSTUNREACH;
+ case ERROR_INSUFFICIENT_BUFFER: return UV_EINVAL;
+ case ERROR_INVALID_DATA: return UV_EINVAL;
+ case ERROR_INVALID_PARAMETER: return UV_EINVAL;
+ case ERROR_SYMLINK_NOT_SUPPORTED: return UV_EINVAL;
+ case WSAEINVAL: return UV_EINVAL;
+ case WSAEPFNOSUPPORT: return UV_EINVAL;
+ case WSAESOCKTNOSUPPORT: return UV_EINVAL;
+ case ERROR_BEGINNING_OF_MEDIA: return UV_EIO;
+ case ERROR_BUS_RESET: return UV_EIO;
+ case ERROR_CRC: return UV_EIO;
+ case ERROR_DEVICE_DOOR_OPEN: return UV_EIO;
+ case ERROR_DEVICE_REQUIRES_CLEANING: return UV_EIO;
+ case ERROR_DISK_CORRUPT: return UV_EIO;
+ case ERROR_EOM_OVERFLOW: return UV_EIO;
+ case ERROR_FILEMARK_DETECTED: return UV_EIO;
+ case ERROR_GEN_FAILURE: return UV_EIO;
+ case ERROR_INVALID_BLOCK_LENGTH: return UV_EIO;
+ case ERROR_IO_DEVICE: return UV_EIO;
+ case ERROR_NO_DATA_DETECTED: return UV_EIO;
+ case ERROR_NO_SIGNAL_SENT: return UV_EIO;
+ case ERROR_OPEN_FAILED: return UV_EIO;
+ case ERROR_SETMARK_DETECTED: return UV_EIO;
+ case ERROR_SIGNAL_REFUSED: return UV_EIO;
+ case WSAEISCONN: return UV_EISCONN;
+ case ERROR_CANT_RESOLVE_FILENAME: return UV_ELOOP;
+ case ERROR_TOO_MANY_OPEN_FILES: return UV_EMFILE;
+ case WSAEMFILE: return UV_EMFILE;
+ case WSAEMSGSIZE: return UV_EMSGSIZE;
+ case ERROR_FILENAME_EXCED_RANGE: return UV_ENAMETOOLONG;
+ case ERROR_NETWORK_UNREACHABLE: return UV_ENETUNREACH;
+ case WSAENETUNREACH: return UV_ENETUNREACH;
+ case WSAENOBUFS: return UV_ENOBUFS;
+ case ERROR_BAD_PATHNAME: return UV_ENOENT;
+ case ERROR_DIRECTORY: return UV_ENOENT;
+ case ERROR_FILE_NOT_FOUND: return UV_ENOENT;
+ case ERROR_INVALID_NAME: return UV_ENOENT;
+ case ERROR_INVALID_DRIVE: return UV_ENOENT;
+ case ERROR_INVALID_REPARSE_DATA: return UV_ENOENT;
+ case ERROR_MOD_NOT_FOUND: return UV_ENOENT;
+ case ERROR_PATH_NOT_FOUND: return UV_ENOENT;
+ case WSAHOST_NOT_FOUND: return UV_ENOENT;
+ case WSANO_DATA: return UV_ENOENT;
+ case ERROR_NOT_ENOUGH_MEMORY: return UV_ENOMEM;
+ case ERROR_OUTOFMEMORY: return UV_ENOMEM;
+ case ERROR_CANNOT_MAKE: return UV_ENOSPC;
+ case ERROR_DISK_FULL: return UV_ENOSPC;
+ case ERROR_EA_TABLE_FULL: return UV_ENOSPC;
+ case ERROR_END_OF_MEDIA: return UV_ENOSPC;
+ case ERROR_HANDLE_DISK_FULL: return UV_ENOSPC;
+ case ERROR_NOT_CONNECTED: return UV_ENOTCONN;
+ case WSAENOTCONN: return UV_ENOTCONN;
+ case ERROR_DIR_NOT_EMPTY: return UV_ENOTEMPTY;
+ case WSAENOTSOCK: return UV_ENOTSOCK;
+ case ERROR_NOT_SUPPORTED: return UV_ENOTSUP;
+ case ERROR_BROKEN_PIPE: return UV_EOF;
+ case ERROR_ACCESS_DENIED: return UV_EPERM;
+ case ERROR_PRIVILEGE_NOT_HELD: return UV_EPERM;
+ case ERROR_BAD_PIPE: return UV_EPIPE;
+ case ERROR_NO_DATA: return UV_EPIPE;
+ case ERROR_PIPE_NOT_CONNECTED: return UV_EPIPE;
+ case WSAESHUTDOWN: return UV_EPIPE;
+ case WSAEPROTONOSUPPORT: return UV_EPROTONOSUPPORT;
+ case ERROR_WRITE_PROTECT: return UV_EROFS;
+ case ERROR_SEM_TIMEOUT: return UV_ETIMEDOUT;
+ case WSAETIMEDOUT: return UV_ETIMEDOUT;
+ case ERROR_NOT_SAME_DEVICE: return UV_EXDEV;
+ case ERROR_INVALID_FUNCTION: return UV_EISDIR;
+ case ERROR_META_EXPANSION_TOO_LONG: return UV_E2BIG;
+ default: return UV_UNKNOWN;
+ }
+#else
+ const int error = -errno;
+ STATIC_ASSERT(-EINTR == UV_EINTR, "Need to translate error codes");
+ STATIC_ASSERT(-EAGAIN == UV_EAGAIN, "Need to translate error codes");
+ STATIC_ASSERT(-ENOMEM == UV_ENOMEM, "Need to translate error codes");
+ return error;
+#endif
+}
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 7687b14f02..f264a26939 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -19,7 +19,7 @@
#include "nvim/getchar.h"
#include "nvim/main.h"
#include "nvim/misc1.h"
-#include "nvim/misc2.h"
+#include "nvim/state.h"
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)
@@ -39,8 +39,6 @@ static int events_enabled = 0;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/input.c.generated.h"
#endif
-// Helper function used to push bytes from the 'event' key sequence partially
-// between calls to os_inchar when maxlen < 3
void input_init(void)
{
@@ -60,8 +58,8 @@ void input_start(int fd)
}
global_fd = fd;
- rstream_init_fd(&loop, &read_stream, fd, READ_BUFFER_SIZE, NULL);
- rstream_start(&read_stream, read_cb);
+ rstream_init_fd(&main_loop, &read_stream, fd, READ_BUFFER_SIZE);
+ rstream_start(&read_stream, read_cb, NULL);
}
void input_stop(void)
@@ -71,7 +69,7 @@ void input_stop(void)
}
rstream_stop(&read_stream);
- stream_close(&read_stream, NULL);
+ stream_close(&read_stream, NULL, NULL);
}
static void cursorhold_event(void **argv)
@@ -87,8 +85,8 @@ static void create_cursorhold_event(void)
// have been called(inbuf_poll would return kInputAvail)
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
- assert(!events_enabled || queue_empty(loop.events));
- queue_put(loop.events, cursorhold_event, 0);
+ assert(!events_enabled || multiqueue_empty(main_loop.events));
+ multiqueue_put(main_loop.events, cursorhold_event, 0);
}
// Low level input function
@@ -147,7 +145,7 @@ bool os_char_avail(void)
void os_breakcheck(void)
{
if (!got_int) {
- loop_poll_events(&loop, 0);
+ loop_poll_events(&main_loop, 0);
}
}
@@ -172,12 +170,13 @@ bool os_isatty(int fd)
size_t input_enqueue(String keys)
{
- char *ptr = keys.data, *end = ptr + keys.size;
+ char *ptr = keys.data;
+ char *end = ptr + keys.size;
while (rbuffer_space(input_buffer) >= 6 && ptr < end) {
uint8_t buf[6] = { 0 };
- unsigned int new_size = trans_special((const uint8_t **)&ptr, keys.size,
- buf, true);
+ unsigned int new_size
+ = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true);
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);
@@ -187,8 +186,7 @@ size_t input_enqueue(String keys)
if (*ptr == '<') {
char *old_ptr = ptr;
- // Invalid or incomplete key sequence, skip until the next '>' or until
- // *end
+ // Invalid or incomplete key sequence, skip until the next '>' or *end.
do {
ptr++;
} while (ptr < end && *ptr != '>');
@@ -266,29 +264,32 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
}
static int orig_num_clicks = 0;
- static int orig_mouse_code = 0;
- static int orig_mouse_col = 0;
- static int orig_mouse_row = 0;
- static uint64_t orig_mouse_time = 0; // time of previous mouse click
- uint64_t mouse_time = os_hrtime(); // time of current mouse click
-
- // compute the time elapsed since the previous mouse click and
- // convert p_mouse from ms to ns
- uint64_t timediff = mouse_time - orig_mouse_time;
- uint64_t mouset = (uint64_t)p_mouset * 1000000;
- if (mouse_code == orig_mouse_code
- && timediff < mouset
- && orig_num_clicks != 4
- && orig_mouse_col == mouse_col
- && orig_mouse_row == mouse_row) {
- orig_num_clicks++;
- } else {
- orig_num_clicks = 1;
+ if (mouse_code != KE_LEFTRELEASE && mouse_code != KE_RIGHTRELEASE
+ && mouse_code != KE_MIDDLERELEASE) {
+ static int orig_mouse_code = 0;
+ static int orig_mouse_col = 0;
+ static int orig_mouse_row = 0;
+ static uint64_t orig_mouse_time = 0; // time of previous mouse click
+ uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns)
+
+ // compute the time elapsed since the previous mouse click and
+ // convert p_mouse from ms to ns
+ uint64_t timediff = mouse_time - orig_mouse_time;
+ uint64_t mouset = (uint64_t)p_mouset * 1000000;
+ if (mouse_code == orig_mouse_code
+ && timediff < mouset
+ && orig_num_clicks != 4
+ && orig_mouse_col == mouse_col
+ && orig_mouse_row == mouse_row) {
+ orig_num_clicks++;
+ } else {
+ orig_num_clicks = 1;
+ }
+ orig_mouse_code = mouse_code;
+ orig_mouse_col = mouse_col;
+ orig_mouse_row = mouse_row;
+ orig_mouse_time = mouse_time;
}
- orig_mouse_code = mouse_code;
- orig_mouse_col = mouse_col;
- orig_mouse_row = mouse_row;
- orig_mouse_time = mouse_time;
uint8_t modifiers = 0;
if (orig_num_clicks == 2) {
@@ -322,7 +323,7 @@ static bool input_poll(int ms)
prof_inchar_enter();
}
- LOOP_PROCESS_EVENTS_UNTIL(&loop, NULL, ms, input_ready() || input_eof);
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, input_ready() || input_eof);
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
@@ -386,6 +387,8 @@ static void process_interrupts(void)
}
}
+// Helper function used to push bytes from the 'event' key sequence partially
+// between calls to os_inchar when maxlen < 3
static int push_event_key(uint8_t *buf, int maxlen)
{
static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT };
@@ -419,5 +422,5 @@ static void read_error_exit(void)
static bool pending_events(void)
{
- return events_enabled && !queue_empty(loop.events);
+ return events_enabled && !multiqueue_empty(main_loop.events);
}
diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h
index eee0cdd10b..14c210c69c 100644
--- a/src/nvim/os/os_defs.h
+++ b/src/nvim/os/os_defs.h
@@ -16,7 +16,7 @@
#define BASENAMELEN (NAME_MAX - 5)
// Use the system path length if it makes sense.
-#if defined(PATH_MAX) && (PATH_MAX > 1000)
+#if defined(PATH_MAX) && (PATH_MAX > 1024)
# define MAXPATHL PATH_MAX
#else
# define MAXPATHL 1024
@@ -46,4 +46,10 @@
/// negative libuv error codes are returned by a number of os functions.
#define os_strerror uv_strerror
+#ifdef WIN32
+# define os_strtok strtok_s
+#else
+# define os_strtok strtok_r
+#endif
+
#endif // NVIM_OS_OS_DEFS_H
diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h
new file mode 100644
index 0000000000..94923499ca
--- /dev/null
+++ b/src/nvim/os/pty_process.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_OS_PTY_PROCESS_H
+#define NVIM_OS_PTY_PROCESS_H
+
+#ifdef WIN32
+# include "nvim/os/pty_process_win.h"
+#else
+# include "nvim/os/pty_process_unix.h"
+#endif
+#endif // NVIM_OS_PTY_PROCESS_H
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
new file mode 100644
index 0000000000..b57a69b82b
--- /dev/null
+++ b/src/nvim/os/pty_process_unix.c
@@ -0,0 +1,245 @@
+// Some of the code came from pangoterm and libuv
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+
+// forkpty is not in POSIX, so headers are platform-specific
+#if defined(__FreeBSD__)
+# include <libutil.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+# include <util.h>
+#else
+# include <pty.h>
+#endif
+
+#include <uv.h>
+
+#include "nvim/lib/klist.h"
+
+#include "nvim/event/loop.h"
+#include "nvim/event/rstream.h"
+#include "nvim/event/wstream.h"
+#include "nvim/event/process.h"
+#include "nvim/os/pty_process_unix.h"
+#include "nvim/log.h"
+#include "nvim/os/os.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_process_unix.c.generated.h"
+#endif
+
+bool pty_process_spawn(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static struct termios termios;
+ if (!termios.c_cflag) {
+ init_termios(&termios);
+ }
+
+ Process *proc = (Process *)ptyproc;
+ assert(!proc->err);
+ uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
+ ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
+ uv_disable_stdio_inheritance();
+ int master;
+ int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize);
+
+ if (pid < 0) {
+ ELOG("forkpty failed: %s", strerror(errno));
+ return false;
+ } else if (pid == 0) {
+ init_child(ptyproc);
+ abort();
+ }
+
+ // make sure the master file descriptor is non blocking
+ int master_status_flags = fcntl(master, F_GETFL);
+ if (master_status_flags == -1) {
+ ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
+ goto error;
+ }
+ if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
+ ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
+ goto error;
+ }
+
+ if (proc->in && !set_duplicating_descriptor(master, &proc->in->uv.pipe)) {
+ goto error;
+ }
+ if (proc->out && !set_duplicating_descriptor(master, &proc->out->uv.pipe)) {
+ goto error;
+ }
+
+ ptyproc->tty_fd = master;
+ proc->pid = pid;
+ return true;
+
+error:
+ close(master);
+ kill(pid, SIGKILL);
+ waitpid(pid, NULL, 0);
+ return false;
+}
+
+void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
+ ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
+}
+
+void pty_process_close(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ pty_process_close_master(ptyproc);
+ Process *proc = (Process *)ptyproc;
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+}
+
+void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
+{
+ if (ptyproc->tty_fd >= 0) {
+ close(ptyproc->tty_fd);
+ ptyproc->tty_fd = -1;
+ }
+}
+
+void pty_process_teardown(Loop *loop)
+{
+ uv_signal_stop(&loop->children_watcher);
+}
+
+static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
+{
+ unsetenv("COLUMNS");
+ unsetenv("LINES");
+ unsetenv("TERMCAP");
+ unsetenv("COLORTERM");
+ unsetenv("COLORFGBG");
+
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+
+ Process *proc = (Process *)ptyproc;
+ if (proc->cwd && os_chdir(proc->cwd) != 0) {
+ fprintf(stderr, "chdir failed: %s\n", strerror(errno));
+ return;
+ }
+
+ setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
+ execvp(ptyproc->process.argv[0], ptyproc->process.argv);
+ fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+}
+
+static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
+{
+ // Taken from pangoterm
+ termios->c_iflag = ICRNL|IXON;
+ termios->c_oflag = OPOST|ONLCR;
+#ifdef TAB0
+ termios->c_oflag |= TAB0;
+#endif
+ termios->c_cflag = CS8|CREAD;
+ termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
+
+ cfsetspeed(termios, 38400);
+
+#ifdef IUTF8
+ termios->c_iflag |= IUTF8;
+#endif
+#ifdef NL0
+ termios->c_oflag |= NL0;
+#endif
+#ifdef CR0
+ termios->c_oflag |= CR0;
+#endif
+#ifdef BS0
+ termios->c_oflag |= BS0;
+#endif
+#ifdef VT0
+ termios->c_oflag |= VT0;
+#endif
+#ifdef FF0
+ termios->c_oflag |= FF0;
+#endif
+#ifdef ECHOCTL
+ termios->c_lflag |= ECHOCTL;
+#endif
+#ifdef ECHOKE
+ termios->c_lflag |= ECHOKE;
+#endif
+
+ termios->c_cc[VINTR] = 0x1f & 'C';
+ termios->c_cc[VQUIT] = 0x1f & '\\';
+ termios->c_cc[VERASE] = 0x7f;
+ termios->c_cc[VKILL] = 0x1f & 'U';
+ termios->c_cc[VEOF] = 0x1f & 'D';
+ termios->c_cc[VEOL] = _POSIX_VDISABLE;
+ termios->c_cc[VEOL2] = _POSIX_VDISABLE;
+ termios->c_cc[VSTART] = 0x1f & 'Q';
+ termios->c_cc[VSTOP] = 0x1f & 'S';
+ termios->c_cc[VSUSP] = 0x1f & 'Z';
+ termios->c_cc[VREPRINT] = 0x1f & 'R';
+ termios->c_cc[VWERASE] = 0x1f & 'W';
+ termios->c_cc[VLNEXT] = 0x1f & 'V';
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
+}
+
+static bool set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int fd_dup = dup(fd);
+ if (fd_dup < 0) {
+ ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
+ return false;
+ }
+ int uv_result = uv_pipe_open(pipe, fd_dup);
+ if (uv_result) {
+ ELOG("Failed to set pipe to descriptor %d: %s",
+ fd_dup, uv_strerror(uv_result));
+ close(fd_dup);
+ return false;
+ }
+ return true;
+}
+
+static void chld_handler(uv_signal_t *handle, int signum)
+{
+ int stat = 0;
+ int pid;
+
+ do {
+ pid = waitpid(-1, &stat, WNOHANG);
+ } while (pid < 0 && errno == EINTR);
+
+ if (pid <= 0) {
+ return;
+ }
+
+ Loop *loop = handle->loop->data;
+
+ kl_iter(WatcherPtr, loop->children, current) {
+ Process *proc = (*current)->data;
+ if (proc->pid == pid) {
+ if (WIFEXITED(stat)) {
+ proc->status = WEXITSTATUS(stat);
+ } else if (WIFSIGNALED(stat)) {
+ proc->status = WTERMSIG(stat);
+ }
+ proc->internal_exit_cb(proc);
+ break;
+ }
+ }
+}
diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h
new file mode 100644
index 0000000000..f7c57b3839
--- /dev/null
+++ b/src/nvim/os/pty_process_unix.h
@@ -0,0 +1,31 @@
+#ifndef NVIM_OS_PTY_PROCESS_UNIX_H
+#define NVIM_OS_PTY_PROCESS_UNIX_H
+
+#include <sys/ioctl.h>
+
+#include "nvim/event/process.h"
+
+typedef struct pty_process {
+ Process process;
+ char *term_name;
+ uint16_t width, height;
+ struct winsize winsize;
+ int tty_fd;
+} PtyProcess;
+
+static inline PtyProcess pty_process_init(Loop *loop, void *data)
+{
+ PtyProcess rv;
+ rv.process = process_init(loop, kProcessTypePty, data);
+ rv.term_name = NULL;
+ rv.width = 80;
+ rv.height = 24;
+ rv.tty_fd = -1;
+ return rv;
+}
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_process_unix.h.generated.h"
+#endif
+
+#endif // NVIM_OS_PTY_PROCESS_UNIX_H
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
new file mode 100644
index 0000000000..20cc589925
--- /dev/null
+++ b/src/nvim/os/pty_process_win.h
@@ -0,0 +1,28 @@
+#ifndef NVIM_OS_PTY_PROCESS_WIN_H
+#define NVIM_OS_PTY_PROCESS_WIN_H
+
+#include "nvim/event/libuv_process.h"
+
+typedef struct pty_process {
+ Process process;
+ char *term_name;
+ uint16_t width, height;
+} PtyProcess;
+
+#define pty_process_spawn(job) libuv_process_spawn((LibuvProcess *)job)
+#define pty_process_close(job) libuv_process_close((LibuvProcess *)job)
+#define pty_process_close_master(job) libuv_process_close((LibuvProcess *)job)
+#define pty_process_resize(job, width, height)
+#define pty_process_teardown(loop)
+
+static inline PtyProcess pty_process_init(Loop *loop, void *data)
+{
+ PtyProcess rv;
+ rv.process = process_init(loop, kProcessTypePty, data);
+ rv.term_name = NULL;
+ rv.width = 80;
+ rv.height = 24;
+ return rv;
+}
+
+#endif // NVIM_OS_PTY_PROCESS_WIN_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index f5a1637c94..9514936ad0 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -14,18 +14,20 @@
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/types.h"
+#include "nvim/main.h"
#include "nvim/vim.h"
#include "nvim/message.h"
#include "nvim/memory.h"
#include "nvim/ui.h"
-#include "nvim/misc2.h"
#include "nvim/screen.h"
#include "nvim/memline.h"
#include "nvim/option_defs.h"
#include "nvim/charset.h"
#include "nvim/strings.h"
-#define DYNAMIC_BUFFER_INIT {NULL, 0, 0}
+#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 }
+#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
+#define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data.
typedef struct {
char *data;
@@ -53,12 +55,12 @@ char **shell_build_argv(const char *cmd, const char *extra_args)
size_t i = tokenize(p_sh, rv);
if (extra_args) {
- rv[i++] = xstrdup(extra_args); // Push a copy of `extra_args`
+ rv[i++] = xstrdup(extra_args); // Push a copy of `extra_args`
}
if (cmd) {
- i += tokenize(p_shcf, rv + i); // Split 'shellcmdflag'
- rv[i++] = xstrdup(cmd); // Push a copy of the command.
+ i += tokenize(p_shcf, rv + i); // Split 'shellcmdflag'
+ rv[i++] = shell_xescape_xquote(cmd); // Copy (and escape) `cmd`.
}
rv[i] = NULL;
@@ -162,7 +164,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
/// @param input The input to the shell (NULL for no input), passed to the
/// stdin of the resulting process.
/// @param len The length of the input buffer (not used if `input` == NULL)
-/// @param[out] output A pointer to to a location where the output will be
+/// @param[out] output Pointer to a location where the output will be
/// allocated and stored. Will point to NULL if the shell
/// command did not output anything. If NULL is passed,
/// the shell output will be ignored.
@@ -187,6 +189,9 @@ static int do_os_system(char **argv,
bool silent,
bool forward_output)
{
+ out_data_decide_throttle(0); // Initialize throttle decider.
+ out_data_ring(NULL, 0); // Initialize output ring-buffer.
+
// the output buffer
DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
stream_read_cb data_cb = system_data_cb;
@@ -205,29 +210,29 @@ static int do_os_system(char **argv,
xstrlcpy(prog, argv[0], MAXPATHL);
Stream in, out, err;
- LibuvProcess uvproc = libuv_process_init(&loop, &buf);
+ LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
Process *proc = &uvproc.process;
- Queue *events = queue_new_child(loop.events);
+ MultiQueue *events = multiqueue_new_child(main_loop.events);
proc->events = events;
proc->argv = argv;
proc->in = input != NULL ? &in : NULL;
proc->out = &out;
proc->err = &err;
if (!process_spawn(proc)) {
- loop_poll_events(&loop, 0);
- // Failed, probably due to `sh` not being executable
+ loop_poll_events(&main_loop, 0);
+ // Failed, probably due to 'sh' not being executable
if (!silent) {
MSG_PUTS(_("\nCannot execute "));
msg_outtrans((char_u *)prog);
msg_putchar('\n');
}
- queue_free(events);
+ multiqueue_free(events);
return -1;
}
// We want to deal with stream events as fast a possible while queueing
// process events, so reset everything to NULL. It prevents closing the
- // streams while there's still data in the OS buffer(due to the process
+ // streams while there's still data in the OS buffer (due to the process
// exiting before all data is read).
if (input != NULL) {
proc->in->events = NULL;
@@ -235,10 +240,10 @@ static int do_os_system(char **argv,
}
proc->out->events = NULL;
rstream_init(proc->out, 0);
- rstream_start(proc->out, data_cb);
+ rstream_start(proc->out, data_cb, &buf);
proc->err->events = NULL;
rstream_init(proc->err, 0);
- rstream_start(proc->err, data_cb);
+ rstream_start(proc->err, data_cb, &buf);
// write the input, if any
if (input) {
@@ -250,14 +255,18 @@ static int do_os_system(char **argv,
return -1;
}
// close the input stream after everything is written
- wstream_set_write_cb(&in, shell_write_cb);
+ wstream_set_write_cb(&in, shell_write_cb, NULL);
}
- // invoke busy_start here so event_poll_until wont change the busy state for
- // the UI
+ // Invoke busy_start here so LOOP_PROCESS_EVENTS_UNTIL will not change the
+ // busy state.
ui_busy_start();
ui_flush();
int status = process_wait(proc, -1, NULL);
+ if (!got_int && out_data_decide_throttle(0)) {
+ // Last chunk of output was skipped; display it now.
+ out_data_ring(NULL, SIZE_MAX);
+ }
ui_busy_stop();
// prepare the out parameters if requested
@@ -277,8 +286,8 @@ static int do_os_system(char **argv,
}
}
- assert(queue_empty(events));
- queue_free(events);
+ assert(multiqueue_empty(events));
+ multiqueue_free(events);
return status;
}
@@ -309,25 +318,192 @@ static void system_data_cb(Stream *stream, RBuffer *buf, size_t count,
dbuf->len += nread;
}
+/// Tracks output received for the current executing shell command, and displays
+/// a pulsing "..." when output should be skipped. Tracking depends on the
+/// synchronous/blocking nature of ":!".
+//
+/// Purpose:
+/// 1. CTRL-C is more responsive. #1234 #5396
+/// 2. Improves performance of :! (UI, esp. TUI, is the bottleneck).
+/// 3. Avoids OOM during long-running, spammy :!.
+///
+/// Vim does not need this hack because:
+/// 1. :! in terminal-Vim runs in cooked mode, so CTRL-C is caught by the
+/// terminal and raises SIGINT out-of-band.
+/// 2. :! in terminal-Vim uses a tty (Nvim uses pipes), so commands
+/// (e.g. `git grep`) may page themselves.
+///
+/// @param size Length of data, used with internal state to decide whether
+/// output should be skipped. size=0 resets the internal state and
+/// returns the previous decision.
+///
+/// @returns true if output should be skipped and pulse was displayed.
+/// Returns the previous decision if size=0.
+static bool out_data_decide_throttle(size_t size)
+{
+ static uint64_t started = 0; // Start time of the current throttle.
+ static size_t received = 0; // Bytes observed since last throttle.
+ static size_t visit = 0; // "Pulse" count of the current throttle.
+ static char pulse_msg[] = { ' ', ' ', ' ', '\0' };
+
+ if (!size) {
+ bool previous_decision = (visit > 0);
+ started = received = visit = 0;
+ return previous_decision;
+ }
+
+ received += size;
+ if (received < OUT_DATA_THRESHOLD
+ // Display at least the first chunk of output even if it is big.
+ || (!started && received < size + 1000)) {
+ return false;
+ } else if (!visit) {
+ started = os_hrtime();
+ } else if (visit % 20 == 0) {
+ uint64_t since = os_hrtime() - started;
+ if (since > (3 * NS_1_SECOND)) {
+ received = visit = 0;
+ return false;
+ }
+ }
+
+ visit++;
+ // Pulse "..." at the bottom of the screen.
+ size_t tick = (visit % 20 == 0)
+ ? 3 // Force all dots "..." on last visit.
+ : (visit % 4);
+ pulse_msg[0] = (tick == 0) ? ' ' : '.';
+ pulse_msg[1] = (tick == 0 || 1 == tick) ? ' ' : '.';
+ pulse_msg[2] = (tick == 0 || 1 == tick || 2 == tick) ? ' ' : '.';
+ if (visit == 1) {
+ screen_del_lines(0, 0, 1, (int)Rows, NULL);
+ }
+ int lastrow = (int)Rows - 1;
+ screen_puts_len((char_u *)pulse_msg, ARRAY_SIZE(pulse_msg), lastrow, 0, 0);
+ ui_flush();
+ return true;
+}
+
+/// Saves output in a quasi-ringbuffer. Used to ensure the last ~page of
+/// output for a shell-command is always displayed.
+///
+/// Init mode: Resets the internal state.
+/// output = NULL
+/// size = 0
+/// Print mode: Displays the current saved data.
+/// output = NULL
+/// size = SIZE_MAX
+///
+/// @param output Data to save, or NULL to invoke a special mode.
+/// @param size Length of `output`.
+static void out_data_ring(char *output, size_t size)
+{
+#define MAX_CHUNK_SIZE (OUT_DATA_THRESHOLD / 2)
+ static char last_skipped[MAX_CHUNK_SIZE]; // Saved output.
+ static size_t last_skipped_len = 0;
+
+ assert(output != NULL || (size == 0 || size == SIZE_MAX));
+
+ if (output == NULL && size == 0) { // Init mode
+ last_skipped_len = 0;
+ return;
+ }
+
+ if (output == NULL && size == SIZE_MAX) { // Print mode
+ out_data_append_to_screen(last_skipped, last_skipped_len, true);
+ return;
+ }
+
+ // This is basically a ring-buffer...
+ if (size >= MAX_CHUNK_SIZE) { // Save mode
+ size_t start = size - MAX_CHUNK_SIZE;
+ memcpy(last_skipped, output + start, MAX_CHUNK_SIZE);
+ last_skipped_len = MAX_CHUNK_SIZE;
+ } else if (size > 0) {
+ // Length of the old data that can be kept.
+ size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size);
+ size_t keep_start = last_skipped_len - keep_len;
+ // Shift the kept part of the old data to the start.
+ if (keep_start) {
+ memmove(last_skipped, last_skipped + keep_start, keep_len);
+ }
+ // Copy the entire new data to the remaining space.
+ memcpy(last_skipped + keep_len, output, size);
+ last_skipped_len = keep_len + size;
+ }
+}
+
+/// Continue to append data to last screen line.
+///
+/// @param output Data to append to screen lines.
+/// @param remaining Size of data.
+/// @param new_line If true, next data output will be on a new line.
+static void out_data_append_to_screen(char *output, size_t remaining,
+ bool new_line)
+{
+ static colnr_T last_col = 0; // Column of last row to append to.
+
+ size_t off = 0;
+ int last_row = (int)Rows - 1;
+
+ while (off < remaining) {
+ // Found end of line?
+ if (output[off] == NL) {
+ // Can we start a new line or do we need to continue the last one?
+ if (last_col == 0) {
+ screen_del_lines(0, 0, 1, (int)Rows, NULL);
+ }
+ screen_puts_len((char_u *)output, (int)off, last_row, last_col, 0);
+ last_col = 0;
+
+ size_t skip = off + 1;
+ output += skip;
+ remaining -= skip;
+ off = 0;
+ continue;
+ }
+
+ // Translate NUL to SOH
+ if (output[off] == NUL) {
+ output[off] = 1;
+ }
+
+ off++;
+ }
+
+ if (remaining) {
+ if (last_col == 0) {
+ screen_del_lines(0, 0, 1, (int)Rows, NULL);
+ }
+ screen_puts_len((char_u *)output, (int)remaining, last_row, last_col, 0);
+ last_col += (colnr_T)remaining;
+ }
+
+ if (new_line) {
+ last_col = 0;
+ }
+
+ ui_flush();
+}
+
static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data,
bool eof)
{
+ // We always output the whole buffer, so the buffer can never
+ // wrap around.
size_t cnt;
char *ptr = rbuffer_read_ptr(buf, &cnt);
- if (!cnt) {
- return;
+ if (ptr != NULL && cnt > 0
+ && out_data_decide_throttle(cnt)) { // Skip output above a threshold.
+ // Save the skipped output. If it is the final chunk, we display it later.
+ out_data_ring(ptr, cnt);
+ } else {
+ out_data_append_to_screen(ptr, cnt, eof);
}
- size_t written = write_output(ptr, cnt, false, eof);
- // No output written, force emptying the Rbuffer if it is full.
- if (!written && rbuffer_size(buf) == rbuffer_capacity(buf)) {
- screen_del_lines(0, 0, 1, (int)Rows, NULL);
- screen_puts_len((char_u *)ptr, (int)cnt, (int)Rows - 1, 0, 0);
- written = cnt;
- }
- if (written) {
- rbuffer_consumed(buf, written);
+ if (cnt) {
+ rbuffer_consumed(buf, cnt);
}
}
@@ -500,5 +676,51 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
static void shell_write_cb(Stream *stream, void *data, int status)
{
- stream_close(stream, NULL);
+ if (status) {
+ // Can happen if system() tries to send input to a shell command that was
+ // backgrounded (:call system("cat - &", "foo")). #3529 #5241
+ msg_schedule_emsgf(_("E5677: Error writing input to shell-command: %s"),
+ uv_err_name(status));
+ }
+ if (stream->closed) { // Process may have exited before this write.
+ ELOG("stream was already closed");
+ return;
+ }
+ stream_close(stream, NULL, NULL);
+}
+
+/// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command.
+///
+/// @param cmd Command string
+/// @return Escaped/quoted command string (allocated).
+static char *shell_xescape_xquote(const char *cmd)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (*p_sxq == NUL) {
+ return xstrdup(cmd);
+ }
+
+ const char *ecmd = cmd;
+ if (*p_sxe != NUL && STRCMP(p_sxq, "(") == 0) {
+ ecmd = (char *)vim_strsave_escaped_ext((char_u *)cmd, p_sxe, '^', false);
+ }
+ size_t ncmd_size = strlen(ecmd) + STRLEN(p_sxq) * 2 + 1;
+ char *ncmd = xmalloc(ncmd_size);
+
+ // When 'shellxquote' is ( append ).
+ // When 'shellxquote' is "( append )".
+ if (STRCMP(p_sxq, "(") == 0) {
+ vim_snprintf(ncmd, ncmd_size, "(%s)", ecmd);
+ } else if (STRCMP(p_sxq, "\"(") == 0) {
+ vim_snprintf(ncmd, ncmd_size, "\"(%s)\"", ecmd);
+ } else {
+ vim_snprintf(ncmd, ncmd_size, "%s%s%s", p_sxq, ecmd, p_sxq);
+ }
+
+ if (ecmd != cmd) {
+ xfree((void *)ecmd);
+ }
+
+ return ncmd;
}
+
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index 0ff6016e32..1ac6d3f5e1 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -2,15 +2,18 @@
#include <stdbool.h>
#include <uv.h>
+#ifndef WIN32
+# include <signal.h> // for sigset_t
+#endif
#include "nvim/ascii.h"
#include "nvim/vim.h"
#include "nvim/globals.h"
#include "nvim/memline.h"
#include "nvim/eval.h"
+#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
-#include "nvim/misc2.h"
#include "nvim/event/signal.h"
#include "nvim/os/signal.h"
#include "nvim/event/loop.h"
@@ -28,10 +31,20 @@ static bool rejecting_deadly;
void signal_init(void)
{
- signal_watcher_init(&loop, &spipe, NULL);
- signal_watcher_init(&loop, &shup, NULL);
- signal_watcher_init(&loop, &squit, NULL);
- signal_watcher_init(&loop, &sterm, NULL);
+#ifndef WIN32
+ // Ensure a clean slate by unblocking all signals. For example, if SIGCHLD is
+ // blocked, libuv may hang after spawning a subprocess on Linux. #5230
+ sigset_t mask;
+ sigemptyset(&mask);
+ if (pthread_sigmask(SIG_SETMASK, &mask, NULL) != 0) {
+ ELOG("Could not unblock signals, nvim might behave strangely.");
+ }
+#endif
+
+ signal_watcher_init(&main_loop, &spipe, NULL);
+ signal_watcher_init(&main_loop, &shup, NULL);
+ signal_watcher_init(&main_loop, &squit, NULL);
+ signal_watcher_init(&main_loop, &sterm, NULL);
#ifdef SIGPIPE
signal_watcher_start(&spipe, on_signal, SIGPIPE);
#endif
@@ -41,7 +54,7 @@ void signal_init(void)
#endif
signal_watcher_start(&sterm, on_signal, SIGTERM);
#ifdef SIGPWR
- signal_watcher_init(&loop, &spwr, NULL);
+ signal_watcher_init(&main_loop, &spwr, NULL);
signal_watcher_start(&spwr, on_signal, SIGPWR);
#endif
}
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index 81ceb919c4..afb9bdec31 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -16,20 +16,29 @@ static const char *xdg_env_vars[] = {
[kXDGDataDirs] = "XDG_DATA_DIRS",
};
+#ifdef WIN32
+static const char *const xdg_defaults_env_vars[] = {
+ [kXDGConfigHome] = "LOCALAPPDATA",
+ [kXDGDataHome] = "LOCALAPPDATA",
+ [kXDGCacheHome] = "TEMP",
+ [kXDGRuntimeDir] = NULL,
+ [kXDGConfigDirs] = NULL,
+ [kXDGDataDirs] = NULL,
+};
+#endif
+
/// Defaults for XDGVarType values
///
/// Used in case environment variables contain nothing. Need to be expanded.
static const char *const xdg_defaults[] = {
#ifdef WIN32
- // Windows
- [kXDGConfigHome] = "$LOCALAPPDATA",
- [kXDGDataHome] = "$LOCALAPPDATA",
- [kXDGCacheHome] = "$TEMP",
+ [kXDGConfigHome] = "~\\AppData\\Local",
+ [kXDGDataHome] = "~\\AppData\\Local",
+ [kXDGCacheHome] = "~\\AppData\\Local\\Temp",
[kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL,
#else
- // Linux, BSD, CYGWIN, Apple
[kXDGConfigHome] = "~/.config",
[kXDGDataHome] = "~/.local/share",
[kXDGCacheHome] = "~/.cache",
@@ -50,7 +59,14 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
const char *const env = xdg_env_vars[idx];
const char *const fallback = xdg_defaults[idx];
- const char *const env_val = os_getenv(env);
+ const char *env_val = os_getenv(env);
+
+#ifdef WIN32
+ if (env_val == NULL) {
+ env_val = os_getenv(xdg_defaults_env_vars[idx]);
+ }
+#endif
+
char *ret = NULL;
if (env_val != NULL) {
ret = xstrdup(env_val);
@@ -100,18 +116,30 @@ char *stdpaths_user_conf_subpath(const char *fname)
///
/// @param[in] fname New component of the path.
/// @param[in] trailing_pathseps Amount of trailing path separators to add.
+/// @param[in] escape_commas If true, all commas will be escaped.
///
-/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`.
char *stdpaths_user_data_subpath(const char *fname,
- const size_t trailing_pathseps)
+ const size_t trailing_pathseps,
+ const bool escape_commas)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
- if (trailing_pathseps) {
- const size_t len = strlen(ret);
- ret = xrealloc(ret, len + trailing_pathseps + 1);
- memset(ret + len, PATHSEP, trailing_pathseps);
- ret[len + trailing_pathseps] = NUL;
+ const size_t len = strlen(ret);
+ const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0);
+ if (numcommas || trailing_pathseps) {
+ ret = xrealloc(ret, len + trailing_pathseps + numcommas + 1);
+ for (size_t i = 0 ; i < len + numcommas ; i++) {
+ if (ret[i] == ',') {
+ memmove(ret + i + 1, ret + i, len - i + numcommas);
+ ret[i] = '\\';
+ i++;
+ }
+ }
+ if (trailing_pathseps) {
+ memset(ret + len + numcommas, PATHSEP, trailing_pathseps);
+ }
+ ret[len + trailing_pathseps + numcommas] = NUL;
}
return ret;
}
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 188f0802c9..8ce2ecf4f4 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -7,8 +7,10 @@
#include <uv.h>
#include "nvim/os/time.h"
+#include "nvim/os/input.h"
#include "nvim/event/loop.h"
#include "nvim/vim.h"
+#include "nvim/main.h"
static uv_mutex_t delay_mutex;
static uv_cond_t delay_cond;
@@ -33,38 +35,54 @@ uint64_t os_hrtime(void)
return uv_hrtime();
}
-/// Sleeps for a certain amount of milliseconds
+/// Sleeps for a certain amount of milliseconds.
///
/// @param milliseconds Number of milliseconds to sleep
-/// @param ignoreinput If true, allow a SIGINT to interrupt us
+/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t milliseconds, bool ignoreinput)
{
if (ignoreinput) {
if (milliseconds > INT_MAX) {
milliseconds = INT_MAX;
}
- LOOP_PROCESS_EVENTS_UNTIL(&loop, NULL, (int)milliseconds, got_int);
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)milliseconds, got_int);
} else {
- os_microdelay(milliseconds * 1000);
+ os_microdelay(milliseconds * 1000u, ignoreinput);
}
}
-/// Sleeps for a certain amount of microseconds
+/// Sleeps for a certain amount of microseconds.
///
-/// @param microseconds Number of microseconds to sleep
-void os_microdelay(uint64_t microseconds)
+/// @param ms Number of microseconds to sleep.
+/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
+/// If false, waiting is aborted on any input.
+void os_microdelay(uint64_t ms, bool ignoreinput)
{
- uint64_t elapsed = 0;
- uint64_t ns = microseconds * 1000; // convert to nanoseconds
+ uint64_t elapsed = 0u;
uint64_t base = uv_hrtime();
+ // Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
+ const uint64_t ns = (ms < UINT64_MAX / 1000u) ? ms * 1000u : UINT64_MAX;
uv_mutex_lock(&delay_mutex);
while (elapsed < ns) {
- if (uv_cond_timedwait(&delay_cond, &delay_mutex, ns - elapsed)
- == UV_ETIMEDOUT)
+ // If ignoring input, we simply wait the full delay.
+ // Else we check for input in ~100ms intervals.
+ const uint64_t ns_delta = ignoreinput
+ ? ns - elapsed
+ : MIN(ns - elapsed, 100000000u); // 100ms
+
+ const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
+ if (0 != rv && UV_ETIMEDOUT != rv) {
+ assert(false);
+ break;
+ } // Else: Timeout proceeded normally.
+
+ if (!ignoreinput && os_char_avail()) {
break;
- uint64_t now = uv_hrtime();
+ }
+
+ const uint64_t now = uv_hrtime();
elapsed += now - base;
base = now;
}
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index 8ebb7562ef..1c94ef0067 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -6,7 +6,6 @@
#include "nvim/os/os.h"
#include "nvim/garray.h"
#include "nvim/memory.h"
-#include "nvim/misc2.h"
#include "nvim/strings.h"
#ifdef HAVE_PWD_H
# include <pwd.h>