diff options
Diffstat (limited to 'src')
86 files changed, 3590 insertions, 1260 deletions
| diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index c46c0bed6d..a166ee6c02 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -10,6 +10,11 @@ if(USE_GCOV)  endif()  endif() +if(WIN32) +  # tell MinGW compiler to enable wmain +  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") +endif() +  set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)  set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)  set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) @@ -111,6 +116,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}) @@ -156,7 +164,7 @@ if(NOT MSVC)    endif()  endif() -if(DEFINED MIN_LOG_LEVEL) +if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")    add_definitions(-DMIN_LOG_LEVEL=${MIN_LOG_LEVEL})  endif() @@ -350,6 +358,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 +427,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 +441,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,11 +540,7 @@ endfunction()  set(NO_SINGLE_CHECK_HEADERS    os/win_defs.h -  regexp_defs.h -  syntax_defs.h -  terminal.h -  undo.h -  undo_defs.h +  os/pty_process_win.h  )  foreach(hfile ${NVIM_HEADERS})    get_test_target(test-includes "${hfile}" relative_path texe) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 82de8fd4a2..c381e92dc7 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -793,7 +793,11 @@ Integer nvim_buf_add_highlight(Buffer buffer,      col_end = MAXCOL;    } -  int hlg_id = syn_name2id((char_u *)(hl_group.data ? hl_group.data : "")); +  int hlg_id = 0; +  if (hl_group.size > 0) { +    hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); +  } +    src_id = bufhl_add_hl(buf, (int)src_id, hlg_id, (linenr_T)line+1,                          (colnr_T)col_start+1, (colnr_T)col_end);    return src_id; 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/api/ui.c b/src/nvim/api/ui.c index 573be23d8e..afbee09c1c 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -215,6 +215,7 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error)  #undef UI_EXT_OPTION  } +/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().  static void push_call(UI *ui, char *name, Array args)  {    Array call = ARRAY_DICT_INIT; @@ -241,39 +242,7 @@ static void push_call(UI *ui, char *name, Array args)  static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)  {    Array args = ARRAY_DICT_INIT; -  Dictionary hl = ARRAY_DICT_INIT; - -  if (attrs.bold) { -    PUT(hl, "bold", BOOLEAN_OBJ(true)); -  } - -  if (attrs.underline) { -    PUT(hl, "underline", BOOLEAN_OBJ(true)); -  } - -  if (attrs.undercurl) { -    PUT(hl, "undercurl", BOOLEAN_OBJ(true)); -  } - -  if (attrs.italic) { -    PUT(hl, "italic", BOOLEAN_OBJ(true)); -  } - -  if (attrs.reverse) { -    PUT(hl, "reverse", BOOLEAN_OBJ(true)); -  } - -  if (attrs.foreground != -1) { -    PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); -  } - -  if (attrs.background != -1) { -    PUT(hl, "background", INTEGER_OBJ(attrs.background)); -  } - -  if (attrs.special != -1) { -    PUT(hl, "special", INTEGER_OBJ(attrs.special)); -  } +  Dictionary hl = hlattrs2dict(attrs);    ADD(args, DICTIONARY_OBJ(hl));    push_call(ui, "highlight_set", args); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 2bc31b2812..98f4410347 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -55,6 +55,47 @@ void nvim_command(String command, Error *err)    try_end(err);  } +/// Gets a highlight definition by name. +/// +/// @param name Highlight group name +/// @param rgb Export RGB colors +/// @param[out] err Error details, if any +/// @return Highlight definition map +/// @see nvim_get_hl_by_id +Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) +  FUNC_API_SINCE(3) +{ +  Dictionary result = ARRAY_DICT_INIT; +  int id = syn_name2id((const char_u *)name.data); + +  if (id == 0) { +    api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", +                  name.data); +    return result; +  } +  result = nvim_get_hl_by_id(id, rgb, err); +  return result; +} + +/// Gets a highlight definition by id. |hlID()| +/// +/// @param hl_id Highlight id as returned by |hlID()| +/// @param rgb Export RGB colors +/// @param[out] err Error details, if any +/// @return Highlight definition map +/// @see nvim_get_hl_by_name +Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) +  FUNC_API_SINCE(3) +{ +  Dictionary dic = ARRAY_DICT_INIT; +  if (syn_get_final_id((int)hl_id) == 0) { +    api_set_error(err, kErrorTypeException, "Invalid highlight id: %d", hl_id); +    return dic; +  } +  int attrcode = syn_id2attr((int)hl_id); +  return hl_get_attr_by_id(attrcode, rgb, err); +} +  /// Passes input keys to Nvim.  /// On VimL error: Does not fail, but updates v:errmsg.  /// @@ -255,12 +296,11 @@ free_vim_args:    return rv;  } -/// Execute lua code. Parameters might be passed, they are available inside -/// the chunk as `...`. The chunk can return a value. +/// Execute lua code. Parameters (if any) are available as `...` inside the +/// chunk. The chunk can return a value.  /// -/// To evaluate an expression, it must be prefixed with "return ". For -/// instance, to call a lua function with arguments sent in and get its -/// return value back, use the code "return my_function(...)". +/// Only statements are executed. To evaluate an expression, prefix it +/// with `return`: return my_function(...)  ///  /// @param code       lua code to execute  /// @param args       Arguments to the code @@ -423,29 +463,18 @@ void nvim_del_var(String name, Error *err)    dict_set_var(&globvardict, name, NIL, true, false, err);  } -/// Sets a global variable -///  /// @deprecated -/// -/// @param name     Variable name -/// @param value    Variable value -/// @param[out] err Error details, if any +/// @see nvim_set_var  /// @return Old value or nil if there was no previous value. -/// -///         @warning It may return nil if there was no previous value -///                  or if previous value was `v:null`. +/// @warning May return nil if there was no previous value +///          OR if previous value was `v:null`.  Object vim_set_var(String name, Object value, Error *err)  {    return dict_set_var(&globvardict, name, value, false, true, err);  } -/// Removes a global variable -///  /// @deprecated -/// -/// @param name     Variable name -/// @param[out] err Error details, if any -/// @return Old value +/// @see nvim_del_var  Object vim_del_var(String name, Error *err)  {    return dict_set_var(&globvardict, name, NIL, true, true, err); @@ -484,7 +513,8 @@ void nvim_set_option(String name, Object value, Error *err)    set_option_to(NULL, SREQ_GLOBAL, name, value, err);  } -/// Writes a message to vim output buffer +/// Writes a message to the Vim output buffer. Does not append "\n", the +/// message is buffered (won't display) until a linefeed is written.  ///  /// @param str Message  void nvim_out_write(String str) @@ -493,7 +523,8 @@ void nvim_out_write(String str)    write_msg(str, false);  } -/// Writes a message to vim error buffer +/// Writes a message to the Vim error buffer. Does not append "\n", the +/// message is buffered (won't display) until a linefeed is written.  ///  /// @param str Message  void nvim_err_write(String str) @@ -502,8 +533,8 @@ void nvim_err_write(String str)    write_msg(str, true);  } -/// Writes a message to vim error buffer. Appends a linefeed to ensure all -/// contents are written. +/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is +/// flushed (and displayed).  ///  /// @param str Message  /// @see nvim_err_write() diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c new file mode 100644 index 0000000000..fc421116ea --- /dev/null +++ b/src/nvim/aucmd.c @@ -0,0 +1,41 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/os/os.h" +#include "nvim/fileio.h" +#include "nvim/vim.h" +#include "nvim/main.h" +#include "nvim/ui.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "aucmd.c.generated.h" +#endif + +static void focusgained_event(void **argv) +{ +  bool *gainedp = argv[0]; +  do_autocmd_focusgained(*gainedp); +  xfree(gainedp); +} +void aucmd_schedule_focusgained(bool gained) +{ +  bool *gainedp = xmalloc(sizeof(*gainedp)); +  *gainedp = gained; +  loop_schedule_deferred(&main_loop, +                         event_create(focusgained_event, 1, gainedp)); +} + +static void do_autocmd_focusgained(bool gained) +  FUNC_ATTR_NONNULL_ALL +{ +  static bool recursive = false; + +  if (recursive) { +    return;  // disallow recursion +  } +  recursive = true; +  apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), +                 NULL, NULL, false, curbuf); +  recursive = false; +} + diff --git a/src/nvim/aucmd.h b/src/nvim/aucmd.h new file mode 100644 index 0000000000..6570ba7a92 --- /dev/null +++ b/src/nvim/aucmd.h @@ -0,0 +1,9 @@ +#ifndef NVIM_AUCMD_H +#define NVIM_AUCMD_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "aucmd.h.generated.h" +#endif + +#endif  // NVIM_AUCMD_H + diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 724a8578ac..fc5bb90973 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3069,8 +3069,8 @@ static bool ti_change(char_u *str, char_u **last)  /// Set current window title  void resettitle(void)  { -  ui_call_set_title(cstr_as_string((char *)lasttitle));    ui_call_set_icon(cstr_as_string((char *)lasticon)); +  ui_call_set_title(cstr_as_string((char *)lasttitle));    ui_flush();  } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 403ef65c4f..577fc13a31 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -762,7 +762,7 @@ bool vim_isIDc(int c)  }  /// Check that "c" is a keyword character: -/// Letters and characters from 'iskeyword' option for current buffer. +/// Letters and characters from 'iskeyword' option for the current buffer.  /// For multi-byte characters mb_get_class() is used (builtin rules).  ///  /// @param  c  character to check diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 60002f3cea..0e97e2203f 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -375,17 +375,30 @@ void check_cursor_col_win(win_T *win)      win->w_cursor.col = 0;    } -  /* If virtual editing is on, we can leave the cursor on the old position, -   * only we must set it to virtual.  But don't do it when at the end of the -   * line. */ -  if (oldcol == MAXCOL) +  // If virtual editing is on, we can leave the cursor on the old position, +  // only we must set it to virtual.  But don't do it when at the end of the +  // line. +  if (oldcol == MAXCOL) {      win->w_cursor.coladd = 0; -  else if (ve_flags == VE_ALL) { -    if (oldcoladd > win->w_cursor.col) +  } else if (ve_flags == VE_ALL) { +    if (oldcoladd > win->w_cursor.col) {        win->w_cursor.coladd = oldcoladd - win->w_cursor.col; -    else -      /* avoid weird number when there is a miscalculation or overflow */ + +      // Make sure that coladd is not more than the char width. +      // Not for the last character, coladd is then used when the cursor +      // is actually after the last character. +      if (win->w_cursor.col + 1 < len && win->w_cursor.coladd > 0) { +        int cs, ce; + +        getvcol(win, &win->w_cursor, &cs, NULL, &ce); +        if (win->w_cursor.coladd > ce - cs) { +          win->w_cursor.coladd = ce - cs; +        } +      } +    } else { +      // avoid weird number when there is a miscalculation or overflow        win->w_cursor.coladd = 0; +    }    }  } diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 7da6665cb7..cc0f3b2629 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -135,6 +135,20 @@ void diff_buf_add(buf_T *buf)    EMSGN(_("E96: Cannot diff more than %" PRId64 " buffers"), DB_COUNT);  } +/// +/// Remove all buffers to make diffs for. +/// +static void diff_buf_clear(void) +{ +  for (int i = 0; i < DB_COUNT; i++) { +    if (curtab->tp_diffbuf[i] != NULL) { +      curtab->tp_diffbuf[i] = NULL; +      curtab->tp_diff_invalid = true; +      diff_redraw(true); +    } +  } +} +  /// Find buffer "buf" in the list of diff buffers for the current tab page.  ///  /// @param buf The buffer to find. @@ -1033,10 +1047,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 @@ -1178,6 +1189,11 @@ void ex_diffoff(exarg_T *eap)      diffwin |= wp->w_p_diff;    } +  // Also remove hidden buffers from the list. +  if (eap->forceit) { +    diff_buf_clear(); +  } +    // Remove "hor" from from 'scrollopt' if there are no diff windows left.    if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) {      do_cmdline_cmd("set sbo-=hor"); @@ -2463,25 +2479,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 +2509,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 +2520,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/edit.c b/src/nvim/edit.c index ca62679fab..2bafb77fef 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -974,14 +974,6 @@ static int insert_handle_key(InsertState *s)      multiqueue_process_events(main_loop.events);      break; -  case K_FOCUSGAINED:  // Neovim has been given focus -    apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); -    break; - -  case K_FOCUSLOST:   // Neovim has lost focus -    apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); -    break; -    case K_HOME:        // <Home>    case K_KHOME:    case K_S_HOME: @@ -2406,6 +2398,7 @@ void set_completion(colnr_T startcol, list_T *list)      ins_compl_prep(' ');    }    ins_compl_clear(); +  ins_compl_free();    compl_direction = FORWARD;    if (startcol > curwin->w_cursor.col) @@ -3166,8 +3159,7 @@ static bool ins_compl_prep(int c)    /* Ignore end of Select mode mapping and mouse scroll buttons. */    if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP -      || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT -      || c == K_FOCUSGAINED || c == K_FOCUSLOST) { +      || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT) {      return retval;    } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c42929ef7c..aab777955c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6733,6 +6733,39 @@ static void prepare_assert_error(garray_T *gap)    }  } +// Append "str" to "gap", escaping unprintable characters. +// Changes NL to \n, CR to \r, etc. +static void ga_concat_esc(garray_T *gap, char_u *str) +{ +  char_u *p; +  char_u buf[NUMBUFLEN]; + +  if (str == NULL) { +    ga_concat(gap, (char_u *)"NULL"); +    return; +  } + +  for (p = str; *p != NUL; p++) { +    switch (*p) { +      case BS: ga_concat(gap, (char_u *)"\\b"); break; +      case ESC: ga_concat(gap, (char_u *)"\\e"); break; +      case FF: ga_concat(gap, (char_u *)"\\f"); break; +      case NL: ga_concat(gap, (char_u *)"\\n"); break; +      case TAB: ga_concat(gap, (char_u *)"\\t"); break; +      case CAR: ga_concat(gap, (char_u *)"\\r"); break; +      case '\\': ga_concat(gap, (char_u *)"\\\\"); break; +      default: +        if (*p < ' ') { +          vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); +          ga_concat(gap, buf); +        } else { +          ga_append(gap, *p); +        } +        break; +    } +  } +} +  // Fill "gap" with information about an assert error.  static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,                                char_u *exp_str, typval_T *exp_tv, @@ -6747,28 +6780,30 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,    } else {      if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) {        ga_concat(gap, (char_u *)"Pattern "); +    } else if (atype == ASSERT_NOTEQUAL) { +      ga_concat(gap, (char_u *)"Expected not equal to ");      } else {        ga_concat(gap, (char_u *)"Expected ");      }      if (exp_str == NULL) { -      tofree = (char_u *) encode_tv2string(exp_tv, NULL); -      ga_concat(gap, tofree); +      tofree = (char_u *)encode_tv2string(exp_tv, NULL); +      ga_concat_esc(gap, tofree);        xfree(tofree);      } else { -      ga_concat(gap, exp_str); +      ga_concat_esc(gap, exp_str);      } -    tofree = (char_u *)encode_tv2string(got_tv, NULL); -    if (atype == ASSERT_MATCH) { -      ga_concat(gap, (char_u *)" does not match "); -    } else if (atype == ASSERT_NOTMATCH) { -      ga_concat(gap, (char_u *)" does match "); -    } else if (atype == ASSERT_NOTEQUAL) { -      ga_concat(gap, (char_u *)" differs from "); -    } else { -      ga_concat(gap, (char_u *)" but got "); +    if (atype != ASSERT_NOTEQUAL) { +      if (atype == ASSERT_MATCH) { +        ga_concat(gap, (char_u *)" does not match "); +      } else if (atype == ASSERT_NOTMATCH) { +        ga_concat(gap, (char_u *)" does match "); +      } else { +        ga_concat(gap, (char_u *)" but got "); +      } +      tofree = (char_u *)encode_tv2string(got_tv, NULL); +      ga_concat_esc(gap, tofree); +      xfree(tofree);      } -    ga_concat(gap, tofree); -    xfree(tofree);    }  } @@ -11031,6 +11066,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 +11076,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 +11102,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 +11163,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); @@ -17488,7 +17528,7 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)    tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);  } -/// Writes list of strings to file +/// Write "list" of strings to file "fd".  ///  /// @param  fp  File to write to.  /// @param[in]  list  List to write. @@ -22770,7 +22810,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)  bool eval_has_provider(const char *name)  { -#define check_provider(name) \ +#define CHECK_PROVIDER(name) \    if (has_##name == -1) { \      has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \      if (!has_##name) { \ @@ -22786,17 +22826,17 @@ bool eval_has_provider(const char *name)    static int has_python3 = -1;    static int has_ruby = -1; -  if (!strcmp(name, "clipboard")) { -    check_provider(clipboard); +  if (strequal(name, "clipboard")) { +    CHECK_PROVIDER(clipboard);      return has_clipboard; -  } else if (!strcmp(name, "python3")) { -    check_provider(python3); +  } else if (strequal(name, "python3")) { +    CHECK_PROVIDER(python3);      return has_python3; -  } else if (!strcmp(name, "python")) { -    check_provider(python); +  } else if (strequal(name, "python")) { +    CHECK_PROVIDER(python);      return has_python; -  } else if (!strcmp(name, "ruby")) { -    check_provider(ruby); +  } else if (strequal(name, "ruby")) { +    CHECK_PROVIDER(ruby);      return has_ruby;    } @@ -22810,3 +22850,32 @@ void eval_format_source_name_line(char *buf, size_t bufsize)             (sourcing_name ? sourcing_name : (char_u *)"?"),             (sourcing_name ? sourcing_lnum : 0));  } + +/// ":checkhealth [plugins]" +void ex_checkhealth(exarg_T *eap) +{ +  bool found = !!find_func((char_u *)"health#check"); +  if (!found +      && script_autoload("health#check", sizeof("health#check") - 1, false)) { +    found = !!find_func((char_u *)"health#check"); +  } +  if (!found) { +    const char *vimruntime_env = os_getenv("VIMRUNTIME"); +    if (vimruntime_env == NULL) { +      EMSG(_("E5009: $VIMRUNTIME is empty or unset")); +      return; +    } else { +      EMSG2(_("E5009: Invalid $VIMRUNTIME: %s"), os_getenv("VIMRUNTIME")); +      return; +    } +  } + +  size_t bufsize = STRLEN(eap->arg) + sizeof("call health#check('')"); +  char *buf = xmalloc(bufsize); +  snprintf(buf, bufsize, "call health#check('%s')", eap->arg); + +  do_cmdline_cmd(buf); + +  xfree(buf); +} + 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/event/loop.c b/src/nvim/event/loop.c index 25701a1621..5adf16c0f3 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -59,7 +59,14 @@ void loop_poll_events(Loop *loop, int ms)    multiqueue_process_events(loop->fast_events);  } -// Schedule an event from another thread +/// Schedules an event from another thread. +/// +/// @note Event is queued into `fast_events`, which is processed outside of the +///       primary `events` queue by loop_poll_events(). For `main_loop`, that +///       means `fast_events` is NOT processed in an "editor mode" +///       (VimState.execute), so redraw and other side-effects are likely to be +///       skipped. +/// @see loop_schedule_deferred  void loop_schedule(Loop *loop, Event event)  {    uv_mutex_lock(&loop->mutex); @@ -68,6 +75,24 @@ void loop_schedule(Loop *loop, Event event)    uv_mutex_unlock(&loop->mutex);  } +/// Schedules an event from another thread. Unlike loop_schedule(), the event +/// is forwarded to `Loop.events`, instead of being processed immediately. +/// +/// @see loop_schedule +void loop_schedule_deferred(Loop *loop, Event event) +{ +  Event *eventp = xmalloc(sizeof(*eventp)); +  *eventp = event; +  loop_schedule(loop, event_create(loop_deferred_event, 2, loop, eventp)); +} +static void loop_deferred_event(void **argv) +{ +  Loop *loop = argv[0]; +  Event *eventp = argv[1]; +  multiqueue_put_event(loop->events, *eventp); +  xfree(eventp); +} +  void loop_on_put(MultiQueue *queue, void *data)  {    Loop *loop = data; diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index e7d7bdd483..b0ddc59469 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -16,10 +16,28 @@ KLIST_INIT(WatcherPtr, WatcherPtr, _noop)  typedef struct loop {    uv_loop_t uv; -  MultiQueue *events, *fast_events, *thread_events; +  MultiQueue *events; +  MultiQueue *thread_events; +  // Immediate events: +  //    "Events that should be processed after exiting uv_run() (to avoid +  //    recursion), but before returning from loop_poll_events()." +  //    502aee690c980fcb3cfcb3f211dcfad06103db46 +  // Practical consequence: these events are processed by +  //    state_enter()..os_inchar() +  // whereas "regular" (main_loop.events) events are processed by +  //    state_enter()..VimState.execute() +  // But state_enter()..os_inchar() can be "too early" if you want the event +  // to trigger UI updates and other user-activity-related side-effects. +  MultiQueue *fast_events; + +  // used by process/job-control subsystem    klist_t(WatcherPtr) *children;    uv_signal_t children_watcher; -  uv_timer_t children_kill_timer, poll_timer; +  uv_timer_t children_kill_timer; + +  // generic timer, used by loop_poll_events() +  uv_timer_t poll_timer; +    size_t children_stop_requests;    uv_async_t async;    uv_mutex_t mutex; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index c936583841..8371d3cd48 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -233,8 +233,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL    switch (proc->type) {      case kProcessTypeUv:        // Close the process's stdin. If the process doesn't close its own -      // stdout/stderr, they will be closed when it exits(possibly due to being -      // terminated after a timeout) +      // stdout/stderr, they will be closed when it exits (voluntarily or not).        process_close_in(proc);        ILOG("Sending SIGTERM to pid %d", proc->pid);        uv_kill(proc->pid, SIGTERM); diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 854af474b2..2c4db08b30 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -118,7 +118,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)          // to `alloc_cb` will return the same unused pointer(`rbuffer_produced`          // won't be called)          && cnt != 0) { -      DLOG("Closing Stream (%p): %s (%s)", stream, +      DLOG("closing Stream: %p: %s (%s)", stream,             uv_err_name((int)cnt), os_strerror((int)cnt));        // Read error or EOF, either way stop the stream and invoke the callback        // with eof == true diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 60ceff9b24..7c865bfe1e 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -7,6 +7,7 @@  #include <uv.h> +#include "nvim/log.h"  #include "nvim/rbuffer.h"  #include "nvim/macros.h"  #include "nvim/event/stream.h" @@ -81,6 +82,7 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data)    FUNC_ATTR_NONNULL_ARG(1)  {    assert(!stream->closed); +  DLOG("closing Stream: %p", stream);    stream->closed = true;    stream->close_cb = on_stream_close;    stream->close_cb_data = data; diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index f453e5898d..320006890d 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -8,6 +8,7 @@  #include <uv.h> +#include "nvim/log.h"  #include "nvim/event/loop.h"  #include "nvim/event/wstream.h"  #include "nvim/vim.h" diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index a555fb77e8..918e7a0c91 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3332,10 +3332,12 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout)      sub = regtilde(sub, p_magic);    // Check for a match on each line. +  // If preview: limit to max('cmdwinheight', viewport).    linenr_T line2 = eap->line2;    for (linenr_T lnum = eap->line1; -       lnum <= line2 && !(got_quit || aborting()) -       && (!preview || matched_lines.size <= (size_t)p_cwh); +       lnum <= line2 && !got_quit && !aborting() +       && (!preview || matched_lines.size < (size_t)p_cwh +           || lnum <= curwin->w_botline);         lnum++) {      long nmatch = vim_regexec_multi(®match, curwin, curbuf, lnum,                                      (colnr_T)0, NULL); @@ -3500,6 +3502,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout)            setmouse();                   /* disable mouse in xterm */            curwin->w_cursor.col = regmatch.startpos[0].col; +          if (curwin->w_p_crb) { +            do_check_cursorbind(); +          } +            /* When 'cpoptions' contains "u" don't sync undo when             * asking for confirmation. */            if (vim_strchr(p_cpo, CPO_UNDO) != NULL) @@ -3659,6 +3665,42 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout)           * use "\=col("."). */          curwin->w_cursor.col = regmatch.startpos[0].col; +        // When the match included the "$" of the last line it may +        // go beyond the last line of the buffer. +        if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { +          nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; +          skip_match = true; +        } + +#define ADJUST_SUB_FIRSTLNUM() \ +        do { \ +          /* For a multi-line match, make a copy of the last matched */ \ +          /* line and continue in that one. */ \ +          if (nmatch > 1) { \ +            sub_firstlnum += nmatch - 1; \ +            xfree(sub_firstline); \ +            sub_firstline = vim_strsave(ml_get(sub_firstlnum)); \ +            /* When going beyond the last line, stop substituting. */ \ +            if (sub_firstlnum <= line2) { \ +              do_again = true; \ +            } else { \ +              subflags.do_all = false; \ +            } \ +          } \ +          if (skip_match) { \ +            /* Already hit end of the buffer, sub_firstlnum is one */ \ +            /* less than what it ought to be. */ \ +            xfree(sub_firstline); \ +            sub_firstline = vim_strsave((char_u *)""); \ +            copycol = 0; \ +          } \ +        } while (0) + +        if (preview && !has_second_delim) { +          ADJUST_SUB_FIRSTLNUM(); +          goto skip; +        } +          // 3. Substitute the string. During 'inccommand' preview only do this if          //    there is a replace pattern.          if (!preview || has_second_delim) { @@ -3685,13 +3727,6 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout)              goto skip;            } -          // When the match included the "$" of the last line it may -          // go beyond the last line of the buffer. -          if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { -            nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; -            skip_match = true; -          } -            // Need room for:            // - result so far in new_start (not for first sub in line)            // - original text up to match @@ -3722,30 +3757,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout)            // is beyond the end of the line after the substitution.            curwin->w_cursor.col = 0; -          // For a multi-line match, make a copy of the last matched -          // line and continue in that one. -          if (nmatch > 1) { -            sub_firstlnum += nmatch - 1; -            xfree(sub_firstline); -            sub_firstline = vim_strsave(ml_get(sub_firstlnum)); -            // When going beyond the last line, stop substituting. -            if (sub_firstlnum <= line2) { -              do_again = true; -            } else { -              subflags.do_all = false; -            } -          } -            // Remember next character to be copied.            copycol = regmatch.endpos[0].col; -          if (skip_match) { -            // Already hit end of the buffer, sub_firstlnum is one -            // less than what it ought to be. -            xfree(sub_firstline); -            sub_firstline = vim_strsave((char_u *)""); -            copycol = 0; -          } +          ADJUST_SUB_FIRSTLNUM();            // Now the trick is to replace CTRL-M chars with a real line            // break.  This would make it impossible to insert a CTRL-M in @@ -4002,6 +4017,7 @@ skip:    kv_destroy(matched_lines);    return preview_buf; +#undef ADJUST_SUB_FIRSTLNUM  }  // NOLINT(readability/fn_size)  /* diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 5a578cd088..e57e662039 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -451,6 +451,12 @@ return {      func='ex_changes',    },    { +    command='checkhealth', +    flags=bit.bor(EXTRA, TRLBAR), +    addr_type=ADDR_LINES, +    func='ex_checkhealth', +  }, +  {      command='checkpath',      flags=bit.bor(TRLBAR, BANG, CMDWIN),      addr_type=ADDR_LINES, @@ -614,7 +620,7 @@ return {    },    {      command='cquit', -    flags=bit.bor(TRLBAR, BANG), +    flags=bit.bor(RANGE, NOTADR, COUNT, ZEROR, TRLBAR, BANG),      addr_type=ADDR_LINES,      func='ex_cquit',    }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 1a728647ca..371f7b3bce 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); @@ -2318,16 +2319,6 @@ static void source_callback(char_u *fname, void *cookie)    (void)do_source(fname, false, DOSO_NONE);  } -/// Source the file "name" from all directories in 'runtimepath'. -/// "name" can contain wildcards. -/// When "flags" has DIP_ALL: source all files, otherwise only the first one. -/// -/// return FAIL when no file could be sourced, OK otherwise. -int source_runtime(char_u *name, int flags) -{ -  return do_in_runtimepath(name, flags, source_callback, NULL); -} -  /// Find the file "name" in all directories in "path" and invoke  /// "callback(fname, cookie)".  /// "name" can contain wildcards. @@ -2433,21 +2424,21 @@ int do_in_path(char_u *path, char_u *name, int flags,    return did_one ? OK : FAIL;  } -/// Find "name" in 'runtimepath'.  When found, invoke the callback function for +/// Find "name" in "path".  When found, invoke the callback function for  /// it: callback(fname, "cookie")  /// When "flags" has DIP_ALL repeat for all matches, otherwise only the first  /// one is used.  /// Returns OK when at least one match found, FAIL otherwise. -/// If "name" is NULL calls callback for each entry in runtimepath. Cookie is +/// If "name" is NULL calls callback for each entry in "path". Cookie is  /// passed by reference in this case, setting it to NULL indicates that callback  /// has done its job. -int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, -                      void *cookie) +int do_in_path_and_pp(char_u *path, char_u *name, int flags, +                      DoInRuntimepathCB callback, void *cookie)  {    int done = FAIL;    if ((flags & DIP_NORTP) == 0) { -    done = do_in_path(p_rtp, name, flags, callback, cookie); +    done = do_in_path(path, name, flags, callback, cookie);    }    if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { @@ -2475,6 +2466,29 @@ int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback,    return done;  } +/// Just like do_in_path_and_pp(), using 'runtimepath' for "path". +int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, +                      void *cookie) +{ +  return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); +} + +/// Source the file "name" from all directories in 'runtimepath'. +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int source_runtime(char_u *name, int flags) +{ +  return source_in_path(p_rtp, name, flags); +} + +/// Just like source_runtime(), but use "path" instead of 'runtimepath'. +int source_in_path(char_u *path, char_u *name, int flags) +{ +  return do_in_path_and_pp(path, name, flags, source_callback, NULL); +} +  // Expand wildcards in "pat" and invoke do_source() for each match.  static void source_all_matches(char_u *pat)  { @@ -2497,6 +2511,7 @@ static int APP_BOTH;  static void add_pack_plugin(char_u *fname, void *cookie)  {    char_u *p4, *p3, *p2, *p1, *p; +  char_u *buf = NULL;    char *const ffname = fix_fname((char *)fname); @@ -2524,26 +2539,30 @@ static void add_pack_plugin(char_u *fname, void *cookie)      // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences      size_t fname_len = strlen(ffname);      const char *insp = (const char *)p_rtp; -    for (;;) { -      if (path_fnamencmp(insp, ffname, fname_len) == 0) { -        break; +    buf = try_malloc(MAXPATHL); +    if (buf == NULL) { +      goto theend; +    } +    while (*insp != NUL) { +      copy_option_part((char_u **)&insp, buf, MAXPATHL, ","); +      add_pathsep((char *)buf); +      char *const rtp_ffname = fix_fname((char *)buf); +      if (rtp_ffname == NULL) { +        goto theend;        } -      insp = strchr(insp, ','); -      if (insp == NULL) { +      bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0; +      xfree(rtp_ffname); +      if (match) {          break;        } -      insp++;      } -    if (insp == NULL) { +    if (*insp == NUL) {        // not found, append at the end        insp = (const char *)p_rtp + STRLEN(p_rtp);      } else {        // append after the matching directory. -      insp += strlen(ffname); -      while (*insp != NUL && *insp != ',') { -        insp++; -      } +      insp--;      }      *p4 = c; @@ -2613,26 +2632,35 @@ static void add_pack_plugin(char_u *fname, void *cookie)    }  theend: +  xfree(buf);    xfree(ffname);  } -static bool did_source_packages = false; +/// Add all packages in the "start" directory to 'runtimepath'. +void add_pack_start_dirs(void) +{ +  do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR,  // NOLINT +             add_pack_plugin, &APP_ADD_DIR); +} + +/// Load plugins from all packages in the "start" directory. +void load_start_packages(void) +{ +  did_source_packages = true; +  do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR,  // NOLINT +             add_pack_plugin, &APP_LOAD); +}  // ":packloadall"  // Find plugins in the package directories and source them. -// "eap" is NULL when invoked during startup.  void ex_packloadall(exarg_T *eap)  { -  if (!did_source_packages || (eap != NULL && eap->forceit)) { -    did_source_packages = true; - +  if (!did_source_packages || eap->forceit) {      // First do a round to add all directories to 'runtimepath', then load      // the plugins. This allows for plugins to use an autoload directory      // of another plugin. -    do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR,  // NOLINT -               add_pack_plugin, &APP_ADD_DIR); -    do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR,  // NOLINT -               add_pack_plugin, &APP_LOAD); +    add_pack_start_dirs(); +    load_start_packages();    }  } @@ -3610,18 +3638,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 +3674,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_docmd.c b/src/nvim/ex_docmd.c index 6e7938046a..f64c9fded8 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,6 +9,7 @@  #include <string.h>  #include <stdbool.h>  #include <stdint.h> +#include <stdlib.h>  #include <inttypes.h>  #include "nvim/vim.h" @@ -1668,8 +1669,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,      if (*ea.cmd == ';') {        if (!ea.skip) {          curwin->w_cursor.lnum = ea.line2; -        // Don't leave the cursor on an illegal line (caused by ';') -        check_cursor_lnum(); +        // don't leave the cursor on an illegal line or column +        check_cursor();        }      } else if (*ea.cmd != ',') {        break; @@ -1813,7 +1814,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,      if (text_locked() && !(ea.argt & CMDWIN)          && !IS_USER_CMDIDX(ea.cmdidx)) {        // Command not allowed when editing the command line. -      errormsg = get_text_locked_msg(); +      errormsg = (char_u *)_(get_text_locked_msg());        goto doend;      }      /* Disallow editing another buffer when "curbuf_lock" is set. @@ -5995,7 +5996,7 @@ static void ex_quit(exarg_T *eap)   */  static void ex_cquit(exarg_T *eap)  { -  getout(1); +  getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE);  }  /* @@ -8810,11 +8811,12 @@ makeopens (          && buf->b_fname != NULL          && buf->b_p_bl) {        if (fprintf(fd, "badd +%" PRId64 " ", -                  buf->b_wininfo == NULL ? -                    (int64_t)1L : -                    (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 -          || ses_fname(fd, buf, &ssop_flags) == FAIL) +                  buf->b_wininfo == NULL +                  ? (int64_t)1L +                  : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 +          || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {          return FAIL; +      }      }    } @@ -8885,11 +8887,13 @@ makeopens (            && !bt_nofile(wp->w_buffer)            ) {          if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 -            || ses_fname(fd, wp->w_buffer, &ssop_flags) == FAIL) +            || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) {            return FAIL; -        need_tabnew = FALSE; -        if (!wp->w_arg_idx_invalid) +        } +        need_tabnew = false; +        if (!wp->w_arg_idx_invalid) {            edited_win = wp; +        }          break;        }      } @@ -8933,6 +8937,8 @@ makeopens (      // resized when moving between windows.      // Do this before restoring the view, so that the topline and the      // cursor can be set.  This is done again below. +    // winminheight and winminwidth need to be set to avoid an error if the +    // user has set winheight or winwidth.      if (put_line(fd, "set winminheight=1 winminwidth=1 winheight=1 winwidth=1")          == FAIL) {        return FAIL; @@ -9221,24 +9227,35 @@ put_view (      if (wp->w_buffer->b_ffname != NULL          && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)          ) { -      /* -       * Editing a file in this buffer: use ":edit file". -       * This may have side effects! (e.g., compressed or network file). -       */ -      if (fputs("edit ", fd) < 0 -          || ses_fname(fd, wp->w_buffer, flagp) == FAIL) +      // Editing a file in this buffer: use ":edit file". +      // This may have side effects! (e.g., compressed or network file). +      // +      // Note, if a buffer for that file already exists, use :badd to +      // edit that buffer, to not lose folding information (:edit resets +      // folds in other buffers) +      if (fputs("if bufexists('", fd) < 0 +          || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL +          || fputs("') | buffer ", fd) < 0 +          || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL +          || fputs(" | else | edit ", fd) < 0 +          || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL +          || fputs(" | endif", fd) < 0 +          || put_eol(fd) == FAIL) {          return FAIL; +      }      } else { -      /* No file in this buffer, just make it empty. */ -      if (put_line(fd, "enew") == FAIL) +      // No file in this buffer, just make it empty. +      if (put_line(fd, "enew") == FAIL) {          return FAIL; +      }        if (wp->w_buffer->b_ffname != NULL) { -        /* The buffer does have a name, but it's not a file name. */ +        // The buffer does have a name, but it's not a file name.          if (fputs("file ", fd) < 0 -            || ses_fname(fd, wp->w_buffer, flagp) == FAIL) +            || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) {            return FAIL; +        }        } -      do_cursor = FALSE; +      do_cursor = false;      }    } @@ -9378,7 +9395,7 @@ ses_arglist (          (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE);          s = buf;        } -      if (fputs("argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL +      if (fputs("$argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL            || put_eol(fd) == FAIL) {          xfree(buf);          return FAIL; @@ -9389,12 +9406,10 @@ ses_arglist (    return OK;  } -/* - * Write a buffer name to the session file. - * Also ends the line. - * Returns FAIL if writing fails. - */ -static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp) +/// Write a buffer name to the session file. +/// Also ends the line, if "add_eol" is true. +/// Returns FAIL if writing fails. +static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol)  {    char_u      *name; @@ -9411,8 +9426,10 @@ static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp)      name = buf->b_sfname;    else      name = buf->b_ffname; -  if (ses_put_fname(fd, name, flagp) == FAIL || put_eol(fd) == FAIL) +  if (ses_put_fname(fd, name, flagp) == FAIL +      || (add_eol && put_eol(fd) == FAIL)) {      return FAIL; +  }    return OK;  } @@ -9835,7 +9852,7 @@ static void ex_terminal(exarg_T *eap)    if (*eap->arg != NUL) {  // Run {cmd} in 'shell'.      char *name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\");      snprintf(ex_cmd, sizeof(ex_cmd), -             ":enew%s | call termopen(\"%s\") | startinsert", +             ":enew%s | call termopen(\"%s\")",               eap->forceit ? "!" : "", name);      xfree(name);    } else {  // No {cmd}: run the job with tokenized 'shell'. @@ -9857,7 +9874,7 @@ static void ex_terminal(exarg_T *eap)      shell_free_argv(argv);      snprintf(ex_cmd, sizeof(ex_cmd), -             ":enew%s | call termopen([%s]) | startinsert", +             ":enew%s | call termopen([%s])",               eap->forceit ? "!" : "", shell_argv + 1);    } 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..54e5bcb9ff 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);      } @@ -1571,14 +1620,6 @@ static int command_line_handle_key(CommandLineState *s)      }      return command_line_not_changed(s); -  case K_FOCUSGAINED:  // Neovim has been given focus -    apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); -    return command_line_not_changed(s); - -  case K_FOCUSLOST:   // Neovim has lost focus -    apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); -    return command_line_not_changed(s); -    default:      // Normal character with no special meaning.  Just set mod_mask      // to 0x0 so that typing Shift-Space in the GUI doesn't enter @@ -1790,41 +1831,54 @@ 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; + +  int msg_silent_saved = msg_silent; +  msg_silent = 0; + +  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) +  msg_silent = msg_silent_saved; +  // 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 +2339,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 +2685,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); +    } +  }  }  /* @@ -5395,6 +5718,7 @@ static int ex_window(void)    i = RedrawingDisabled;    RedrawingDisabled = 0; +  int save_count = save_batch_count();    /*     * Call the main loop until <CR> or CTRL-C is typed. @@ -5403,6 +5727,7 @@ static int ex_window(void)    normal_enter(true, false);    RedrawingDisabled = i; +  restore_batch_count(save_count);    int save_KeyTyped = KeyTyped; diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index ca0134043c..36562c0be9 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -74,6 +74,7 @@ local get_flags = function(o)      {'gettext'},      {'noglob'},      {'normal_fname_chars', 'P_NFNAME'}, +    {'normal_dname_chars', 'P_NDNAME'},      {'pri_mkrc'},      {'deny_in_modelines', 'P_NO_ML'},      {'deny_duplicates', 'P_NODUP'}, diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index a22b716bb6..4f8a8528a0 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -92,17 +92,15 @@ static int typeahead_char = 0;          /* typeahead char that's not flushed */   */  static int block_redo = FALSE; -/* - * Make a hash value for a mapping. - * "mode" is the lower 4 bits of the State for the mapping. - * "c1" is the first character of the "lhs". - * Returns a value between 0 and 255, index in maphash. - * Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode. - */ +// Make a hash value for a mapping. +// "mode" is the lower 4 bits of the State for the mapping. +// "c1" is the first character of the "lhs". +// Returns a value between 0 and 255, index in maphash. +// Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode.  #define MAP_HASH(mode, \                   c1) (((mode) & \                         (NORMAL + VISUAL + SELECTMODE + \ -                        OP_PENDING)) ? (c1) : ((c1) ^ 0x80)) +                        OP_PENDING + TERM_FOCUS)) ? (c1) : ((c1) ^ 0x80))  // Each mapping is put in one of the MAX_MAPHASH hash lists,  // to speed up finding it. @@ -870,20 +868,15 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent)    addlen = (int)STRLEN(str); -  /* -   * Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off] -   */    if (offset == 0 && addlen <= typebuf.tb_off) { +    // Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off]      typebuf.tb_off -= addlen;      memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen); -  } -  /* -   * Need to allocate a new buffer. -   * In typebuf.tb_buf there must always be room for 3 * MAXMAPLEN + 4 -   * characters.  We add some extra room to avoid having to allocate too -   * often. -   */ -  else { +  } else { +    // Need to allocate a new buffer. +    // In typebuf.tb_buf there must always be room for 3 * MAXMAPLEN + 4 +    // characters.  We add some extra room to avoid having to allocate too +    // often.      newoff = MAXMAPLEN + 4;      newlen = typebuf.tb_len + addlen + newoff + 4 * (MAXMAPLEN + 4);      if (newlen < 0) {               /* string is getting too long */ @@ -1665,10 +1658,10 @@ static int vgetorpeek(int advance)      }      if (c != NUL && !got_int) {        if (advance) { -        /* KeyTyped = FALSE;  When the command that stuffed something -         * was typed, behave like the stuffed command was typed. -         * needed for CTRL-W CTRl-] to open a fold, for example. */ -        KeyStuffed = TRUE; +        // KeyTyped = FALSE;  When the command that stuffed something +        // was typed, behave like the stuffed command was typed. +        // needed for CTRL-W CTRL-] to open a fold, for example. +        KeyStuffed = true;        }        if (typebuf.tb_no_abbr_cnt == 0)          typebuf.tb_no_abbr_cnt = 1;             /* no abbreviations now */ diff --git a/src/nvim/gettext.h b/src/nvim/gettext.h index aa0e97233e..60317b8484 100644 --- a/src/nvim/gettext.h +++ b/src/nvim/gettext.h @@ -13,6 +13,7 @@  #else  # define _(x) ((char *)(x))  # define N_(x) x +# define ngettext(x, xs, n) ((n) == 1 ? (x) : (xs))  # define bindtextdomain(x, y)  // empty  # define bind_textdomain_codeset(x, y)  // empty  # define textdomain(x)  // empty diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 13ecafcbe3..300e506854 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -407,6 +407,9 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);  /* ID of script being sourced or was sourced to define the current function. */  EXTERN scid_T current_SID INIT(= 0); + +EXTERN bool did_source_packages INIT(= false); +  // Scope information for the code that indirectly triggered the current  // provider function call  EXTERN struct caller_scope { @@ -1143,8 +1146,9 @@ EXTERN char_u e_winheight[] INIT(= N_(  EXTERN char_u e_winwidth[] INIT(= N_(          "E592: 'winwidth' cannot be smaller than 'winminwidth'"));  EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); -EXTERN char_u e_zerocount[] INIT(= N_("Zero count")); -EXTERN char_u e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context")); +EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); +EXTERN char_u e_usingsid[] INIT(= N_( +    "E81: Using <SID> not in a script context"));  EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s"));  EXTERN char_u e_maxmempat[] INIT(= N_(          "E363: pattern uses more memory than 'maxmempattern'")); diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index fd194a4080..279d45bb0a 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -515,34 +515,41 @@ int cin_isscopedecl(char_u *s)  /* Maximum number of lines to search back for a "namespace" line. */  #define FIND_NAMESPACE_LIM 20 -/* - * Recognize a "namespace" scope declaration. - */ -static int cin_is_cpp_namespace(char_u *s) +// Recognize a "namespace" scope declaration. +static bool cin_is_cpp_namespace(char_u *s)  { -  char_u      *p; -  int has_name = FALSE; +  char_u *p; +  bool has_name = false; +  bool has_name_start = false;    s = cin_skipcomment(s);    if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) {      p = cin_skipcomment(skipwhite(s + 9));      while (*p != NUL) {        if (ascii_iswhite(*p)) { -        has_name = TRUE;         /* found end of a name */ +        has_name = true;         // found end of a name          p = cin_skipcomment(skipwhite(p));        } else if (*p == '{') {          break;        } else if (vim_iswordc(*p)) { -        if (has_name) -          return FALSE;           /* word character after skipping past name */ -        ++p; +        has_name_start = true; +        if (has_name) { +          return false;           // word character after skipping past name +        } +        p++; +      } else if (p[0] == ':' && p[1] == ':' && vim_iswordc(p[2])) { +        if (!has_name_start || has_name) { +          return false; +        } +        // C++ 17 nested namespace +        p += 3;        } else { -        return FALSE; +        return false;        }      } -    return TRUE; +    return true;    } -  return FALSE; +  return false;  }  /* @@ -727,16 +734,20 @@ static int cin_ispreproc(char_u *s)    return FALSE;  } -/* - * Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a - * continuation line of a preprocessor statement.  Decrease "*lnump" to the - * start and return the line in "*pp". - */ -static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump) +/// Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a +/// continuation line of a preprocessor statement.  Decrease "*lnump" to the +/// start and return the line in "*pp". +/// Put the amount of indent in "*amount". +static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount)  {    char_u      *line = *pp;    linenr_T lnum = *lnump; -  int retval = FALSE; +  int retval = false; +  int candidate_amount = *amount; + +  if (*line != NUL && line[STRLEN(line) - 1] == '\\') { +    candidate_amount = get_indent_lnum(lnum); +  }    for (;; ) {      if (cin_ispreproc(line)) { @@ -751,8 +762,12 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump)        break;    } -  if (lnum != *lnump) +  if (lnum != *lnump) {      *pp = ml_get(*lnump); +  } +  if (retval) { +    *amount = candidate_amount; +  }    return retval;  } @@ -1987,10 +2002,12 @@ int get_c_indent(void)          amount = -1;          for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) {            l = skipwhite(ml_get(lnum)); -          if (cin_nocode(l))                    /* skip comment lines */ +          if (cin_nocode(l)) {                   // skip comment lines              continue; -          if (cin_ispreproc_cont(&l, &lnum)) -            continue;                           /* ignore #define, #if, etc. */ +          } +          if (cin_ispreproc_cont(&l, &lnum, &amount)) { +            continue;                           // ignore #define, #if, etc. +          }            curwin->w_cursor.lnum = lnum;            /* Skip a comment or raw string. XXX */ @@ -2346,15 +2363,14 @@ int get_c_indent(void)             * up with it.             */            if (curwin->w_cursor.lnum <= ourscope) { -            /* we reached end of scope: -             * if looking for an enum or structure initialization -             * go further back: -             * if it is an initializer (enum xxx or xxx =), then -             * don't add ind_continuation, otherwise it is a variable -             * declaration: -             * int x, -             *     here; <-- add ind_continuation -             */ +            // We reached end of scope: +            // If looking for a enum or structure initialization +            // go further back: +            // If it is an initializer (enum xxx or xxx =), then +            // don't add ind_continuation, otherwise it is a variable +            // declaration: +            // int x, +            //     here; <-- add ind_continuation              if (lookfor == LOOKFOR_ENUM_OR_INIT) {                if (curwin->w_cursor.lnum == 0                    || curwin->w_cursor.lnum @@ -2382,11 +2398,12 @@ int get_c_indent(void)                  continue;                } -              /* -               * Skip preprocessor directives and blank lines. -               */ -              if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum)) +              // +              // Skip preprocessor directives and blank lines. +              // +              if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {                  continue; +              }                if (cin_nocode(l))                  continue; @@ -2490,9 +2507,10 @@ int get_c_indent(void)                    continue;                  } -                /* Skip preprocessor directives and blank lines. */ -                if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum)) +                // Skip preprocessor directives and blank lines. +                if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {                    continue; +                }                  /* Finally the actual check for "namespace". */                  if (cin_is_cpp_namespace(l)) { @@ -2655,9 +2673,10 @@ int get_c_indent(void)             * unlocked it)             */            l = get_cursor_line_ptr(); -          if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum) -              || cin_nocode(l)) +          if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount) +              || cin_nocode(l)) {              continue; +          }            /*             * Are we at the start of a cpp base class declaration or @@ -3302,11 +3321,12 @@ term_again:        break;      } -    /* -     * Skip preprocessor directives and blank lines. -     */ -    if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum)) +    // +    // Skip preprocessor directives and blank lines. +    // +    if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {        continue; +    }      if (cin_nocode(l))        continue; @@ -3398,9 +3418,10 @@ term_again:        while (curwin->w_cursor.lnum > 1) {          look = ml_get(--curwin->w_cursor.lnum); -        if (!(cin_nocode(look) || cin_ispreproc_cont( -                &look, &curwin->w_cursor.lnum))) +        if (!(cin_nocode(look) +              || cin_ispreproc_cont(&look, &curwin->w_cursor.lnum, &amount))) {            break; +        }        }        if (curwin->w_cursor.lnum > 0            && cin_ends_in(look, (char_u *)"}", NULL)) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 3d7ebb6382..a75fe793ac 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -140,155 +140,153 @@ static char_u modifier_keys_table[] =  };  static struct key_name_entry { -  int key;              /* Special key code or ascii value */ -  char_u  *name;        /* Name of key */ +  int key;              // Special key code or ascii value +  char *name;           // Name of key  } key_names_table[] =  { -  {' ',               (char_u *)"Space"}, -  {TAB,               (char_u *)"Tab"}, -  {K_TAB,             (char_u *)"Tab"}, -  {NL,                (char_u *)"NL"}, -  {NL,                (char_u *)"NewLine"},     /* Alternative name */ -  {NL,                (char_u *)"LineFeed"},    /* Alternative name */ -  {NL,                (char_u *)"LF"},          /* Alternative name */ -  {CAR,               (char_u *)"CR"}, -  {CAR,               (char_u *)"Return"},      /* Alternative name */ -  {CAR,               (char_u *)"Enter"},       /* Alternative name */ -  {K_BS,              (char_u *)"BS"}, -  {K_BS,              (char_u *)"BackSpace"},   /* Alternative name */ -  {ESC,               (char_u *)"Esc"}, -  {CSI,               (char_u *)"CSI"}, -  {K_CSI,             (char_u *)"xCSI"}, -  {'|',               (char_u *)"Bar"}, -  {'\\',              (char_u *)"Bslash"}, -  {K_DEL,             (char_u *)"Del"}, -  {K_DEL,             (char_u *)"Delete"},      /* Alternative name */ -  {K_KDEL,            (char_u *)"kDel"}, -  {K_UP,              (char_u *)"Up"}, -  {K_DOWN,            (char_u *)"Down"}, -  {K_LEFT,            (char_u *)"Left"}, -  {K_RIGHT,           (char_u *)"Right"}, -  {K_XUP,             (char_u *)"xUp"}, -  {K_XDOWN,           (char_u *)"xDown"}, -  {K_XLEFT,           (char_u *)"xLeft"}, -  {K_XRIGHT,          (char_u *)"xRight"}, - -  {K_F1,              (char_u *)"F1"}, -  {K_F2,              (char_u *)"F2"}, -  {K_F3,              (char_u *)"F3"}, -  {K_F4,              (char_u *)"F4"}, -  {K_F5,              (char_u *)"F5"}, -  {K_F6,              (char_u *)"F6"}, -  {K_F7,              (char_u *)"F7"}, -  {K_F8,              (char_u *)"F8"}, -  {K_F9,              (char_u *)"F9"}, -  {K_F10,             (char_u *)"F10"}, - -  {K_F11,             (char_u *)"F11"}, -  {K_F12,             (char_u *)"F12"}, -  {K_F13,             (char_u *)"F13"}, -  {K_F14,             (char_u *)"F14"}, -  {K_F15,             (char_u *)"F15"}, -  {K_F16,             (char_u *)"F16"}, -  {K_F17,             (char_u *)"F17"}, -  {K_F18,             (char_u *)"F18"}, -  {K_F19,             (char_u *)"F19"}, -  {K_F20,             (char_u *)"F20"}, - -  {K_F21,             (char_u *)"F21"}, -  {K_F22,             (char_u *)"F22"}, -  {K_F23,             (char_u *)"F23"}, -  {K_F24,             (char_u *)"F24"}, -  {K_F25,             (char_u *)"F25"}, -  {K_F26,             (char_u *)"F26"}, -  {K_F27,             (char_u *)"F27"}, -  {K_F28,             (char_u *)"F28"}, -  {K_F29,             (char_u *)"F29"}, -  {K_F30,             (char_u *)"F30"}, - -  {K_F31,             (char_u *)"F31"}, -  {K_F32,             (char_u *)"F32"}, -  {K_F33,             (char_u *)"F33"}, -  {K_F34,             (char_u *)"F34"}, -  {K_F35,             (char_u *)"F35"}, -  {K_F36,             (char_u *)"F36"}, -  {K_F37,             (char_u *)"F37"}, - -  {K_XF1,             (char_u *)"xF1"}, -  {K_XF2,             (char_u *)"xF2"}, -  {K_XF3,             (char_u *)"xF3"}, -  {K_XF4,             (char_u *)"xF4"}, - -  {K_HELP,            (char_u *)"Help"}, -  {K_UNDO,            (char_u *)"Undo"}, -  {K_INS,             (char_u *)"Insert"}, -  {K_INS,             (char_u *)"Ins"},         /* Alternative name */ -  {K_KINS,            (char_u *)"kInsert"}, -  {K_HOME,            (char_u *)"Home"}, -  {K_KHOME,           (char_u *)"kHome"}, -  {K_XHOME,           (char_u *)"xHome"}, -  {K_ZHOME,           (char_u *)"zHome"}, -  {K_END,             (char_u *)"End"}, -  {K_KEND,            (char_u *)"kEnd"}, -  {K_XEND,            (char_u *)"xEnd"}, -  {K_ZEND,            (char_u *)"zEnd"}, -  {K_PAGEUP,          (char_u *)"PageUp"}, -  {K_PAGEDOWN,        (char_u *)"PageDown"}, -  {K_KPAGEUP,         (char_u *)"kPageUp"}, -  {K_KPAGEDOWN,       (char_u *)"kPageDown"}, - -  {K_KPLUS,           (char_u *)"kPlus"}, -  {K_KMINUS,          (char_u *)"kMinus"}, -  {K_KDIVIDE,         (char_u *)"kDivide"}, -  {K_KMULTIPLY,       (char_u *)"kMultiply"}, -  {K_KENTER,          (char_u *)"kEnter"}, -  {K_KPOINT,          (char_u *)"kPoint"}, - -  {K_K0,              (char_u *)"k0"}, -  {K_K1,              (char_u *)"k1"}, -  {K_K2,              (char_u *)"k2"}, -  {K_K3,              (char_u *)"k3"}, -  {K_K4,              (char_u *)"k4"}, -  {K_K5,              (char_u *)"k5"}, -  {K_K6,              (char_u *)"k6"}, -  {K_K7,              (char_u *)"k7"}, -  {K_K8,              (char_u *)"k8"}, -  {K_K9,              (char_u *)"k9"}, - -  {'<',               (char_u *)"lt"}, - -  {K_MOUSE,           (char_u *)"Mouse"}, -  {K_LEFTMOUSE,       (char_u *)"LeftMouse"}, -  {K_LEFTMOUSE_NM,    (char_u *)"LeftMouseNM"}, -  {K_LEFTDRAG,        (char_u *)"LeftDrag"}, -  {K_LEFTRELEASE,     (char_u *)"LeftRelease"}, -  {K_LEFTRELEASE_NM,  (char_u *)"LeftReleaseNM"}, -  {K_MIDDLEMOUSE,     (char_u *)"MiddleMouse"}, -  {K_MIDDLEDRAG,      (char_u *)"MiddleDrag"}, -  {K_MIDDLERELEASE,   (char_u *)"MiddleRelease"}, -  {K_RIGHTMOUSE,      (char_u *)"RightMouse"}, -  {K_RIGHTDRAG,       (char_u *)"RightDrag"}, -  {K_RIGHTRELEASE,    (char_u *)"RightRelease"}, -  {K_MOUSEDOWN,       (char_u *)"ScrollWheelUp"}, -  {K_MOUSEUP,         (char_u *)"ScrollWheelDown"}, -  {K_MOUSELEFT,       (char_u *)"ScrollWheelRight"}, -  {K_MOUSERIGHT,      (char_u *)"ScrollWheelLeft"}, -  {K_MOUSEDOWN,       (char_u *)"MouseDown"},   /* OBSOLETE: Use	  */ -  {K_MOUSEUP,         (char_u *)"MouseUp"},     /* ScrollWheelXXX instead */ -  {K_X1MOUSE,         (char_u *)"X1Mouse"}, -  {K_X1DRAG,          (char_u *)"X1Drag"}, -  {K_X1RELEASE,               (char_u *)"X1Release"}, -  {K_X2MOUSE,         (char_u *)"X2Mouse"}, -  {K_X2DRAG,          (char_u *)"X2Drag"}, -  {K_X2RELEASE,               (char_u *)"X2Release"}, -  {K_DROP,            (char_u *)"Drop"}, -  {K_ZERO,            (char_u *)"Nul"}, -  {K_SNR,             (char_u *)"SNR"}, -  {K_PLUG,            (char_u *)"Plug"}, -  {K_PASTE,           (char_u *)"Paste"}, -  {K_FOCUSGAINED,     (char_u *)"FocusGained"}, -  {K_FOCUSLOST,       (char_u *)"FocusLost"}, -  {0,                 NULL} +  { ' ',               "Space" }, +  { TAB,               "Tab" }, +  { K_TAB,             "Tab" }, +  { NL,                "NL" }, +  { NL,                "NewLine" },     // Alternative name +  { NL,                "LineFeed" },    // Alternative name +  { NL,                "LF" },          // Alternative name +  { CAR,               "CR" }, +  { CAR,               "Return" },      // Alternative name +  { CAR,               "Enter" },       // Alternative name +  { K_BS,              "BS" }, +  { K_BS,              "BackSpace" },   // Alternative name +  { ESC,               "Esc" }, +  { CSI,               "CSI" }, +  { K_CSI,             "xCSI" }, +  { '|',               "Bar" }, +  { '\\',              "Bslash" }, +  { K_DEL,             "Del" }, +  { K_DEL,             "Delete" },      // Alternative name +  { K_KDEL,            "kDel" }, +  { K_UP,              "Up" }, +  { K_DOWN,            "Down" }, +  { K_LEFT,            "Left" }, +  { K_RIGHT,           "Right" }, +  { K_XUP,             "xUp" }, +  { K_XDOWN,           "xDown" }, +  { K_XLEFT,           "xLeft" }, +  { K_XRIGHT,          "xRight" }, + +  { K_F1,              "F1" }, +  { K_F2,              "F2" }, +  { K_F3,              "F3" }, +  { K_F4,              "F4" }, +  { K_F5,              "F5" }, +  { K_F6,              "F6" }, +  { K_F7,              "F7" }, +  { K_F8,              "F8" }, +  { K_F9,              "F9" }, +  { K_F10,             "F10" }, + +  { K_F11,             "F11" }, +  { K_F12,             "F12" }, +  { K_F13,             "F13" }, +  { K_F14,             "F14" }, +  { K_F15,             "F15" }, +  { K_F16,             "F16" }, +  { K_F17,             "F17" }, +  { K_F18,             "F18" }, +  { K_F19,             "F19" }, +  { K_F20,             "F20" }, + +  { K_F21,             "F21" }, +  { K_F22,             "F22" }, +  { K_F23,             "F23" }, +  { K_F24,             "F24" }, +  { K_F25,             "F25" }, +  { K_F26,             "F26" }, +  { K_F27,             "F27" }, +  { K_F28,             "F28" }, +  { K_F29,             "F29" }, +  { K_F30,             "F30" }, + +  { K_F31,             "F31" }, +  { K_F32,             "F32" }, +  { K_F33,             "F33" }, +  { K_F34,             "F34" }, +  { K_F35,             "F35" }, +  { K_F36,             "F36" }, +  { K_F37,             "F37" }, + +  { K_XF1,             "xF1" }, +  { K_XF2,             "xF2" }, +  { K_XF3,             "xF3" }, +  { K_XF4,             "xF4" }, + +  { K_HELP,            "Help" }, +  { K_UNDO,            "Undo" }, +  { K_INS,             "Insert" }, +  { K_INS,             "Ins" },         // Alternative name +  { K_KINS,            "kInsert" }, +  { K_HOME,            "Home" }, +  { K_KHOME,           "kHome" }, +  { K_XHOME,           "xHome" }, +  { K_ZHOME,           "zHome" }, +  { K_END,             "End" }, +  { K_KEND,            "kEnd" }, +  { K_XEND,            "xEnd" }, +  { K_ZEND,            "zEnd" }, +  { K_PAGEUP,          "PageUp" }, +  { K_PAGEDOWN,        "PageDown" }, +  { K_KPAGEUP,         "kPageUp" }, +  { K_KPAGEDOWN,       "kPageDown" }, + +  { K_KPLUS,           "kPlus" }, +  { K_KMINUS,          "kMinus" }, +  { K_KDIVIDE,         "kDivide" }, +  { K_KMULTIPLY,       "kMultiply" }, +  { K_KENTER,          "kEnter" }, +  { K_KPOINT,          "kPoint" }, + +  { K_K0,              "k0" }, +  { K_K1,              "k1" }, +  { K_K2,              "k2" }, +  { K_K3,              "k3" }, +  { K_K4,              "k4" }, +  { K_K5,              "k5" }, +  { K_K6,              "k6" }, +  { K_K7,              "k7" }, +  { K_K8,              "k8" }, +  { K_K9,              "k9" }, + +  { '<',               "lt" }, + +  { K_MOUSE,           "Mouse" }, +  { K_LEFTMOUSE,       "LeftMouse" }, +  { K_LEFTMOUSE_NM,    "LeftMouseNM" }, +  { K_LEFTDRAG,        "LeftDrag" }, +  { K_LEFTRELEASE,     "LeftRelease" }, +  { K_LEFTRELEASE_NM,  "LeftReleaseNM" }, +  { K_MIDDLEMOUSE,     "MiddleMouse" }, +  { K_MIDDLEDRAG,      "MiddleDrag" }, +  { K_MIDDLERELEASE,   "MiddleRelease" }, +  { K_RIGHTMOUSE,      "RightMouse" }, +  { K_RIGHTDRAG,       "RightDrag" }, +  { K_RIGHTRELEASE,    "RightRelease" }, +  { K_MOUSEDOWN,       "ScrollWheelUp" }, +  { K_MOUSEUP,         "ScrollWheelDown" }, +  { K_MOUSELEFT,       "ScrollWheelRight" }, +  { K_MOUSERIGHT,      "ScrollWheelLeft" }, +  { K_MOUSEDOWN,       "MouseDown" },   // OBSOLETE: Use +  { K_MOUSEUP,         "MouseUp" },     // ScrollWheelXXX instead +  { K_X1MOUSE,         "X1Mouse" }, +  { K_X1DRAG,          "X1Drag" }, +  { K_X1RELEASE,       "X1Release" }, +  { K_X2MOUSE,         "X2Mouse" }, +  { K_X2DRAG,          "X2Drag" }, +  { K_X2RELEASE,       "X2Release" }, +  { K_DROP,            "Drop" }, +  { K_ZERO,            "Nul" }, +  { K_SNR,             "SNR" }, +  { K_PLUG,            "Plug" }, +  { K_PASTE,           "Paste" }, +  { 0,                 NULL }  };  static struct mousetable { @@ -721,7 +719,7 @@ int find_special_key_in_table(int c)   */  int get_special_key_code(const char_u *name)  { -  char_u  *table_name; +  char *table_name;    int i, j;    for (i = 0; key_names_table[i].name != NULL; i++) { diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index b8fed77a90..ee64854c98 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -428,8 +428,6 @@ enum key_extra {  #define K_CMDWIN        TERMCAP2KEY(KS_EXTRA, KE_CMDWIN)  #define K_DROP          TERMCAP2KEY(KS_EXTRA, KE_DROP) -#define K_FOCUSGAINED   TERMCAP2KEY(KS_EXTRA, KE_FOCUSGAINED) -#define K_FOCUSLOST     TERMCAP2KEY(KS_EXTRA, KE_FOCUSLOST)  #define K_EVENT         TERMCAP2KEY(KS_EXTRA, KE_EVENT)  #define K_PASTE         TERMCAP2KEY(KS_EXTRA, KE_PASTE) diff --git a/src/nvim/log.c b/src/nvim/log.c index 3baf0b2ebd..7bfe5c4089 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -95,8 +95,12 @@ void log_unlock(void)    uv_mutex_unlock(&mutex);  } -bool do_log(int log_level, const char *func_name, int line_num, bool eol, -            const char* fmt, ...) FUNC_ATTR_UNUSED +/// @param context    description of a shared context or subsystem +/// @param func_name  function name, or NULL +/// @param line_num   source line number, or -1 +bool do_log(int log_level, const char *context, const char *func_name, +            int line_num, bool eol, const char *fmt, ...) +  FUNC_ATTR_UNUSED  {    if (log_level < MIN_LOG_LEVEL) {      return false; @@ -112,8 +116,8 @@ bool do_log(int log_level, const char *func_name, int line_num, bool eol,    va_list args;    va_start(args, fmt); -  ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol, -                              fmt, args); +  ret = v_do_log_to_file(log_file, log_level, context, func_name, line_num, +                         eol, fmt, args);    va_end(args);    if (log_file != stderr && log_file != stdout) { @@ -151,7 +155,7 @@ FILE *open_log_file(void)    static bool opening_log_file = false;    // check if it's a recursive call    if (opening_log_file) { -    do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, true, +    do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true,                     "Cannot LOG() recursively.");      return stderr;    } @@ -171,7 +175,7 @@ FILE *open_log_file(void)    //  - LOG() is called before early_init()    //  - Directory does not exist    //  - File is not writable -  do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, true, +  do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true,                   "Logging to stderr, failed to open $" LOG_FILE_ENV ": %s",                   log_file_path);    return stderr; @@ -201,7 +205,7 @@ void log_callstack_to_file(FILE *log_file, const char *const func_name,    // Now we have a command string like:    //    addr2line -e /path/to/exe -f -p 0x123 0x456 ... -  do_log_to_file(log_file, DEBUG_LOG_LEVEL, func_name, line_num, true, +  do_log_to_file(log_file, DEBUG_LOG_LEVEL, NULL, func_name, line_num, true,                   "trace:");    FILE *fp = popen(cmdbuf, "r");    char linebuf[IOSIZE]; @@ -230,27 +234,28 @@ end:  }  #endif -static bool do_log_to_file(FILE *log_file, int log_level, +static bool do_log_to_file(FILE *log_file, int log_level, const char *context,                             const char *func_name, int line_num, bool eol,                             const char* fmt, ...)  {    va_list args;    va_start(args, fmt); -  bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol, -                              fmt, args); +  bool ret = v_do_log_to_file(log_file, log_level, context, func_name, +                              line_num, eol, fmt, args);    va_end(args);    return ret;  }  static bool v_do_log_to_file(FILE *log_file, int log_level, -                             const char *func_name, int line_num, bool eol, -                             const char* fmt, va_list args) +                             const char *context, const char *func_name, +                             int line_num, bool eol, const char *fmt, +                             va_list args)  {    static const char *log_levels[] = {      [DEBUG_LOG_LEVEL]   = "DEBUG",      [INFO_LOG_LEVEL]    = "INFO ", -    [WARNING_LOG_LEVEL] = "WARN ", +    [WARN_LOG_LEVEL]    = "WARN ",      [ERROR_LOG_LEVEL]   = "ERROR",    };    assert(log_level >= DEBUG_LOG_LEVEL && log_level <= ERROR_LOG_LEVEL); @@ -268,8 +273,15 @@ static bool v_do_log_to_file(FILE *log_file, int log_level,    // print the log message prefixed by the current timestamp and pid    int64_t pid = os_get_pid(); -  if (fprintf(log_file, "%s %s %" PRId64 "/%s:%d: ", date_time, -              log_levels[log_level], pid, func_name, line_num) < 0) { +  int rv = (line_num == -1 || func_name == NULL) +    ? fprintf(log_file, "%s %s %" PRId64 " %s", date_time, +              log_levels[log_level], pid, +              (context == NULL ? "?:" : context)) +    : fprintf(log_file, "%s %s %" PRId64 " %s%s:%d: ", date_time, +              log_levels[log_level], pid, +              (context == NULL ? "" : context), +              func_name, line_num); +  if (rv < 0) {      return false;    }    if (vfprintf(log_file, fmt, args) < 0) { diff --git a/src/nvim/log.h b/src/nvim/log.h index 5064d9333b..f378b92039 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -6,7 +6,7 @@  #define DEBUG_LOG_LEVEL 0  #define INFO_LOG_LEVEL 1 -#define WARNING_LOG_LEVEL 2 +#define WARN_LOG_LEVEL 2  #define ERROR_LOG_LEVEL 3  #define DLOG(...) @@ -22,42 +22,42 @@  #  define MIN_LOG_LEVEL INFO_LOG_LEVEL  #endif -#define LOG(level, ...) do_log((level), __func__, __LINE__, true, \ +#define LOG(level, ...) do_log((level), NULL, __func__, __LINE__, true, \                                 __VA_ARGS__)  #if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL  # undef DLOG  # undef DLOGN -# define DLOG(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, true, \ +# define DLOG(...) do_log(DEBUG_LOG_LEVEL, NULL, __func__, __LINE__, true, \                            __VA_ARGS__) -# define DLOGN(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, false, \ +# define DLOGN(...) do_log(DEBUG_LOG_LEVEL, NULL, __func__, __LINE__, false, \                             __VA_ARGS__)  #endif  #if MIN_LOG_LEVEL <= INFO_LOG_LEVEL  # undef ILOG  # undef ILOGN -# define ILOG(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, true, \ +# define ILOG(...) do_log(INFO_LOG_LEVEL, NULL, __func__, __LINE__, true, \                            __VA_ARGS__) -# define ILOGN(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, false, \ +# define ILOGN(...) do_log(INFO_LOG_LEVEL, NULL, __func__, __LINE__, false, \                             __VA_ARGS__)  #endif -#if MIN_LOG_LEVEL <= WARNING_LOG_LEVEL +#if MIN_LOG_LEVEL <= WARN_LOG_LEVEL  # undef WLOG  # undef WLOGN -# define WLOG(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, true, \ +# define WLOG(...) do_log(WARN_LOG_LEVEL, NULL, __func__, __LINE__, true, \                            __VA_ARGS__) -# define WLOGN(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, false, \ +# define WLOGN(...) do_log(WARN_LOG_LEVEL, NULL, __func__, __LINE__, false, \                             __VA_ARGS__)  #endif  #if MIN_LOG_LEVEL <= ERROR_LOG_LEVEL  # undef ELOG  # undef ELOGN -# define ELOG(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, true, \ +# define ELOG(...) do_log(ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true, \                            __VA_ARGS__) -# define ELOGN(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, false, \ +# define ELOGN(...) do_log(ERROR_LOG_LEVEL, NULL, __func__, __LINE__, false, \                             __VA_ARGS__)  #endif diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 54973a6e94..16bb4169c4 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -145,9 +145,8 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL        }        if (nul1 != NULL) {          assert(nul2 != NULL); -        // Due to lowercase letter having possibly different byte length then -        // uppercase letter can’t shift both strings by the same amount of -        // bytes. +        // Can't shift both strings by the same amount of bytes: lowercase +        // letter may have different byte-length than uppercase.          s1_len -= (size_t)(nul1 - s1) + 1;          s2_len -= (size_t)(nul2 - s2) + 1;          s1 = nul1 + 1; @@ -315,7 +314,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL  /// Initialize lua interpreter  /// -/// Crashes NeoVim if initialization fails. Should be called once per lua +/// Crashes Nvim if initialization fails. Should be called once per lua  /// interpreter instance.  ///  /// @return New lua interpreter instance. diff --git a/src/nvim/main.c b/src/nvim/main.c index 3f828d7be9..ea7a58bda3 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -7,6 +7,11 @@  #include <string.h>  #include <stdbool.h> +#ifdef WIN32 +# include <wchar.h> +# include <winnls.h> +#endif +  #include <msgpack.h>  #include "nvim/ascii.h" @@ -215,10 +220,22 @@ void early_init(void)  #ifdef MAKE_LIB  int nvim_main(int argc, char **argv) +#elif defined(WIN32) +int wmain(int argc, wchar_t **argv_w)  // multibyte args on Windows. #7060  #else  int main(int argc, char **argv)  #endif  { +#if defined(WIN32) && !defined(MAKE_LIB) +  char *argv[argc]; +  for (int i = 0; i < argc; i++) { +    char *buf = NULL; +    utf16_to_utf8(argv_w[i], &buf); +    assert(buf); +    argv[i] = buf; +  } +#endif +    argv0 = argv[0];    char_u *fname = NULL;   // file name from command line @@ -632,6 +649,11 @@ void getout(int exitval)    /* Position the cursor again, the autocommands may have moved it */    ui_cursor_goto((int)Rows - 1, 0); +  // Apply 'titleold'. +  if (p_title && *p_titleold != NUL) { +    ui_call_set_title(cstr_as_string((char *)p_titleold)); +  } +  #if defined(USE_ICONV) && defined(DYNAMIC_ICONV)    iconv_end();  #endif @@ -1291,10 +1313,29 @@ static void set_window_layout(mparm_T *paramp)  static void load_plugins(void)  {    if (p_lpl) { -    source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL | DIP_NOAFTER);  // NOLINT +    char_u *rtp_copy = NULL; + +    // First add all package directories to 'runtimepath', so that their +    // autoload directories can be found.  Only if not done already with a +    // :packloadall command. +    // Make a copy of 'runtimepath', so that source_runtime does not use the +    // pack directories. +    if (!did_source_packages) { +      rtp_copy = vim_strsave(p_rtp); +      add_pack_start_dirs(); +    } + +    source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy, +                   (char_u *)"plugin/**/*.vim",  // NOLINT +                   DIP_ALL | DIP_NOAFTER);      TIME_MSG("loading plugins"); +    xfree(rtp_copy); -    ex_packloadall(NULL); +    // Only source "start" packages if not done already with a :packloadall +    // command. +    if (!did_source_packages) { +      load_start_packages(); +    }      TIME_MSG("loading packages");      source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL | DIP_AFTER); @@ -1878,54 +1919,47 @@ static void usage(void)    signal_stop();              // kill us with CTRL-C here, if you like    mch_msg(_("Usage:\n")); -  mch_msg(_("  nvim [arguments] [file ...]      Edit specified file(s)\n")); -  mch_msg(_("  nvim [arguments] -               Read text from stdin\n")); -  mch_msg(_("  nvim [arguments] -t <tag>        Edit file where tag is defined\n")); -  mch_msg(_("  nvim [arguments] -q [errorfile]  Edit file with first error\n")); -  mch_msg(_("\nArguments:\n")); +  mch_msg(_("  nvim [options] [file ...]      Edit file(s)\n")); +  mch_msg(_("  nvim [options] -               Read text from stdin\n")); +  mch_msg(_("  nvim [options] -t <tag>        Edit file where tag is defined\n")); +  mch_msg(_("  nvim [options] -q [errorfile]  Edit file with first error\n")); +  mch_msg(_("\nOptions:\n"));    mch_msg(_("  --                    Only file names after this\n")); -#if !defined(UNIX) -  mch_msg(_("  --literal             Don't expand wildcards\n")); -#endif -  mch_msg(_("  -e                    Ex mode\n")); -  mch_msg(_("  -E                    Improved Ex mode\n")); -  mch_msg(_("  -s                    Silent (batch) mode (only for ex mode)\n")); +  mch_msg(_("  +                     Start at end of file\n")); +  mch_msg(_("  --cmd <cmd>           Execute <cmd> before any config\n")); +  mch_msg(_("  +<cmd>, -c <cmd>      Execute <cmd> after config and first file\n")); +  mch_msg("\n"); +  mch_msg(_("  -b                    Binary mode\n"));    mch_msg(_("  -d                    Diff mode\n")); -  mch_msg(_("  -R                    Read-only mode\n")); -  mch_msg(_("  -Z                    Restricted mode\n")); +  mch_msg(_("  -e, -E                Ex mode, Improved Ex mode\n")); +  mch_msg(_("  -es                   Silent (batch) mode\n")); +  mch_msg(_("  -h, --help            Print this help message\n")); +  mch_msg(_("  -i <shada>            Use this shada file\n"));    mch_msg(_("  -m                    Modifications (writing files) not allowed\n"));    mch_msg(_("  -M                    Modifications in text not allowed\n")); -  mch_msg(_("  -b                    Binary mode\n")); -  mch_msg(_("  -l                    Lisp mode\n")); -  mch_msg(_("  -A                    Arabic mode\n")); -  mch_msg(_("  -F                    Farsi mode\n")); -  mch_msg(_("  -H                    Hebrew mode\n")); -  mch_msg(_("  -V[N][file]           Be verbose [level N][log messages to file]\n")); -  mch_msg(_("  -D                    Debugging mode\n"));    mch_msg(_("  -n                    No swap file, use memory only\n")); -  mch_msg(_("  -r, -L                List swap files and exit\n")); -  mch_msg(_("  -r <file>             Recover crashed session\n")); -  mch_msg(_("  -u <vimrc>            Use <vimrc> instead of the default\n")); -  mch_msg(_("  -i <shada>            Use <shada> instead of the default\n")); -  mch_msg(_("  --noplugin            Don't load plugin scripts\n")); -  mch_msg(_("  -o[N]                 Open N windows (default: one for each file)\n")); -  mch_msg(_("  -O[N]                 Like -o but split vertically\n")); -  mch_msg(_("  -p[N]                 Open N tab pages (default: one for each file)\n")); -  mch_msg(_("  +                     Start at end of file\n")); -  mch_msg(_("  +<linenum>            Start at line <linenum>\n")); -  mch_msg(_("  +/<pattern>           Start at first occurrence of <pattern>\n")); -  mch_msg(_("  --cmd <command>       Execute <command> before loading any vimrc\n")); -  mch_msg(_("  -c <command>          Execute <command> after loading the first file\n")); +  mch_msg(_("  -o[N]                 Open N windows (default: one per file)\n")); +  mch_msg(_("  -O[N]                 Open N vertical windows (default: one per file)\n")); +  mch_msg(_("  -p[N]                 Open N tab pages (default: one per file)\n")); +  mch_msg(_("  -r, -L                List swap files\n")); +  mch_msg(_("  -r <file>             Recover edit state for this file\n")); +  mch_msg(_("  -R                    Read-only mode\n"));    mch_msg(_("  -S <session>          Source <session> after loading the first file\n"));    mch_msg(_("  -s <scriptin>         Read Normal mode commands from <scriptin>\n")); -  mch_msg(_("  -w <scriptout>        Append all typed characters to <scriptout>\n")); -  mch_msg(_("  -W <scriptout>        Write all typed characters to <scriptout>\n")); -  mch_msg(_("  --startuptime <file>  Write startup timing messages to <file>\n")); -  mch_msg(_("  --api-info            Dump API metadata serialized to msgpack and exit\n")); +  mch_msg(_("  -u <config>           Use this config file\n")); +  mch_msg(_("  -v, --version         Print version information\n")); +  mch_msg(_("  -V[N][file]           Verbose [level][file]\n")); +  mch_msg(_("  -Z                    Restricted mode\n")); +  mch_msg("\n"); +  mch_msg(_("  --api-info            Write msgpack-encoded API metadata to stdout\n"));    mch_msg(_("  --embed               Use stdin/stdout as a msgpack-rpc channel\n"));    mch_msg(_("  --headless            Don't start a user interface\n")); -  mch_msg(_("  -v, --version         Print version information and exit\n")); -  mch_msg(_("  -h, --help            Print this help message and exit\n")); +#if !defined(UNIX) +  mch_msg(_("  --literal             Don't expand wildcards\n")); +#endif +  mch_msg(_("  --noplugin            Don't load plugins\n")); +  mch_msg(_("  --startuptime <file>  Write startup timing messages to <file>\n")); +  mch_msg(_("\nSee \":help startup-options\" for all options.\n"));  } 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..88d968704b 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,11 +677,15 @@ 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);    } +  if (menu->actext) { +    tv_dict_add_str(dict, S_LEN("actext"), (char *)menu->actext); +  } +    if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) {      tv_dict_add_str(dict, S_LEN("tooltip"),                      (char *)menu->strings[MENU_INDEX_TIP]); @@ -697,11 +699,9 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)      for (int bit = 0; bit < MENU_MODES; bit++) {        if ((menu->modes & modes & (1 << bit)) != 0) {          dict_T *impl = tv_dict_alloc(); -        if (*menu->strings[bit] == NUL) { -          tv_dict_add_str(impl, S_LEN("rhs"), (char *)"<Nop>"); -        } else { -          tv_dict_add_str(impl, S_LEN("rhs"), (char *)menu->strings[bit]); -        } +        tv_dict_add_allocated_str(impl, S_LEN("rhs"), +                                  str2special_save((char *)menu->strings[bit], +                                                   false, false));          tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]);          tv_dict_add_nr(impl, S_LEN("enabled"),                         (menu->enabled & (1 << bit)) ? 1 : 0); @@ -717,7 +717,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..9693132846 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; @@ -2166,16 +2164,21 @@ void do_check_cursorbind(void)          int restart_edit_save = restart_edit;          restart_edit = true;          check_cursor(); +        if (curwin->w_p_cul || curwin->w_p_cuc) { +          validate_cursor(); +        }          restart_edit = restart_edit_save;        } -      /* Correct cursor for multi-byte character. */ -      if (has_mbyte) +      // Correct cursor for multi-byte character. +      if (has_mbyte) {          mb_adjust_cursor(); +      }        redraw_later(VALID); -      /* Only scroll when 'scrollbind' hasn't done this. */ -      if (!curwin->w_p_scb) +      // Only scroll when 'scrollbind' hasn't done this. +      if (!curwin->w_p_scb) {          update_topline(); +      }        curwin->w_redr_status = true;      }    } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 6fd1af1ba6..88232a55de 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -62,7 +62,7 @@ typedef struct {    ChannelType type;    msgpack_unpacker *unpacker;    union { -    Stream stream; +    Stream stream;  // bidirectional (socket)      Process *proc;      struct {        Stream in; @@ -133,6 +133,9 @@ uint64_t channel_from_process(Process *proc, uint64_t id, char *source)    rstream_init(proc->out, 0);    rstream_start(proc->out, receive_msgpack, channel); +  DLOG("ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, proc->in, +       proc->out); +    return channel->id;  } @@ -150,6 +153,9 @@ void channel_from_connection(SocketWatcher *watcher)    wstream_init(&channel->data.stream, 0);    rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE);    rstream_start(&channel->data.stream, receive_msgpack, channel); + +  DLOG("ch %" PRIu64 " in/out-stream=%p", channel->id, +       &channel->data.stream);  }  /// @param source description of source function, rplugin name, TCP addr, etc @@ -182,12 +188,11 @@ uint64_t channel_connect(bool tcp, const char *address, int timeout,    return channel->id;  } -/// Sends event/arguments to channel +/// Publishes an event to a channel.  /// -/// @param id The channel id. If 0, the event will be sent to all -///        channels that have subscribed to the event type -/// @param name The event name, an arbitrary string -/// @param args Array with event arguments +/// @param id Channel id. 0 means "broadcast to all subscribed channels" +/// @param name Event name (application-defined) +/// @param args Array of event arguments  /// @return True if the event was sent successfully, false otherwise.  bool channel_send_event(uint64_t id, const char *name, Array args)  { @@ -209,7 +214,6 @@ bool channel_send_event(uint64_t id, const char *name, Array args)        send_event(channel, name, args);      }    }  else { -    // TODO(tarruda): Implement event broadcasting in vimscript      broadcast_event(name, args);    } @@ -344,6 +348,9 @@ void channel_from_stdio(void)    rstream_start(&channel->data.std.in, receive_msgpack, channel);    // write stream    wstream_init_fd(&main_loop, &channel->data.std.out, 1, 0); + +  DLOG("ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, +       &channel->data.std.in, &channel->data.std.out);  }  /// Creates a loopback channel. This is used to avoid deadlock @@ -363,6 +370,7 @@ void channel_process_exit(uint64_t id, int status)    decref(channel);  } +// rstream.c:read_event() invokes this as stream->read_cb().  static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c,                              void *data, bool eof)  { @@ -374,12 +382,24 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c,      char buf[256];      snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",               channel->id); -    call_set_error(channel, buf, WARNING_LOG_LEVEL); +    call_set_error(channel, buf, WARN_LOG_LEVEL); +    goto end; +  } + +  if ((chan_wstream(channel) != NULL && chan_wstream(channel)->closed) +      || (chan_rstream(channel) != NULL && chan_rstream(channel)->closed)) { +    char buf[256]; +    snprintf(buf, sizeof(buf), +             "ch %" PRIu64 ": stream closed unexpectedly. " +             "closing channel", +             channel->id); +    call_set_error(channel, buf, WARN_LOG_LEVEL);      goto end;    }    size_t count = rbuffer_size(rbuf); -  DLOG("parsing %u bytes of msgpack data from Stream(%p)", count, stream); +  DLOG("ch %" PRIu64 ": parsing %u bytes from msgpack Stream: %p", +       channel->id, count, stream);    // Feed the unpacker with data    msgpack_unpacker_reserve_buffer(channel->unpacker, count); @@ -435,8 +455,8 @@ static void parse_msgpack(Channel *channel)      // causes for this error(search for 'goto _failed')      //      // A not so uncommon cause for this might be deserializing objects with -    // a high nesting level: msgpack will break when it's internal parse stack -    // size exceeds MSGPACK_EMBED_STACK_SIZE(defined as 32 by default) +    // a high nesting level: msgpack will break when its internal parse stack +    // size exceeds MSGPACK_EMBED_STACK_SIZE (defined as 32 by default)      send_error(channel, 0, "Invalid msgpack payload. "                             "This error can also happen when deserializing "                             "an object with high level of nesting"); @@ -534,6 +554,39 @@ static void on_request_event(void **argv)    api_clear_error(&error);  } +/// Returns the Stream that a Channel writes to. +static Stream *chan_wstream(Channel *chan) +{ +  switch (chan->type) { +    case kChannelTypeSocket: +      return &chan->data.stream; +    case kChannelTypeProc: +      return chan->data.proc->in; +    case kChannelTypeStdio: +      return &chan->data.std.out; +    case kChannelTypeInternal: +      return NULL; +  } +  abort(); +} + +/// Returns the Stream that a Channel reads from. +static Stream *chan_rstream(Channel *chan) +{ +  switch (chan->type) { +    case kChannelTypeSocket: +      return &chan->data.stream; +    case kChannelTypeProc: +      return chan->data.proc->out; +    case kChannelTypeStdio: +      return &chan->data.std.in; +    case kChannelTypeInternal: +      return NULL; +  } +  abort(); +} + +  static bool channel_write(Channel *channel, WBuffer *buffer)  {    bool success = false; @@ -545,13 +598,9 @@ static bool channel_write(Channel *channel, WBuffer *buffer)    switch (channel->type) {      case kChannelTypeSocket: -      success = wstream_write(&channel->data.stream, buffer); -      break;      case kChannelTypeProc: -      success = wstream_write(channel->data.proc->in, buffer); -      break;      case kChannelTypeStdio: -      success = wstream_write(&channel->data.std.out, buffer); +      success = wstream_write(chan_wstream(channel), buffer);        break;      case kChannelTypeInternal:        incref(channel); @@ -565,8 +614,8 @@ static bool channel_write(Channel *channel, WBuffer *buffer)      char buf[256];      snprintf(buf,               sizeof(buf), -             "Before returning from a RPC call, ch %" PRIu64 " was " -             "closed due to a failed write", +             "ch %" PRIu64 ": stream write failed. " +             "RPC canceled; closing channel",               channel->id);      call_set_error(channel, buf, ERROR_LOG_LEVEL);    } @@ -817,6 +866,7 @@ static void call_set_error(Channel *channel, char *msg, int loglevel)      ChannelCallFrame *frame = kv_A(channel->call_stack, i);      frame->returned = true;      frame->errored = true; +    api_free_object(frame->result);      frame->result = STRING_OBJ(cstr_to_string(msg));    } diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 444c6cc256..fecae11d45 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -88,7 +88,12 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)  {    bool ret = true;    kvec_t(MPToAPIObjectStackItem) stack = KV_INITIAL_VALUE; -  kv_push(stack, ((MPToAPIObjectStackItem) { obj, arg, false, 0 })); +  kv_push(stack, ((MPToAPIObjectStackItem) { +    .mobj = obj, +    .aobj = arg, +    .container = false, +    .idx = 0, +  }));    while (ret && kv_size(stack)) {      MPToAPIObjectStackItem cur = kv_last(stack);      if (!cur.container) { @@ -361,7 +366,7 @@ typedef struct {    size_t idx;  } APIToMPObjectStackItem; -/// Convert type used by Neovim API to msgpack +/// Convert type used by Nvim API to msgpack type.  ///  /// @param[in]  result  Object to convert.  /// @param[out]  res  Structure that defines where conversion results are saved. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c1676780d8..1103fe15d2 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -13,6 +13,7 @@  #include <stdbool.h>  #include <stdlib.h> +#include "nvim/log.h"  #include "nvim/vim.h"  #include "nvim/ascii.h"  #include "nvim/normal.h" @@ -344,8 +345,6 @@ static const struct nv_cmd {    { K_F8,      farsi_f8,       0,                      0 },    { K_F9,      farsi_f9,       0,                      0 },    { K_EVENT,   nv_event,       NV_KEEPREG,             0 }, -  { K_FOCUSGAINED, nv_focusgained, NV_KEEPREG,         0 }, -  { K_FOCUSLOST,   nv_focuslost,   NV_KEEPREG,         0 },  };  /* Number of commands in nv_cmds[]. */ @@ -1549,8 +1548,10 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)        }        oap->start = VIsual; -      if (VIsual_mode == 'V') +      if (VIsual_mode == 'V') {          oap->start.col = 0; +        oap->start.coladd = 0; +      }      }      /* @@ -1943,8 +1944,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)           * the lines. */          auto_format(false, true); -        if (restart_edit == 0) +        if (restart_edit == 0) {            restart_edit = restart_edit_save; +        } else { +          cap->retval |= CA_COMMAND_BUSY; +        }        }        break; @@ -6258,15 +6262,18 @@ static void nv_gomark(cmdarg_T *cap)    } else      nv_cursormark(cap, cap->arg, pos); -  /* May need to clear the coladd that a mark includes. */ -  if (!virtual_active()) +  // May need to clear the coladd that a mark includes. +  if (!virtual_active()) {      curwin->w_cursor.coladd = 0; +  } +  check_cursor_col();    if (cap->oap->op_type == OP_NOP        && pos != NULL        && (pos == (pos_T *)-1 || !equalpos(old_cursor, *pos))        && (fdo_flags & FDO_MARK) -      && old_KeyTyped) +      && old_KeyTyped) {      foldOpenCursor(); +  }  }  /* @@ -7957,18 +7964,7 @@ static void nv_event(cmdarg_T *cap)    may_garbage_collect = false;    multiqueue_process_events(main_loop.events);    cap->retval |= CA_COMMAND_BUSY;       // don't call edit() now -} - -/// Trigger FocusGained event. -static void nv_focusgained(cmdarg_T *cap) -{ -  apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); -} - -/// Trigger FocusLost event. -static void nv_focuslost(cmdarg_T *cap) -{ -  apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); +  finish_op = false;  }  /* diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 5c6f4d0d07..c6df71ea46 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -55,12 +55,11 @@ static yankreg_T y_regs[NUM_REGISTERS];  static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */ -static bool clipboard_didwarn_unnamed = false; -  // for behavior between start_batch_changes() and end_batch_changes()) -static bool clipboard_delay_update = false;  // delay clipboard update  static int batch_change_count = 0;           // inside a script +static bool clipboard_delay_update = false;  // delay clipboard update  static bool clipboard_needs_update = false;  // clipboard was updated +static bool clipboard_didwarn = false;  /*   * structure used by block_prep, op_delete and op_yank for blockwise operators @@ -2061,7 +2060,7 @@ void op_insert(oparg_T *oap, long count1)    }    t1 = oap->start; -  edit(NUL, false, (linenr_T)count1); +  (void)edit(NUL, false, (linenr_T)count1);    // When a tab was inserted, and the characters in front of the tab    // have been converted to a tab as well, the column of the cursor @@ -5524,7 +5523,7 @@ int get_default_register_name(void)  }  /// Determine if register `*name` should be used as a clipboard. -/// In an unnammed operation, `*name` is `NUL` and will be adjusted to `'*'/'+'` if +/// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if  /// `clipboard=unnamed[plus]` is set.  ///  /// @param name The name of register, or `NUL` if unnamed. @@ -5535,33 +5534,41 @@ int get_default_register_name(void)  /// if the register isn't a clipboard or provider isn't available.  static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)  { -  if (*name == '*' || *name == '+') { -    if(!eval_has_provider("clipboard")) { -      if (!quiet) { -        EMSG("clipboard: No provider. Try \":CheckHealth\" or " -             "\":h clipboard\"."); -      } -      return NULL; -    } -    return &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER]; -  } else if ((*name == NUL) && (cb_flags & CB_UNNAMEDMASK)) { -    if(!eval_has_provider("clipboard")) { -      if (!quiet && !clipboard_didwarn_unnamed) { -        msg((char_u *)"clipboard: No provider. Try \":CheckHealth\" or " -            "\":h clipboard\"."); -        clipboard_didwarn_unnamed = true; -      } -      return NULL; +#define MSG_NO_CLIP "clipboard: No provider. " \ +  "Try \":checkhealth\" or \":h clipboard\"." + +  yankreg_T *target = NULL; +  bool explicit_cb_reg = (*name == '*' || *name == '+'); +  bool implicit_cb_reg = (*name == NUL) && (cb_flags & CB_UNNAMEDMASK); +  if (!explicit_cb_reg && !implicit_cb_reg) { +    goto end; +  } + +  if (!eval_has_provider("clipboard")) { +    if (batch_change_count == 1 && !quiet +        && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) { +      clipboard_didwarn = true; +      // Do NOT error (emsg()) here--if it interrupts :redir we get into +      // a weird state, stuck in "redirect mode". +      msg((char_u *)MSG_NO_CLIP);      } +    // ... else, be silent (don't flood during :while, :redir, etc.). +    goto end; +  } + +  if (explicit_cb_reg) { +    target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER]; +    goto end; +  } else {  // unnamed register: "implicit" clipboard      if (writing && clipboard_delay_update) { +      // For "set" (copy), defer the clipboard call.        clipboard_needs_update = true; -      return NULL; +      goto end;      } else if (!writing && clipboard_needs_update) { -      // use the internal value -      return NULL; +      // For "get" (paste), use the internal value. +      goto end;      } -    yankreg_T *target;      if (cb_flags & CB_UNNAMEDPLUS) {        *name = (cb_flags & CB_UNNAMED && writing) ? '"': '+';        target = &y_regs[PLUS_REGISTER]; @@ -5569,10 +5576,11 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)        *name = '*';        target = &y_regs[STAR_REGISTER];      } -    return target; // unnamed register +    goto end;    } -  // don't do anything for other register names -  return NULL; + +end: +  return target;  }  static bool get_clipboard(int name, yankreg_T **target, bool quiet) @@ -5740,17 +5748,16 @@ static void set_clipboard(int name, yankreg_T *reg)    (void)eval_call_provider("clipboard", "set", args);  } -/// Avoid clipboard (slow) during batch operations (i.e., a script). +/// Avoid slow things (clipboard) during batch operations (while/for-loops).  void start_batch_changes(void)  {    if (++batch_change_count > 1) {      return;    }    clipboard_delay_update = true; -  clipboard_needs_update = false;  } -/// Update the clipboard after batch changes finished. +/// Counterpart to start_batch_changes().  void end_batch_changes(void)  {    if (--batch_change_count > 0) { @@ -5759,11 +5766,37 @@ void end_batch_changes(void)    }    clipboard_delay_update = false;    if (clipboard_needs_update) { +    // must be before, as set_clipboard will invoke +    // start/end_batch_changes recursively +    clipboard_needs_update = false; +    // unnamed ("implicit" clipboard)      set_clipboard(NUL, y_previous); +  } +} + +int save_batch_count(void) +{ +  int save_count = batch_change_count; +  batch_change_count = 0; +  clipboard_delay_update = false; +  if (clipboard_needs_update) {      clipboard_needs_update = false; +    // unnamed ("implicit" clipboard) +    set_clipboard(NUL, y_previous);    } +  return save_count;  } +void restore_batch_count(int save_count) +{ +  assert(batch_change_count == 0); +  batch_change_count = save_count; +  if (batch_change_count > 0) { +    clipboard_delay_update = true; +  } +} + +  /// Check whether register is empty  static inline bool reg_empty(const yankreg_T *const reg)    FUNC_ATTR_PURE diff --git a/src/nvim/option.c b/src/nvim/option.c index 8ba10fd38a..f6f334f432 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -242,6 +242,7 @@ typedef struct vimoption {  #define P_NO_DEF_EXP   0x8000000U  ///< Do not expand default value.  #define P_RWINONLY     0x10000000U  ///< only redraw current window +#define P_NDNAME       0x20000000U  ///< only normal dir name chars allowed  #define HIGHLIGHT_INIT \    "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1749,7 +1750,7 @@ do_set (                if (flags & P_FLAGLIST) {                  // Remove flags that appear twice. -                for (s = newval; *s; s++) { +                for (s = newval; *s;) {                    // if options have P_FLAGLIST and P_ONECOMMA such as                    // 'whichwrap'                    if (flags & P_ONECOMMA) { @@ -1757,15 +1758,16 @@ do_set (                          && vim_strchr(s + 2, *s) != NULL) {                        // Remove the duplicated value and the next comma.                        STRMOVE(s, s + 2); -                      s -= 2; +                      continue;                      }                    } else {                      if ((!(flags & P_COMMA) || *s != ',')                          && vim_strchr(s + 1, *s) != NULL) {                        STRMOVE(s, s + 1); -                      s--; +                      continue;                      }                    } +                  s++;                  }                } @@ -2453,11 +2455,14 @@ did_set_string_option (    if ((secure || sandbox != 0)        && (options[opt_idx].flags & P_SECURE)) {      errmsg = e_secure; -  } else if ((options[opt_idx].flags & P_NFNAME) -             && vim_strpbrk(*varp, (char_u *)"/\\*?[|;&<>\r\n") != NULL) { -    // Check for a "normal" file name in some options.  Disallow a path -    // separator (slash and/or backslash), wildcards and characters that are -    // often illegal in a file name. +  } else if (((options[opt_idx].flags & P_NFNAME) +              && vim_strpbrk(*varp, (char_u *)(secure ? "/\\*?[|;&<>\r\n" +                                               : "/\\*?[<>\r\n")) != NULL) +             || ((options[opt_idx].flags & P_NDNAME) +                 && vim_strpbrk(*varp, (char_u *)"*?[|;&<>\r\n") != NULL)) { +    // Check for a "normal" directory or file name in some options.  Disallow a +    // path separator (slash and/or backslash), wildcards and characters that +    // are often illegal in a file name. Be more permissive if "secure" is off.      errmsg = e_invarg;    }    /* 'backupcopy' */ @@ -2996,9 +3001,10 @@ did_set_string_option (          if (s[-1] == 'k' || s[-1] == 's') {            /* skip optional filename after 'k' and 's' */            while (*s && *s != ',' && *s != ' ') { -            if (*s == '\\') -              ++s; -            ++s; +            if (*s == '\\' && s[1] != NUL) { +              s++; +            } +            s++;            }          } else {            if (errbuf != NULL) { @@ -3171,17 +3177,18 @@ did_set_string_option (    } else {      // Options that are a list of flags.      p = NULL; -    if (varp == &p_ww) +    if (varp == &p_ww) {  // 'whichwrap'        p = (char_u *)WW_ALL; -    if (varp == &p_shm) +    } +    if (varp == &p_shm) {  // 'shortmess'        p = (char_u *)SHM_ALL; -    else if (varp == &(p_cpo)) +    } else if (varp == &(p_cpo)) {  // 'cpoptions'        p = (char_u *)CPO_VI; -    else if (varp == &(curbuf->b_p_fo)) +    } else if (varp == &(curbuf->b_p_fo)) {  // 'formatoptions'        p = (char_u *)FO_ALL; -    else if (varp == &curwin->w_p_cocu) +    } else if (varp == &curwin->w_p_cocu) {  // 'concealcursor'        p = (char_u *)COCU_ALL; -    else if (varp == &p_mouse) { +    } else if (varp == &p_mouse) {  // 'mouse'        p = (char_u *)MOUSE_ALL;      }      if (p != NULL) { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 103227f6b5..7cecb16686 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7,7 +7,7 @@  --    enable_if=nil,  --    defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil},  --    secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, ---    pri_mkrc=nil, deny_in_modelines=nil, +--    pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil,  --    expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true,  --    alloced=nil,  --    save_pv_indir=nil, @@ -575,6 +575,7 @@ return {        full_name='dictionary', abbreviation='dict',        type='string', list='onecomma', scope={'global', 'buffer'},        deny_duplicates=true, +      normal_dname_chars=true,        vi_def=true,        expand=true,        varname='p_dict', @@ -1750,6 +1751,7 @@ return {      {        full_name='printexpr', abbreviation='pexpr',        type='string', scope={'global'}, +      secure=true,        vi_def=true,        varname='p_pexpr',        defaults={if_true={vi=""}} @@ -2449,6 +2451,7 @@ return {        full_name='thesaurus', abbreviation='tsr',        type='string', list='onecomma', scope={'global', 'buffer'},        deny_duplicates=true, +      normal_dname_chars=true,        vi_def=true,        expand=true,        varname='p_tsr', @@ -2498,7 +2501,7 @@ return {        no_mkrc=true,        vi_def=true,        varname='p_titleold', -      defaults={if_true={vi=N_("Thanks for flying Vim")}} +      defaults={if_true={vi=N_("")}}      },      {        full_name='titlestring', 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/path.c b/src/nvim/path.c index f2339c8046..51adcfb135 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1690,6 +1690,9 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force)    if (strlen(fname) > (len - 1)) {      xstrlcpy(buf, fname, len);  // truncate +#ifdef WIN32 +    slash_adjust(buf); +#endif      return FAIL;    } @@ -1702,6 +1705,9 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force)    if (rv == FAIL) {      xstrlcpy(buf, fname, len);  // something failed; use the filename    } +#ifdef WIN32 +  slash_adjust(buf); +#endif    return rv;  } @@ -2196,11 +2202,11 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf,    // expand it if forced or not an absolute path    if (force || !path_is_absolute_path(fname)) { -    if ((p = vim_strrchr(fname, '/')) != NULL) { +    if ((p = vim_strrchr(fname, PATHSEP)) != NULL) {        // relative to root        if (p == fname) {          // only one path component -        relative_directory[0] = '/'; +        relative_directory[0] = PATHSEP;          relative_directory[1] = NUL;        } else {          assert(p >= fname); diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 847b2f273e..ae611a0005 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -3316,6 +3316,47 @@ bt_regexec_nl (    return (int)r;  } +/// Wrapper around strchr which accounts for case-insensitive searches and +/// non-ASCII characters. +/// +/// This function is used a lot for simple searches, keep it fast! +/// +/// @param  s  string to search +/// @param  c  character to find in @a s +/// +/// @return  NULL if no match, otherwise pointer to the position in @a s +static inline char_u *cstrchr(const char_u *const s, const int c) +  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +  FUNC_ATTR_ALWAYS_INLINE +{ +  if (!rex.reg_ic) { +    return vim_strchr(s, c); +  } + +  // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is +  // expected to be highly optimized. +  if (c > 0x80) { +    const int folded_c = utf_fold(c); +    for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { +      if (utf_fold(utf_ptr2char(p)) == folded_c) { +        return (char_u *)p; +      } +    } +    return NULL; +  } + +  int cc; +  if (ASCII_ISUPPER(c)) { +    cc = TOLOWER_ASC(c); +  } else if (ASCII_ISLOWER(c)) { +    cc = TOUPPER_ASC(c); +  } else { +    return vim_strchr(s, c); +  } + +  char tofind[] = { (char)c, (char)cc, NUL }; +  return (char_u *)strpbrk((const char *)s, tofind); +}  /// Matches a regexp against multiple lines.  /// "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). @@ -6320,42 +6361,6 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n)    return result;  } -/* - * cstrchr: This function is used a lot for simple searches, keep it fast! - */ -static inline char_u *cstrchr(const char_u *const s, const int c) -  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -  FUNC_ATTR_ALWAYS_INLINE -{ -  if (!rex.reg_ic) { -    return vim_strchr(s, c); -  } - -  // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is -  // expected to be highly optimized. -  if (c > 0x80) { -    const int folded_c = utf_fold(c); -    for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { -      if (utf_fold(utf_ptr2char(p)) == folded_c) { -        return (char_u *)p; -      } -    } -    return NULL; -  } - -  int cc; -  if (ASCII_ISUPPER(c)) { -    cc = TOLOWER_ASC(c); -  } else if (ASCII_ISLOWER(c)) { -    cc = TOUPPER_ASC(c); -  } else { -    return vim_strchr(s, c); -  } - -  char tofind[] = { (char)c, (char)cc, NUL }; -  return (char_u *)strpbrk((const char *)s, tofind); -} -  /***************************************************************  *		      regsub stuff			       *  ***************************************************************/ diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 6426ee441b..b5d56e07fc 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -15,6 +15,8 @@  #include <stdbool.h>  #include "nvim/pos.h" +#include "nvim/types.h" +#include "nvim/profile.h"  /*   * The number of sub-matches is limited to 10. @@ -41,18 +43,36 @@  #define NFA_ENGINE          2  typedef struct regengine regengine_T; +typedef struct regprog regprog_T; +typedef struct reg_extmatch reg_extmatch_T; + +/// Structure to be used for multi-line matching. +/// Sub-match "no" starts in line "startpos[no].lnum" column "startpos[no].col" +/// and ends in line "endpos[no].lnum" just before column "endpos[no].col". +/// The line numbers are relative to the first line, thus startpos[0].lnum is +/// always 0. +/// When there is no match, the line number is -1. +typedef struct { +  regprog_T           *regprog; +  lpos_T startpos[NSUBEXP]; +  lpos_T endpos[NSUBEXP]; +  int rmm_ic; +  colnr_T rmm_maxcol;  /// when not zero: maximum column +} regmmatch_T; + +#include "nvim/buffer_defs.h"  /*   * Structure returned by vim_regcomp() to pass on to vim_regexec().   * This is the general structure. For the actual matcher, two specific   * structures are used. See code below.   */ -typedef struct regprog { +struct regprog {    regengine_T *engine;    unsigned regflags;    unsigned re_engine;  ///< Automatic, backtracking or NFA engine.    unsigned re_flags;   ///< Second argument for vim_regcomp(). -} regprog_T; +};  /*   * Structure used by the back track matcher. @@ -126,30 +146,14 @@ typedef struct {  } regmatch_T;  /* - * Structure to be used for multi-line matching. - * Sub-match "no" starts in line "startpos[no].lnum" column "startpos[no].col" - * and ends in line "endpos[no].lnum" just before column "endpos[no].col". - * The line numbers are relative to the first line, thus startpos[0].lnum is - * always 0. - * When there is no match, the line number is -1. - */ -typedef struct { -  regprog_T           *regprog; -  lpos_T startpos[NSUBEXP]; -  lpos_T endpos[NSUBEXP]; -  int rmm_ic; -  colnr_T rmm_maxcol;                   /* when not zero: maximum column */ -} regmmatch_T; - -/*   * Structure used to store external references: "\z\(\)" to "\z\1".   * Use a reference count to avoid the need to copy this around.  When it goes   * from 1 to zero the matches need to be freed.   */ -typedef struct { -  short refcnt; +struct reg_extmatch { +  int16_t refcnt;    char_u              *matches[NSUBEXP]; -} reg_extmatch_T; +};  struct regengine {    regprog_T   *(*regcomp)(char_u*, int); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 95973354bc..f5730cf70a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2201,16 +2201,16 @@ win_line (    int change_end = -1;                  /* last col of changed area */    colnr_T trailcol = MAXCOL;            /* start of trailing spaces */    int need_showbreak = false;           // overlong line, skip first x chars -  int line_attr = 0;                    /* attribute for the whole line */ -  matchitem_T *cur;                     /* points to the match list */ -  match_T     *shl;                     /* points to search_hl or a match */ -  int shl_flag;                         /* flag to indicate whether search_hl -                                           has been processed or not */ -  int prevcol_hl_flag;                  /* flag to indicate whether prevcol -                                           equals startcol of search_hl or one -                                           of the matches */ -  int prev_c = 0;                       /* previous Arabic character */ -  int prev_c1 = 0;                      /* first composing char for prev_c */ +  int line_attr = 0;                    // attribute for the whole line +  matchitem_T *cur;                     // points to the match list +  match_T     *shl;                     // points to search_hl or a match +  int shl_flag;                         // flag to indicate whether search_hl +                                        // has been processed or not +  int prevcol_hl_flag;                  // flag to indicate whether prevcol +                                        // equals startcol of search_hl or one +                                        // of the matches +  int prev_c = 0;                       // previous Arabic character +  int prev_c1 = 0;                      // first composing char for prev_c    int did_line_attr = 0;    bool search_attr_from_match = false;  // if search_attr is from :match @@ -2427,10 +2427,11 @@ win_line (      filler_lines = wp->w_topfill;    filler_todo = filler_lines; -  /* If this line has a sign with line highlighting set line_attr. */ +  // If this line has a sign with line highlighting set line_attr.    v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL); -  if (v != 0) -      line_attr = sign_get_attr((int)v, TRUE); +  if (v != 0) { +    line_attr = sign_get_attr((int)v, true); +  }    // Highlight the current line in the quickfix window.    if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { @@ -2663,9 +2664,9 @@ win_line (        cur = cur->next;    } -  /* Cursor line highlighting for 'cursorline' in the current window.  Not -   * when Visual mode is active, because it's not clear what is selected -   * then. */ +  // Cursor line highlighting for 'cursorline' in the current window.  Not +  // when Visual mode is active, because it's not clear what is selected +  // then.    if (wp->w_p_cul && lnum == wp->w_cursor.lnum        && !(wp == curwin && VIsual_active)) {      if (line_attr != 0 && !(State & INSERT) && bt_quickfix(wp->w_buffer) @@ -3594,15 +3595,13 @@ win_line (                     && lcs_eol_one > 0) {            // Display a '$' after the line or highlight an extra            // character if the line break is included. -          // For a diff line the highlighting continues after the -          // "$". +          // For a diff line the highlighting continues after the "$".            if (diff_hlf == (hlf_T)0 && line_attr == 0) { -            /* In virtualedit, visual selections may extend -             * beyond end of line. */ +            // In virtualedit, visual selections may extend beyond end of line.              if (area_highlighting && virtual_active() -                && tocol != MAXCOL && vcol < tocol) +                && tocol != MAXCOL && vcol < tocol) {                n_extra = 0; -            else { +            } else {                p_extra = at_end_str;                n_extra = 1;                c_extra = NUL; @@ -4035,10 +4034,10 @@ win_line (        if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol            && lnum != wp->w_cursor.lnum) {          vcol_save_attr = char_attr; -        char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUC)); +        char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);        } else if (draw_color_col && VCOL_HLC == *color_cols) {          vcol_save_attr = char_attr; -        char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_MC)); +        char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr);        }      } @@ -5847,7 +5846,7 @@ static void screen_start_highlight(int attr)    ui_start_highlight(attr);  } -void screen_stop_highlight(void) +static void screen_stop_highlight(void)  {    ui_stop_highlight();    screen_attr = 0; diff --git a/src/nvim/search.c b/src/nvim/search.c index 1bf2317d2a..387614fd09 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3557,11 +3557,15 @@ extend:        --start_lnum;    if (VIsual_active) { -    /* Problem: when doing "Vipipip" nothing happens in a single white -     * line, we get stuck there.  Trap this here. */ -    if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) +    // Problem: when doing "Vipipip" nothing happens in a single white +    // line, we get stuck there.  Trap this here. +    if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) {        goto extend; -    VIsual.lnum = start_lnum; +    } +    if (VIsual.lnum != start_lnum) { +        VIsual.lnum = start_lnum; +        VIsual.col = 0; +    }      VIsual_mode = 'V';      redraw_curbuf_later(INVERTED);      /* update the inversion */      showmode(); diff --git a/src/nvim/state.c b/src/nvim/state.c index eb0b590a9b..4d9032b7a5 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -26,10 +26,11 @@ void state_enter(VimState *s)      int check_result = s->check ? s->check(s) : 1;      if (!check_result) { -      break; +      break;     // Terminate this state.      } else if (check_result == -1) { -      continue; +      continue;  // check() again.      } +    // Execute this state.      int key; @@ -48,11 +49,13 @@ getkey:        ui_flush();        // Call `os_inchar` directly to block for events or user input without        // consuming anything from `input_buffer`(os/input.c) or calling the -      // mapping engine. If an event was put into the queue, we send K_EVENT -      // directly. +      // mapping engine.        (void)os_inchar(NULL, 0, -1, 0);        input_disable_events(); -      key = !multiqueue_empty(main_loop.events) ? K_EVENT : safe_vgetc(); +      // If an event was put into the queue, we send K_EVENT directly. +      key = !multiqueue_empty(main_loop.events) +            ? K_EVENT +            : safe_vgetc();      }      if (key == K_EVENT) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f0171fa525..70bda42d83 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -42,6 +42,7 @@  #include "nvim/ui.h"  #include "nvim/os/os.h"  #include "nvim/os/time.h" +#include "nvim/api/private/helpers.h"  static bool did_syntax_onoff = false; @@ -81,7 +82,10 @@ struct hl_group {  // highlight groups for 'highlight' option  static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; -#define HL_TABLE() ((struct hl_group *)((highlight_ga.ga_data))) +static inline struct hl_group * HL_TABLE(void) +{ +  return ((struct hl_group *)((highlight_ga.ga_data))); +}  #define MAX_HL_ID       20000   /* maximum value for a highlight ID. */ @@ -100,10 +104,8 @@ static int include_none = 0;    /* when 1 include "nvim/None" */  static int include_default = 0; /* when 1 include "nvim/default" */  static int include_link = 0;    /* when 2 include "nvim/link" and "clear" */ -/* - * The "term", "cterm" and "gui" arguments can be any combination of the - * following names, separated by commas (but no spaces!). - */ +/// The "term", "cterm" and "gui" arguments can be any combination of the +/// following names, separated by commas (but no spaces!).  static char *(hl_name_table[]) =  {"bold", "standout", "underline", "undercurl",   "italic", "reverse", "inverse", "NONE"}; @@ -1775,8 +1777,9 @@ syn_current_attr (                    cur_si->si_trans_id = CUR_STATE(                        current_state.ga_len - 2).si_trans_id;                  } -              } else +              } else {                  cur_si->si_attr = syn_id2attr(syn_id); +              }                cur_si->si_cont_list = NULL;                cur_si->si_next_list = next_list;                check_keepend(); @@ -5252,12 +5255,10 @@ get_id_list (          /*           * Handle full group name.           */ -        if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL) +        if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL) {            id = syn_check_group(name + 1, (int)(end - p)); -        else { -          /* -           * Handle match of regexp with group names. -           */ +        } else { +          // Handle match of regexp with group names.            *name = '^';            STRCAT(name, "$");            regmatch.regprog = vim_regcomp(name, RE_MAGIC); @@ -5567,8 +5568,10 @@ bool syntax_present(win_T *win)  static enum { -  EXP_SUBCMD,       /* expand ":syn" sub-commands */ -  EXP_CASE          /* expand ":syn case" arguments */ +  EXP_SUBCMD,       // expand ":syn" sub-commands +  EXP_CASE,         // expand ":syn case" arguments +  EXP_SPELL,        // expand ":syn spell" arguments +  EXP_SYNC          // expand ":syn sync" arguments  } expand_what;  /* @@ -5612,6 +5615,10 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg)          xp->xp_context = EXPAND_NOTHING;        } else if (STRNICMP(arg, "case", p - arg) == 0) {          expand_what = EXP_CASE; +      } else if (STRNICMP(arg, "spell", p - arg) == 0) { +        expand_what = EXP_SPELL; +      } else if (STRNICMP(arg, "sync", p - arg) == 0) { +        expand_what = EXP_SYNC;        } else if (STRNICMP(arg, "keyword", p - arg) == 0                   || STRNICMP(arg, "region", p - arg) == 0                   || STRNICMP(arg, "match", p - arg) == 0 @@ -5624,17 +5631,33 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg)    }  } -static char *(case_args[]) = {"match", "ignore", NULL}; -  /*   * Function given to ExpandGeneric() to obtain the list syntax names for   * expansion.   */  char_u *get_syntax_name(expand_T *xp, int idx)  { -  if (expand_what == EXP_SUBCMD) -    return (char_u *)subcommands[idx].name; -  return (char_u *)case_args[idx]; +  switch (expand_what) { +    case EXP_SUBCMD: +        return (char_u *)subcommands[idx].name; +    case EXP_CASE: { +        static char *case_args[] = { "match", "ignore", NULL }; +        return (char_u *)case_args[idx]; +    } +    case EXP_SPELL: { +        static char *spell_args[] = +        { "toplevel", "notoplevel", "default", NULL }; +        return (char_u *)spell_args[idx]; +    } +    case EXP_SYNC: { +        static char *sync_args[] = +        { "ccomment", "clear", "fromstart", +         "linebreaks=", "linecont", "lines=", "match", +         "maxlines=", "minlines=", "region", NULL }; +        return (char_u *)sync_args[idx]; +    } +  } +  return NULL;  } @@ -5845,9 +5868,12 @@ static void syntime_report(void)      }    } -  /* sort on total time */ -  qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T), -      syn_compare_syntime); +  // Sort on total time. Skip if there are no items to avoid passing NULL +  // pointer to qsort(). +  if (ga.ga_len > 1) { +    qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T), +          syn_compare_syntime); +  }    MSG_PUTS_TITLE(_(            "  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN")); @@ -5958,6 +5984,7 @@ static char *highlight_init_light[] =    "Title        ctermfg=DarkMagenta gui=bold guifg=Magenta",    "Visual       guibg=LightGrey",    "WarningMsg   ctermfg=DarkRed guifg=Red", +  "Normal       gui=NONE",    NULL  }; @@ -5991,23 +6018,25 @@ static char *highlight_init_dark[] =    "Title        ctermfg=LightMagenta gui=bold guifg=Magenta",    "Visual       guibg=DarkGrey",    "WarningMsg   ctermfg=LightRed guifg=Red", +  "Normal       gui=NONE",    NULL  }; -void  -init_highlight ( -    int both,                   /* include groups where 'bg' doesn't matter */ -    int reset                  /* clear group first */ -) + +/// Load colors from a file if "g:colors_name" is set, otherwise load builtin +/// colors +/// +/// @param both include groups where 'bg' doesn't matter +/// @param reset clear groups first +void +init_highlight(int both, int reset)  {    int i;    char        **pp;    static int had_both = FALSE; -  /* -   * Try finding the color scheme file.  Used when a color file was loaded -   * and 'background' or 't_Co' is changed. -   */ +  // Try finding the color scheme file.  Used when a color file was loaded +  // and 'background' or 't_Co' is changed.    char_u *p = get_var_value("g:colors_name");    if (p != NULL) {      // Value of g:colors_name could be freed in load_colors() and make @@ -6026,33 +6055,34 @@ init_highlight (    if (both) {      had_both = TRUE;      pp = highlight_init_both; -    for (i = 0; pp[i] != NULL; ++i) -      do_highlight((char_u *)pp[i], reset, TRUE); -  } else if (!had_both) -    /* Don't do anything before the call with both == TRUE from main(). -     * Not everything has been setup then, and that call will overrule -     * everything anyway. */ +    for (i = 0; pp[i] != NULL; i++) { +      do_highlight((char_u *)pp[i], reset, true); +    } +  } else if (!had_both) { +    // Don't do anything before the call with both == TRUE from main(). +    // Not everything has been setup then, and that call will overrule +    // everything anyway.      return; +  } -  if (*p_bg == 'l') -    pp = highlight_init_light; -  else -    pp = highlight_init_dark; -  for (i = 0; pp[i] != NULL; ++i) -    do_highlight((char_u *)pp[i], reset, TRUE); +  pp = (*p_bg == 'l') ?  highlight_init_light : highlight_init_dark; + +  for (i = 0; pp[i] != NULL; i++) { +    do_highlight((char_u *)pp[i], reset, true); +  }    /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it     * depend on the number of colors available.     * With 8 colors brown is equal to yellow, need to use black for Search fg     * to avoid Statement highlighted text disappears.     * Clear the attributes, needed when changing the t_Co value. */ -  if (t_colors > 8) +  if (t_colors > 8) {      do_highlight(          (char_u *)(*p_bg == 'l'                     ? "Visual cterm=NONE ctermbg=LightGrey" -                   : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, -        TRUE); -  else { +                   : "Visual cterm=NONE ctermbg=DarkGrey"), false, +        true); +  } else {      do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",          FALSE, TRUE);      if (*p_bg == 'l') @@ -6112,12 +6142,7 @@ int load_colors(char_u *name)  /// "forceit" and "init" both TRUE.  /// @param init TRUE when called for initializing  void -do_highlight( -    char_u *line, -    int forceit, -    int init -) -{ +do_highlight(char_u *line, int forceit, int init) {    char_u      *name_end;    char_u      *linep;    char_u      *key_start; @@ -6134,15 +6159,16 @@ do_highlight(    int dolink = FALSE;    int error = FALSE;    int color; -  int is_normal_group = FALSE;                  /* "Normal" group */ +  bool is_normal_group = false;   // "Normal" group    /*     * If no argument, list current highlighting.     */    if (ends_excmd(*line)) { -    for (int i = 1; i <= highlight_ga.ga_len && !got_int; ++i) -      /* TODO: only call when the group has attributes set */ +    for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { +      // todo(vim): only call when the group has attributes set        highlight_list_one(i); +    }      return;    } @@ -6270,12 +6296,12 @@ do_highlight(      return;    idx = id - 1;                         /* index is ID minus one */ -  /* Return if "default" was used and the group already has settings. */ -  if (dodefault && hl_has_settings(idx, TRUE)) +  // Return if "default" was used and the group already has settings +  if (dodefault && hl_has_settings(idx, true)) {      return; +  } -  if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0) -    is_normal_group = TRUE; +  is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0);    /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */    if (doclear || (forceit && init)) { @@ -6284,7 +6310,7 @@ do_highlight(        HL_TABLE()[idx].sg_set = 0;    } -  if (!doclear) +  if (!doclear) {      while (!ends_excmd(*linep)) {        key_start = linep;        if (*linep == '=') { @@ -6390,12 +6416,12 @@ do_highlight(            }          }        } else if (STRCMP(key, "FONT") == 0)   { -        /* in non-GUI fonts are simply ignored */ -      } else if (STRCMP(key, -                     "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)   { +        // in non-GUI fonts are simply ignored +      } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) {          if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { -          if (!init) +          if (!init) {              HL_TABLE()[idx].sg_set |= SG_CTERM; +          }            /* When setting the foreground color, and previously the "bold"             * flag was set for a light color, reset it now */ @@ -6489,9 +6515,10 @@ do_highlight(                     * colors (on some terminals, e.g. "linux") */                    if (color & 8) {                      HL_TABLE()[idx].sg_cterm |= HL_BOLD; -                    HL_TABLE()[idx].sg_cterm_bold = TRUE; -                  } else +                    HL_TABLE()[idx].sg_cterm_bold = true; +                  } else {                      HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; +                  }                  }                  color &= 7;             // truncate to 8 colors                } else if (t_colors == 16 || t_colors == 88 || t_colors >= 256) { @@ -6603,38 +6630,40 @@ do_highlight(        /*         * When highlighting has been given for a group, don't link it.         */ -      if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) +      if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) {          HL_TABLE()[idx].sg_link = 0; +      }        /*         * Continue with next argument.         */        linep = skipwhite(linep);      } +  }    /*     * If there is an error, and it's a new entry, remove it from the table.     */ -  if (error && idx == highlight_ga.ga_len) +  if (error && idx == highlight_ga.ga_len) {      syn_unadd_group(); -  else { +  } else {      if (is_normal_group) { -      HL_TABLE()[idx].sg_attr = 0;        // Need to update all groups, because they might be using "bg" and/or        // "fg", which have been changed now.        highlight_attr_set_all();        // If the normal group has changed, it is simpler to refresh every UI        ui_refresh(); -    } else +    } else {        set_hl_attr(idx); +    }      HL_TABLE()[idx].sg_scriptID = current_SID;      redraw_all_later(NOT_VALID);    }    xfree(key);    xfree(arg); -  /* Only call highlight_changed() once, after sourcing a syntax file */ -  need_highlight_changed = TRUE; +  // Only call highlight_changed() once, after sourcing a syntax file +  need_highlight_changed = true;  }  #if defined(EXITFREE) @@ -6707,14 +6736,15 @@ static void highlight_clear(int idx)  } -/* - * Table with the specifications for an attribute number. - * Note that this table is used by ALL buffers.  This is required because the - * GUI can redraw at any time for any buffer. - */ +/// Table with the specifications for an attribute number. +/// Note that this table is used by ALL buffers.  This is required because the +/// GUI can redraw at any time for any buffer.  static garray_T attr_table = GA_EMPTY_INIT_VALUE; -#define ATTR_ENTRY(idx) ((attrentry_T *)attr_table.ga_data)[idx] +static inline attrentry_T * ATTR_ENTRY(int idx) +{ +  return &((attrentry_T *)attr_table.ga_data)[idx]; +}  /// Return the attr number for a set of colors and font. @@ -6804,7 +6834,7 @@ int hl_combine_attr(int char_attr, int prim_attr)  {    attrentry_T *char_aep = NULL;    attrentry_T *spell_aep; -  attrentry_T new_en; +  attrentry_T new_en = ATTRENTRY_INIT;    if (char_attr == 0) {      return prim_attr; @@ -6820,8 +6850,6 @@ int hl_combine_attr(int char_attr, int prim_attr)    if (char_aep != NULL) {      // Copy all attributes from char_aep to the new entry      new_en = *char_aep; -  } else { -    memset(&new_en, 0, sizeof(new_en));    }    spell_aep = syn_cterm_attr2entry(prim_attr); @@ -6852,17 +6880,25 @@ int hl_combine_attr(int char_attr, int prim_attr)    return get_attr_entry(&new_en);  } +/// \note this function does not apply exclusively to cterm attr contrary +/// to what its name implies +/// \warn don't call it with attr 0 (i.e., the null attribute)  attrentry_T *syn_cterm_attr2entry(int attr)  {    attr -= ATTR_OFF; -  if (attr >= attr_table.ga_len)          /* did ":syntax clear" */ +  if (attr >= attr_table.ga_len) { +    // did ":syntax clear"      return NULL; -  return &(ATTR_ENTRY(attr)); +  } +  return ATTR_ENTRY(attr);  } +/// \addtogroup LIST_XXX +/// @{  #define LIST_ATTR   1  #define LIST_STRING 2  #define LIST_INT    3 +/// @}  static void highlight_list_one(int id)  { @@ -6901,7 +6937,13 @@ static void highlight_list_one(int id)      last_set_msg(sgp->sg_scriptID);  } -static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg, char *name) +/// Outputs a highlight when doing ":hi MyHighlight" +/// +/// @param type one of \ref LIST_XXX +/// @param iarg integer argument used if \p type == LIST_INT +/// @param sarg string used if \p type == LIST_STRING +static int highlight_list_arg(int id, int didh, int type, int iarg, +                              char_u *sarg, const char *name)  {    char_u buf[100];    char_u      *ts; @@ -7041,24 +7083,23 @@ const char *highlight_color(const int id, const char *const what,    return NULL;  } -/* - * Output the syntax list header. - * Return TRUE when started a new line. - */ -static int  -syn_list_header ( -    int did_header,                 /* did header already */ -    int outlen,                     /* length of string that comes */ -    int id                         /* highlight group id */ -) +/// Output the syntax list header. +/// +/// @param did_header did header already +/// @param outlen length of string that comes +/// @param id highlight group id +/// @return true when started a new line. +static int +syn_list_header(int did_header, int outlen, int id)  {    int endcol = 19;    int newline = TRUE;    if (!did_header) {      msg_putchar('\n'); -    if (got_int) -      return TRUE; +    if (got_int) { +      return true; +    }      msg_outtrans(HL_TABLE()[id - 1].sg_name);      endcol = 15;    } else if (msg_col + outlen + 1 >= Columns)   { @@ -7086,21 +7127,14 @@ syn_list_header (    return newline;  } -/* - * Set the attribute numbers for a highlight group. - * Called after one of the attributes has changed. - */ -static void  -set_hl_attr ( -    int idx                    /* index in array */ -) +/// Set the attribute numbers for a highlight group. +/// Called after one of the attributes has changed. +/// @param idx corrected highlight index +static void set_hl_attr(int idx)  { -  attrentry_T at_en; +  attrentry_T at_en = ATTRENTRY_INIT;    struct hl_group     *sgp = HL_TABLE() + idx; -  /* The "Normal" group doesn't need an attribute number */ -  if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0) -    return;    at_en.cterm_ae_attr = sgp->sg_cterm;    at_en.cterm_fg_color = sgp->sg_cterm_fg; @@ -7124,10 +7158,10 @@ set_hl_attr (    }  } -/* - * Lookup a highlight group name and return it's ID. - * If it is not found, 0 is returned. - */ +/// Lookup a highlight group name and return its ID. +/// +/// @param highlight name e.g. 'Cursor', 'Normal' +/// @return the highlight id, else 0 if \p name does not exist  int syn_name2id(const char_u *name)  {    int i; @@ -7176,7 +7210,7 @@ int syn_namen2id(char_u *linep, int len)    return id;  } -/// Find highlight group name in the table and return it's ID. +/// Find highlight group name in the table and return its ID.  /// If it doesn't exist yet, a new entry is created.  ///  /// @param pp Highlight group name @@ -7195,11 +7229,11 @@ int syn_check_group(char_u *pp, int len)    return id;  } -/* - * Add new highlight group and return it's ID. - * "name" must be an allocated string, it will be consumed. - * Return 0 for failure. - */ +/// Add new highlight group and return it's ID. +/// +/// @param name must be an allocated string, it will be consumed. +/// @return 0 for failure, else the allocated group id +/// @see syn_check_group syn_unadd_group  static int syn_add_group(char_u *name)  {    char_u      *p; @@ -7237,25 +7271,26 @@ static int syn_add_group(char_u *name)    struct hl_group* hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga);    memset(hlgp, 0, sizeof(*hlgp));    hlgp->sg_name = name; +  hlgp->sg_rgb_bg = -1; +  hlgp->sg_rgb_fg = -1; +  hlgp->sg_rgb_sp = -1;    hlgp->sg_name_u = vim_strsave_up(name);    return highlight_ga.ga_len;               /* ID is index plus one */  } -/* - * When, just after calling syn_add_group(), an error is discovered, this - * function deletes the new name. - */ +/// When, just after calling syn_add_group(), an error is discovered, this +/// function deletes the new name.  static void syn_unadd_group(void)  { -  --highlight_ga.ga_len; +  highlight_ga.ga_len--;    xfree(HL_TABLE()[highlight_ga.ga_len].sg_name);    xfree(HL_TABLE()[highlight_ga.ga_len].sg_name_u);  } -/* - * Translate a group ID to highlight attributes. - */ + +/// Translate a group ID to highlight attributes. +/// @see syn_cterm_attr2entry  int syn_id2attr(int hl_id)  {    struct hl_group     *sgp; @@ -8208,6 +8243,30 @@ RgbValue name_to_color(const uint8_t *name)    return -1;  } +/// Gets highlight description for id `attr_id` as a map. +Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) +{ +  HlAttrs attrs = HLATTRS_INIT; +  Dictionary dic = ARRAY_DICT_INIT; + +  if (attr_id == 0) { +    goto end; +  } + +  attrentry_T *aep = syn_cterm_attr2entry((int)attr_id); +  if (!aep) { +    api_set_error(err, kErrorTypeException, +                  "Invalid attribute id: %d", attr_id); +    return dic; +  } + +  attrs = attrentry2hlattrs(aep, rgb); + +end: +  return hlattrs2dict(attrs); +} + +  /**************************************  *  End of Highlighting stuff	      *  **************************************/ diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index 9f309451b0..7260853703 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -2,7 +2,6 @@  #define NVIM_SYNTAX_DEFS_H  #include "nvim/highlight_defs.h" -#include "nvim/regexp_defs.h"  # define SST_MIN_ENTRIES 150    /* minimal size for state stack array */  # define SST_MAX_ENTRIES 1000   /* maximal size for state stack array */ @@ -10,6 +9,11 @@  # define SST_DIST        16     /* normal distance between entries */  # define SST_INVALID    (synstate_T *)-1        /* invalid syn_state pointer */ +typedef struct syn_state synstate_T; + +#include "nvim/buffer_defs.h" +#include "nvim/regexp_defs.h" +  typedef unsigned short disptick_T;      /* display tick type */  /* struct passed to in_id_list() */ @@ -48,8 +52,6 @@ typedef struct buf_state {   * syn_state contains the syntax state stack for the start of one line.   * Used by b_sst_array[].   */ -typedef struct syn_state synstate_T; -  struct syn_state {    synstate_T  *sst_next;        /* next entry in used or free list */    linenr_T sst_lnum;            /* line number for this state */ @@ -73,4 +75,14 @@ typedef struct attr_entry {    int cterm_fg_color, cterm_bg_color;  } attrentry_T; +#define ATTRENTRY_INIT { \ +  .rgb_ae_attr = 0, \ +  .cterm_ae_attr = 0, \ +  .rgb_fg_color = -1, \ +  .rgb_bg_color = -1, \ +  .rgb_sp_color = -1, \ +  .cterm_fg_color = 0, \ +  .cterm_bg_color = 0, \ +} +  #endif // NVIM_SYNTAX_DEFS_H diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index deec930ebd..1dac9c69bd 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -432,14 +432,6 @@ static int terminal_execute(VimState *state, int key)    TerminalState *s = (TerminalState *)state;    switch (key) { -    case K_FOCUSGAINED:  // nvim has been given focus -      apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); -      break; - -    case K_FOCUSLOST:   // nvim has lost focus -      apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); -      break; -      // Temporary fix until paste events gets implemented      case K_PASTE:        break; @@ -530,6 +522,12 @@ void terminal_send(Terminal *term, char *data, size_t size)  void terminal_send_key(Terminal *term, int c)  {    VTermModifier mod = VTERM_MOD_NONE; + +  // Convert K_ZERO back to ASCII +  if (c == K_ZERO) { +    c = Ctrl_AT; +  } +    VTermKey key = convert_key(c, &mod);    if (key) { @@ -554,7 +552,7 @@ void terminal_receive(Terminal *term, char *data, size_t len)  }  void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, -    int *term_attrs) +                                  int *term_attrs)  {    int height, width;    vterm_get_size(term->vt, &height, &width); @@ -783,26 +781,60 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)  // }}}  // input handling {{{ -static void convert_modifiers(VTermModifier *statep) +static void convert_modifiers(int key, VTermModifier *statep)  {    if (mod_mask & MOD_MASK_SHIFT) { *statep |= VTERM_MOD_SHIFT; }    if (mod_mask & MOD_MASK_CTRL)  { *statep |= VTERM_MOD_CTRL; }    if (mod_mask & MOD_MASK_ALT)   { *statep |= VTERM_MOD_ALT; } + +  switch (key) { +    case K_S_TAB: +    case K_S_UP: +    case K_S_DOWN: +    case K_S_LEFT: +    case K_S_RIGHT: +    case K_S_F1: +    case K_S_F2: +    case K_S_F3: +    case K_S_F4: +    case K_S_F5: +    case K_S_F6: +    case K_S_F7: +    case K_S_F8: +    case K_S_F9: +    case K_S_F10: +    case K_S_F11: +    case K_S_F12: +      *statep |= VTERM_MOD_SHIFT; +      break; + +    case K_C_LEFT: +    case K_C_RIGHT: +      *statep |= VTERM_MOD_CTRL; +      break; +  }  }  static VTermKey convert_key(int key, VTermModifier *statep)  { -  convert_modifiers(statep); +  convert_modifiers(key, statep);    switch (key) {      case K_BS:        return VTERM_KEY_BACKSPACE; +    case K_S_TAB:     // FALLTHROUGH      case TAB:         return VTERM_KEY_TAB;      case Ctrl_M:      return VTERM_KEY_ENTER;      case ESC:         return VTERM_KEY_ESCAPE; +    case K_S_UP:      // FALLTHROUGH      case K_UP:        return VTERM_KEY_UP; +    case K_S_DOWN:    // FALLTHROUGH      case K_DOWN:      return VTERM_KEY_DOWN; +    case K_S_LEFT:    // FALLTHROUGH +    case K_C_LEFT:    // FALLTHROUGH      case K_LEFT:      return VTERM_KEY_LEFT; +    case K_S_RIGHT:   // FALLTHROUGH +    case K_C_RIGHT:   // FALLTHROUGH      case K_RIGHT:     return VTERM_KEY_RIGHT;      case K_INS:       return VTERM_KEY_INS; @@ -812,22 +844,22 @@ static VTermKey convert_key(int key, VTermModifier *statep)      case K_PAGEUP:    return VTERM_KEY_PAGEUP;      case K_PAGEDOWN:  return VTERM_KEY_PAGEDOWN; -    case K_K0: +    case K_K0:        // FALLTHROUGH      case K_KINS:      return VTERM_KEY_KP_0; -    case K_K1: +    case K_K1:        // FALLTHROUGH      case K_KEND:      return VTERM_KEY_KP_1;      case K_K2:        return VTERM_KEY_KP_2; -    case K_K3: +    case K_K3:        // FALLTHROUGH      case K_KPAGEDOWN: return VTERM_KEY_KP_3;      case K_K4:        return VTERM_KEY_KP_4;      case K_K5:        return VTERM_KEY_KP_5;      case K_K6:        return VTERM_KEY_KP_6; -    case K_K7: +    case K_K7:        // FALLTHROUGH      case K_KHOME:     return VTERM_KEY_KP_7;      case K_K8:        return VTERM_KEY_KP_8; -    case K_K9: +    case K_K9:        // FALLTHROUGH      case K_KPAGEUP:   return VTERM_KEY_KP_9; -    case K_KDEL: +    case K_KDEL:      // FALLTHROUGH      case K_KPOINT:    return VTERM_KEY_KP_PERIOD;      case K_KENTER:    return VTERM_KEY_KP_ENTER;      case K_KPLUS:     return VTERM_KEY_KP_PLUS; @@ -835,6 +867,57 @@ static VTermKey convert_key(int key, VTermModifier *statep)      case K_KMULTIPLY: return VTERM_KEY_KP_MULT;      case K_KDIVIDE:   return VTERM_KEY_KP_DIVIDE; +    case K_S_F1:      // FALLTHROUGH +    case K_F1:        return VTERM_KEY_FUNCTION(1); +    case K_S_F2:      // FALLTHROUGH +    case K_F2:        return VTERM_KEY_FUNCTION(2); +    case K_S_F3:      // FALLTHROUGH +    case K_F3:        return VTERM_KEY_FUNCTION(3); +    case K_S_F4:      // FALLTHROUGH +    case K_F4:        return VTERM_KEY_FUNCTION(4); +    case K_S_F5:      // FALLTHROUGH +    case K_F5:        return VTERM_KEY_FUNCTION(5); +    case K_S_F6:      // FALLTHROUGH +    case K_F6:        return VTERM_KEY_FUNCTION(6); +    case K_S_F7:      // FALLTHROUGH +    case K_F7:        return VTERM_KEY_FUNCTION(7); +    case K_S_F8:      // FALLTHROUGH +    case K_F8:        return VTERM_KEY_FUNCTION(8); +    case K_S_F9:      // FALLTHROUGH +    case K_F9:        return VTERM_KEY_FUNCTION(9); +    case K_S_F10:     // FALLTHROUGH +    case K_F10:       return VTERM_KEY_FUNCTION(10); +    case K_S_F11:     // FALLTHROUGH +    case K_F11:       return VTERM_KEY_FUNCTION(11); +    case K_S_F12:     // FALLTHROUGH +    case K_F12:       return VTERM_KEY_FUNCTION(12); + +    case K_F13:       return VTERM_KEY_FUNCTION(13); +    case K_F14:       return VTERM_KEY_FUNCTION(14); +    case K_F15:       return VTERM_KEY_FUNCTION(15); +    case K_F16:       return VTERM_KEY_FUNCTION(16); +    case K_F17:       return VTERM_KEY_FUNCTION(17); +    case K_F18:       return VTERM_KEY_FUNCTION(18); +    case K_F19:       return VTERM_KEY_FUNCTION(19); +    case K_F20:       return VTERM_KEY_FUNCTION(20); +    case K_F21:       return VTERM_KEY_FUNCTION(21); +    case K_F22:       return VTERM_KEY_FUNCTION(22); +    case K_F23:       return VTERM_KEY_FUNCTION(23); +    case K_F24:       return VTERM_KEY_FUNCTION(24); +    case K_F25:       return VTERM_KEY_FUNCTION(25); +    case K_F26:       return VTERM_KEY_FUNCTION(26); +    case K_F27:       return VTERM_KEY_FUNCTION(27); +    case K_F28:       return VTERM_KEY_FUNCTION(28); +    case K_F29:       return VTERM_KEY_FUNCTION(29); +    case K_F30:       return VTERM_KEY_FUNCTION(30); +    case K_F31:       return VTERM_KEY_FUNCTION(31); +    case K_F32:       return VTERM_KEY_FUNCTION(32); +    case K_F33:       return VTERM_KEY_FUNCTION(33); +    case K_F34:       return VTERM_KEY_FUNCTION(34); +    case K_F35:       return VTERM_KEY_FUNCTION(35); +    case K_F36:       return VTERM_KEY_FUNCTION(36); +    case K_F37:       return VTERM_KEY_FUNCTION(37); +      default:          return VTERM_KEY_NONE;    }  } @@ -1176,6 +1259,10 @@ static void redraw(bool restore_cursor)      update_screen(0);    } +  if (need_maketitle) {  // Update title in terminal-mode. #7248 +    maketitle(); +  } +    if (restore_cursor) {      ui_cursor_goto(save_row, save_col);    } else if (term) { diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h index 25e609fb68..f2b0e232c3 100644 --- a/src/nvim/terminal.h +++ b/src/nvim/terminal.h @@ -10,6 +10,8 @@ typedef void (*terminal_write_cb)(char *buffer, size_t size, void *data);  typedef void (*terminal_resize_cb)(uint16_t width, uint16_t height, void *data);  typedef void (*terminal_close_cb)(void *data); +#include "nvim/buffer_defs.h" +  typedef struct {    void *data;    uint16_t width, height; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 7f1e25900b..38caa8815d 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -58,6 +58,8 @@ NEW_TESTS ?= \  	    test_match.res \  	    test_matchadd_conceal.res \  	    test_matchadd_conceal_utf8.res \ +	    test_mksession.res \ +	    test_mksession_utf8.res \  	    test_nested_function.res \  	    test_normal.res \  	    test_quickfix.res \ @@ -207,7 +209,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_alot.vim b/src/nvim/testdir/test_alot.vim index d55170c27c..535e290a34 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -34,4 +34,5 @@ source test_taglist.vim  source test_true_false.vim  source test_unlet.vim  source test_utf8.vim +source test_virtualedit.vim  source test_window_cmd.vim diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index bab700284f..c0f04f4730 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -230,12 +230,41 @@ func Test_paste_in_cmdline()    call feedkeys("f;:aaa \<C-R>\<C-A> bbb\<C-B>\"\<CR>", 'tx')    call assert_equal('"aaa a;b-c*d bbb', @:) + +  call feedkeys(":\<C-\>etoupper(getline(1))\<CR>\<C-B>\"\<CR>", 'tx') +  call assert_equal('"ASDF.X /TMP/SOME VERYLONGWORD A;B-C*D ', @:)    bwipe!  endfunc -func Test_illegal_address() +func Test_remove_char_in_cmdline() +    call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') +    call assert_equal('"abc ef', @:) + +    call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') +    call assert_equal('"abcdef', @:) + +    call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') +    call assert_equal('"abc ghi', @:) + +    call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') +    call assert_equal('"def', @:) +endfunc + +func Test_illegal_address1()    new    2;'(    2;')    quit  endfunc + +func Test_illegal_address2() +  call writefile(['c', 'x', '  x', '.', '1;y'], 'Xtest.vim') +  new +  source Xtest.vim +  " Trigger calling validate_cursor() +  diffsp Xtest.vim +  quit! +  bwipe! +  call delete('Xtest.vim') +endfunc + diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 5de394de8e..8ee82bd538 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -212,9 +212,63 @@ func Test_diffoff()    call setline(1, ['One', '', 'Two', 'Three'])    diffthis    redraw +  call assert_notequal(normattr, screenattr(1, 1))    diffoff!    redraw    call assert_equal(normattr, screenattr(1, 1))    bwipe!    bwipe!  endfunc + +func Test_diffoff_hidden() +  set diffopt=filler,foldcolumn:0 +  e! one +  call setline(1, ['Two', 'Three']) +  let normattr = screenattr(1, 1) +  diffthis +  botright vert new two +  call setline(1, ['One', 'Four']) +  diffthis +  redraw +  call assert_notequal(normattr, screenattr(1, 1)) +  set hidden +  close +  redraw +  " diffing with hidden buffer two +  call assert_notequal(normattr, screenattr(1, 1)) +  diffoff +  redraw +  call assert_equal(normattr, screenattr(1, 1)) +  diffthis +  redraw +  " still diffing with hidden buffer two +  call assert_notequal(normattr, screenattr(1, 1)) +  diffoff! +  redraw +  call assert_equal(normattr, screenattr(1, 1)) +  diffthis +  redraw +  " no longer diffing with hidden buffer two +  call assert_equal(normattr, screenattr(1, 1)) + +  bwipe! +  bwipe! +  set hidden& diffopt& +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_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 1ca0f722cf..65d99c644c 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -89,17 +89,8 @@ func s:doc_config_teardown()    endif  endfunc -func s:get_cmd_compl_list(cmd) -  let list = [] -  let str = '' -  for cnt in range(1, 999) -    call feedkeys(a:cmd . repeat("\<Tab>", cnt) . "'\<C-B>let str='\<CR>", 'tx') -    if str ==# a:cmd[1:] -      break -    endif -    call add(list, str) -  endfor -  return list +func s:get_help_compl_list(cmd) +  return getcompletion(a:cmd, 'help')  endfunc  func Test_help_complete() @@ -111,49 +102,49 @@ func Test_help_complete()      if has('multi_lang')        set helplang=      endif -    let list = s:get_cmd_compl_list(":h test") -    call assert_equal(['h test-col', 'h test-char'], list) +    let list = s:get_help_compl_list("test") +    call assert_equal(['test-col', 'test-char'], list)      if has('multi_lang')        " 'helplang=ab' and help file lang is 'en'        set helplang=ab -      let list = s:get_cmd_compl_list(":h test") -      call assert_equal(['h test-col', 'h test-char'], list) +      let list = s:get_help_compl_list("test") +      call assert_equal(['test-col', 'test-char'], list)        " 'helplang=' and help file lang is 'en' and 'ab'        set rtp+=Xdir1/doc-ab        set helplang= -      let list = s:get_cmd_compl_list(":h test") -      call assert_equal(sort(['h test-col@en', 'h test-col@ab', -            \             'h test-char@en', 'h test-char@ab']), sort(list)) +      let list = s:get_help_compl_list("test") +      call assert_equal(sort(['test-col@en', 'test-col@ab', +            \             'test-char@en', 'test-char@ab']), sort(list))        " 'helplang=ab' and help file lang is 'en' and 'ab'        set helplang=ab -      let list = s:get_cmd_compl_list(":h test") -      call assert_equal(sort(['h test-col', 'h test-col@en', -            \             'h test-char', 'h test-char@en']), sort(list)) +      let list = s:get_help_compl_list("test") +      call assert_equal(sort(['test-col', 'test-col@en', +            \             'test-char', 'test-char@en']), sort(list))        " 'helplang=' and help file lang is 'en', 'ab' and 'ja'        set rtp+=Xdir1/doc-ja        set helplang= -      let list = s:get_cmd_compl_list(":h test") -      call assert_equal(sort(['h test-col@en', 'h test-col@ab', -            \             'h test-col@ja', 'h test-char@en', -            \             'h test-char@ab', 'h test-char@ja']), sort(list)) +      let list = s:get_help_compl_list("test") +      call assert_equal(sort(['test-col@en', 'test-col@ab', +            \             'test-col@ja', 'test-char@en', +            \             'test-char@ab', 'test-char@ja']), sort(list))        " 'helplang=ab' and help file lang is 'en', 'ab' and 'ja'        set helplang=ab -      let list = s:get_cmd_compl_list(":h test") -      call assert_equal(sort(['h test-col', 'h test-col@en', -            \             'h test-col@ja', 'h test-char', -            \             'h test-char@en', 'h test-char@ja']), sort(list)) +      let list = s:get_help_compl_list("test") +      call assert_equal(sort(['test-col', 'test-col@en', +            \             'test-col@ja', 'test-char', +            \             'test-char@en', 'test-char@ja']), sort(list))        " 'helplang=ab,ja' and help file lang is 'en', 'ab' and 'ja'        set helplang=ab,ja -      let list = s:get_cmd_compl_list(":h test") -      call assert_equal(sort(['h test-col', 'h test-col@ja', -            \             'h test-col@en', 'h test-char', -            \             'h test-char@ja', 'h test-char@en']), sort(list)) +      let list = s:get_help_compl_list("test") +      call assert_equal(sort(['test-col', 'test-col@ja', +            \             'test-col@en', 'test-char', +            \             'test-char@ja', 'test-char@en']), sort(list))      endif    catch      call assert_exception('X') diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim new file mode 100644 index 0000000000..4774cf4af5 --- /dev/null +++ b/src/nvim/testdir/test_mksession.vim @@ -0,0 +1,155 @@ +" Test for :mksession, :mkview and :loadview in latin1 encoding + +scriptencoding latin1 + +if !has('multi_byte') || !has('mksession') +  finish +endif + +func Test_mksession() +  tabnew +  let wrap_save = &wrap +  set sessionoptions=buffers splitbelow fileencoding=latin1 +  call setline(1, [ +    \   'start:', +    \   'no multibyte chAracter', +    \   '	one leaDing tab', +    \   '    four leadinG spaces', +    \   'two		consecutive tabs', +    \   'two	tabs	in one line', +    \   'one  multibyteCharacter', +    \   'a   two multiByte characters', +    \   'A  three mulTibyte characters' +    \ ]) +  let tmpfile = 'Xtemp' +  exec 'w! ' . tmpfile +  /^start: +  set wrap +  vsplit +  norm! j16| +  split +  norm! j16| +  split +  norm! j16| +  split +  norm! j8| +  split +  norm! j8| +  split +  norm! j16| +  split +  norm! j16| +  split +  norm! j16| +  wincmd l + +  set nowrap +  /^start: +  norm! j16|3zl +  split +  norm! j016|3zl +  split +  norm! j016|3zl +  split +  norm! j08|3zl +  split +  norm! j08|3zl +  split +  norm! j016|3zl +  split +  norm! j016|3zl +  split +  norm! j016|3zl +  split +  call wincol() +  mksession! Xtest_mks.out +  let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"') +  let expected = [ +    \   'normal! 016|', +    \   'normal! 016|', +    \   'normal! 016|', +    \   'normal! 08|', +    \   'normal! 08|', +    \   'normal! 016|', +    \   'normal! 016|', +    \   'normal! 016|', +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 8 . '|'", +    \   "  normal! 08|", +    \   "  exe 'normal! ' . s:c . '|zs' . 8 . '|'", +    \   "  normal! 08|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|" +    \ ] +  call assert_equal(expected, li) +  tabclose! + +  call delete('Xtest_mks.out') +  call delete(tmpfile) +  let &wrap = wrap_save +endfunc + +func Test_mksession_winheight() +  new +  set winheight=10 winminheight=2 +  mksession! Xtest_mks.out +  source Xtest_mks.out + +  call delete('Xtest_mks.out') +endfunc + +" Verify that arglist is stored correctly to the session file. +func Test_mksession_arglist() +  argdel * +  next file1 file2 file3 file4 +  mksession! Xtest_mks.out +  source Xtest_mks.out +  call assert_equal(['file1', 'file2', 'file3', 'file4'], argv()) + +  call delete('Xtest_mks.out') +  argdel * +endfunc + + +func Test_mksession_one_buffer_two_windows() +  edit Xtest1 +  new Xtest2 +  split +  mksession! Xtest_mks.out +  let lines = readfile('Xtest_mks.out') +  let count1 = 0 +  let count2 = 0 +  let count2buf = 0 +  for line in lines +    if line =~ 'edit \f*Xtest1$' +      let count1 += 1 +    endif +    if line =~ 'edit \f\{-}Xtest2' +      let count2 += 1 +    endif +    if line =~ 'buffer \f\{-}Xtest2' +      let count2buf += 1 +    endif +  endfor +  call assert_equal(1, count1, 'Xtest1 count') +  call assert_equal(2, count2, 'Xtest2 count') +  call assert_equal(2, count2buf, 'Xtest2 buffer count') + +  close +  bwipe! +  call delete('Xtest_mks.out') +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim new file mode 100644 index 0000000000..c05a1d3b6d --- /dev/null +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -0,0 +1,104 @@ +" Test for :mksession, :mkview and :loadview in utf-8 encoding + +set encoding=utf-8 +scriptencoding utf-8 + +if !has('multi_byte') || !has('mksession') +  finish +endif + +func Test_mksession_utf8() +  tabnew +  let wrap_save = &wrap +  set sessionoptions=buffers splitbelow fileencoding=utf-8 +  call setline(1, [ +    \   'start:', +    \   'no multibyte chAracter', +    \   '	one leaDing tab', +    \   '    four leadinG spaces', +    \   'two		consecutive tabs', +    \   'two	tabs	in one line', +    \   'one … multibyteCharacter', +    \   'a “b” two multiByte characters', +    \   '“c”1€ three mulTibyte characters' +    \ ]) +  let tmpfile = tempname() +  exec 'w! ' . tmpfile +  /^start: +  set wrap +  vsplit +  norm! j16| +  split +  norm! j16| +  split +  norm! j16| +  split +  norm! j8| +  split +  norm! j8| +  split +  norm! j16| +  split +  norm! j16| +  split +  norm! j16| +  wincmd l + +  set nowrap +  /^start: +  norm! j16|3zl +  split +  norm! j016|3zl +  split +  norm! j016|3zl +  split +  norm! j08|3zl +  split +  norm! j08|3zl +  split +  norm! j016|3zl +  split +  norm! j016|3zl +  split +  norm! j016|3zl +  split +  call wincol() +  mksession! test_mks.out +  let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"') +  let expected = [ +    \   'normal! 016|', +    \   'normal! 016|', +    \   'normal! 016|', +    \   'normal! 08|', +    \   'normal! 08|', +    \   'normal! 016|', +    \   'normal! 016|', +    \   'normal! 016|', +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 8 . '|'", +    \   "  normal! 08|", +    \   "  exe 'normal! ' . s:c . '|zs' . 8 . '|'", +    \   "  normal! 08|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|", +    \   "  exe 'normal! ' . s:c . '|zs' . 16 . '|'", +    \   "  normal! 016|" +    \ ] +  call assert_equal(expected, li) +  tabclose! + +  call delete('test_mks.out') +  call delete(tmpfile) +  let &wrap = wrap_save +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 5ee0919e18..08ee00e352 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -13,6 +13,12 @@ function! Test_whichwrap()    set whichwrap+=h,l    call assert_equal('b,s,h,l', &whichwrap) +  set whichwrap=h,h +  call assert_equal('h', &whichwrap) + +  set whichwrap=h,h,h +  call assert_equal('h', &whichwrap) +    set whichwrap&  endfunction @@ -97,3 +103,36 @@ func Test_keymap_valid()    call assert_fails(":set kmp=trunc\x00name", "E544:")    call assert_fails(":set kmp=trunc\x00name", "trunc")  endfunc + +func Check_dir_option(name) +  " Check that it's possible to set the option. +  exe 'set ' . a:name . '=/usr/share/dict/words' +  call assert_equal('/usr/share/dict/words', eval('&' . a:name)) +  exe 'set ' . a:name . '=/usr/share/dict/words,/and/there' +  call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name)) +  exe 'set ' . a:name . '=/usr/share/dict\ words' +  call assert_equal('/usr/share/dict words', eval('&' . a:name)) + +  " Check rejecting weird characters. +  call assert_fails("set " . a:name . "=/not&there", "E474:") +  call assert_fails("set " . a:name . "=/not>there", "E474:") +  call assert_fails("set " . a:name . "=/not.*there", "E474:") +endfunc + +func Test_dictionary() +  call Check_dir_option('dictionary') +endfunc + +func Test_thesaurus() +  call Check_dir_option('thesaurus') +endfunc + +func Test_complete() +  " Trailing single backslash used to cause invalid memory access. +  set complete=s\ +  new +  call feedkeys("i\<C-N>\<Esc>", 'xt') +  bwipe! +  set complete& +endfun + diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 519d855cd8..e1ba142d1c 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -7,10 +7,10 @@ func! ListMonths()    if g:setting != ''      exe ":set" g:setting    endif -  let mth=copy(g:months) +  let mth = copy(g:months)    let entered = strcharpart(getline('.'),0,col('.'))    if !empty(entered) -    let mth=filter(mth, 'v:val=~"^".entered') +    let mth = filter(mth, 'v:val=~"^".entered')    endif    call complete(1, mth)    return '' @@ -468,7 +468,7 @@ endfunc  " auto-wrap text.  func Test_completion_ctrl_e_without_autowrap()    new -  let tw_save=&tw +  let tw_save = &tw    set tw=78    let li = [          \ '"                                                        zzz', @@ -478,7 +478,7 @@ func Test_completion_ctrl_e_without_autowrap()    call feedkeys("A\<C-X>\<C-N>\<C-E>\<Esc>", "tx")    call assert_equal(li, getline(1, '$')) -  let &tw=tw_save +  let &tw = tw_save    q!  endfunc @@ -541,4 +541,33 @@ func Test_completion_comment_formatting()    bwipe!  endfunc +function! DummyCompleteSix() +  call complete(1, ['Hello', 'World']) +  return '' +endfunction + +" complete() correctly clears the list of autocomplete candidates +func Test_completion_clear_candidate_list() +  new +  %d +  " select first entry from the completion popup +  call feedkeys("a    xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>", "tx") +  call assert_equal('Hello', getline(1)) +  %d +  " select second entry from the completion popup +  call feedkeys("a    xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>", "tx") +  call assert_equal('World', getline(1)) +  %d +  " select original text +  call feedkeys("a    xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>", "tx") +  call assert_equal('    xxx', getline(1)) +  %d +  " back at first entry from completion list +  call feedkeys("a    xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>\<C-N>", "tx") +  call assert_equal('Hello', getline(1)) + +  bw! +endfunc + +  " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 64f7f31294..11e26d03aa 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -24,28 +24,34 @@ func Test_after_comes_later()  	\ 'set guioptions+=M',  	\ 'let $HOME = "/does/not/exist"',  	\ 'set loadplugins', -	\ 'set rtp=Xhere,Xafter', +	\ 'set rtp=Xhere,Xafter,Xanother',  	\ 'set packpath=Xhere,Xafter',  	\ 'set nomore', +	\ 'let g:sequence = ""',  	\ ]    let after = [  	\ 'redir! > Xtestout',  	\ 'scriptnames',  	\ 'redir END', +	\ 'redir! > Xsequence', +	\ 'echo g:sequence', +	\ 'redir END',  	\ 'quit',  	\ ]    call mkdir('Xhere/plugin', 'p') -  call writefile(['let done = 1'], 'Xhere/plugin/here.vim') +  call writefile(['let g:sequence .= "here "'], 'Xhere/plugin/here.vim') +  call mkdir('Xanother/plugin', 'p') +  call writefile(['let g:sequence .= "another "'], 'Xanother/plugin/another.vim')    call mkdir('Xhere/pack/foo/start/foobar/plugin', 'p') -  call writefile(['let done = 1'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim') +  call writefile(['let g:sequence .= "pack "'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim')    call mkdir('Xafter/plugin', 'p') -  call writefile(['let done = 1'], 'Xafter/plugin/later.vim') +  call writefile(['let g:sequence .= "after "'], 'Xafter/plugin/later.vim')    if RunVim(before, after, '')      let lines = readfile('Xtestout') -    let expected = ['Xbefore.vim', 'here.vim', 'foo.vim', 'later.vim', 'Xafter.vim'] +    let expected = ['Xbefore.vim', 'here.vim', 'another.vim', 'foo.vim', 'later.vim', 'Xafter.vim']      let found = []      for line in lines        for one in expected @@ -57,11 +63,47 @@ func Test_after_comes_later()      call assert_equal(expected, found)    endif +  call assert_equal('here another pack after', substitute(join(readfile('Xsequence', 1), ''), '\s\+$', '', '')) +    call delete('Xtestout') +  call delete('Xsequence')    call delete('Xhere', 'rf') +  call delete('Xanother', 'rf')    call delete('Xafter', 'rf')  endfunc +func Test_pack_in_rtp_when_plugins_run() +  if !has('packages') +    return +  endif +  let before = [ +	\ 'set nocp viminfo+=nviminfo', +	\ 'set guioptions+=M', +	\ 'let $HOME = "/does/not/exist"', +	\ 'set loadplugins', +	\ 'set rtp=Xhere', +	\ 'set packpath=Xhere', +	\ 'set nomore', +	\ ] +  let after = [ +	\ 'quit', +	\ ] +  call mkdir('Xhere/plugin', 'p') +  call writefile(['redir! > Xtestout', 'silent set runtimepath?', 'silent! call foo#Trigger()', 'redir END'], 'Xhere/plugin/here.vim') +  call mkdir('Xhere/pack/foo/start/foobar/autoload', 'p') +  call writefile(['function! foo#Trigger()', 'echo "autoloaded foo"', 'endfunction'], 'Xhere/pack/foo/start/foobar/autoload/foo.vim') + +  if RunVim(before, after, '') + +    let lines = filter(readfile('Xtestout'), '!empty(v:val)') +    call assert_match('Xhere[/\\]pack[/\\]foo[/\\]start[/\\]foobar', get(lines, 0)) +    call assert_match('autoloaded foo', get(lines, 1)) +  endif + +  call delete('Xtestout') +  call delete('Xhere', 'rf') +endfunc +  func Test_help_arg()    if !has('unix') && has('gui')      " this doesn't work with gvim on MS-Windows @@ -76,11 +118,11 @@ func Test_help_arg()      let found = []      for line in lines        if line =~ '-R.*Read-only mode' -	call add(found, 'Readonly mode') +        call add(found, 'Readonly mode')        endif        " Watch out for a second --version line in the Gnome version. -      if line =~ '--version.*Print version information and exit' -	call add(found, "--version") +      if line =~ '--version.*Print version information' +        call add(found, "--version")        endif      endfor      call assert_equal(['Readonly mode', '--version'], found) diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index af2cbbfe8e..6c084dd2a7 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -50,7 +50,7 @@ func Test_syn_iskeyword()    setlocal isk-=_    call assert_equal('DLTD_BY', GetSyntaxItem('DLTD'))    /\<D\k\+\>/:norm! ygn -  let b2=@0 +  let b2 = @0    call assert_equal('DLTD', @0)    syn iskeyword clear @@ -76,3 +76,85 @@ func Test_syntax_after_reload()    call assert_true(exists('g:gotit'))    call delete('Xsomefile')  endfunc + +func Test_syntime() +  if !has('profile') +    return +  endif + +  syntax on +  syntime on +  let a = execute('syntime report') +  call assert_equal("\nNo Syntax items defined for this buffer", a) + +  view ../memfile_test.c +  setfiletype cpp +  redraw +  let a = execute('syntime report') +  call assert_match('^  TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a) +  call assert_match(' \d*\.\d* \+[^0]\d* .* cppRawString ', a) +  call assert_match(' \d*\.\d* \+[^0]\d* .* cppNumber ', a) + +  syntime off +  syntime clear +  let a = execute('syntime report') +  call assert_match('^  TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a) +  call assert_notmatch('.* cppRawString *', a) +  call assert_notmatch('.* cppNumber*', a) +  call assert_notmatch('[1-9]', a) + +  call assert_fails('syntime abc', 'E475') + +  syntax clear +  let a = execute('syntime report') +  call assert_equal("\nNo Syntax items defined for this buffer", a) + +  bd +endfunc + +func Test_syntax_list() +  syntax on +  let a = execute('syntax list') +  call assert_equal("\nNo Syntax items defined for this buffer", a) + +  view ../memfile_test.c +  setfiletype c + +  let a = execute('syntax list') +  call assert_match('cInclude*', a) +  call assert_match('cDefine', a) + +  let a = execute('syntax list cDefine') +  call assert_notmatch('cInclude*', a) +  call assert_match('cDefine', a) +  call assert_match(' links to Macro$', a) + +  call assert_fails('syntax list ABCD', 'E28:') +  call assert_fails('syntax list @ABCD', 'E392:') + +  syntax clear +  let a = execute('syntax list') +  call assert_equal("\nNo Syntax items defined for this buffer", a) + +  bd +endfunc + +func Test_syntax_completion() +  call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx') +  call assert_equal('"syn case clear cluster conceal enable include iskeyword keyword list manual match off on region reset spell sync', @:) + +  call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx') +  call assert_equal('"syn case ignore match', @:) + +  call feedkeys(":syn spell \<C-A>\<C-B>\"\<CR>", 'tx') +  call assert_equal('"syn spell default notoplevel toplevel', @:) + +  call feedkeys(":syn sync \<C-A>\<C-B>\"\<CR>", 'tx') +  call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) + +  call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') +  call assert_match('^"syn list Boolean Character ', @:) + +  call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') +  call assert_match('^"syn match Boolean Character ', @:) +endfunc
\ No newline at end of file diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim new file mode 100644 index 0000000000..2b8849f488 --- /dev/null +++ b/src/nvim/testdir/test_virtualedit.vim @@ -0,0 +1,43 @@ +" Tests for 'virtualedit'. + +func Test_yank_move_change() +  new +  call setline(1, [ +	\ "func foo() error {", +	\ "\tif n, err := bar();", +	\ "\terr != nil {", +	\ "\t\treturn err", +	\ "\t}", +	\ "\tn = n * n", +	\ ]) +  set virtualedit=all +  set ts=4 +  function! MoveSelectionDown(count) abort +    normal! m` +    silent! exe "'<,'>move'>+".a:count +    norm! `` +  endfunction + +  xmap ]e :<C-U>call MoveSelectionDown(v:count1)<CR> +  2 +  normal 2gg +  normal J +  normal jVj +  normal ]e +  normal ce +  bwipe! +  set virtualedit= +  set ts=8 +endfunc + +func Test_paste_end_of_line() +  new +  set virtualedit=all +  call setline(1, ['456', '123']) +  normal! gg0"ay$ +  exe "normal! 2G$lllA\<C-O>:normal! \"agP\r" +  call assert_equal('123456', getline(2)) + +  bwipe! +  set virtualedit= +endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index cf0e535937..1694adbd32 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1,13 +1,13 @@ -" Tests for Visual mode -if !has('multi_byte') -  finish -endif - +" Tests for various Visual mode.  if !has('visual')    finish  endif  func Test_block_shift_multibyte() +  " Uses double-wide character. +  if !has('multi_byte') +    return +  endif    split    call setline(1, ['xヹxxx', 'ヹxxx'])    exe "normal 1G0l\<C-V>jl>" @@ -15,3 +15,31 @@ func Test_block_shift_multibyte()    call assert_equal('	ヹxxx', getline(2))    q!  endfunc + +func Test_Visual_ctrl_o() +  new +  call setline(1, ['one', 'two', 'three']) +  call cursor(1,2) +  set noshowmode +  set tw=0 +  call feedkeys("\<c-v>jjlIa\<c-\>\<c-o>:set tw=88\<cr>\<esc>", 'tx') +  call assert_equal(['oane', 'tawo', 'tahree'], getline(1, 3)) +  call assert_equal(88, &tw) +  set tw& +  bw! +endfu + +func Test_Visual_vapo() +  new +  normal oxx +  normal vapo +  bwipe! +endfunc + +func Test_dotregister_paste() +  new +  exe "norm! ihello world\<esc>" +  norm! 0ve".p +  call assert_equal('hello world world', getline(1)) +  q! +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/tui/input.c b/src/nvim/tui/input.c index 03587d68f0..8bb5971bd4 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -8,6 +8,7 @@  #include "nvim/api/private/helpers.h"  #include "nvim/ascii.h"  #include "nvim/main.h" +#include "nvim/aucmd.h"  #include "nvim/os/os.h"  #include "nvim/os/input.h"  #include "nvim/event/rstream.h" @@ -280,9 +281,9 @@ static void timer_cb(TimeWatcher *watcher, void *data)  /// Handle focus events.  /// -/// If the upcoming sequence of bytes in the input stream matches either the -/// escape code for focus gained `<ESC>[I` or focus lost `<ESC>[O` then consume -/// that sequence and push the appropriate event into the input queue +/// If the upcoming sequence of bytes in the input stream matches the termcode +/// for "focus gained" or "focus lost", consume that sequence and schedule an +/// event on the main loop.  ///  /// @param input the input stream  /// @return true iff handle_focus_event consumed some input @@ -294,11 +295,7 @@ static bool handle_focus_event(TermInput *input)      // Advance past the sequence      bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';      rbuffer_consumed(input->read_stream.buffer, 3); -    if (focus_gained) { -      enqueue_input(input, FOCUSGAINED_KEY, sizeof(FOCUSGAINED_KEY) - 1); -    } else { -      enqueue_input(input, FOCUSLOST_KEY, sizeof(FOCUSLOST_KEY) - 1); -    } +    aucmd_schedule_focusgained(focus_gained);      return true;    }    return false; diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index b8fffcb7d6..586fafba97 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -104,7 +104,9 @@ unibi_term *load_builtin_terminfo(const char * term)      return unibi_from_mem((const char *)interix_8colour_terminfo,                            sizeof interix_8colour_terminfo);    } else if (terminfo_is_term_family(term, "iterm") -             || terminfo_is_term_family(term, "iTerm.app")) { +             || terminfo_is_term_family(term, "iterm2") +             || terminfo_is_term_family(term, "iTerm.app") +             || terminfo_is_term_family(term, "iTerm2.app")) {      return unibi_from_mem((const char *)iterm_256colour_terminfo,                            sizeof iterm_256colour_terminfo);    } else if (terminfo_is_term_family(term, "st")) { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b5af5b0333..8e0e905bcd 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -52,6 +52,15 @@  #define LINUXSET0C "\x1b[?0c"  #define LINUXSET1C "\x1b[?1c" +#ifdef NVIM_UNIBI_HAS_VAR_FROM +#define UNIBI_SET_NUM_VAR(var, num) \ +  do { \ +    (var) = unibi_var_from_num((num)); \ +  } while (0) +#else +#define UNIBI_SET_NUM_VAR(var, num) (var).i = (num); +#endif +  // Per the commentary in terminfo, only a minus sign is a true suffix  // separator.  bool terminfo_is_term_family(const char *term, const char *family) @@ -234,9 +243,9 @@ static void terminfo_start(UI *ui)    unibi_out(ui, unibi_keypad_xmit);    unibi_out(ui, unibi_clear_screen);    // Enable bracketed paste -  unibi_out(ui, data->unibi_ext.enable_bracketed_paste); +  unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste);    // Enable focus reporting -  unibi_out(ui, data->unibi_ext.enable_focus_reporting); +  unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);    uv_loop_init(&data->write_loop);    if (data->out_isatty) {      uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); @@ -263,9 +272,9 @@ static void terminfo_stop(UI *ui)    unibi_out(ui, unibi_keypad_local);    unibi_out(ui, unibi_exit_ca_mode);    // Disable bracketed paste -  unibi_out(ui, data->unibi_ext.disable_bracketed_paste); +  unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste);    // Disable focus reporting -  unibi_out(ui, data->unibi_ext.disable_focus_reporting); +  unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting);    flush_buf(ui, true);    uv_tty_reset_mode();    uv_close((uv_handle_t *)&data->output_handle, NULL); @@ -279,7 +288,7 @@ static void terminfo_stop(UI *ui)  static void tui_terminal_start(UI *ui)  {    TUIData *data = ui->data; -  data->print_attrs = EMPTY_ATTRS; +  data->print_attrs = HLATTRS_INIT;    ugrid_init(&data->grid);    terminfo_start(ui);    update_size(ui); @@ -391,15 +400,15 @@ static void update_attrs(UI *ui, HlAttrs attrs)    if (unibi_get_str(data->ut, unibi_set_attributes)) {      if (attrs.bold || attrs.reverse || attrs.underline || attrs.undercurl) { -      data->params[0].i = 0;   // standout -      data->params[1].i = attrs.underline || attrs.undercurl; -      data->params[2].i = attrs.reverse; -      data->params[3].i = 0;   // blink -      data->params[4].i = 0;   // dim -      data->params[5].i = attrs.bold; -      data->params[6].i = 0;   // blank -      data->params[7].i = 0;   // protect -      data->params[8].i = 0;   // alternate character set +      UNIBI_SET_NUM_VAR(data->params[0], 0);   // standout +      UNIBI_SET_NUM_VAR(data->params[1], attrs.underline || attrs.undercurl); +      UNIBI_SET_NUM_VAR(data->params[2], attrs.reverse); +      UNIBI_SET_NUM_VAR(data->params[3], 0);   // blink +      UNIBI_SET_NUM_VAR(data->params[4], 0);   // dim +      UNIBI_SET_NUM_VAR(data->params[5], attrs.bold); +      UNIBI_SET_NUM_VAR(data->params[6], 0);   // blank +      UNIBI_SET_NUM_VAR(data->params[7], 0);   // protect +      UNIBI_SET_NUM_VAR(data->params[8], 0);   // alternate character set        unibi_out(ui, unibi_set_attributes);      } else if (!data->default_attr) {        unibi_out(ui, unibi_exit_attribute_mode); @@ -423,26 +432,26 @@ static void update_attrs(UI *ui, HlAttrs attrs)    }    if (ui->rgb) {      if (fg != -1) { -      data->params[0].i = (fg >> 16) & 0xff;  // red -      data->params[1].i = (fg >> 8) & 0xff;   // green -      data->params[2].i = fg & 0xff;          // blue -      unibi_out(ui, data->unibi_ext.set_rgb_foreground); +      UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff);  // red +      UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff);   // green +      UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff);          // blue +      unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground);      }      if (bg != -1) { -      data->params[0].i = (bg >> 16) & 0xff;  // red -      data->params[1].i = (bg >> 8) & 0xff;   // green -      data->params[2].i = bg & 0xff;          // blue -      unibi_out(ui, data->unibi_ext.set_rgb_background); +      UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff);  // red +      UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff);   // green +      UNIBI_SET_NUM_VAR(data->params[2], bg & 0xff);          // blue +      unibi_out_ext(ui, data->unibi_ext.set_rgb_background);      }    } else {      if (fg != -1) { -      data->params[0].i = fg; +      UNIBI_SET_NUM_VAR(data->params[0], fg);        unibi_out(ui, unibi_set_a_foreground);      }      if (bg != -1) { -      data->params[0].i = bg; +      UNIBI_SET_NUM_VAR(data->params[0], bg);        unibi_out(ui, unibi_set_a_background);      }    } @@ -558,7 +567,7 @@ static void cursor_goto(UI *ui, int row, int col)            unibi_out(ui, unibi_cursor_left);          }        } else { -        data->params[0].i = n; +        UNIBI_SET_NUM_VAR(data->params[0], n);          unibi_out(ui, unibi_parm_left_cursor);        }        ugrid_goto(grid, row, col); @@ -570,7 +579,7 @@ static void cursor_goto(UI *ui, int row, int col)            unibi_out(ui, unibi_cursor_right);          }        } else { -        data->params[0].i = n; +        UNIBI_SET_NUM_VAR(data->params[0], n);          unibi_out(ui, unibi_parm_right_cursor);        }        ugrid_goto(grid, row, col); @@ -585,7 +594,7 @@ static void cursor_goto(UI *ui, int row, int col)            unibi_out(ui, unibi_cursor_down);          }        } else { -        data->params[0].i = n; +        UNIBI_SET_NUM_VAR(data->params[0], n);          unibi_out(ui, unibi_parm_down_cursor);        }        ugrid_goto(grid, row, col); @@ -597,7 +606,7 @@ static void cursor_goto(UI *ui, int row, int col)            unibi_out(ui, unibi_cursor_up);          }        } else { -        data->params[0].i = n; +        UNIBI_SET_NUM_VAR(data->params[0], n);          unibi_out(ui, unibi_parm_up_cursor);        }        ugrid_goto(grid, row, col); @@ -619,7 +628,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right)    if (grid->bg == -1 && right == ui->width -1) {      // Background is set to the default color and the right edge matches the      // screen end, try to use terminal codes for clearing the requested area. -    HlAttrs clear_attrs = EMPTY_ATTRS; +    HlAttrs clear_attrs = HLATTRS_INIT;      clear_attrs.foreground = grid->fg;      clear_attrs.background = grid->bg;      update_attrs(ui, clear_attrs); @@ -675,19 +684,19 @@ static void set_scroll_region(UI *ui)    TUIData *data = ui->data;    UGrid *grid = &data->grid; -  data->params[0].i = grid->top; -  data->params[1].i = grid->bot; +  UNIBI_SET_NUM_VAR(data->params[0], grid->top); +  UNIBI_SET_NUM_VAR(data->params[1], grid->bot);    unibi_out(ui, unibi_change_scroll_region);    if (grid->left != 0 || grid->right != ui->width - 1) { -    unibi_out(ui, data->unibi_ext.enable_lr_margin); +    unibi_out_ext(ui, data->unibi_ext.enable_lr_margin);      if (data->can_set_lr_margin) { -      data->params[0].i = grid->left; -      data->params[1].i = grid->right; +      UNIBI_SET_NUM_VAR(data->params[0], grid->left); +      UNIBI_SET_NUM_VAR(data->params[1], grid->right);        unibi_out(ui, unibi_set_lr_margin);      } else { -      data->params[0].i = grid->left; +      UNIBI_SET_NUM_VAR(data->params[0], grid->left);        unibi_out(ui, unibi_set_left_margin_parm); -      data->params[0].i = grid->right; +      UNIBI_SET_NUM_VAR(data->params[0], grid->right);        unibi_out(ui, unibi_set_right_margin_parm);      }    } @@ -700,24 +709,24 @@ static void reset_scroll_region(UI *ui)    UGrid *grid = &data->grid;    if (0 <= data->unibi_ext.reset_scroll_region) { -    unibi_out(ui, data->unibi_ext.reset_scroll_region); +    unibi_out_ext(ui, data->unibi_ext.reset_scroll_region);    } else { -    data->params[0].i = 0; -    data->params[1].i = ui->height - 1; +    UNIBI_SET_NUM_VAR(data->params[0], 0); +    UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1);      unibi_out(ui, unibi_change_scroll_region);    }    if (grid->left != 0 || grid->right != ui->width - 1) {      if (data->can_set_lr_margin) { -      data->params[0].i = 0; -      data->params[1].i = ui->width - 1; +      UNIBI_SET_NUM_VAR(data->params[0], 0); +      UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1);        unibi_out(ui, unibi_set_lr_margin);      } else { -      data->params[0].i = 0; +      UNIBI_SET_NUM_VAR(data->params[0], 0);        unibi_out(ui, unibi_set_left_margin_parm); -      data->params[0].i = ui->width - 1; +      UNIBI_SET_NUM_VAR(data->params[0], ui->width - 1);        unibi_out(ui, unibi_set_right_margin_parm);      } -    unibi_out(ui, data->unibi_ext.disable_lr_margin); +    unibi_out_ext(ui, data->unibi_ext.disable_lr_margin);    }    unibi_goto(ui, grid->row, grid->col);  } @@ -728,9 +737,9 @@ static void tui_resize(UI *ui, Integer width, Integer height)    ugrid_resize(&data->grid, (int)width, (int)height);    if (!got_winch) {  // Try to resize the terminal window. -    data->params[0].i = (int)height; -    data->params[1].i = (int)width; -    unibi_out(ui, data->unibi_ext.resize_screen); +    UNIBI_SET_NUM_VAR(data->params[0], (int)height); +    UNIBI_SET_NUM_VAR(data->params[1], (int)width); +    unibi_out_ext(ui, data->unibi_ext.resize_screen);      // DECSLPP does not reset the scroll region.      if (data->scroll_region_is_full_screen) {        reset_scroll_region(ui); @@ -836,7 +845,7 @@ static void tui_mouse_on(UI *ui)  {    TUIData *data = ui->data;    if (!data->mouse_enabled) { -    unibi_out(ui, data->unibi_ext.enable_mouse); +    unibi_out_ext(ui, data->unibi_ext.enable_mouse);      data->mouse_enabled = true;    }  } @@ -845,7 +854,7 @@ static void tui_mouse_off(UI *ui)  {    TUIData *data = ui->data;    if (data->mouse_enabled) { -    unibi_out(ui, data->unibi_ext.disable_mouse); +    unibi_out_ext(ui, data->unibi_ext.disable_mouse);      data->mouse_enabled = false;    }  } @@ -863,8 +872,8 @@ static void tui_set_mode(UI *ui, ModeShape mode)      int attr = syn_id2attr(c.id);      if (attr > 0) {        attrentry_T *aep = syn_cterm_attr2entry(attr); -      data->params[0].i = aep->rgb_bg_color; -      unibi_out(ui, data->unibi_ext.set_cursor_color); +      UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); +      unibi_out_ext(ui, data->unibi_ext.set_cursor_color);      }    } @@ -874,8 +883,8 @@ static void tui_set_mode(UI *ui, ModeShape mode)      case SHAPE_VER:   shape = 5; break;      default: WLOG("Unknown shape value %d", shape); break;    } -  data->params[0].i = shape + (int)(c.blinkon == 0); -  unibi_out(ui, data->unibi_ext.set_cursor_style); +  UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); +  unibi_out_ext(ui, data->unibi_ext.set_cursor_style);  }  /// @param mode editor mode @@ -917,7 +926,7 @@ static void tui_scroll(UI *ui, Integer count)      cursor_goto(ui, grid->top, grid->left);      // also set default color attributes or some terminals can become funny      if (scroll_clears_to_current_colour) { -      HlAttrs clear_attrs = EMPTY_ATTRS; +      HlAttrs clear_attrs = HLATTRS_INIT;        clear_attrs.foreground = grid->fg;        clear_attrs.background = grid->bg;        update_attrs(ui, clear_attrs); @@ -927,14 +936,14 @@ static void tui_scroll(UI *ui, Integer count)        if (count == 1) {          unibi_out(ui, unibi_delete_line);        } else { -        data->params[0].i = (int)count; +        UNIBI_SET_NUM_VAR(data->params[0], (int)count);          unibi_out(ui, unibi_parm_delete_line);        }      } else {        if (count == -1) {          unibi_out(ui, unibi_insert_line);        } else { -        data->params[0].i = -(int)count; +        UNIBI_SET_NUM_VAR(data->params[0], -(int)count);          unibi_out(ui, unibi_parm_insert_line);        }      } @@ -1177,30 +1186,33 @@ end:  static void unibi_goto(UI *ui, int row, int col)  {    TUIData *data = ui->data; -  data->params[0].i = row; -  data->params[1].i = col; +  UNIBI_SET_NUM_VAR(data->params[0], row); +  UNIBI_SET_NUM_VAR(data->params[1], col);    unibi_out(ui, unibi_cursor_address);  } +#define UNIBI_OUT(fn) \ +  do { \ +    TUIData *data = ui->data; \ +    const char *str = NULL; \ +    if (unibi_index >= 0) { \ +      str = fn(data->ut, (unsigned)unibi_index); \ +    } \ +    if (str) { \ +      unibi_var_t vars[26 + 26]; \ +      memset(&vars, 0, sizeof(vars)); \ +      unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ +    } \ +  } while (0)  static void unibi_out(UI *ui, int unibi_index)  { -  TUIData *data = ui->data; - -  const char *str = NULL; - -  if (unibi_index >= 0) { -    if (unibi_index < unibi_string_begin_) { -      str = unibi_get_ext_str(data->ut, (unsigned)unibi_index); -    } else { -      str = unibi_get_str(data->ut, (unsigned)unibi_index); -    } -  } - -  if (str) { -    unibi_var_t vars[26 + 26] = {{0}}; -    unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); -  } +  UNIBI_OUT(unibi_get_str); +} +static void unibi_out_ext(UI *ui, int unibi_index) +{ +  UNIBI_OUT(unibi_get_ext_str);  } +#undef UNIBI_OUT  static void out(void *ctx, const char *str, size_t len)  { @@ -1261,7 +1273,9 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,    bool gnome = terminfo_is_term_family(term, "gnome")      || terminfo_is_term_family(term, "vte");    bool iterm = terminfo_is_term_family(term, "iterm") -    || terminfo_is_term_family(term, "iTerm.app"); +    || terminfo_is_term_family(term, "iterm2") +    || terminfo_is_term_family(term, "iTerm.app") +    || terminfo_is_term_family(term, "iTerm2.app");    // None of the following work over SSH; see :help TERM .    bool iterm_pretending_xterm = xterm && iterm_env;    bool konsole_pretending_xterm = xterm && konsole; @@ -1444,7 +1458,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,      // teminfo entries.  See      // https://github.com/gnachman/iTerm2/pull/92 for more.      // xterm even has an extended version that has a vertical bar. -    if (true_xterm    // per xterm ctlseqs doco (since version 282) +    if (!konsole && (true_xterm    // per xterm ctlseqs doco (since version 282)          // per MinTTY 0.4.3-1 release notes from 2009          || putty          // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 @@ -1459,7 +1473,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,          // Allows forcing the use of DECSCUSR on linux type terminals, such as          // console-terminal-emulator from the nosh toolset, which does indeed          // implement the xterm extension: -        || (linuxvt && (xterm_version || (vte_version > 0) || colorterm))) { +        || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) {        data->unibi_ext.set_cursor_style =          (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q");        if (-1 == data->unibi_ext.reset_cursor_style) { @@ -1533,7 +1547,9 @@ static void augment_terminfo(TUIData *data, const char *term,    bool screen = terminfo_is_term_family(term, "screen");    bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX");    bool iterm = terminfo_is_term_family(term, "iterm") -    || terminfo_is_term_family(term, "iTerm.app"); +    || terminfo_is_term_family(term, "iterm2") +    || terminfo_is_term_family(term, "iTerm.app") +    || terminfo_is_term_family(term, "iTerm2.app");    // None of the following work over SSH; see :help TERM .    bool iterm_pretending_xterm = xterm && iterm_env; diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 7a0a16687e..2b5e96ee60 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -16,7 +16,7 @@  void ugrid_init(UGrid *grid)  { -  grid->attrs = EMPTY_ATTRS; +  grid->attrs = HLATTRS_INIT;    grid->fg = grid->bg = -1;    grid->cells = NULL;  } @@ -118,7 +118,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)  static void clear_region(UGrid *grid, int top, int bot, int left, int right)  { -  HlAttrs clear_attrs = EMPTY_ATTRS; +  HlAttrs clear_attrs = HLATTRS_INIT;    clear_attrs.foreground = grid->fg;    clear_attrs.background = grid->bg;    UGRID_FOREACH_CELL(grid, top, bot, left, right, { diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 268362bf1b..1cf047502d 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -21,8 +21,6 @@ struct ugrid {    UCell **cells;  }; -#define EMPTY_ATTRS ((HlAttrs){ false, false, false, false, false, -1, -1, -1 }) -  #define UGRID_FOREACH_CELL(grid, top, bot, left, right, code) \    do { \      for (int row = top; row <= bot; row++) { \ diff --git a/src/nvim/ui.c b/src/nvim/ui.c index b85a01814d..afe7a51d43 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -71,10 +71,10 @@ static char uilog_last_event[1024] = { 0 };        uilog_seen++; \      } else { \        if (uilog_seen > 0) { \ -        do_log(DEBUG_LOG_LEVEL, "ui", 0, true, \ +        do_log(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, \                 "%s (+%zu times...)", uilog_last_event, uilog_seen); \        } \ -      DLOG("ui: " STR(funname)); \ +      do_log(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, STR(funname)); \        uilog_seen = 0; \        xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \      } \ @@ -166,6 +166,90 @@ void ui_event(char *name, Array args)    }  } + +/// Converts an attrentry_T into an HlAttrs +/// +/// @param[in] aep data to convert +/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' +HlAttrs attrentry2hlattrs(const attrentry_T *aep, bool use_rgb) +{ +  assert(aep); + +  HlAttrs attrs = HLATTRS_INIT; +  int mask = 0; + +  mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; + +  attrs.bold = mask & HL_BOLD; +  attrs.underline = mask & HL_UNDERLINE; +  attrs.undercurl = mask & HL_UNDERCURL; +  attrs.italic = mask & HL_ITALIC; +  attrs.reverse = mask & (HL_INVERSE | HL_STANDOUT); + +  if (use_rgb) { +    if (aep->rgb_fg_color != -1) { +      attrs.foreground = aep->rgb_fg_color; +    } + +    if (aep->rgb_bg_color != -1) { +      attrs.background = aep->rgb_bg_color; +    } + +    if (aep->rgb_sp_color != -1) { +      attrs.special = aep->rgb_sp_color; +    } +  } else { +    if (cterm_normal_fg_color != aep->cterm_fg_color) { +      attrs.foreground = aep->cterm_fg_color - 1; +    } + +    if (cterm_normal_bg_color != aep->cterm_bg_color) { +        attrs.background = aep->cterm_bg_color - 1; +    } +  } + +  return attrs; +} + +Dictionary hlattrs2dict(HlAttrs attrs) +{ +  Dictionary hl = ARRAY_DICT_INIT; + +  if (attrs.bold) { +    PUT(hl, "bold", BOOLEAN_OBJ(true)); +  } + +  if (attrs.underline) { +    PUT(hl, "underline", BOOLEAN_OBJ(true)); +  } + +  if (attrs.undercurl) { +    PUT(hl, "undercurl", BOOLEAN_OBJ(true)); +  } + +  if (attrs.italic) { +    PUT(hl, "italic", BOOLEAN_OBJ(true)); +  } + +  if (attrs.reverse) { +    PUT(hl, "reverse", BOOLEAN_OBJ(true)); +  } + +  if (attrs.foreground != -1) { +    PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); +  } + +  if (attrs.background != -1) { +    PUT(hl, "background", INTEGER_OBJ(attrs.background)); +  } + +  if (attrs.special != -1) { +    PUT(hl, "special", INTEGER_OBJ(attrs.special)); +  } + +  return hl; +} +  void ui_refresh(void)  {    if (!ui_active()) { @@ -405,54 +489,20 @@ void ui_flush(void)  static void set_highlight_args(int attr_code)  { -  HlAttrs rgb_attrs = { false, false, false, false, false, -1, -1, -1 }; +  HlAttrs rgb_attrs = HLATTRS_INIT;    HlAttrs cterm_attrs = rgb_attrs;    if (attr_code == HL_NORMAL) {      goto end;    } - -  int rgb_mask = 0; -  int cterm_mask = 0;    attrentry_T *aep = syn_cterm_attr2entry(attr_code);    if (!aep) {      goto end;    } -  rgb_mask = aep->rgb_ae_attr; -  cterm_mask = aep->cterm_ae_attr; - -  rgb_attrs.bold = rgb_mask & HL_BOLD; -  rgb_attrs.underline = rgb_mask & HL_UNDERLINE; -  rgb_attrs.undercurl = rgb_mask & HL_UNDERCURL; -  rgb_attrs.italic = rgb_mask & HL_ITALIC; -  rgb_attrs.reverse = rgb_mask & (HL_INVERSE | HL_STANDOUT); -  cterm_attrs.bold = cterm_mask & HL_BOLD; -  cterm_attrs.underline = cterm_mask & HL_UNDERLINE; -  cterm_attrs.undercurl = cterm_mask & HL_UNDERCURL; -  cterm_attrs.italic = cterm_mask & HL_ITALIC; -  cterm_attrs.reverse = cterm_mask & (HL_INVERSE | HL_STANDOUT); - -  if (aep->rgb_fg_color != normal_fg) { -    rgb_attrs.foreground = aep->rgb_fg_color; -  } - -  if (aep->rgb_bg_color != normal_bg) { -    rgb_attrs.background = aep->rgb_bg_color; -  } - -  if (aep->rgb_sp_color != normal_sp) { -    rgb_attrs.special = aep->rgb_sp_color; -  } - -  if (cterm_normal_fg_color != aep->cterm_fg_color) { -    cterm_attrs.foreground = aep->cterm_fg_color - 1; -  } - -  if (cterm_normal_bg_color != aep->cterm_bg_color) { -    cterm_attrs.background = aep->cterm_bg_color - 1; -  } +  rgb_attrs = attrentry2hlattrs(aep, true); +  cterm_attrs = attrentry2hlattrs(aep, false);  end:    UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs)); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 064f77fee1..f1ea0716e6 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -21,6 +21,9 @@ typedef struct {    int foreground, background, special;  } HlAttrs; +#define HLATTRS_INIT \ +  ((HlAttrs){ false, false, false, false, false, -1, -1, -1 }) +  typedef struct ui_t UI;  struct ui_t { diff --git a/src/nvim/undo.h b/src/nvim/undo.h index ab8584fbb2..802cdc5583 100644 --- a/src/nvim/undo.h +++ b/src/nvim/undo.h @@ -2,6 +2,7 @@  #define NVIM_UNDO_H  #include "nvim/undo_defs.h" +#include "nvim/ex_cmds_defs.h"  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "undo.h.generated.h" diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index d841210815..6c7e2bba41 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -4,9 +4,10 @@  #include <time.h>  // for time_t  #include "nvim/pos.h" -#include "nvim/buffer_defs.h"  #include "nvim/mark_defs.h" +typedef struct u_header u_header_T; +  /* Structure to store info about the Visual area. */  typedef struct {    pos_T vi_start;               /* start pos of last VIsual */ @@ -15,8 +16,9 @@ typedef struct {    colnr_T vi_curswant;          /* MAXCOL from w_curswant */  } visualinfo_T; +#include "nvim/buffer_defs.h" +  typedef struct u_entry u_entry_T; -typedef struct u_header u_header_T;  struct u_entry {    u_entry_T   *ue_next;         /* pointer to next entry in list */    linenr_T ue_top;              /* number of line above undo block */ diff --git a/src/nvim/version.c b/src/nvim/version.c index f4984864f3..30ebbb22bc 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -77,6 +77,157 @@ static char *features[] = {  // clang-format off  static const int included_patches[] = { +  // 1026, +  1025, +  1024, +  // 1023, +  // 1022, +  // 1021, +  // 1020, +  // 1019, +  // 1018, +  // 1017, +  // 1016, +  // 1015, +  // 1014, +  // 1013, +  // 1012, +  // 1011, +  // 1010, +  // 1009, +  // 1008, +  // 1007, +  // 1006, +  // 1005, +  // 1004, +  // 1003, +  // 1002, +  // 1001, +  // 1000, +  // 999, +  // 998, +  // 997, +  // 996, +  // 995, +  // 994, +  // 993, +  // 992, +  // 991, +  // 990, +  // 989, +  // 988, +  // 987, +  // 986, +  // 985, +  // 984, +  // 983, +  // 982, +  // 981, +  // 980, +  // 979, +  // 978, +  // 977, +  // 976, +  // 975, +  // 974, +  // 973, +  // 972, +  // 971, +  // 970, +  // 969, +  // 968, +  // 967, +  // 966, +  // 965, +  // 964, +  // 963, +  // 962, +  // 961, +  // 960, +  // 959, +  // 958, +  // 957, +  // 956, +  // 955, +  // 954, +  // 953, +  // 952, +  // 951, +  // 950, +  // 949, +  // 948, +  // 947, +  // 946, +  // 945, +  // 944, +  // 943, +  // 942, +  // 941, +  // 940, +  // 939, +  // 938, +  // 937, +  // 936, +  // 935, +  // 934, +  // 933, +  // 932, +  // 931, +  // 930, +  // 929, +  // 928, +  // 927, +  // 926, +  // 925, +  // 924, +  // 923, +  // 922, +  // 921, +  // 920, +  // 919, +  // 918, +  // 917, +  // 916, +  // 915, +  // 914, +  // 913, +  // 912, +  // 911, +  // 910, +  // 909, +  // 908, +  // 907, +  // 906, +  // 905, +  // 904, +  // 903, +  // 902, +  // 901, +  // 900, +  // 899, +  // 898, +  // 897, +  // 896, +  // 895, +  // 894, +  // 893, +  // 892, +  // 891, +  // 890, +  // 889, +  // 888, +  // 887, +  // 886, +  // 885, +  // 884, +  // 883, +  // 882, +  // 881, +  // 880, +  // 879, +  // 878, +  // 877, +  // 876,    // 875,    // 874,    // 873, @@ -272,9 +423,9 @@ static const int included_patches[] = {    // 683,    // 682,    // 681, -  // 680, -  // 679, -  // 678, +  680, +  679, +  678,    // 677,    // 676,    // 675, @@ -340,7 +491,7 @@ static const int included_patches[] = {    // 615,    614,    // 613, -  // 612, +  612,    // 611,    // 610,    // 609, @@ -515,7 +666,7 @@ static const int included_patches[] = {    // 440,    // 439,    // 438, -  // 437, +  437,    // 436,    // 435,    // 434, @@ -619,16 +770,16 @@ static const int included_patches[] = {    // 336,    // 335,    // 334, -  // 333, +  333,    // 332,    331, -  // 330, +  330,    // 329, -  // 328, -  // 327, -  // 326, -  // 325, -  // 324, +  328, +  327, +  326, +  325, +  324,    // 323,    322,    // 321, @@ -644,24 +795,24 @@ static const int included_patches[] = {    311,    // 310,    // 309, -  // 308, +  308,    307,    // 306, -  // 305, +  305,    // 304,    // 303, -  // 302, +  // 302, NA    // 301, -  // 300, +  300,    // 299,    // 298,    297,    // 296,    // 295, -  // 294, +  294,    // 293,    // 292, -  // 291, +  291,    290,    // 289,    // 288 NA @@ -670,7 +821,7 @@ static const int included_patches[] = {    // 285 NA    // 284 NA    // 283, -  // 282, +  282,    // 281 NA    280,    // 279 NA @@ -694,18 +845,18 @@ static const int included_patches[] = {    // 261,    // 260 NA    259, -  // 258, +  258,    // 257 NA    // 256,    // 255,    // 254, -  // 253, +  253,    // 252,    // 251,    250,    // 249 NA    // 248, -  // 247, +  247,    // 246 NA    // 245,    // 244, @@ -743,7 +894,7 @@ static const int included_patches[] = {    // 212,    // 211 NA    // 210, -  // 209, +  209,    208,    // 207,    // 206, @@ -764,21 +915,21 @@ static const int included_patches[] = {    // 191 NA    190,    // 189, -  // 188, +  188,    // 187 NA -  // 186, +  186,    // 185,    // 184, -  // 183, -  // 182, -  // 181, +  // 183 NA +  182, +  181,    // 180,    179,    178,    177,    176,    // 175, -  // 174, +  174,    // 173 NA    172,    // 171, @@ -788,31 +939,31 @@ static const int included_patches[] = {    167,    // 166,    165, -  // 164, +  164,    // 163 NA    // 162 NA    // 161 NA    // 160,    159,    158, -  // 157, +  157,    156, -  // 155, +  155,    // 154,    // 153,    // 152 NA    // 151,    150,    149, -  // 148, +  148,    147,    146,    // 145 NA    // 144 NA    143, -  // 142, +  142,    // 141, -  // 140, +  140,    // 139 NA    // 138 NA    137, @@ -820,38 +971,38 @@ static const int included_patches[] = {    135,    134,    133, -  // 132, -  // 131, +  132, +  131,    // 130 NA    // 129 NA    128,    127,    126, -  // 125, +  125,    124,    // 123 NA    // 122 NA    121,    // 120 NA    119, -  // 118, +  118,    // 117 NA    116,    // 115 NA    // 114 NA    // 113 NA -  // 112, +  112,    111,    110,    // 109 NA    // 108 NA    // 107 NA -  // 106, +  106,    // 105 NA -  // 104, +  104,    // 103 NA -  // 102, -  // 101, +  102, +  101,    100,    99,    // 98 NA @@ -860,8 +1011,8 @@ static const int included_patches[] = {    // 95 NA    // 94 NA    // 93 NA -  // 92, -  // 91, +  92, +  91,    90,    // 89 NA    88, @@ -908,7 +1059,7 @@ static const int included_patches[] = {    47,    46,    // 45 NA -  // 44, +  44,    43,    42,    41, @@ -1089,13 +1240,7 @@ static void list_features(void)            msg_putchar('\n');          }        } else { -        while (msg_col % width) { -          int old_msg_col = msg_col; -          msg_putchar(' '); -          if (old_msg_col == msg_col) { -            break;  // XXX: Avoid infinite loop. -          } -        } +        msg_putchar(' ');        }      } else {        if (msg_col > 0) { @@ -1103,7 +1248,7 @@ static void list_features(void)        }      }    } -  MSG_PUTS("For differences from Vim, see :help vim-differences\n\n"); +  MSG_PUTS("See \":help feature-compile\"\n\n");  }  void list_version(void) @@ -1144,7 +1289,7 @@ void list_version(void)    }  #endif  // ifdef HAVE_PATHDEF -  version_msg(_("\n\nOptional features included (+) or not (-): ")); +  version_msg(_("\n\nFeatures: "));    list_features(); @@ -1216,12 +1361,11 @@ void intro_message(int colon)    static char *(lines[]) = {      N_(NVIM_VERSION_LONG),      "", -    N_("by al."),      N_("Nvim is open source and freely distributable"),      N_("https://neovim.io/community"),      "",      N_("type  :help nvim<Enter>       if you are new! "), -    N_("type  :CheckHealth<Enter>     to optimize Nvim"), +    N_("type  :checkhealth<Enter>     to optimize Nvim"),      N_("type  :q<Enter>               to exit         "),      N_("type  :help<Enter>            for help        "),      "", diff --git a/src/nvim/window.c b/src/nvim/window.c index faf5bceb56..c2d0a9b3b1 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();    } @@ -5512,11 +5515,14 @@ void restore_buffer(bufref_T *save_curbuf)  } -// Add match to the match list of window 'wp'.  The pattern 'pat' will be -// highlighted with the group 'grp' with priority 'prio'. -// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). -// If no particular ID is desired, -1 must be specified for 'id'. -// Return ID of added match, -1 on failure. +/// Add match to the match list of window 'wp'.  The pattern 'pat' will be +/// highlighted with the group 'grp' with priority 'prio'. +/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). +/// +/// @param[in] id a desired ID 'id' can be specified +///               (greater than or equal to 1). -1 must be specified if no +///               particular ID is desired +/// @return ID of added match, -1 on failure.  int match_add(win_T *wp, const char *const grp, const char *const pat,                int prio, int id, list_T *pos_list,                const char *const conceal_char) @@ -5694,10 +5700,9 @@ fail:    return -1;  } -/* - * Delete match with ID 'id' in the match list of window 'wp'. - * Print error messages if 'perr' is TRUE. - */ + +/// Delete match with ID 'id' in the match list of window 'wp'. +/// Print error messages if 'perr' is TRUE.  int match_delete(win_T *wp, int id, int perr)  {    matchitem_T *cur = wp->w_match_head; | 
