diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.c | 69 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.h | 16 | ||||
| -rw-r--r-- | src/nvim/diff.c | 67 | ||||
| -rw-r--r-- | src/nvim/eval.c | 13 | ||||
| -rw-r--r-- | src/nvim/eval/typval.h | 2 | ||||
| -rw-r--r-- | src/nvim/ex_cmds2.c | 28 | ||||
| -rw-r--r-- | src/nvim/ex_eval.c | 4 | ||||
| -rw-r--r-- | src/nvim/ex_getln.c | 489 | ||||
| -rw-r--r-- | src/nvim/mbyte.c | 2 | ||||
| -rw-r--r-- | src/nvim/mbyte.h | 6 | ||||
| -rw-r--r-- | src/nvim/menu.c | 8 | ||||
| -rw-r--r-- | src/nvim/message.c | 21 | ||||
| -rw-r--r-- | src/nvim/move.c | 12 | ||||
| -rw-r--r-- | src/nvim/os/pty_process_win.c | 410 | ||||
| -rw-r--r-- | src/nvim/os/pty_process_win.h | 28 | ||||
| -rw-r--r-- | src/nvim/screen.c | 12 | ||||
| -rw-r--r-- | src/nvim/testdir/Makefile | 2 | ||||
| -rw-r--r-- | src/nvim/testdir/test_diffmode.vim | 17 | ||||
| -rw-r--r-- | src/nvim/testdir/test_window_cmd.vim | 44 | ||||
| -rw-r--r-- | src/nvim/ui.c | 5 | ||||
| -rw-r--r-- | src/nvim/version.c | 8 | ||||
| -rw-r--r-- | src/nvim/window.c | 5 | 
23 files changed, 1108 insertions, 170 deletions
| diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index c46c0bed6d..688912eda6 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -111,6 +111,9 @@ foreach(sfile ${NVIM_SOURCES})    if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")      list(APPEND to_remove ${sfile})    endif() +  if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$") +    list(APPEND to_remove ${sfile}) +  endif()  endforeach()  list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) @@ -350,6 +353,10 @@ if(Iconv_LIBRARIES)    list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES})  endif() +if(WIN32) +  list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES}) +endif() +  # Put these last on the link line, since multiple things may depend on them.  list(APPEND NVIM_LINK_LIBRARIES    ${LIBUV_LIBRARIES} @@ -415,6 +422,7 @@ if(WIN32)      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tee.exe"             ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe"            ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe"       ${PROJECT_BINARY_DIR}/windows_runtime_deps/ +    COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.exe"    ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll"  ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll"          ${PROJECT_BINARY_DIR}/windows_runtime_deps/ @@ -428,6 +436,7 @@ if(WIN32)      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Network.dll"      ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Svg.dll"          ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Widgets.dll"      ${PROJECT_BINARY_DIR}/windows_runtime_deps/ +    COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty.dll"          ${PROJECT_BINARY_DIR}/windows_runtime_deps/      COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/      ) @@ -526,6 +535,7 @@ endfunction()  set(NO_SINGLE_CHECK_HEADERS    os/win_defs.h +  os/pty_process_win.h    regexp_defs.h    syntax_defs.h    terminal.h diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 1ed2bc013e..e736e29e2d 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -37,7 +37,72 @@ typedef struct {  # include "api/private/ui_events_metadata.generated.h"  #endif +/// Start block that may cause VimL exceptions while evaluating another code +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +/// +/// @param[out]  tstate  Location where try state should be saved. +void try_enter(TryState *const tstate) +{ +  *tstate = (TryState) { +    .current_exception = current_exception, +    .msg_list = (const struct msglist *const *)msg_list, +    .private_msg_list = NULL, +    .trylevel = trylevel, +    .got_int = got_int, +    .did_throw = did_throw, +    .need_rethrow = need_rethrow, +    .did_emsg = did_emsg, +  }; +  msg_list = &tstate->private_msg_list; +  current_exception = NULL; +  trylevel = 1; +  got_int = false; +  did_throw = false; +  need_rethrow = false; +  did_emsg = false; +} + +/// End try block, set the error message if any and restore previous state +/// +/// @warning Return is consistent with most functions (false on error), not with +///          try_end (true on error). +/// +/// @param[in]  tstate  Previous state to restore. +/// @param[out]  err  Location where error should be saved. +/// +/// @return false if error occurred, true otherwise. +bool try_leave(const TryState *const tstate, Error *const err) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  const bool ret = !try_end(err); +  assert(trylevel == 0); +  assert(!need_rethrow); +  assert(!got_int); +  assert(!did_throw); +  assert(!did_emsg); +  assert(msg_list == &tstate->private_msg_list); +  assert(*msg_list == NULL); +  assert(current_exception == NULL); +  msg_list = (struct msglist **)tstate->msg_list; +  current_exception = tstate->current_exception; +  trylevel = tstate->trylevel; +  got_int = tstate->got_int; +  did_throw = tstate->did_throw; +  need_rethrow = tstate->need_rethrow; +  did_emsg = tstate->did_emsg; +  return ret; +} +  /// Start block that may cause vimscript exceptions +/// +/// Each try_start() call should be mirrored by try_end() call. +/// +/// To be used as a replacement of `:try … catch … endtry` in C code, in cases +/// when error flag could not already be set. If there may be pending error +/// state at the time try_start() is executed which needs to be preserved, +/// try_enter()/try_leave() pair should be used instead.  void try_start(void)  {    ++trylevel; @@ -50,7 +115,9 @@ void try_start(void)  /// @return true if an error occurred  bool try_end(Error *err)  { -  --trylevel; +  // Note: all globals manipulated here should be saved/restored in +  // try_enter/try_leave. +  trylevel--;    // Without this it stops processing all subsequent VimL commands and    // generates strange error messages if I e.g. try calling Test() in a diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 159b9d5c2a..87f334ac30 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -6,6 +6,7 @@  #include "nvim/api/private/defs.h"  #include "nvim/vim.h"  #include "nvim/memory.h" +#include "nvim/ex_eval.h"  #include "nvim/lib/kvec.h"  #define OBJECT_OBJ(o) o @@ -82,6 +83,21 @@  #define api_free_window(value)  #define api_free_tabpage(value) +/// Structure used for saving state for :try +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +typedef struct { +  except_T *current_exception; +  struct msglist *private_msg_list; +  const struct msglist *const *msg_list; +  int trylevel; +  int got_int; +  int did_throw; +  int need_rethrow; +  int did_emsg; +} TryState; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "api/private/helpers.h.generated.h"  #endif diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 7da6665cb7..17de4284ce 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1033,10 +1033,7 @@ void ex_diffsplit(exarg_T *eap)          if (bufref_valid(&old_curbuf)) {            // Move the cursor position to that of the old window.            curwin->w_cursor.lnum = diff_get_corresponding_line( -              old_curbuf.br_buf, -              old_curwin->w_cursor.lnum, -              curbuf, -              curwin->w_cursor.lnum); +              old_curbuf.br_buf, old_curwin->w_cursor.lnum);          }        }        // Now that lines are folded scroll to show the cursor at the same @@ -2463,25 +2460,17 @@ int diff_move_to(int dir, long count)    return OK;  } -/// Finds the corresponding line in a diff. -/// -/// @param buf1 -/// @param lnum1 -/// @param buf2 -/// @param lnum3 -/// -/// @return The corresponding line. -linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1, buf_T *buf2, -                                     linenr_T lnum3) +/// Return the line number in the current window that is closest to "lnum1" in +/// "buf1" in diff mode. +static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1)  {    int idx1;    int idx2;    diff_T *dp;    int baseline = 0; -  linenr_T lnum2;    idx1 = diff_buf_idx(buf1); -  idx2 = diff_buf_idx(buf2); +  idx2 = diff_buf_idx(curbuf);    if ((idx1 == DB_COUNT)        || (idx2 == DB_COUNT) @@ -2501,15 +2490,9 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1, buf_T *buf2,    for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) {      if (dp->df_lnum[idx1] > lnum1) { -      lnum2 = lnum1 - baseline; - -      // don't end up past the end of the file -      if (lnum2 > buf2->b_ml.ml_line_count) { -        lnum2 = buf2->b_ml.ml_line_count; -      } - -      return lnum2; -    } else if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) { +      return lnum1 - baseline; +    } +    if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) {        // Inside the diffblock        baseline = lnum1 - dp->df_lnum[idx1]; @@ -2518,30 +2501,42 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1, buf_T *buf2,        }        return dp->df_lnum[idx2] + baseline; -    } else if ((dp->df_lnum[idx1] == lnum1) -               && (dp->df_count[idx1] == 0) -               && (dp->df_lnum[idx2] <= lnum3) -               && ((dp->df_lnum[idx2] + dp->df_count[idx2]) > lnum3)) { +    } +    if ((dp->df_lnum[idx1] == lnum1) +        && (dp->df_count[idx1] == 0) +        && (dp->df_lnum[idx2] <= curwin->w_cursor.lnum) +        && ((dp->df_lnum[idx2] + dp->df_count[idx2]) +            > curwin->w_cursor.lnum)) {        // Special case: if the cursor is just after a zero-count        // block (i.e. all filler) and the target cursor is already        // inside the corresponding block, leave the target cursor        // unmoved. This makes repeated CTRL-W W operations work        // as expected. -      return lnum3; +      return curwin->w_cursor.lnum;      }      baseline = (dp->df_lnum[idx1] + dp->df_count[idx1]) -               - (dp->df_lnum[idx2] + dp->df_count[idx2]); +                - (dp->df_lnum[idx2] + dp->df_count[idx2]);    }    // If we get here then the cursor is after the last diff -  lnum2 = lnum1 - baseline; +  return lnum1 - baseline; +} + +/// Finds the corresponding line in a diff. +/// +/// @param buf1 +/// @param lnum1 +/// +/// @return The corresponding line. +linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1) +{ +  linenr_T lnum = diff_get_corresponding_line_int(buf1, lnum1);    // don't end up past the end of the file -  if (lnum2 > buf2->b_ml.ml_line_count) { -    lnum2 = buf2->b_ml.ml_line_count; +  if (lnum > curbuf->b_ml.ml_line_count) { +    return curbuf->b_ml.ml_line_count;    } - -  return lnum2; +  return lnum;  }  /// For line "lnum" in the current window find the equivalent lnum in window diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c42929ef7c..ac22d75a83 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11031,6 +11031,7 @@ void get_user_input(const typval_T *const argvars,    const char *defstr = "";    const char *cancelreturn = NULL;    const char *xp_name = NULL; +  Callback input_callback = { .type = kCallbackNone };    char prompt_buf[NUMBUFLEN];    char defstr_buf[NUMBUFLEN];    char cancelreturn_buf[NUMBUFLEN]; @@ -11040,7 +11041,7 @@ void get_user_input(const typval_T *const argvars,        emsgf(_("E5050: {opts} must be the only argument"));        return;      } -    const dict_T *const dict = argvars[0].vval.v_dict; +    dict_T *const dict = argvars[0].vval.v_dict;      prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, "");      if (prompt == NULL) {        return; @@ -11066,6 +11067,9 @@ void get_user_input(const typval_T *const argvars,      if (xp_name == def) {  // default to NULL        xp_name = NULL;      } +    if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { +      return; +    }    } else {      prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);      if (prompt == NULL) { @@ -11124,12 +11128,13 @@ void get_user_input(const typval_T *const argvars,    stuffReadbuffSpec(defstr); -  int save_ex_normal_busy = ex_normal_busy; +  const int save_ex_normal_busy = ex_normal_busy;    ex_normal_busy = 0;    rettv->vval.v_string = -    getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr, -                      xp_type, (char_u *)xp_arg); +    (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, +                                xp_type, xp_arg, input_callback);    ex_normal_busy = save_ex_normal_busy; +  callback_free(&input_callback);    if (rettv->vval.v_string == NULL && cancelreturn != NULL) {      rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 3f8ed3b3f9..c44b85644d 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -43,7 +43,7 @@ typedef struct partial_S partial_T;  typedef struct ufunc ufunc_T;  typedef enum { -  kCallbackNone, +  kCallbackNone = 0,    kCallbackFuncref,    kCallbackPartial,  } CallbackType; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 1a728647ca..33fe30cd5a 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -189,7 +189,8 @@ void do_debug(char_u *cmd)      }      xfree(cmdline); -    cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL); +    cmdline = (char_u *)getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, +                                          CALLBACK_NONE);      if (typeahead_saved) {        restore_typeahead(&typeaheadbuf); @@ -3610,18 +3611,11 @@ void ex_language(exarg_T *eap)  static char_u **locales = NULL;       // Array of all available locales -static bool did_init_locales = false; -/// Lazy initialization of all available locales. -static void init_locales(void) -{ -  if (!did_init_locales) { -    did_init_locales = true; -    locales = find_locales(); -  } -} +#ifndef WIN32 +static bool did_init_locales = false; -// Return an array of strings for all available locales + NULL for the +/// Return an array of strings for all available locales + NULL for the  /// last element.  Return NULL in case of error.  static char_u **find_locales(void)  { @@ -3653,6 +3647,18 @@ static char_u **find_locales(void)    ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;    return (char_u **)locales_ga.ga_data;  } +#endif + +/// Lazy initialization of all available locales. +static void init_locales(void) +{ +#ifndef WIN32 +  if (!did_init_locales) { +    did_init_locales = true; +    locales = find_locales(); +  } +#endif +}  #  if defined(EXITFREE)  void free_locales(void) diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index c029df2f13..9037b3c151 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -561,7 +561,9 @@ static void discard_exception(except_T *excp, int was_finished)   */  void discard_current_exception(void)  { -  discard_exception(current_exception, FALSE); +  discard_exception(current_exception, false); +  // Note: all globals manipulated here should be saved/restored in +  // try_enter/try_leave.    current_exception = NULL;    did_throw = FALSE;    need_rethrow = FALSE; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index ecd5c81822..5e216925df 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -63,6 +63,9 @@  #include "nvim/os/os.h"  #include "nvim/event/loop.h"  #include "nvim/os/time.h" +#include "nvim/lib/kvec.h" +#include "nvim/api/private/helpers.h" +#include "nvim/highlight_defs.h"  /*   * Variables shared between getcmdline(), redrawcmdline() and others. @@ -70,23 +73,27 @@   * structure.   */  struct cmdline_info { -  char_u      *cmdbuff;         /* pointer to command line buffer */ -  int cmdbufflen;               /* length of cmdbuff */ -  int cmdlen;                   /* number of chars in command line */ -  int cmdpos;                   /* current cursor position */ -  int cmdspos;                  /* cursor column on screen */ -  int cmdfirstc;                /* ':', '/', '?', '=', '>' or NUL */ -  int cmdindent;                /* number of spaces before cmdline */ -  char_u      *cmdprompt;       /* message in front of cmdline */ -  int cmdattr;                  /* attributes for prompt */ -  int overstrike;               /* Typing mode on the command line.  Shared by -                                   getcmdline() and put_on_cmdline(). */ -  expand_T    *xpc;             /* struct being used for expansion, xp_pattern -                                   may point into cmdbuff */ -  int xp_context;               /* type of expansion */ -  char_u      *xp_arg;          /* user-defined expansion arg */ -  int input_fn;                 /* when TRUE Invoked for input() function */ +  char_u      *cmdbuff;         // pointer to command line buffer +  int cmdbufflen;               // length of cmdbuff +  int cmdlen;                   // number of chars in command line +  int cmdpos;                   // current cursor position +  int cmdspos;                  // cursor column on screen +  int cmdfirstc;                // ':', '/', '?', '=', '>' or NUL +  int cmdindent;                // number of spaces before cmdline +  char_u      *cmdprompt;       // message in front of cmdline +  int cmdattr;                  // attributes for prompt +  int overstrike;               // Typing mode on the command line.  Shared by +                                // getcmdline() and put_on_cmdline(). +  expand_T    *xpc;             // struct being used for expansion, xp_pattern +                                // may point into cmdbuff +  int xp_context;               // type of expansion +  char_u      *xp_arg;          // user-defined expansion arg +  int input_fn;                 // when TRUE Invoked for input() function +  unsigned prompt_id;  ///< Prompt number, used to disable coloring on errors. +  Callback highlight_callback;  ///< Callback used for coloring user input.  }; +/// Last value of prompt_id, incremented when doing new prompt +static unsigned last_prompt_id = 0;  typedef struct command_line_state {    VimState state; @@ -136,6 +143,38 @@ typedef struct command_line_state {    struct cmdline_info save_ccline;  } CommandLineState; +/// Command-line colors: one chunk +/// +/// Defines a region which has the same highlighting. +typedef struct { +  int start;  ///< Colored chunk start. +  int end;  ///< Colored chunk end (exclusive, > start). +  int attr;  ///< Highlight attr. +} CmdlineColorChunk; + +/// Command-line colors +/// +/// Holds data about all colors. +typedef kvec_t(CmdlineColorChunk) CmdlineColors; + +/// Command-line coloring +/// +/// Holds both what are the colors and what have been colored. Latter is used to +/// suppress unnecessary calls to coloring callbacks. +typedef struct { +  unsigned prompt_id;  ///< ID of the prompt which was colored last. +  char *cmdbuff;  ///< What exactly was colored last time or NULL. +  CmdlineColors colors;  ///< Last colors. +} ColoredCmdline; + +/// Last command-line colors. +ColoredCmdline last_ccline_colors = { +  .cmdbuff = NULL, +  .colors = KV_INITIAL_VALUE +}; + +typedef struct cmdline_info CmdlineInfo; +  /* The current cmdline_info.  It is initialized in getcmdline() and after that   * used by other functions.  When invoking getcmdline() recursively it needs   * to be saved with save_cmdline() and restored with restore_cmdline(). @@ -157,6 +196,12 @@ static int hisnum[HIST_COUNT] = {0, 0, 0, 0, 0};  /* identifying (unique) number of newest history entry */  static int hislen = 0;                  /* actual length of history tables */ +/// Flag for command_line_handle_key to ignore <C-c> +/// +/// Used if it was received while processing highlight function in order for +/// user interrupting highlight function to not interrupt command-line. +static bool getln_interrupted_highlight = false; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "ex_getln.c.generated.h" @@ -193,6 +238,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)      cmd_hkmap = 0;    } +  ccline.prompt_id = last_prompt_id++;    ccline.overstrike = false;                // always start in insert mode    clearpos(&s->match_end);    s->save_cursor = curwin->w_cursor;        // may be restored later @@ -1160,8 +1206,11 @@ static int command_line_handle_key(CommandLineState *s)    case ESC:           // get here if p_wc != ESC or when ESC typed twice    case Ctrl_C:      // In exmode it doesn't make sense to return.  Except when -    // ":normal" runs out of characters. -    if (exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) { +    // ":normal" runs out of characters. Also when highlight callback is active +    // <C-c> should interrupt only it. +    if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) +        || (getln_interrupted_highlight && s->c == Ctrl_C)) { +      getln_interrupted_highlight = false;        return command_line_not_changed(s);      } @@ -1790,41 +1839,50 @@ getcmdline (    return command_line_enter(firstc, count, indent);  } -/* - * Get a command line with a prompt. - * This is prepared to be called recursively from getcmdline() (e.g. by - * f_input() when evaluating an expression from CTRL-R =). - * Returns the command line in allocated memory, or NULL. - */ -char_u * -getcmdline_prompt ( -    int firstc, -    char_u *prompt,            /* command line prompt */ -    int attr,                       /* attributes for prompt */ -    int xp_context,                 /* type of expansion */ -    char_u *xp_arg            /* user-defined expansion argument */ -) +/// Get a command line with a prompt +/// +/// This is prepared to be called recursively from getcmdline() (e.g. by +/// f_input() when evaluating an expression from `<C-r>=`). +/// +/// @param[in]  firstc  Prompt type: e.g. '@' for input(), '>' for debug. +/// @param[in]  prompt  Prompt string: what is displayed before the user text. +/// @param[in]  attr  Prompt highlighting. +/// @param[in]  xp_context  Type of expansion. +/// @param[in]  xp_arg  User-defined expansion argument. +/// @param[in]  highlight_callback  Callback used for highlighting user input. +/// +/// @return [allocated] Command line or NULL. +char *getcmdline_prompt(const char firstc, const char *const prompt, +                        const int attr, const int xp_context, +                        const char *const xp_arg, +                        const Callback highlight_callback) +  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC  { -  char_u              *s; -  struct cmdline_info save_ccline; -  int msg_col_save = msg_col; +  const int msg_col_save = msg_col; +  struct cmdline_info save_ccline;    save_cmdline(&save_ccline); -  ccline.cmdprompt = prompt; + +  ccline.prompt_id = last_prompt_id++; +  ccline.cmdprompt = (char_u *)prompt;    ccline.cmdattr = attr;    ccline.xp_context = xp_context; -  ccline.xp_arg = xp_arg; +  ccline.xp_arg = (char_u *)xp_arg;    ccline.input_fn = (firstc == '@'); -  s = getcmdline(firstc, 1L, 0); +  ccline.highlight_callback = highlight_callback; + +  char *const ret = (char *)getcmdline(firstc, 1L, 0); +    restore_cmdline(&save_ccline); -  /* Restore msg_col, the prompt from input() may have changed it. -   * But only if called recursively and the commandline is therefore being -   * restored to an old one; if not, the input() prompt stays on the screen, -   * so we need its modified msg_col left intact. */ -  if (ccline.cmdbuff != NULL) +  // Restore msg_col, the prompt from input() may have changed it. +  // But only if called recursively and the commandline is therefore being +  // restored to an old one; if not, the input() prompt stays on the screen, +  // so we need its modified msg_col left intact. +  if (ccline.cmdbuff != NULL) {      msg_col = msg_col_save; +  } -  return s; +  return ret;  }  /* @@ -2285,75 +2343,329 @@ void free_cmdline_buf(void)  # endif +enum { MAX_CB_ERRORS = 1 }; + +/// Color command-line +/// +/// Should use built-in command parser or user-specified one. Currently only the +/// latter is supported. +/// +/// @param[in]  colored_ccline  Command-line to color. +/// @param[out]  ret_ccline_colors  What should be colored. Also holds a cache: +///                                 if ->prompt_id and ->cmdbuff values happen +///                                 to be equal to those from colored_cmdline it +///                                 will just do nothing, assuming that ->colors +///                                 already contains needed data. +/// +/// Always colors the whole cmdline. +/// +/// @return true if draw_cmdline may proceed, false if it does not need anything +///         to do. +static bool color_cmdline(const CmdlineInfo *const colored_ccline, +                          ColoredCmdline *const ret_ccline_colors) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  bool printed_errmsg = false; +#define PRINT_ERRMSG(...) \ +  do { \ +    msg_putchar('\n'); \ +    msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, __VA_ARGS__); \ +    printed_errmsg = true; \ +  } while (0) +  bool ret = true; + +  // Check whether result of the previous call is still valid. +  if (ret_ccline_colors->prompt_id == colored_ccline->prompt_id +      && ret_ccline_colors->cmdbuff != NULL +      && STRCMP(ret_ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) { +    return ret; +  } + +  kv_size(ret_ccline_colors->colors) = 0; + +  if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) { +    // Nothing to do, exiting. +    xfree(ret_ccline_colors->cmdbuff); +    ret_ccline_colors->cmdbuff = NULL; +    return ret; +  } + +  bool arg_allocated = false; +  typval_T arg = { +    .v_type = VAR_STRING, +    .vval.v_string = colored_ccline->cmdbuff, +  }; +  typval_T tv = { .v_type = VAR_UNKNOWN }; + +  static unsigned prev_prompt_id = UINT_MAX; +  static int prev_prompt_errors = 0; +  Callback color_cb = { .type = kCallbackNone }; +  bool can_free_cb = false; +  TryState tstate; +  Error err = ERROR_INIT; +  const char *err_errmsg = (const char *)e_intern2; +  bool dgc_ret = true; +  bool tl_ret = true; + +  if (colored_ccline->prompt_id != prev_prompt_id) { +    prev_prompt_errors = 0; +    prev_prompt_id = colored_ccline->prompt_id; +  } else if (prev_prompt_errors >= MAX_CB_ERRORS) { +    goto color_cmdline_end; +  } +  if (colored_ccline->highlight_callback.type != kCallbackNone) { +    // Currently this should only happen while processing input() prompts. +    assert(colored_ccline->input_fn); +    color_cb = colored_ccline->highlight_callback; +  } else if (colored_ccline->cmdfirstc == ':') { +    try_enter(&tstate); +    err_errmsg = N_( +        "E5408: Unable to get g:Nvim_color_cmdline callback: %s"); +    dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"), +                                   &color_cb); +    tl_ret = try_leave(&tstate, &err); +    can_free_cb = true; +  } else if (colored_ccline->cmdfirstc == '=') { +    try_enter(&tstate); +    err_errmsg = N_( +        "E5409: Unable to get g:Nvim_color_expr callback: %s"); +    dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), +                                   &color_cb); +    tl_ret = try_leave(&tstate, &err); +    can_free_cb = true; +  } +  if (!tl_ret || !dgc_ret) { +    goto color_cmdline_error; +  } + +  if (color_cb.type == kCallbackNone) { +    goto color_cmdline_end; +  } +  if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) { +    arg_allocated = true; +    arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff, +                                 (size_t)colored_ccline->cmdlen); +  } +  // msg_start() called by e.g. :echo may shift command-line to the first column +  // even though msg_silent is here. Two ways to workaround this problem without +  // altering message.c: use full_screen or save and restore msg_col. +  // +  // Saving and restoring full_screen does not work well with :redraw!. Saving +  // and restoring msg_col is neither ideal, but while with full_screen it +  // appears shifted one character to the right and cursor position is no longer +  // correct, with msg_col it just misses leading `:`. Since `redraw!` in +  // callback lags this is least of the user problems. +  // +  // Also using try_enter() because error messages may overwrite typed +  // command-line which is not expected. +  getln_interrupted_highlight = false; +  try_enter(&tstate); +  err_errmsg = N_("E5407: Callback has thrown an exception: %s"); +  const int saved_msg_col = msg_col; +  msg_silent++; +  const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv); +  msg_silent--; +  msg_col = saved_msg_col; +  if (got_int) { +    getln_interrupted_highlight = true; +  } +  if (!try_leave(&tstate, &err) || !cbcall_ret) { +    goto color_cmdline_error; +  } +  if (tv.v_type != VAR_LIST) { +    PRINT_ERRMSG(_("E5400: Callback should return list")); +    goto color_cmdline_error; +  } +  if (tv.vval.v_list == NULL) { +    goto color_cmdline_end; +  } +  varnumber_T prev_end = 0; +  int i = 0; +  for (const listitem_T *li = tv.vval.v_list->lv_first; +       li != NULL; li = li->li_next, i++) { +    if (li->li_tv.v_type != VAR_LIST) { +      PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); +      goto color_cmdline_error; +    } +    const list_T *const l = li->li_tv.vval.v_list; +    if (tv_list_len(l) != 3) { +      PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), +                   i, tv_list_len(l)); +      goto color_cmdline_error; +    } +    bool error = false; +    const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); +    if (error) { +      goto color_cmdline_error; +    } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { +      PRINT_ERRMSG(_("E5403: Chunk %i start %" PRIdVARNUMBER " not in range " +                     "[%" PRIdVARNUMBER ", %i)"), +                   i, start, prev_end, colored_ccline->cmdlen); +      goto color_cmdline_error; +    } else if (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[start]] == 0) { +      PRINT_ERRMSG(_("E5405: Chunk %i start %" PRIdVARNUMBER " splits " +                     "multibyte character"), i, start); +      goto color_cmdline_error; +    } +    if (start != prev_end) { +      kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { +        .start = prev_end, +        .end = start, +        .attr = 0, +      })); +    } +    const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, +                                              &error); +    if (error) { +      goto color_cmdline_error; +    } else if (!(start < end && end <= colored_ccline->cmdlen)) { +      PRINT_ERRMSG(_("E5404: Chunk %i end %" PRIdVARNUMBER " not in range " +                     "(%" PRIdVARNUMBER ", %i]"), +                   i, end, start, colored_ccline->cmdlen); +      goto color_cmdline_error; +    } else if (end < colored_ccline->cmdlen +               && (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[end]] +                   == 0)) { +      PRINT_ERRMSG(_("E5406: Chunk %i end %" PRIdVARNUMBER " splits multibyte " +                     "character"), i, end); +      goto color_cmdline_error; +    } +    prev_end = end; +    const char *const group = tv_get_string_chk(&l->lv_last->li_tv); +    if (group == NULL) { +      goto color_cmdline_error; +    } +    const int id = syn_name2id((char_u *)group); +    const int attr = (id == 0 ? 0 : syn_id2attr(id)); +    kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { +      .start = start, +      .end = end, +      .attr = attr, +    })); +  } +  if (prev_end < colored_ccline->cmdlen) { +    kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { +      .start = prev_end, +      .end = colored_ccline->cmdlen, +      .attr = 0, +    })); +  } +  prev_prompt_errors = 0; +color_cmdline_end: +  assert(!ERROR_SET(&err)); +  if (can_free_cb) { +    callback_free(&color_cb); +  } +  xfree(ret_ccline_colors->cmdbuff); +  // Note: errors “output” is cached just as well as regular results. +  ret_ccline_colors->prompt_id = colored_ccline->prompt_id; +  if (arg_allocated) { +    ret_ccline_colors->cmdbuff = (char *)arg.vval.v_string; +  } else { +    ret_ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff, +                                          (size_t)colored_ccline->cmdlen); +  } +  tv_clear(&tv); +  return ret; +color_cmdline_error: +  if (ERROR_SET(&err)) { +    PRINT_ERRMSG(_(err_errmsg), err.msg); +    api_clear_error(&err); +  } +  assert(printed_errmsg); +  (void)printed_errmsg; + +  prev_prompt_errors++; +  kv_size(ret_ccline_colors->colors) = 0; +  redrawcmdline(); +  ret = false; +  goto color_cmdline_end; +#undef PRINT_ERRMSG +} +  /*   * Draw part of the cmdline at the current cursor position.  But draw stars   * when cmdline_star is TRUE.   */  static void draw_cmdline(int start, int len)  { -  int i; +  if (!color_cmdline(&ccline, &last_ccline_colors)) { +    return; +  } -  if (cmdline_star > 0) -    for (i = 0; i < len; ++i) { +  if (cmdline_star > 0) { +    for (int i = 0; i < len; i++) {        msg_putchar('*'); -      if (has_mbyte) +      if (has_mbyte) {          i += (*mb_ptr2len)(ccline.cmdbuff + start + i) - 1; +      }      } -  else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { -    static int buflen = 0; -    char_u          *p; -    int j; -    int newlen = 0; +  } else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { +    bool do_arabicshape = false;      int mb_l; -    int pc, pc1 = 0; -    int prev_c = 0; -    int prev_c1 = 0; -    int u8c; -    int u8cc[MAX_MCO]; -    int nc = 0; +    for (int i = start; i < start + len; i += mb_l) { +      char_u *p = ccline.cmdbuff + i; +      int u8cc[MAX_MCO]; +      int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); +      mb_l = utfc_ptr2len_len(p, start + len - i); +      if (arabic_char(u8c)) { +        do_arabicshape = true; +        break; +      } +    } +    if (!do_arabicshape) { +      goto draw_cmdline_no_arabicshape; +    } -    /* -     * Do arabic shaping into a temporary buffer.  This is very -     * inefficient! -     */ +    static int buflen = 0; + +    // Do arabic shaping into a temporary buffer.  This is very +    // inefficient!      if (len * 2 + 2 > buflen) { -      /* Re-allocate the buffer.  We keep it around to avoid a lot of -       * alloc()/free() calls. */ +      // Re-allocate the buffer.  We keep it around to avoid a lot of +      // alloc()/free() calls.        xfree(arshape_buf);        buflen = len * 2 + 2;        arshape_buf = xmalloc(buflen);      } +    int newlen = 0;      if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) { -      /* Prepend a space to draw the leading composing char on. */ +      // Prepend a space to draw the leading composing char on.        arshape_buf[0] = ' ';        newlen = 1;      } -    for (j = start; j < start + len; j += mb_l) { -      p = ccline.cmdbuff + j; -      u8c = utfc_ptr2char_len(p, u8cc, start + len - j); -      mb_l = utfc_ptr2len_len(p, start + len - j); +    int prev_c = 0; +    int prev_c1 = 0; +    for (int i = start; i < start + len; i += mb_l) { +      char_u *p = ccline.cmdbuff + i; +      int u8cc[MAX_MCO]; +      int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); +      mb_l = utfc_ptr2len_len(p, start + len - i);        if (arabic_char(u8c)) { -        /* Do Arabic shaping. */ +        int pc; +        int pc1 = 0; +        int nc = 0; +        // Do Arabic shaping.          if (cmdmsg_rl) { -          /* displaying from right to left */ +          // Displaying from right to left.            pc = prev_c;            pc1 = prev_c1;            prev_c1 = u8cc[0]; -          if (j + mb_l >= start + len) +          if (i + mb_l >= start + len) {              nc = NUL; -          else +          } else {              nc = utf_ptr2char(p + mb_l); +          }          } else { -          /* displaying from left to right */ -          if (j + mb_l >= start + len) +          // Displaying from left to right. +          if (i + mb_l >= start + len) {              pc = NUL; -          else { +          } else {              int pcc[MAX_MCO]; -            pc = utfc_ptr2char_len(p + mb_l, pcc, -                start + len - j - mb_l); +            pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l);              pc1 = pcc[0];            }            nc = prev_c; @@ -2377,8 +2689,23 @@ static void draw_cmdline(int start, int len)      }      msg_outtrans_len(arshape_buf, newlen); -  } else -    msg_outtrans_len(ccline.cmdbuff + start, len); +  } else { +draw_cmdline_no_arabicshape: +    if (kv_size(last_ccline_colors.colors)) { +      for (size_t i = 0; i < kv_size(last_ccline_colors.colors); i++) { +        CmdlineColorChunk chunk = kv_A(last_ccline_colors.colors, i); +        if (chunk.end <= start) { +          continue; +        } +        const int chunk_start = MAX(chunk.start, start); +        msg_outtrans_len_attr(ccline.cmdbuff + chunk_start, +                              chunk.end - chunk_start, +                              chunk.attr); +      } +    } else { +      msg_outtrans_len(ccline.cmdbuff + start, len); +    } +  }  }  /* diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 3fad6c789d..b24770a409 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -75,7 +75,7 @@ struct interval {  /*   * Like utf8len_tab above, but using a zero for illegal lead bytes.   */ -static uint8_t utf8len_tab_zero[256] = +const uint8_t utf8len_tab_zero[256] =  {    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index ad9e38004c..bf6ccb13a5 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -1,6 +1,7 @@  #ifndef NVIM_MBYTE_H  #define NVIM_MBYTE_H +#include <stdint.h>  #include <stdbool.h>  #include <string.h> @@ -18,6 +19,9 @@  #define MB_BYTE2LEN(b)         utf8len_tab[b]  #define MB_BYTE2LEN_CHECK(b)   (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b]) +// max length of an unicode char +#define MB_MAXCHAR     6 +  /* properties used in enc_canon_table[] (first three mutually exclusive) */  #define ENC_8BIT       0x01  #define ENC_DBCS       0x02 @@ -67,6 +71,8 @@ typedef struct {                   ///< otherwise use '?'.  } vimconv_T; +extern const uint8_t utf8len_tab_zero[256]; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "mbyte.h.generated.h"  #endif diff --git a/src/nvim/menu.c b/src/nvim/menu.c index a498916e5e..0db250d111 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -666,8 +666,6 @@ static void free_menu_string(vimmenu_T *menu, int idx)  static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)  {    dict_T *dict; -  char buf[sizeof(menu->mnemonic)]; -  int mnemonic_len;    if (!menu || (menu->modes & modes) == 0x0) {      return NULL; @@ -679,8 +677,8 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)    tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname));    if (menu->mnemonic) { -    mnemonic_len = utf_char2bytes(menu->mnemonic, (u_char *)buf); -    buf[mnemonic_len] = '\0'; +    char buf[MB_MAXCHAR + 1] = { 0 };  // > max value of utf8_char2bytes +    utf_char2bytes(menu->mnemonic, (char_u *)buf);      tv_dict_add_str(dict, S_LEN("shortcut"), buf);    } @@ -717,7 +715,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)      list_T *children_list = tv_list_alloc();      for (menu = menu->children; menu != NULL; menu = menu->next) {          dict_T *dic = menu_get_recursive(menu, modes); -        if (dict && tv_dict_len(dict) > 0) { +        if (tv_dict_len(dict) > 0) {            tv_list_append_dict(children_list, dic);          }      } diff --git a/src/nvim/message.c b/src/nvim/message.c index 28c88f5a14..b90c475ede 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1628,6 +1628,27 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)    }  } +/// Print a formatted message +/// +/// Message printed is limited by #IOSIZE. Must not be used from inside +/// msg_puts_attr(). +/// +/// @param[in]  attr  Highlight attributes. +/// @param[in]  fmt  Format string. +void msg_printf_attr(const int attr, const char *const fmt, ...) +  FUNC_ATTR_NONNULL_ARG(2) +{ +  static char msgbuf[IOSIZE]; + +  va_list ap; +  va_start(ap, fmt); +  const size_t len = vim_vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap, NULL); +  va_end(ap); + +  msg_scroll = true; +  msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr); +} +  /*   * The display part of msg_puts_attr_len().   * May be called recursively to display scroll-back text. diff --git a/src/nvim/move.c b/src/nvim/move.c index 81d46a7f17..4d646f5a4b 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -2147,14 +2147,12 @@ void do_check_cursorbind(void)      curbuf = curwin->w_buffer;      /* skip original window  and windows with 'noscrollbind' */      if (curwin != old_curwin && curwin->w_p_crb) { -      if (curwin->w_p_diff) -        curwin->w_cursor.lnum -          = diff_get_corresponding_line(old_curbuf, -            line, -            curbuf, -            curwin->w_cursor.lnum); -      else +      if (curwin->w_p_diff) { +        curwin->w_cursor.lnum = +          diff_get_corresponding_line(old_curbuf, line); +      } else {          curwin->w_cursor.lnum = line; +      }        curwin->w_cursor.col = col;        curwin->w_cursor.coladd = coladd;        curwin->w_curswant = curswant; diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c new file mode 100644 index 0000000000..ef8a699c56 --- /dev/null +++ b/src/nvim/os/pty_process_win.c @@ -0,0 +1,410 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> + +#include <winpty_constants.h> + +#include "nvim/os/os.h" +#include "nvim/ascii.h" +#include "nvim/memory.h" +#include "nvim/mbyte.h"  // for utf8_to_utf16, utf16_to_utf8 +#include "nvim/os/pty_process_win.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process_win.c.generated.h" +#endif + +static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) +  FUNC_ATTR_NONNULL_ALL +{ +  PtyProcess *ptyproc = (PtyProcess *)context; +  Process *proc = (Process *)ptyproc; + +  uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); +  ptyproc->wait_eof_timer.data = (void *)ptyproc; +  uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); +} + +/// @returns zero on success, or negative error code. +int pty_process_spawn(PtyProcess *ptyproc) +  FUNC_ATTR_NONNULL_ALL +{ +  Process *proc = (Process *)ptyproc; +  int status = 0; +  winpty_error_ptr_t err = NULL; +  winpty_config_t *cfg = NULL; +  winpty_spawn_config_t *spawncfg = NULL; +  winpty_t *winpty_object = NULL; +  char *in_name = NULL; +  char *out_name = NULL; +  HANDLE process_handle = NULL; +  uv_connect_t *in_req = NULL; +  uv_connect_t *out_req = NULL; +  wchar_t *cmd_line = NULL; +  wchar_t *cwd = NULL; +  const char *emsg = NULL; + +  assert(!proc->err); + +  cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); +  if (cfg == NULL) { +    emsg = "Failed, winpty_config_new."; +    goto cleanup; +  } + +  winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); +  winpty_object = winpty_open(cfg, &err); +  if (winpty_object == NULL) { +    emsg = "Failed, winpty_open."; +    goto cleanup; +  } + +  status = utf16_to_utf8(winpty_conin_name(winpty_object), &in_name); +  if (status != 0) { +    emsg = "Failed to convert in_name from utf16 to utf8."; +    goto cleanup; +  } + +  status = utf16_to_utf8(winpty_conout_name(winpty_object), &out_name); +  if (status != 0) { +    emsg = "Failed to convert out_name from utf16 to utf8."; +    goto cleanup; +  } + +  if (proc->in != NULL) { +    in_req = xmalloc(sizeof(uv_connect_t)); +    uv_pipe_connect( +        in_req, +        &proc->in->uv.pipe, +        in_name, +        pty_process_connect_cb); +  } + +  if (proc->out != NULL) { +    out_req = xmalloc(sizeof(uv_connect_t)); +    uv_pipe_connect( +        out_req, +        &proc->out->uv.pipe, +        out_name, +        pty_process_connect_cb); +  } + +  if (proc->cwd != NULL) { +    status = utf8_to_utf16(proc->cwd, &cwd); +    if (status != 0) { +      emsg = "Failed to convert pwd form utf8 to utf16."; +      goto cleanup; +    } +  } + +  status = build_cmd_line(proc->argv, &cmd_line, +                          os_shell_is_cmdexe(proc->argv[0])); +  if (status != 0) { +    emsg = "Failed to convert cmd line form utf8 to utf16."; +    goto cleanup; +  } + +  spawncfg = winpty_spawn_config_new( +      WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, +      NULL,  // Optional application name +      cmd_line, +      cwd, +      NULL,  // Optional environment variables +      &err); +  if (spawncfg == NULL) { +    emsg = "Failed winpty_spawn_config_new."; +    goto cleanup; +  } + +  DWORD win_err = 0; +  if (!winpty_spawn(winpty_object, +                    spawncfg, +                    &process_handle, +                    NULL,  // Optional thread handle +                    &win_err, +                    &err)) { +    if (win_err) { +      status = (int)win_err; +      emsg = "Failed spawn process."; +    } else { +      emsg = "Failed winpty_spawn."; +    } +    goto cleanup; +  } +  proc->pid = GetProcessId(process_handle); + +  if (!RegisterWaitForSingleObject( +      &ptyproc->finish_wait, +      process_handle, +      pty_process_finish1, +      ptyproc, +      INFINITE, +      WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) { +    abort(); +  } + +  // Wait until pty_process_connect_cb is called. +  while ((in_req != NULL && in_req->handle != NULL) +         || (out_req != NULL && out_req->handle != NULL)) { +    uv_run(&proc->loop->uv, UV_RUN_ONCE); +  } + +  ptyproc->winpty_object = winpty_object; +  ptyproc->process_handle = process_handle; +  winpty_object = NULL; +  process_handle = NULL; + +cleanup: +  if (status) { +    // In the case of an error of MultiByteToWideChar or CreateProcessW. +    ELOG("%s error code: %d", emsg, status); +    status = os_translate_sys_error(status); +  } else if (err != NULL) { +    status = (int)winpty_error_code(err); +    ELOG("%s error code: %d", emsg, status); +    status = translate_winpty_error(status); +  } +  winpty_error_free(err); +  winpty_config_free(cfg); +  winpty_spawn_config_free(spawncfg); +  winpty_free(winpty_object); +  xfree(in_name); +  xfree(out_name); +  if (process_handle != NULL) { +    CloseHandle(process_handle); +  } +  xfree(in_req); +  xfree(out_req); +  xfree(cmd_line); +  xfree(cwd); +  return status; +} + +void pty_process_resize(PtyProcess *ptyproc, uint16_t width, +                        uint16_t height) +  FUNC_ATTR_NONNULL_ALL +{ +  if (ptyproc->winpty_object != NULL) { +    winpty_set_size(ptyproc->winpty_object, width, height, NULL); +  } +} + +void pty_process_close(PtyProcess *ptyproc) +  FUNC_ATTR_NONNULL_ALL +{ +  Process *proc = (Process *)ptyproc; + +  pty_process_close_master(ptyproc); + +  if (proc->internal_close_cb) { +    proc->internal_close_cb(proc); +  } +} + +void pty_process_close_master(PtyProcess *ptyproc) +  FUNC_ATTR_NONNULL_ALL +{ +  if (ptyproc->winpty_object != NULL) { +    winpty_free(ptyproc->winpty_object); +    ptyproc->winpty_object = NULL; +  } +} + +void pty_process_teardown(Loop *loop) +  FUNC_ATTR_NONNULL_ALL +{ +} + +static void pty_process_connect_cb(uv_connect_t *req, int status) +  FUNC_ATTR_NONNULL_ALL +{ +  assert(status == 0); +  req->handle = NULL; +} + +static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) +  FUNC_ATTR_NONNULL_ALL +{ +  PtyProcess *ptyproc = wait_eof_timer->data; +  Process *proc = (Process *)ptyproc; + +  if (!proc->out || !uv_is_readable(proc->out->uvstream)) { +    uv_timer_stop(&ptyproc->wait_eof_timer); +    pty_process_finish2(ptyproc); +  } +} + +static void pty_process_finish2(PtyProcess *ptyproc) +  FUNC_ATTR_NONNULL_ALL +{ +  Process *proc = (Process *)ptyproc; + +  UnregisterWaitEx(ptyproc->finish_wait, NULL); +  uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL); + +  DWORD exit_code = 0; +  GetExitCodeProcess(ptyproc->process_handle, &exit_code); +  proc->status = (int)exit_code; + +  CloseHandle(ptyproc->process_handle); +  ptyproc->process_handle = NULL; + +  proc->internal_exit_cb(proc); +} + +/// Build the command line to pass to CreateProcessW. +/// +/// @param[in]  argv  Array with string arguments. +/// @param[out]  cmd_line  Location where saved builded cmd line. +/// +/// @returns zero on success, or error code of MultiByteToWideChar function. +/// +static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) +  FUNC_ATTR_NONNULL_ALL +{ +  size_t utf8_cmd_line_len = 0; +  size_t argc = 0; +  QUEUE args_q; + +  QUEUE_INIT(&args_q); +  while (*argv) { +    size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3); +    ArgNode *arg_node = xmalloc(sizeof(*arg_node)); +    arg_node->arg = xmalloc(buf_len); +    if (is_cmdexe) { +      xstrlcpy(arg_node->arg, *argv, buf_len); +    } else { +      quote_cmd_arg(arg_node->arg, buf_len, *argv); +    } +    utf8_cmd_line_len += strlen(arg_node->arg); +    QUEUE_INIT(&arg_node->node); +    QUEUE_INSERT_TAIL(&args_q, &arg_node->node); +    argc++; +    argv++; +  } + +  utf8_cmd_line_len += argc; +  char *utf8_cmd_line = xmalloc(utf8_cmd_line_len); +  *utf8_cmd_line = NUL; +  while (1) { +    QUEUE *head = QUEUE_HEAD(&args_q); +    QUEUE_REMOVE(head); +    ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node); +    xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); +    xfree(arg_node->arg); +    xfree(arg_node); +    if (QUEUE_EMPTY(&args_q)) { +      break; +    } else { +      xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); +    } +  } + +  int result = utf8_to_utf16(utf8_cmd_line, cmd_line); +  xfree(utf8_cmd_line); +  return result; +} + +/// Emulate quote_cmd_arg of libuv and quotes command line argument. +/// Most of the code came from libuv. +/// +/// @param[out]  dest  Location where saved quotes argument. +/// @param  dest_remaining  Destination buffer size. +/// @param[in]  src Pointer to argument. +/// +static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src) +  FUNC_ATTR_NONNULL_ALL +{ +  size_t src_len = strlen(src); +  bool quote_hit = true; +  char *start = dest; + +  if (src_len == 0) { +    // Need double quotation for empty argument. +    snprintf(dest, dest_remaining, "\"\""); +    return; +  } + +  if (NULL == strpbrk(src, " \t\"")) { +    // No quotation needed. +    xstrlcpy(dest, src, dest_remaining); +    return; +  } + +  if (NULL == strpbrk(src, "\"\\")) { +    // No embedded double quotes or backlashes, so I can just wrap quote marks. +    // around the whole thing. +    snprintf(dest, dest_remaining, "\"%s\"", src); +    return; +  } + +  // Expected input/output: +  //   input : hello"world +  //   output: "hello\"world" +  //   input : hello""world +  //   output: "hello\"\"world" +  //   input : hello\world +  //   output: hello\world +  //   input : hello\\world +  //   output: hello\\world +  //   input : hello\"world +  //   output: "hello\\\"world" +  //   input : hello\\"world +  //   output: "hello\\\\\"world" +  //   input : hello world\ +  //   output: "hello world\\" + +  assert(dest_remaining--); +  *(dest++) = NUL; +  assert(dest_remaining--); +  *(dest++) = '"'; +  for (size_t i = src_len; i > 0; i--) { +    assert(dest_remaining--); +    *(dest++) = src[i - 1]; +    if (quote_hit && src[i - 1] == '\\') { +      assert(dest_remaining--); +      *(dest++) = '\\'; +    } else if (src[i - 1] == '"') { +      quote_hit = true; +      assert(dest_remaining--); +      *(dest++) = '\\'; +    } else { +      quote_hit = false; +    } +  } +  assert(dest_remaining); +  *dest = '"'; + +  while (start < dest) { +    char tmp = *start; +    *start = *dest; +    *dest = tmp; +    start++; +    dest--; +  } +} + +/// Translate winpty error code to libuv error. +/// +/// @param[in]  winpty_errno  Winpty error code returned by winpty_error_code +///                           function. +/// +/// @returns  Error code of libuv error. +int translate_winpty_error(int winpty_errno) +{ +  if (winpty_errno <= 0) { +    return winpty_errno;  // If < 0 then it's already a libuv error. +  } + +  switch (winpty_errno) { +    case WINPTY_ERROR_OUT_OF_MEMORY:                return UV_ENOMEM; +    case WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED:  return UV_EAI_FAIL; +    case WINPTY_ERROR_LOST_CONNECTION:              return UV_ENOTCONN; +    case WINPTY_ERROR_AGENT_EXE_MISSING:            return UV_ENOENT; +    case WINPTY_ERROR_UNSPECIFIED:                   return UV_UNKNOWN; +    case WINPTY_ERROR_AGENT_DIED:                   return UV_ESRCH; +    case WINPTY_ERROR_AGENT_TIMEOUT:                return UV_ETIMEDOUT; +    case WINPTY_ERROR_AGENT_CREATION_FAILED:        return UV_EAI_FAIL; +    default:                                        return UV_UNKNOWN; +  } +} diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 8e2b37a1c1..1a4019e654 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -1,20 +1,27 @@  #ifndef NVIM_OS_PTY_PROCESS_WIN_H  #define NVIM_OS_PTY_PROCESS_WIN_H -#include "nvim/event/libuv_process.h" +#include <uv.h> +#include <winpty.h> + +#include "nvim/event/process.h" +#include "nvim/lib/queue.h"  typedef struct pty_process {    Process process;    char *term_name;    uint16_t width, height; +  winpty_t *winpty_object; +  HANDLE finish_wait; +  HANDLE process_handle; +  uv_timer_t wait_eof_timer;  } 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) ( \ -    (void)job, (void)width, (void)height, 0) -#define pty_process_teardown(loop) ((void)loop, 0) +// Structure used by build_cmd_line() +typedef struct arg_node { +  char *arg;  // pointer to argument. +  QUEUE node;  // QUEUE structure. +} ArgNode;  static inline PtyProcess pty_process_init(Loop *loop, void *data)  { @@ -23,7 +30,14 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)    rv.term_name = NULL;    rv.width = 80;    rv.height = 24; +  rv.winpty_object = NULL; +  rv.finish_wait = NULL; +  rv.process_handle = NULL;    return rv;  } +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process_win.h.generated.h" +#endif +  #endif  // NVIM_OS_PTY_PROCESS_WIN_H diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f8d519ab36..95973354bc 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -7440,13 +7440,7 @@ int number_width(win_T *wp)    return n;  } -/* - * Set size of the Vim shell. - * If 'mustset' is TRUE, we must set Rows and Columns, do not get the real - * window size (this is used for the :win command). - * If 'mustset' is FALSE, we may try to get the real window size and if - * it fails use 'width' and 'height'. - */ +/// Set dimensions of the Nvim application "shell".  void screen_resize(int width, int height)  {    static int busy = FALSE; @@ -7531,8 +7525,8 @@ void screen_resize(int width, int height)    --busy;  } -// Check if the new shell size is valid, correct it if it's too small or way -// too big. +/// Check if the new Nvim application "shell" dimensions are valid. +/// Correct it if it's too small or way too big.  void check_shellsize(void)  {    if (Rows < min_rows()) { diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 7f1e25900b..96de7224c5 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -207,7 +207,7 @@ nolog:  # New style of tests uses Vim script with assert calls.  These are easier  # to write and a lot easier to read and debug.  # Limitation: Only works with the +eval feature. -RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(VALGRIND) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin +RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin  newtests: newtestssilent  	@/bin/sh -c "if test -f messages && grep -q 'FAILED' messages; then \ diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 5de394de8e..f40e06ff33 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -218,3 +218,20 @@ func Test_diffoff()    bwipe!    bwipe!  endfunc + +func Test_setting_cursor() +  new Xtest1 +  put =range(1,90) +  wq +  new Xtest2 +  put =range(1,100) +  wq +   +  tabe Xtest2 +  $ +  diffsp Xtest1 +  tabclose + +  call delete('Xtest1') +  call delete('Xtest2') +endfunc diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 188a7ed0f3..9d61921988 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -336,6 +336,50 @@ func Test_window_width()    bw Xa Xb Xc  endfunc +func Test_equalalways_on_close() +  set equalalways +  vsplit +  windo split +  split +  wincmd J +  " now we have a frame top-left with two windows, a frame top-right with two +  " windows and a frame at the bottom, full-width. +  let height_1 = winheight(1) +  let height_2 = winheight(2) +  let height_3 = winheight(3) +  let height_4 = winheight(4) +  " closing the bottom window causes all windows to be resized. +  close +  call assert_notequal(height_1, winheight(1)) +  call assert_notequal(height_2, winheight(2)) +  call assert_notequal(height_3, winheight(3)) +  call assert_notequal(height_4, winheight(4)) +  call assert_equal(winheight(1), winheight(3)) +  call assert_equal(winheight(2), winheight(4)) + +  1wincmd w +  split +  4wincmd w +  resize + 5 +  " left column has three windows, equalized heights. +  " right column has two windows, top one a bit higher +  let height_1 = winheight(1) +  let height_2 = winheight(2) +  let height_4 = winheight(4) +  let height_5 = winheight(5) +  3wincmd w +  " closing window in left column equalizes heights in left column but not in +  " the right column +  close +  call assert_notequal(height_1, winheight(1)) +  call assert_notequal(height_2, winheight(2)) +  call assert_equal(height_4, winheight(3)) +  call assert_equal(height_5, winheight(4)) + +  only +  set equalalways& +endfunc +  func Test_window_jump_tag()    help    /iccf diff --git a/src/nvim/ui.c b/src/nvim/ui.c index a60c061949..b85a01814d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -193,7 +193,12 @@ void ui_refresh(void)    }    row = col = 0; + +  int save_p_lz = p_lz; +  p_lz = false;  // convince redrawing() to return true ...    screen_resize(width, height); +  p_lz = save_p_lz; +    for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) {      ui_set_external(i, ext_widgets[i]);    } diff --git a/src/nvim/version.c b/src/nvim/version.c index f4984864f3..f5b45caefc 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -273,8 +273,8 @@ static const int included_patches[] = {    // 682,    // 681,    // 680, -  // 679, -  // 678, +  679, +  678,    // 677,    // 676,    // 675, @@ -778,7 +778,7 @@ static const int included_patches[] = {    177,    176,    // 175, -  // 174, +  174,    // 173 NA    172,    // 171, @@ -908,7 +908,7 @@ static const int included_patches[] = {    47,    46,    // 45 NA -  // 44, +  44,    43,    42,    41, diff --git a/src/nvim/window.c b/src/nvim/window.c index faf5bceb56..081fc98816 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1878,6 +1878,7 @@ int win_close(win_T *win, int free_buf)    int dir;    int help_window = FALSE;    tabpage_T   *prev_curtab = curtab; +  frame_T *win_frame = win->w_frame->fr_parent;    if (last_window()) {      EMSG(_("E444: Cannot close last window")); @@ -2027,7 +2028,9 @@ int win_close(win_T *win, int free_buf)      check_cursor();    }    if (p_ea && (*p_ead == 'b' || *p_ead == dir)) { -    win_equal(curwin, true, dir); +    // If the frame of the closed window contains the new current window, +    // only resize that frame.  Otherwise resize all windows. +    win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);    } else {      win_comp_pos();    } | 
