diff options
Diffstat (limited to 'src')
93 files changed, 2175 insertions, 596 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index db77931c16..2c9d655a15 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -284,7 +284,7 @@ foreach(sfile ${NVIM_SOURCES} endif() add_custom_command( OUTPUT "${gf_c_h}" "${gf_h_h}" - COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY} + COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}" DEPENDS ${depends}) list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}") @@ -513,8 +513,44 @@ if(WIN32) tidy.exe win32yank.exe winpty-agent.exe + winpty.dll xxd.exe + # Dependencies for neovim-qt + bearer/qgenericbearer.dll + iconengines/qsvgicon.dll + imageformats/qgif.dll + imageformats/qicns.dll + imageformats/qico.dll + imageformats/qjpeg.dll + imageformats/qsvg.dll + imageformats/qtga.dll + imageformats/qtiff.dll + imageformats/qwbmp.dll + imageformats/qwebp.dll + platforms/qwindows.dll + styles/qwindowsvistastyle.dll + translations/qt_ar.qm + translations/qt_bg.qm + translations/qt_ca.qm + translations/qt_cs.qm + translations/qt_da.qm + translations/qt_de.qm + translations/qt_en.qm + translations/qt_es.qm + translations/qt_fi.qm + translations/qt_fr.qm + translations/qt_gd.qm + translations/qt_he.qm + translations/qt_hu.qm + translations/qt_it.qm + translations/qt_ja.qm + translations/qt_ko.qm + translations/qt_lv.qm + translations/qt_pl.qm + translations/qt_ru.qm + translations/qt_sk.qm + translations/qt_uk.qm D3Dcompiler_47.dll libEGL.dll libgcc_s_dw2-1.dll @@ -522,14 +558,13 @@ if(WIN32) libstdc++-6.dll libwinpthread-1.dll nvim-qt.exe + opengl32sw.dll Qt5Core.dll Qt5Gui.dll Qt5Network.dll Qt5Svg.dll Qt5Widgets.dll - winpty.dll - platforms/qwindows.dll ) get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY) set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n" diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 67f4f92bf6..2c2e8a024f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -111,6 +111,24 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - byte count of previous contents /// - deleted_codepoints (if `utf_sizes` is true) /// - deleted_codeunits (if `utf_sizes` is true) +/// - on_bytes: lua callback invoked on change. +/// This callback receives more granular information about the +/// change compared to on_lines. +/// Return `true` to detach. +/// Args: +/// - the string "bytes" +/// - buffer handle +/// - b:changedtick +/// - start row of the changed text (zero-indexed) +/// - start column of the changed text +/// - byte offset of the changed text (from the start of +/// the buffer) +/// - old end row of the changed text +/// - old end column of the changed text +/// - old end byte length of the changed text +/// - new end row of the changed text +/// - new end column of the changed text +/// - new end byte length of the changed text /// - on_changedtick: Lua callback invoked on changedtick /// increment without text change. Args: /// - the string "changedtick" @@ -119,6 +137,10 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - on_detach: Lua callback invoked on detach. Args: /// - the string "detach" /// - buffer handle +/// - on_reload: Lua callback invoked on reload. The entire buffer +/// content should be considered changed. Args: +/// - the string "detach" +/// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. /// - preview: also attach to command preview (i.e. 'inccommand') @@ -141,50 +163,57 @@ Boolean nvim_buf_attach(uint64_t channel_id, bool is_lua = (channel_id == LUA_INTERNAL_CALL); BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT; + struct { + const char *name; + LuaRef *dest; + } cbs[] = { + { "on_lines", &cb.on_lines }, + { "on_bytes", &cb.on_bytes }, + { "on_changedtick", &cb.on_changedtick }, + { "on_detach", &cb.on_detach }, + { "on_reload", &cb.on_reload }, + { NULL, NULL }, + }; + for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; - if (is_lua && strequal("on_lines", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_lines = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_bytes", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_bytes = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_changedtick", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_changedtick = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_detach", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_detach = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("utf_sizes", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); - goto error; + bool key_used = false; + if (is_lua) { + for (size_t j = 0; cbs[j].name; j++) { + if (strequal(cbs[j].name, k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", cbs[j].name); + goto error; + } + *(cbs[j].dest) = v->data.luaref; + v->data.luaref = LUA_NOREF; + key_used = true; + break; + } } - cb.utf_sizes = v->data.boolean; - } else if (is_lua && strequal("preview", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "preview must be boolean"); - goto error; + + if (key_used) { + continue; + } else if (strequal("utf_sizes", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + goto error; + } + cb.utf_sizes = v->data.boolean; + key_used = true; + } else if (strequal("preview", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "preview must be boolean"); + goto error; + } + cb.preview = v->data.boolean; + key_used = true; } - cb.preview = v->data.boolean; - } else { + } + + if (!key_used) { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; } @@ -722,7 +751,8 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, kExtmarkUndo); - changed_lines((linenr_T)start_row, 0, (linenr_T)end_row, (long)extra, true); + changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, + (long)extra, true); // adjust cursor like an extmark ( i e it was inside last_part_len) if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) { @@ -1395,6 +1425,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - hl_group : name of the highlight group used to highlight /// this mark. /// - virt_text : virtual text to link to this mark. +/// - virt_text_pos : positioning of virtual text. Possible +/// values: +/// - "eol": right after eol character (default) +/// - "overlay": display over the specified column, without +/// shifting the underlying text. /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the @@ -1444,8 +1479,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, uint64_t id = 0; int line2 = -1, hl_id = 0; DecorPriority priority = DECOR_PRIORITY_BASE; - colnr_T col2 = 0; + colnr_T col2 = -1; VirtText virt_text = KV_INITIAL_VALUE; + VirtTextPos virt_text_pos = kVTEndOfLine; bool right_gravity = true; bool end_right_gravity = false; bool end_gravity_set = false; @@ -1514,6 +1550,22 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ERROR_SET(err)) { goto error; } + } else if (strequal("virt_text_pos", k.data)) { + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos is not a String"); + goto error; + } + String str = v->data.string; + if (strequal("eol", str.data)) { + virt_text_pos = kVTEndOfLine; + } else if (strequal("overlay", str.data)) { + virt_text_pos = kVTOverlay; + } else { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos: invalid value"); + goto error; + } } else if (strequal("ephemeral", k.data)) { ephemeral = api_object_to_bool(*v, "ephemeral", false, err); if (ERROR_SET(err)) { @@ -1555,7 +1607,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, // Only error out if they try to set end_right_gravity without // setting end_col or end_line - if (line2 == -1 && col2 == 0 && end_gravity_set) { + if (line2 == -1 && col2 == -1 && end_gravity_set) { api_set_error(err, kErrorTypeValidation, "cannot set end_right_gravity " "without setting end_line or end_col"); @@ -1579,30 +1631,28 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, col2 = 0; } + Decoration *decor = NULL, tmp = { 0 }; + + if (kv_size(virt_text) || priority != DECOR_PRIORITY_BASE) { + // TODO(bfredl): this is a bit sketchy. eventually we should + // have predefined decorations for both marks/ephemerals + decor = ephemeral ? &tmp : xcalloc(1, sizeof(*decor)); + decor->hl_id = hl_id; + decor->virt_text = virt_text; + decor->priority = priority; + decor->virt_text_pos = virt_text_pos; + } else if (hl_id) { + decor = decor_hl(hl_id); + } + // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0; - VirtText *vt_allocated = NULL; - if (kv_size(virt_text)) { - vt_allocated = xmalloc(sizeof *vt_allocated); - *vt_allocated = virt_text; - } - decor_add_ephemeral(attr_id, (int)line, (colnr_T)col, - (int)line2, (colnr_T)col2, priority, vt_allocated); + decor_add_ephemeral((int)line, (int)col, line2, col2, decor, 0); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); goto error; } - Decoration *decor = NULL; - if (kv_size(virt_text)) { - decor = xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->virt_text = virt_text; - } else if (hl_id) { - decor = decor_hl(hl_id); - decor->priority = priority; - } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, line2, col2, decor, right_gravity, @@ -1673,7 +1723,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, /// @param[out] err Error details, if any /// @return The ns_id that was used Integer nvim_buf_add_highlight(Buffer buffer, - Integer src_id, + Integer ns_id, String hl_group, Integer line, Integer col_start, @@ -1698,18 +1748,18 @@ Integer nvim_buf_add_highlight(Buffer buffer, col_end = MAXCOL; } - uint64_t ns_id = src2ns(&src_id); + uint64_t ns = src2ns(&ns_id); if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range - return src_id; + return ns_id; } int hl_id = 0; if (hl_group.size > 0) { hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); } else { - return src_id; + return ns_id; } int end_line = (int)line; @@ -1718,11 +1768,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, end_line++; } - extmark_set(buf, ns_id, 0, + extmark_set(buf, ns, 0, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, decor_hl(hl_id), true, false, kExtmarkNoUndo); - return src_id; + return ns_id; } /// Clears namespaced objects (highlights, extmarks, virtual text) from @@ -1918,7 +1968,6 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) curwin->w_cursor.lnum += extra; check_cursor_col(); } else if (extra < 0) { - curwin->w_cursor.lnum = lo; check_cursor(); } else { check_cursor_col(); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 09895a2119..586123aac1 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -545,6 +545,26 @@ Object nvim_exec_lua(String code, Array args, Error *err) return nlua_exec(code, args, err); } +/// Notify the user with a message +/// +/// Relays the call to vim.notify . By default forwards your message in the +/// echo area but can be overriden to trigger desktop notifications. +/// +/// @param msg Message to display to the user +/// @param log_level The log level +/// @param opts Reserved for future use. +/// @param[out] err Error details, if any +Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = STRING_OBJ(msg); + args.items[1] = INTEGER_OBJ(log_level); + args.items[2] = DICTIONARY_OBJ(opts); + + return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err); +} + /// Calls a VimL function. /// /// @param fn Function name @@ -1526,8 +1546,8 @@ theend: /// - "c" |charwise| mode /// - "l" |linewise| mode /// - "" guess by contents, see |setreg()| -/// @param after Insert after cursor (like |p|), or before (like |P|). -/// @param follow Place cursor at end of inserted text. +/// @param after If true insert after cursor (like |p|), or before (like |P|). +/// @param follow If true place cursor at end of inserted text. /// @param[out] err Error details, if any void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err) diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 140a9c6bcb..3de2e0f342 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -544,7 +544,7 @@ char_u *au_event_disable(char *what) } else { STRCAT(new_ei, what); } - set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE); + set_string_option_direct("ei", -1, new_ei, OPT_FREE, SID_NONE); xfree(new_ei); return save_ei; @@ -553,7 +553,7 @@ char_u *au_event_disable(char *what) void au_event_restore(char_u *old_ei) { if (old_ei != NULL) { - set_string_option_direct((char_u *)"ei", -1, old_ei, OPT_FREE, SID_NONE); + set_string_option_direct("ei", -1, old_ei, OPT_FREE, SID_NONE); xfree(old_ei); } } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index ec39de14f5..c42a0e2dad 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -596,7 +596,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // Disable buffer-updates for the current buffer. // No need to check `unload_buf`: in that case the function returned above. - buf_updates_unregister_all(buf); + buf_updates_unload(buf, false); /* * Remove the buffer from the list. @@ -821,7 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); - buf_updates_unregister_all(buf); + buf_updates_unload(buf, false); } /* @@ -1726,7 +1726,8 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, file_id_valid)) != NULL) { xfree(ffname); if (lnum != 0) { - buflist_setfpos(buf, curwin, lnum, (colnr_T)0, false); + buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin, + lnum, (colnr_T)0, false); } if ((flags & BLN_NOOPT) == 0) { // Copy the options now, if 'cpo' doesn't have 's' and not done already. @@ -2308,6 +2309,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) *num_file = 0; // return values in case of FAIL *file = NULL; + if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff) { + return FAIL; + } + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)". if (*pat == '^') { patc = xmalloc(STRLEN(pat) + 11); @@ -2344,6 +2349,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) if (!buf->b_p_bl) { // skip unlisted buffers continue; } + if (options & BUF_DIFF_FILTER) { + // Skip buffers not suitable for + // :diffget or :diffput completion. + if (buf == curbuf || !diff_mode_buf(buf)) { + continue; + } + } p = buflist_match(®match, buf, p_wic); if (p != NULL) { if (round == 1) { @@ -2486,6 +2498,7 @@ buflist_nr2name( /// /// @param[in,out] buf Buffer for which line and column are set. /// @param[in,out] win Window for which line and column are set. +/// May be NULL when using :badd. /// @param[in] lnum Line number to be set. If it is zero then only /// options are touched. /// @param[in] col Column number to be set. @@ -2493,7 +2506,7 @@ buflist_nr2name( void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T col, bool copy_options) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1) { wininfo_T *wip; @@ -2528,7 +2541,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, wip->wi_fpos.lnum = lnum; wip->wi_fpos.col = col; } - if (copy_options) { + if (copy_options && win != NULL) { // Save the window-specific option values. copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); wip->wi_fold_manual = win->w_fold_manual; @@ -2566,29 +2579,39 @@ static bool wininfo_other_tab_diff(wininfo_T *wip) return false; } -/* - * Find info for the current window in buffer "buf". - * If not found, return the info for the most recently used window. - * When "skip_diff_buffer" is true avoid windows with 'diff' set that is in - * another tab page. - * Returns NULL when there isn't any info. - */ -static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer) +// Find info for the current window in buffer "buf". +// If not found, return the info for the most recently used window. +// When "need_options" is true skip entries where wi_optset is false. +// When "skip_diff_buffer" is true avoid windows with 'diff' set that is in +// another tab page. +// Returns NULL when there isn't any info. +static wininfo_T *find_wininfo(buf_T *buf, bool need_options, + bool skip_diff_buffer) + FUNC_ATTR_NONNULL_ALL { wininfo_T *wip; - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win == curwin && (!skip_diff_buffer || !wininfo_other_tab_diff(wip)) - ) + && (!need_options || wip->wi_optset)) { break; + } + } - /* If no wininfo for curwin, use the first in the list (that doesn't have - * 'diff' set and is in another tab page). */ + // If no wininfo for curwin, use the first in the list (that doesn't have + // 'diff' set and is in another tab page). + // If "need_options" is true skip entries that don't have options set, + // unless the window is editing "buf", so we can copy from the window + // itself. if (wip == NULL) { if (skip_diff_buffer) { for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { - if (!wininfo_other_tab_diff(wip)) { + if (!wininfo_other_tab_diff(wip) + && (!need_options + || wip->wi_optset + || (wip->wi_win != NULL + && wip->wi_win->w_buffer == buf))) { break; } } @@ -2607,12 +2630,10 @@ static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer) */ void get_winopts(buf_T *buf) { - wininfo_T *wip; - clear_winopt(&curwin->w_onebuf_opt); clearFolding(curwin); - wip = find_wininfo(buf, true); + wininfo_T *const wip = find_wininfo(buf, true, true); if (wip != NULL && wip->wi_win != curwin && wip->wi_win != NULL && wip->wi_win->w_buffer == buf) { win_T *wp = wip->wi_win; @@ -2649,7 +2670,7 @@ pos_T *buflist_findfpos(buf_T *buf) { static pos_T no_position = { 1, 0, 0 }; - wininfo_T *wip = find_wininfo(buf, false); + wininfo_T *const wip = find_wininfo(buf, false, false); return (wip == NULL) ? &no_position : &(wip->wi_fpos); } @@ -3236,7 +3257,7 @@ void maketitle(void) 0, maxlen, NULL, NULL); title_str = (char_u *)buf; if (called_emsg) { - set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"", + set_string_option_direct("titlestring", -1, (char_u *)"", OPT_FREE, SID_ERROR); } called_emsg |= save_called_emsg; @@ -3346,7 +3367,7 @@ void maketitle(void) p_iconstring, use_sandbox, 0, 0, NULL, NULL); if (called_emsg) { - set_string_option_direct((char_u *)"iconstring", -1, + set_string_option_direct("iconstring", -1, (char_u *)"", OPT_FREE, SID_ERROR); } called_emsg |= save_called_emsg; @@ -3486,7 +3507,7 @@ int build_stl_str_hl( stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); stl_groupitems = xmalloc(sizeof(int) * stl_items_len); stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); - stl_tabtab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); + stl_tabtab = xmalloc(sizeof(StlClickRecord) * stl_items_len); stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } @@ -3940,9 +3961,9 @@ int build_stl_str_hl( // Store the current buffer number as a string variable vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); - set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp); + set_internal_string_var("g:actual_curbuf", buf_tmp); vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); - set_internal_string_var((char_u *)"g:actual_curwin", win_tmp); + set_internal_string_var("g:actual_curwin", win_tmp); buf_T *const save_curbuf = curbuf; win_T *const save_curwin = curwin; @@ -4482,22 +4503,15 @@ int build_stl_str_hl( int num_separators = 0; for (int i = 0; i < itemcnt; i++) { if (stl_items[i].type == Separate) { + // Create an array of the start location for each + // separator mark. + stl_separator_locations[num_separators] = i; num_separators++; } } // If we have separated groups, then we deal with it now if (num_separators) { - // Create an array of the start location for each - // separator mark. - int index = 0; - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].type == Separate) { - stl_separator_locations[index] = i; - index++; - } - } - int standard_spaces = (maxwidth - width) / num_separators; int final_spaces = (maxwidth - width) - standard_spaces * (num_separators - 1); diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index ee3fda5f6d..ac7ead5f92 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -33,6 +33,9 @@ enum bln_values { BLN_DUMMY = 4, // Allocating dummy buffer BLN_NEW = 8, // create a new buffer BLN_NOOPT = 16, // Don't copy options to existing buffer + // BLN_DUMMY_OK = 32, // also find an existing dummy buffer + // BLN_REUSE = 64, // may re-use number from buf_reuse + BLN_NOCURWIN = 128, // buffer is not associated with curwin }; // Values for action argument for do_buffer() diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index cc09b7e989..a05bd6fcc7 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -296,13 +296,11 @@ typedef struct arglist { int id; ///< id of this arglist } alist_T; -/* - * For each argument remember the file name as it was given, and the buffer - * number that contains the expanded file name (required for when ":cd" is - * used. - * - * TODO: move aentry_T to another header - */ +// For each argument remember the file name as it was given, and the buffer +// number that contains the expanded file name (required for when ":cd" is +// used). +// +// TODO(Felipe): move aentry_T to another header typedef struct argentry { char_u *ae_fname; // file name as specified int ae_fnum; // buffer number with expanded file name @@ -490,11 +488,12 @@ typedef struct { LuaRef on_bytes; LuaRef on_changedtick; LuaRef on_detach; + LuaRef on_reload; bool utf_sizes; bool preview; } BufUpdateCallbacks; #define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, false, false } + LUA_NOREF, LUA_NOREF, false, false } EXTERN int curbuf_splice_pending INIT(= 0); @@ -1036,10 +1035,10 @@ struct matchitem { int id; ///< match ID int priority; ///< match priority char_u *pattern; ///< pattern to highlight - int hlg_id; ///< highlight group ID regmmatch_T match; ///< regexp program for pattern posmatch_T pos; ///< position matches match_T hl; ///< struct for doing the actual highlighting + int hlg_id; ///< highlight group ID int conceal_char; ///< cchar for Conceal highlighting }; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 68e123896b..97562eace6 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -135,7 +135,7 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid) } } -void buf_updates_unregister_all(buf_T *buf) +void buf_updates_unload(buf_T *buf, bool can_reload) { size_t size = kv_size(buf->update_channels); if (size) { @@ -146,9 +146,20 @@ void buf_updates_unregister_all(buf_T *buf) kv_init(buf->update_channels); } + size_t j = 0; for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); - if (cb.on_detach != LUA_NOREF) { + LuaRef thecb = LUA_NOREF; + + bool keep = false; + if (can_reload && cb.on_reload != LUA_NOREF) { + keep = true; + thecb = cb.on_reload; + } else if (cb.on_detach != LUA_NOREF) { + thecb = cb.on_detach; + } + + if (thecb != LUA_NOREF) { Array args = ARRAY_DICT_INIT; Object items[1]; args.size = 1; @@ -158,15 +169,24 @@ void buf_updates_unregister_all(buf_T *buf) args.items[0] = BUFFER_OBJ(buf->handle); textlock++; - nlua_call_ref(cb.on_detach, "detach", args, false, NULL); + nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL); textlock--; } - free_update_callbacks(cb); + + if (keep) { + kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); + } else { + free_update_callbacks(cb); + } + } + kv_size(buf->update_callbacks) = j; + if (kv_size(buf->update_callbacks) == 0) { + kv_destroy(buf->update_callbacks); + kv_init(buf->update_callbacks); } - kv_destroy(buf->update_callbacks); - kv_init(buf->update_callbacks); } + void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 5628ede2e6..09a34ca9fe 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -292,7 +292,6 @@ static void close_cb(Stream *stream, void *data) /// directory if `cwd` is NULL /// @param[in] pty_width Width of the pty, ignored if `pty` is false /// @param[in] pty_height Height of the pty, ignored if `pty` is false -/// @param[in] term_name `$TERM` for the pty /// @param[in] env Nvim's configured environment is used if this is NULL, /// otherwise defines all environment variables /// @param[out] status_out 0 for invalid arguments, > 0 for the channel id, @@ -304,8 +303,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, bool pty, bool rpc, bool overlapped, bool detach, const char *cwd, uint16_t pty_width, uint16_t pty_height, - char *term_name, dict_T *env, - varnumber_T *status_out) + dict_T *env, varnumber_T *status_out) { assert(cwd == NULL || os_isdir_executable(cwd)); @@ -318,7 +316,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (detach) { EMSG2(_(e_invarg2), "terminal/pty job cannot be detached"); shell_free_argv(argv); - xfree(term_name); + if (env) { + tv_dict_free(env); + } channel_destroy_early(chan); *status_out = 0; return NULL; @@ -330,9 +330,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (pty_height > 0) { chan->stream.pty.height = pty_height; } - if (term_name) { - chan->stream.pty.term_name = term_name; - } } else { chan->stream.uv = libuv_process_init(&main_loop, chan); } @@ -362,9 +359,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (proc->env) { tv_dict_free(proc->env); } - if (proc->type == kProcessTypePty) { - xfree(chan->stream.pty.term_name); - } channel_destroy_early(chan); *status_out = proc->status; return NULL; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index be265e3f27..5ad1fe0dfd 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -708,7 +708,7 @@ int ptr2cells(const char_u *p) /// @return number of character cells. int vim_strsize(char_u *s) { - return vim_strnsize(s, (int)MAXCOL); + return vim_strnsize(s, MAXCOL); } /// Return the number of character cells string "s[len]" will take on the diff --git a/src/nvim/context.c b/src/nvim/context.c index 1ae0510762..4162daa6ca 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -126,7 +126,7 @@ bool ctx_restore(Context *ctx, const int flags) } char_u *op_shada; - get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL); + get_option_value("shada", NULL, &op_shada, OPT_GLOBAL); set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL); if (flags & kCtxRegs) { diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f3ee42fab1..a1289f202a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -188,20 +188,21 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) goto next_mark; } - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - HlRange range; if (mark.id&MARKTREE_END_FLAG) { - range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, decor->priority, vt, false }; + decor_add(state, altpos.row, altpos.col, mark.row, mark.col, + decor, false, 0); } else { - range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, decor->priority, vt, false }; + if (altpos.row == -1) { + altpos.row = mark.row; + altpos.col = mark.col; + } + decor_add(state, mark.row, mark.col, altpos.row, altpos.col, + decor, false, 0); } - hlrange_activate(range, state); next_mark: if (marktree_itr_node_done(state->itr)) { + marktree_itr_next(buf->b_marktree, state->itr); break; } marktree_itr_next(buf->b_marktree, state->itr); @@ -220,41 +221,39 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) return true; // TODO(bfredl): be more precise } -static void hlrange_activate(HlRange range, DecorState *state) +static void decor_add(DecorState *state, int start_row, int start_col, + int end_row, int end_col, Decoration *decor, bool owned, + DecorPriority priority) { - // Get size before preparing the push, to have the number of elements - size_t s = kv_size(state->active); - - kv_pushp(state->active); + int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - size_t dest_index = 0; + HlRange range = { start_row, start_col, end_row, end_col, + attr_id, MAX(priority, decor->priority), + kv_size(decor->virt_text) ? &decor->virt_text : NULL, + decor->virt_text_pos, + kv_size(decor->virt_text) && owned, -1 }; - // Determine insertion dest_index - while (dest_index < s) { - HlRange item = kv_A(state->active, dest_index); - if (item.priority > range.priority) { + kv_pushp(state->active); + size_t index; + for (index = kv_size(state->active)-1; index > 0; index--) { + HlRange item = kv_A(state->active, index-1); + if (item.priority <= range.priority) { break; } - - dest_index++; - } - - // Splice - for (size_t index = s; index > dest_index; index--) { kv_A(state->active, index) = kv_A(state->active, index-1); } - - // Insert - kv_A(state->active, dest_index) = range; + kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, DecorState *state) +int decor_redraw_col(buf_T *buf, int col, int virt_col, DecorState *state) { if (col <= state->col_until) { return state->current; } state->col_until = MAXCOL; while (true) { + // TODO(bfredl): check duplicate entry in "intersection" + // branch mtmark_t mark = marktree_itr_current(state->itr); if (mark.row < 0 || mark.row > state->row) { break; @@ -278,6 +277,11 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state) } Decoration *decor = item->decor; + if (endpos.row == -1) { + endpos.row = mark.row; + endpos.col = mark.col; + } + if (endpos.row < mark.row || (endpos.row == mark.row && endpos.col <= mark.col)) { if (!kv_size(decor->virt_text)) { @@ -285,12 +289,8 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state) } } - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - hlrange_activate((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, decor->priority, - vt, false }, state); + decor_add(state, mark.row, mark.col, endpos.row, endpos.col, + decor, false, 0); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -310,7 +310,7 @@ next_mark: if (item.start_row < state->row || (item.start_row == state->row && item.start_col <= col)) { active = true; - if (item.end_row == state->row) { + if (item.end_row == state->row && item.end_col > col) { state->col_until = MIN(state->col_until, item.end_col-1); } } else { @@ -322,8 +322,12 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } + if ((item.start_row == state->row && item.start_col <= col) + && item.virt_text && item.virt_col == -1) { + item.virt_col = virt_col; + } if (keep) { - kv_A(state->active, j++) = kv_A(state->active, i); + kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { clear_virttext(item.virt_text); xfree(item.virt_text); @@ -341,21 +345,20 @@ void decor_redraw_end(DecorState *state) VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) { - decor_redraw_col(buf, MAXCOL, state); + decor_redraw_col(buf, MAXCOL, MAXCOL, state); for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text) { + if (item.start_row == state->row && item.virt_text + && item.virt_text_pos == kVTEndOfLine) { return item.virt_text; } } return NULL; } -void decor_add_ephemeral(int attr_id, int start_row, int start_col, - int end_row, int end_col, DecorPriority priority, - VirtText *virt_text) +void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, + Decoration *decor, DecorPriority priority) { -hlrange_activate(((HlRange){ start_row, start_col, end_row, end_col, attr_id, - priority, virt_text, virt_text != NULL }), - &decor_state); + decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, + priority); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 2533a641dd..47bd9abbc3 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -18,10 +18,16 @@ typedef kvec_t(VirtTextChunk) VirtText; typedef uint16_t DecorPriority; #define DECOR_PRIORITY_BASE 0x1000 +typedef enum { + kVTEndOfLine, + kVTOverlay, +} VirtTextPos; + struct Decoration { int hl_id; // highlight group VirtText virt_text; + VirtTextPos virt_text_pos; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free @@ -35,7 +41,9 @@ typedef struct { int attr_id; DecorPriority priority; VirtText *virt_text; + VirtTextPos virt_text_pos; bool virt_text_owned; + int virt_col; } HlRange; typedef struct { diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 93bc34fa4b..31b7b1bd8f 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -58,6 +58,7 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden #define DIFF_INTERNAL 0x200 // use internal xdiff algorithm #define DIFF_CLOSE_OFF 0x400 // diffoff when closing window +#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; @@ -1361,11 +1362,12 @@ void diff_win_options(win_T *wp, int addbuf) wp->w_p_crb_save = wp->w_p_crb; } wp->w_p_crb = true; - - if (!wp->w_p_diff) { - wp->w_p_wrap_save = wp->w_p_wrap; + if (!(diff_flags & DIFF_FOLLOWWRAP)) { + if (!wp->w_p_diff) { + wp->w_p_wrap_save = wp->w_p_wrap; + } + wp->w_p_wrap = false; } - wp->w_p_wrap = false; curwin = wp; // -V519 curbuf = curwin->w_buffer; @@ -1375,7 +1377,7 @@ void diff_win_options(win_T *wp, int addbuf) } wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); } - set_string_option_direct((char_u *)"fdm", -1, (char_u *)"diff", + set_string_option_direct("fdm", -1, (char_u *)"diff", OPT_LOCAL | OPT_FREE, 0); curwin = old_curwin; curbuf = curwin->w_buffer; @@ -1437,11 +1439,11 @@ void ex_diffoff(exarg_T *eap) if (wp->w_p_crb) { wp->w_p_crb = wp->w_p_crb_save; } - - if (!wp->w_p_wrap) { - wp->w_p_wrap = wp->w_p_wrap_save; + if (!(diff_flags & DIFF_FOLLOWWRAP)) { + if (!wp->w_p_wrap) { + wp->w_p_wrap = wp->w_p_wrap_save; + } } - free_string_option(wp->w_p_fdm); wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save ? wp->w_p_fdm_save @@ -2158,6 +2160,9 @@ int diffopt_changed(void) } else if (STRNCMP(p, "closeoff", 8) == 0) { p += 8; diff_flags_new |= DIFF_CLOSE_OFF; + } else if (STRNCMP(p, "followwrap", 10) == 0) { + p += 10; + diff_flags_new |= DIFF_FOLLOWWRAP; } else if (STRNCMP(p, "indent-heuristic", 16) == 0) { p += 16; diff_indent_heuristic = XDF_INDENT_HEURISTIC; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 100e88e261..68c7438ea3 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1024,7 +1024,7 @@ static int insert_handle_key(InsertState *s) break; case K_EVENT: // some event - multiqueue_process_events(main_loop.events); + state_handle_k_event(); goto check_pum; case K_COMMAND: // some command @@ -1627,11 +1627,11 @@ static void init_prompt(int cmdchar_todo) ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); } curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); changed_bytes(curbuf->b_ml.ml_line_count, 0); } if (cmdchar_todo == 'A') { - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) { curwin->w_cursor.col = STRLEN(prompt); @@ -6246,9 +6246,10 @@ void auto_format( if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { // "cannot happen" curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); - } else + coladvance(MAXCOL); + } else { check_cursor_col(); + } // Insert mode: If the cursor is now after the end of the line while it // previously wasn't, the line was broken. Because of the rule above we @@ -8432,8 +8433,8 @@ static void ins_left(void) // if 'whichwrap' set for cursor in insert mode may go to previous line. // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); - --(curwin->w_cursor.lnum); - coladvance((colnr_T)MAXCOL); + curwin->w_cursor.lnum--; + coladvance(MAXCOL); curwin->w_set_curswant = true; // so we stay at the end } else { vim_beep(BO_CRSR); @@ -8467,7 +8468,7 @@ static void ins_end(int c) tpos = curwin->w_cursor; if (c == K_C_END) curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); curwin->w_curswant = MAXCOL; start_arrow(&tpos); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a25b140b2f..6d97310c1c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -176,7 +176,6 @@ static struct vimvar { VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), - VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_REG, "register", VAR_STRING, VV_RO), VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), @@ -211,13 +210,9 @@ static struct vimvar { VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), VV(VV_ERRORS, "errors", VAR_LIST, 0), - VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), - VV(VV_EVENT, "event", VAR_DICT, VV_RO), VV(VV_FALSE, "false", VAR_BOOL, VV_RO), VV(VV_TRUE, "true", VAR_BOOL, VV_RO), VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), - VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), - VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), VV(VV_TESTING, "testing", VAR_NUMBER, 0), VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), @@ -227,10 +222,16 @@ static struct vimvar { VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), + VV(VV_EVENT, "event", VAR_DICT, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), + VV(VV_ARGV, "argv", VAR_LIST, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + // Neovim + VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), + VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), + VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), - VV(VV_ARGV, "argv", VAR_LIST, VV_RO), }; #undef VV @@ -455,14 +456,15 @@ void eval_clear(void) * Set an internal variable to a string value. Creates the variable if it does * not already exist. */ -void set_internal_string_var(char_u *name, char_u *value) +void set_internal_string_var(const char *name, char_u *value) + FUNC_ATTR_NONNULL_ARG(1) { - const typval_T tv = { + typval_T tv = { .v_type = VAR_STRING, .vval.v_string = value, }; - set_var((const char *)name, STRLEN(name), (typval_T *)&tv, true); + set_var(name, strlen(name), &tv, true); } static lval_T *redir_lval = NULL; @@ -522,9 +524,9 @@ var_redir_start( tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; if (append) { - set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, true, false, "."); } else { - set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); + set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); err = did_emsg; @@ -584,7 +586,7 @@ void var_redir_stop(void) redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, false, false, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) { - set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, false, false, "."); } clear_lval(redir_lval); } @@ -1847,7 +1849,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, s = tv_get_string_chk(tv); // != NULL if number or string. } if (s != NULL && op != NULL && *op != '=') { - opt_type = get_option_value(arg, &numval, (char_u **)&stringval, + opt_type = get_option_value((char *)arg, &numval, (char_u **)&stringval, opt_flags); if ((opt_type == 1 && *op == '.') || (opt_type == 0 && *op != '.')) { @@ -1924,7 +1926,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { EMSG(_(e_letunexp)); } else { - set_var_lval(&lv, p, tv, copy, is_const, op); + set_var_lval(&lv, p, tv, copy, is_const, (const char *)op); arg_end = p; } } @@ -2121,9 +2123,10 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, return NULL; } } - lp->ll_range = TRUE; - } else - lp->ll_range = FALSE; + lp->ll_range = true; + } else { + lp->ll_range = false; + } if (*p != ']') { if (!quiet) { @@ -2240,12 +2243,10 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, return NULL; } - /* - * May need to find the item or absolute index for the second - * index of a range. - * When no index given: "lp->ll_empty2" is TRUE. - * Otherwise "lp->ll_n2" is set to the second index. - */ + // May need to find the item or absolute index for the second + // index of a range. + // When no index given: "lp->ll_empty2" is true. + // Otherwise "lp->ll_n2" is set to the second index. if (lp->ll_range && !lp->ll_empty2) { lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string. tv_clear(&var2); @@ -2299,7 +2300,7 @@ void clear_lval(lval_T *lp) * "%" for "%=", "." for ".=" or "=" for "=". */ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, - int copy, const bool is_const, const char_u *op) + int copy, const bool is_const, const char *op) { int cc; listitem_T *ri; @@ -2326,7 +2327,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, TV_CSTRING) && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name, TV_CSTRING))) - && eexe_mod_op(&tv, rettv, (const char *)op) == OK) { + && eexe_mod_op(&tv, rettv, op) == OK) { set_var(lp->ll_name, lp->ll_name_len, &tv, false); } tv_clear(&tv); @@ -2369,8 +2370,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, */ for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) { if (op != NULL && *op != '=') { - eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), - (const char *)op); + eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op); } else { tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li)); @@ -2428,7 +2428,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, } if (op != NULL && *op != '=') { - eexe_mod_op(lp->ll_tv, rettv, (const char *)op); + eexe_mod_op(lp->ll_tv, rettv, op); goto notify; } else { tv_clear(lp->ll_tv); @@ -4538,7 +4538,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, c = *option_end; *option_end = NUL; - opt_type = get_option_value((char_u *)(*arg), &numval, + opt_type = get_option_value(*arg, &numval, rettv == NULL ? NULL : &stringval, opt_flags); if (opt_type == -3) { // invalid name diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 06b7f9e21d..4f03d5d259 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -56,10 +56,10 @@ typedef struct lval_S { ///< isn't NULL it's the Dict to which to add the item. listitem_T *ll_li; ///< The list item or NULL. list_T *ll_list; ///< The list or NULL. - int ll_range; ///< TRUE when a [i:j] range was used. + bool ll_range; ///< true when a [i:j] range was used. + bool ll_empty2; ///< Second index is empty: [i:]. long ll_n1; ///< First index for list. long ll_n2; ///< Second index for list range. - int ll_empty2; ///< Second index is empty: [i:]. dict_T *ll_dict; ///< The Dictionary or NULL. dictitem_T *ll_di; ///< The dictitem or NULL. char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL. @@ -105,7 +105,6 @@ typedef enum { VV_DYING, VV_EXCEPTION, VV_THROWPOINT, - VV_STDERR, VV_REG, VV_CMDBANG, VV_INSERTMODE, @@ -140,13 +139,9 @@ typedef enum { VV_OPTION_OLD, VV_OPTION_TYPE, VV_ERRORS, - VV_MSGPACK_TYPES, - VV_EVENT, VV_FALSE, VV_TRUE, VV_NULL, - VV__NULL_LIST, // List with NULL value. For test purposes only. - VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV_VIM_DID_ENTER, VV_TESTING, VV_TYPE_NUMBER, @@ -156,10 +151,16 @@ typedef enum { VV_TYPE_DICT, VV_TYPE_FLOAT, VV_TYPE_BOOL, + VV_EVENT, VV_ECHOSPACE, + VV_ARGV, VV_EXITING, + // Neovim + VV_STDERR, + VV_MSGPACK_TYPES, + VV__NULL_LIST, // List with NULL value. For test purposes only. + VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV_LUA, - VV_ARGV, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 952fa35b83..eac0feafcf 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -63,6 +63,7 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}}, + charidx={args={2, 3}}, cindent={args=1}, clearmatches={args={0, 1}}, col={args=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 1a7bbf2d0e..60229e1ebc 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -940,6 +940,52 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) (const char_u *)tv_get_string(&argvars[0])); } +// "charidx()" function +static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING + || argvars[1].v_type != VAR_NUMBER + || (argvars[2].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_NUMBER)) { + EMSG(_(e_invarg)); + return; + } + + const char *str = tv_get_string_chk(&argvars[0]); + varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); + if (str == NULL || idx < 0) { + return; + } + int countcc = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { + countcc = (int)tv_get_number(&argvars[2]); + } + if (countcc < 0 || countcc > 1) { + EMSG(_(e_invarg)); + return; + } + + int (*ptr2len)(const char_u *); + if (countcc) { + ptr2len = utf_ptr2len; + } else { + ptr2len = utfc_ptr2len; + } + + const char *p; + int len; + for (p = str, len = 0; p <= str + idx; len++) { + if (*p == NUL) { + return; + } + p += ptr2len((const char_u *)p); + } + + rettv->vval.v_number = len > 0 ? len - 1 : 0; +} + /* * "cindent(lnum)" function */ @@ -1629,27 +1675,26 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (u_save(first - 1, last + 1) == FAIL) { rettv->vval.v_number = 1; // FAIL - return; - } - - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } + } else { + for (linenr_T lnum = first; lnum <= last; lnum++) { + ml_delete(first, true); + } - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= count; - } else if (wp->w_cursor.lnum> first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + if (wp->w_cursor.lnum > last) { + wp->w_cursor.lnum -= count; + } else if (wp->w_cursor.lnum> first) { + wp->w_cursor.lnum = first; + } + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + } } } + check_cursor_col(); + deleted_lines_mark(first, count); } - check_cursor_col(); - deleted_lines_mark(first, count); if (!is_curbuf) { curbuf = curbuf_save; @@ -2149,7 +2194,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { - const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); + const char *const strmodes = tv_get_string(&argvars[1]); modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); } menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); @@ -2984,10 +3029,11 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. + // TODO(bfredl): deduplicate shared logic with state_enter ? if (!(char_avail() || using_script() || input_available())) { (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { - multiqueue_process_events(main_loop.events); + state_handle_k_event(); continue; } } @@ -3135,6 +3181,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH | WILD_NO_BEEP; + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "type must be a string"); + return; + } + const char *const type = tv_get_string(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { filtered = (bool)tv_get_number_chk(&argvars[2], NULL); } @@ -3148,12 +3200,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) options |= WILD_KEEP_ALL; } - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + if (argvars[0].v_type != VAR_STRING) { EMSG(_(e_invarg)); return; } - if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { + if (strcmp(type, "cmdline") == 0) { set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); goto theend; @@ -3162,15 +3214,14 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) ExpandInit(&xpc); xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type( - (char_u *)tv_get_string(&argvars[1])); + xpc.xp_context = cmdcomplete_str_to_type(type); if (xpc.xp_context == EXPAND_NOTHING) { - EMSG2(_(e_invarg2), argvars[1].vval.v_string); + EMSG2(_(e_invarg2), type); return; } if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); + set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); } @@ -4081,7 +4132,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef _WIN64 "win64", #endif +#ifndef CASE_INSENSITIVE_FILENAME "fname_case", +#endif #ifdef HAVE_ACL "acl", #endif @@ -4912,7 +4965,8 @@ static const char *required_env_vars[] = { static dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, - const bool pty) + const bool pty, + const char * const pty_term_name) { dict_T * env = tv_dict_alloc(); @@ -4946,6 +5000,18 @@ static dict_T *create_environment(const dictitem_T *job_env, } } + // For a pty, we need a sane $TERM set. We can't rely on nvim's environment, + // because the child process is going to be communicating with nvim, not the + // parent terminal. Set a sane default, but let the user override it in the + // job's environment if they want. + if (pty) { + dictitem_T *dv = tv_dict_find(env, S_LEN("TERM")); + if (dv) { + tv_dict_item_remove(env, dv); + } + tv_dict_add_str(env, S_LEN("TERM"), pty_term_name); + } + if (job_env) { tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force"); } @@ -5055,20 +5121,25 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - env = create_environment(job_env, clear_env, pty); - uint16_t width = 0, height = 0; char *term_name = NULL; if (pty) { width = (uint16_t)tv_dict_get_number(job_opts, "width"); height = (uint16_t)tv_dict_get_number(job_opts, "height"); - term_name = tv_dict_get_string(job_opts, "TERM", true); + // Legacy method, before env option existed, to specify $TERM. No longer + // documented, but still usable to avoid breaking scripts. + term_name = tv_dict_get_string(job_opts, "TERM", false); + if (!term_name) { + term_name = "ansi"; + } } + env = create_environment(job_env, clear_env, pty, term_name); + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, cwd, width, height, - term_name, env, &rettv->vval.v_number); + env, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -7511,7 +7582,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, false, true, false, false, NULL, 0, 0, - NULL, NULL, &rettv->vval.v_number); + NULL, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -10618,7 +10689,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - env = create_environment(job_env, clear_env, pty); + env = create_environment(job_env, clear_env, pty, "xterm-256color"); const bool rpc = false; const bool overlapped = false; @@ -10627,8 +10698,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, cwd, term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), env, - &rettv->vval.v_number); + env, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index d8ede1e3ba..6fcb01aace 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -315,6 +315,7 @@ struct ufunc { int uf_calls; ///< nr of active calls bool uf_cleared; ///< func_clear() was already called garray_T uf_args; ///< arguments + garray_T uf_def_args; ///< default argument expressions garray_T uf_lines; ///< function lines int uf_profiling; ///< true when func is being profiled int uf_prof_initialized; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 70c998ef39..689d05e079 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -67,7 +67,7 @@ void func_init(void) /// Get function arguments. static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, - int *varargs, bool skip) + int *varargs, garray_T *default_args, bool skip) { bool mustend = false; char_u *arg = *argp; @@ -78,12 +78,16 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, if (newargs != NULL) { ga_init(newargs, (int)sizeof(char_u *), 3); } + if (default_args != NULL) { + ga_init(default_args, (int)sizeof(char_u *), 3); + } if (varargs != NULL) { *varargs = false; } // Isolate the arguments: "arg1, arg2, ...)" + bool any_default = false; while (*p != endchar) { if (p[0] == '.' && p[1] == '.' && p[2] == '.') { if (varargs != NULL) { @@ -123,6 +127,38 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, *p = c; } + if (*skipwhite(p) == '=' && default_args != NULL) { + typval_T rettv; + + any_default = true; + p = skipwhite(p) + 1; + p = skipwhite(p); + char_u *expr = p; + if (eval1(&p, &rettv, false) != FAIL) { + ga_grow(default_args, 1); + + // trim trailing whitespace + while (p > expr && ascii_iswhite(p[-1])) { + p--; + } + c = *p; + *p = NUL; + expr = vim_strsave(expr); + if (expr == NULL) { + *p = c; + goto err_ret; + } + ((char_u **)(default_args->ga_data)) + [default_args->ga_len] = expr; + default_args->ga_len++; + *p = c; + } else { + mustend = true; + } + } else if (any_default) { + EMSG(_("E989: Non-default argument follows default argument")); + mustend = true; + } if (*p == ',') { p++; } else { @@ -149,6 +185,9 @@ err_ret: if (newargs != NULL) { ga_clear_strings(newargs); } + if (default_args != NULL) { + ga_clear_strings(default_args); + } return FAIL; } @@ -195,7 +234,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) bool eval_lavars = false; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, true); + ret = get_function_args(&start, '-', NULL, NULL, NULL, true); if (ret == FAIL || *start != '>') { return NOTDONE; } @@ -207,7 +246,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) pnewargs = NULL; } *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', pnewargs, &varargs, false); + ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, false); if (ret == FAIL || **arg != '>') { goto errret; } @@ -259,6 +298,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; + ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1); fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { flags |= FC_CLOSURE; @@ -715,6 +755,7 @@ static bool func_remove(ufunc_T *fp) static void func_clear_items(ufunc_T *fp) { ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); if (fp->uf_cb_free != NULL) { @@ -857,12 +898,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } // Init a: variables, unless none found (in lambda). - // Set a:0 to "argcount". + // Set a:0 to "argcount" less number of named arguments, if >= 0. // Set a:000 to a list with room for the "..." arguments. init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); if ((fp->uf_flags & FC_NOARGS) == 0) { add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", - (varnumber_T)(argcount - fp->uf_args.ga_len)); + (varnumber_T)(argcount >= fp->uf_args.ga_len + ? argcount - fp->uf_args.ga_len : 0)); } fc->l_avars.dv_lock = VAR_FIXED; if ((fp->uf_flags & FC_NOARGS) == 0) { @@ -892,8 +934,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "lastline", (varnumber_T)lastline); } - for (int i = 0; i < argcount; i++) { + bool default_arg_err = false; + for (int i = 0; i < argcount || i < fp->uf_args.ga_len; i++) { bool addlocal = false; + bool isdefault = false; + typval_T def_rettv; ai = i - fp->uf_args.ga_len; if (ai < 0) { @@ -902,6 +947,21 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (islambda) { addlocal = true; } + + // evaluate named argument default expression + isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount; + if (isdefault) { + char_u *default_expr = NULL; + def_rettv.v_type = VAR_NUMBER; + def_rettv.vval.v_number = -1; + + default_expr = ((char_u **)(fp->uf_def_args.ga_data)) + [ai + fp->uf_def_args.ga_len]; + if (eval1(&default_expr, &def_rettv, true) == FAIL) { + default_arg_err = true; + break; + } + } } else { if ((fp->uf_flags & FC_NOARGS) != 0) { // Bail out if no a: arguments used (in lambda). @@ -922,7 +982,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // Note: the values are copied directly to avoid alloc/free. // "argvars" must have VAR_FIXED for v_lock. - v->di_tv = argvars[i]; + v->di_tv = isdefault ? def_rettv : argvars[i]; v->di_tv.v_lock = VAR_FIXED; if (addlocal) { @@ -1046,7 +1106,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, save_did_emsg = did_emsg; did_emsg = FALSE; - if (islambda) { + if (default_arg_err && (fp->uf_flags & FC_ABORT)) { + did_emsg = true; + } else if (islambda) { char_u *p = *(char_u **)fp->uf_lines.ga_data + 7; // A Lambda always has the command "return {expr}". It is much faster @@ -1490,7 +1552,7 @@ call_func( if (fp->uf_flags & FC_RANGE) { *doesrange = true; } - if (argcount < fp->uf_args.ga_len) { + if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { error = ERROR_TOOFEW; } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { error = ERROR_TOOMANY; @@ -1573,6 +1635,11 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) msg_puts(", "); } msg_puts((const char *)FUNCARG(fp, j)); + if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) { + msg_puts(" = "); + msg_puts(((char **)(fp->uf_def_args.ga_data)) + [j - fp->uf_args.ga_len + fp->uf_def_args.ga_len]); + } } if (fp->uf_varargs) { if (j) { @@ -1836,6 +1903,7 @@ void ex_function(exarg_T *eap) char_u *arg; char_u *line_arg = NULL; garray_T newargs; + garray_T default_args; garray_T newlines; int varargs = false; int flags = 0; @@ -2039,7 +2107,8 @@ void ex_function(exarg_T *eap) } } - if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + if (get_function_args(&p, ')', &newargs, &varargs, + &default_args, eap->skip) == FAIL) { goto errret_2; } @@ -2458,6 +2527,7 @@ void ex_function(exarg_T *eap) fp->uf_refcount = 1; } fp->uf_args = newargs; + fp->uf_def_args = default_args; fp->uf_lines = newlines; if ((flags & FC_CLOSURE) != 0) { register_closure(fp); @@ -2480,6 +2550,7 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); + ga_clear_strings(&default_args); errret_2: ga_clear_strings(&newlines); ret_free: diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 8e9964bd37..b93d6cc0ab 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -270,9 +270,6 @@ static void process_close_event(void **argv) { Process *proc = argv[0]; shell_free_argv(proc->argv); - if (proc->type == kProcessTypePty) { - xfree(((PtyProcess *)proc)->term_name); - } if (proc->cb) { // "on_exit" for jobstart(). See channel_job_start(). proc->cb(proc, proc->status, proc->data); } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2da8c205c1..854faf5377 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -790,7 +790,10 @@ void ex_retab(exarg_T *eap) for (col = 0; col < len; col++) { ptr[col] = (col < num_tabs) ? '\t' : ' '; } - ml_replace(lnum, new_line, false); + if (ml_replace(lnum, new_line, false) == OK) { + // "new_line" may have been copied + new_line = curbuf->b_ml.ml_line_ptr; + } if (first_line == 0) { first_line = lnum; } @@ -1404,19 +1407,20 @@ do_shell( * For autocommands we want to get the output on the current screen, to * avoid having to type return below. */ - msg_putchar('\r'); /* put cursor at start of line */ - msg_putchar('\n'); /* may shift screen one line up */ + msg_putchar('\r'); // put cursor at start of line + msg_putchar('\n'); // may shift screen one line up - /* warning message before calling the shell */ + // warning message before calling the shell if (p_warn && !autocmd_busy - && msg_silent == 0) + && msg_silent == 0) { FOR_ALL_BUFFERS(buf) { if (bufIsChanged(buf)) { MSG_PUTS(_("[No write since last change]\n")); break; } } + } // This ui_cursor_goto is required for when the '\n' resulted in a "delete line // 1" command to the terminal. @@ -2178,6 +2182,8 @@ theend: /// ECMD_OLDBUF: use existing buffer if it exists /// ECMD_FORCEIT: ! used for Ex command /// ECMD_ADDBUF: don't edit, just add to buffer list +/// ECMD_ALTBUF: like ECMD_ADDBUF and also set the alternate +/// file /// @param oldwin Should be "curwin" when editing a new buffer in the current /// window, NULL when splitting the window first. When not NULL /// info of the previous buffer for "oldwin" is stored. @@ -2234,8 +2240,10 @@ int do_ecmd( path_fix_case(sfname); // set correct case for sfname #endif - if ((flags & ECMD_ADDBUF) && (ffname == NULL || *ffname == NUL)) + if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF)) + && (ffname == NULL || *ffname == NUL)) { goto theend; + } if (ffname == NULL) other_file = TRUE; @@ -2265,15 +2273,16 @@ int do_ecmd( // If the file was changed we may not be allowed to abandon it: // - if we are going to re-edit the same file // - or if we are the only window on this file and if ECMD_HIDE is FALSE - if ( ((!other_file && !(flags & ECMD_OLDBUF)) - || (curbuf->b_nwindows == 1 - && !(flags & (ECMD_HIDE | ECMD_ADDBUF)))) - && check_changed(curbuf, (p_awa ? CCGD_AW : 0) - | (other_file ? 0 : CCGD_MULTWIN) - | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) - | (eap == NULL ? 0 : CCGD_EXCMD))) { - if (fnum == 0 && other_file && ffname != NULL) + if (((!other_file && !(flags & ECMD_OLDBUF)) + || (curbuf->b_nwindows == 1 + && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF)))) + && check_changed(curbuf, (p_awa ? CCGD_AW : 0) + | (other_file ? 0 : CCGD_MULTWIN) + | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) + | (eap == NULL ? 0 : CCGD_EXCMD))) { + if (fnum == 0 && other_file && ffname != NULL) { (void)setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum); + } goto theend; } @@ -2303,25 +2312,35 @@ int do_ecmd( * Otherwise we re-use the current buffer. */ if (other_file) { - if (!(flags & ECMD_ADDBUF)) { - if (!cmdmod.keepalt) + if (!(flags & (ECMD_ADDBUF | ECMD_ALTBUF))) { + if (!cmdmod.keepalt) { curwin->w_alt_fnum = curbuf->b_fnum; - if (oldwin != NULL) + } + if (oldwin != NULL) { buflist_altfpos(oldwin); + } } if (fnum) { buf = buflist_findnr(fnum); } else { - if (flags & ECMD_ADDBUF) { - linenr_T tlnum = 1L; + if (flags & (ECMD_ADDBUF | ECMD_ALTBUF)) { + // Default the line number to zero to avoid that a wininfo item + // is added for the current window. + linenr_T tlnum = 0; if (command != NULL) { tlnum = atol((char *)command); if (tlnum <= 0) tlnum = 1L; } - (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED); + // Add BLN_NOCURWIN to avoid a new wininfo items are associated + // with the current window. + const buf_T *const newbuf + = buflist_new(ffname, sfname, tlnum, BLN_LISTED | BLN_NOCURWIN); + if (newbuf != NULL && (flags & ECMD_ALTBUF)) { + curwin->w_alt_fnum = newbuf->b_fnum; + } goto theend; } buf = buflist_new(ffname, sfname, 0L, @@ -2413,7 +2432,10 @@ int do_ecmd( (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, false); - the_curwin->w_closing = false; + // Autocommands may have closed the window. + if (win_valid(the_curwin)) { + the_curwin->w_closing = false; + } buf->b_locked--; // autocmds may abort script processing @@ -2464,8 +2486,7 @@ int do_ecmd( curwin->w_pcmark.lnum = 1; curwin->w_pcmark.col = 0; } else { // !other_file - if ((flags & ECMD_ADDBUF) - || check_fname() == FAIL) { + if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF)) || check_fname() == FAIL) { goto theend; } oldbuf = (flags & ECMD_OLDBUF); @@ -2531,13 +2552,13 @@ int do_ecmd( goto theend; } u_unchanged(curbuf); - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, false); buf_freeall(curbuf, BFA_KEEP_UNDO); // Tell readfile() not to clear or reload undo info. readfile_flags = READ_KEEP_UNDO; } else { - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, false); buf_freeall(curbuf, 0); // Free all things for buffer. } // If autocommands deleted the buffer we were going to re-edit, give @@ -3310,11 +3331,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int save_b_changed = curbuf->b_changed; bool preview = (State & CMDPREVIEW); - // inccommand tests fail without this check - if (!preview) { - // Required for Undo to work for extmarks. - u_save_cursor(); - } + bool did_save = false; if (!global_busy) { sub_nsubs = 0; @@ -3991,6 +4008,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int matchcols = end.col - ((end.lnum == start.lnum) ? start.col : 0); int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0); + if (!did_save) { + // Required for Undo to work for extmarks. + u_save_cursor(); + did_save = true; + } extmark_splice(curbuf, lnum_start-1, start_col, end.lnum-start.lnum, matchcols, replaced_bytes, lnum-lnum_start, subcols, sublen-1, kExtmarkUndo); @@ -4177,7 +4199,7 @@ skip: // when interactive leave cursor on the match if (!subflags.do_ask) { if (endcolumn) { - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } else { beginline(BL_WHITE | BL_FIX); } @@ -4220,7 +4242,7 @@ skip: size_t subsize = preview_lines.subresults.size; if (preview && !aborting()) { if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable. - set_string_option_direct((char_u *)"icm", -1, (char_u *)"", OPT_FREE, + set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE, SID_NONE); } else if (*p_icm != NUL && pat != NULL) { if (pre_src_id == 0) { @@ -4521,7 +4543,7 @@ prepare_tagpreview ( RESET_BINDING(curwin); /* don't take over 'scrollbind' and 'cursorbind' */ curwin->w_p_diff = false; // no 'diff' - set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn' + set_string_option_direct("fdc", -1, // no 'foldcolumn' (char_u *)"0", OPT_FREE, SID_NONE); return true; } @@ -4999,7 +5021,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, if (keep_lang) { flags |= TAG_KEEP_LANG; } - if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK + if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK && *num_matches > 0) { /* Sort the matches found on the heuristic number that is after the * tag name. */ @@ -5016,7 +5038,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, static void prepare_help_buffer(void) { curbuf->b_help = true; - set_string_option_direct((char_u *)"buftype", -1, (char_u *)"help", + set_string_option_direct("buftype", -1, (char_u *)"help", OPT_FREE|OPT_LOCAL, 0); // Always set these options after jumping to a help tag, because the @@ -5026,13 +5048,13 @@ static void prepare_help_buffer(void) // Only set it when needed, buf_init_chartab() is some work. char_u *p = (char_u *)"!-~,^*,^|,^\",192-255"; if (STRCMP(curbuf->b_p_isk, p) != 0) { - set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0); + set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); check_buf_options(curbuf); (void)buf_init_chartab(curbuf, FALSE); } // Don't use the global foldmethod. - set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual", + set_string_option_direct("fdm", -1, (char_u *)"manual", OPT_FREE|OPT_LOCAL, 0); curbuf->b_p_ts = 8; // 'tabstop' is 8. @@ -5652,7 +5674,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, cmdmod.tab = 0; // disable :tab modifier cmdmod.noswapfile = true; // disable swap for preview buffer // disable file info message - set_string_option_direct((char_u *)"shm", -1, (char_u *)"F", OPT_FREE, + set_string_option_direct("shm", -1, (char_u *)"F", OPT_FREE, SID_NONE); bool outside_curline = (eap->line1 != old_cusr.lnum @@ -5775,7 +5797,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, update_screen(SOME_VALID); RedrawingDisabled = save_rd; - set_string_option_direct((char_u *)"shm", -1, save_shm_p, OPT_FREE, SID_NONE); + set_string_option_direct("shm", -1, save_shm_p, OPT_FREE, SID_NONE); xfree(save_shm_p); cmdmod = save_cmdmod; diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index b564cde56c..1b54b3a898 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -16,7 +16,7 @@ #define ECMD_OLDBUF 0x04 // use existing buffer if it exists #define ECMD_FORCEIT 0x08 // ! used in Ex command #define ECMD_ADDBUF 0x10 // don't edit, just add to buffer list - +#define ECMD_ALTBUF 0x20 // like ECMD_ADDBUF and set the alternate file /* for lnum argument in do_ecmd() */ #define ECMD_LASTL (linenr_T)0 /* use last position in loaded file */ diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index e9046da800..2965ea7496 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -176,6 +176,12 @@ module.cmds = { func='ex_edit', }, { + command='balt', + flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN), + addr_type='ADDR_NONE', + func='ex_edit', + }, + { command='bdelete', flags=bit.bor(BANG, RANGE, BUFNAME, COUNT, EXTRA, TRLBAR), addr_type='ADDR_BUFFERS', @@ -2515,8 +2521,8 @@ module.cmds = { }, { command='source', - flags=bit.bor(BANG, FILE1, TRLBAR, SBOXOK, CMDWIN), - addr_type='ADDR_NONE', + flags=bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN), + addr_type='ADDR_LINES', func='ex_source', }, { diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index e49bb99aa0..c4c18c4324 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -29,6 +29,7 @@ #include "nvim/getchar.h" #include "nvim/mark.h" #include "nvim/mbyte.h" +#include "nvim/memline.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/garray.h" @@ -1657,9 +1658,11 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) /// AL_DEL: remove files in 'str' from the argument list. /// @param after /// 0 means before first one +/// @param will_edit will edit added argument /// /// @return FAIL for failure, OK otherwise. -static int do_arglist(char_u *str, int what, int after) +static int do_arglist(char_u *str, int what, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL { garray_T new_ga; int exp_count; @@ -1733,10 +1736,11 @@ static int do_arglist(char_u *str, int what, int after) } if (what == AL_ADD) { - (void)alist_add_list(exp_count, exp_files, after); + alist_add_list(exp_count, exp_files, after, will_edit); xfree(exp_files); - } else { // what == AL_SET - alist_set(ALIST(curwin), exp_count, exp_files, false, NULL, 0); + } else { + assert(what == AL_SET); + alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); } } @@ -1956,7 +1960,7 @@ void ex_next(exarg_T *eap) | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) { if (*eap->arg != NUL) { // redefine file list - if (do_arglist(eap->arg, AL_SET, 0) == FAIL) { + if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { return; } i = 0; @@ -1974,7 +1978,7 @@ void ex_argedit(exarg_T *eap) // Whether curbuf will be reused, curbuf->b_ffname will be set. bool curbuf_is_reusable = curbuf_reusable(); - if (do_arglist(eap->arg, AL_ADD, i) == FAIL) { + if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { return; } maketitle(); @@ -1994,7 +1998,8 @@ void ex_argedit(exarg_T *eap) void ex_argadd(exarg_T *eap) { do_arglist(eap->arg, AL_ADD, - eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1); + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, + false); maketitle(); } @@ -2041,7 +2046,7 @@ void ex_argdelete(exarg_T *eap) } } } else { - do_arglist(eap->arg, AL_DEL, 0); + do_arglist(eap->arg, AL_DEL, 0, false); } maketitle(); } @@ -2292,9 +2297,9 @@ void ex_listdo(exarg_T *eap) /// Files[] itself is not taken over. /// /// @param after: where to add: 0 = before first one -/// -/// @return index of first added argument -static int alist_add_list(int count, char_u **files, int after) +/// @param will_edit will edit adding argument +static void alist_add_list(int count, char_u **files, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL { int old_argcount = ARGCOUNT; ga_grow(&ALIST(curwin)->al_ga, count); @@ -2310,15 +2315,15 @@ static int alist_add_list(int count, char_u **files, int after) (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); } for (int i = 0; i < count; i++) { + const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); ARGLIST[after + i].ae_fname = files[i]; - ARGLIST[after + i].ae_fnum = buflist_add(files[i], - BLN_LISTED | BLN_CURBUF); + ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); } ALIST(curwin)->al_ga.ga_len += count; if (old_argcount > 0 && curwin->w_arg_idx >= after) { curwin->w_arg_idx += count; } - return after; + return; } } @@ -2375,13 +2380,13 @@ void ex_compiler(exarg_T *eap) // Set "b:current_compiler" from "current_compiler". p = get_var_value("g:current_compiler"); if (p != NULL) { - set_internal_string_var((char_u *)"b:current_compiler", p); + set_internal_string_var("b:current_compiler", p); } // Restore "current_compiler" for ":compiler {name}". if (!eap->forceit) { if (old_cur_comp != NULL) { - set_internal_string_var((char_u *)"g:current_compiler", + set_internal_string_var("g:current_compiler", old_cur_comp); xfree(old_cur_comp); } else { @@ -2522,7 +2527,7 @@ void ex_pyxdo(exarg_T *eap) } } -/// ":source {fname}" +/// ":source [{fname}]" void ex_source(exarg_T *eap) { cmd_source(eap->arg, eap); @@ -2531,7 +2536,7 @@ void ex_source(exarg_T *eap) static void cmd_source(char_u *fname, exarg_T *eap) { if (*fname == NUL) { - EMSG(_(e_argreq)); + cmd_source_buffer(eap); } else if (eap != NULL && eap->forceit) { // ":source!": read Normal mode commands // Need to execute the commands directly. This is required at least @@ -2549,6 +2554,37 @@ static void cmd_source(char_u *fname, exarg_T *eap) } } +typedef struct { + linenr_T curr_lnum; + const linenr_T final_lnum; +} GetBufferLineCookie; + +/// Get one line from the current selection in the buffer. +/// Called by do_cmdline() when it's called from cmd_source_buffer(). +/// +/// @return pointer to allocated line, or NULL for end-of-file or +/// some error. +static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat) +{ + GetBufferLineCookie *p = cookie; + if (p->curr_lnum > p->final_lnum) { + return NULL; + } + char_u *curr_line = ml_get(p->curr_lnum); + p->curr_lnum++; + return (char_u *)xstrdup((const char *)curr_line); +} + +static void cmd_source_buffer(exarg_T *eap) +{ + GetBufferLineCookie cookie = { + .curr_lnum = eap->line1, + .final_lnum = eap->line2, + }; + source_using_linegetter((void *)&cookie, get_buffer_line, + ":source (no file)"); +} + /// ":source" and associated commands. /// /// @return address holding the next breakpoint line for a source cookie @@ -2620,10 +2656,9 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)xstrdup(buf); } -/// Executes lines in `src` as Ex commands. -/// -/// @see do_source() -int do_source_str(const char *cmd, const char *traceback_name) +static int source_using_linegetter(void *cookie, + LineGetter fgetline, + const char *traceback_name) { char_u *save_sourcing_name = sourcing_name; linenr_T save_sourcing_lnum = sourcing_lnum; @@ -2638,22 +2673,33 @@ int do_source_str(const char *cmd, const char *traceback_name) } sourcing_lnum = 0; - GetStrLineCookie cookie = { - .buf = (char_u *)cmd, - .offset = 0, - }; const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; - int retval = do_cmdline(NULL, get_str_line, (void *)&cookie, + funccal_entry_T entry; + save_funccal(&entry); + int retval = do_cmdline(NULL, fgetline, cookie, DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); - current_sctx = save_current_sctx; sourcing_lnum = save_sourcing_lnum; sourcing_name = save_sourcing_name; + current_sctx = save_current_sctx; + restore_funccal(); return retval; } +/// Executes lines in `src` as Ex commands. +/// +/// @see do_source() +int do_source_str(const char *cmd, const char *traceback_name) +{ + GetStrLineCookie cookie = { + .buf = (char_u *)cmd, + .offset = 0, + }; + return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); +} + /// Reads the file `fname` and executes its lines as Ex commands. /// /// This function may be called recursively! @@ -2978,6 +3024,7 @@ void scriptnames_slash_adjust(void) # endif /// Get a pointer to a script name. Used for ":verbose set". +/// Message appended to "Last set from " char_u *get_scriptname(LastSet last_set, bool *should_free) { *should_free = false; @@ -2993,6 +3040,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) return (char_u *)_("environment variable"); case SID_ERROR: return (char_u *)_("error handler"); + case SID_WINLAYOUT: + return (char_u *)_("changed window size"); case SID_LUA: return (char_u *)_("Lua"); case SID_API_CLIENT: @@ -3766,7 +3815,7 @@ void ex_drop(exarg_T *eap) // and mostly only one file is dropped. // This also ignores wildcards, since it is very unlikely the user is // editing a file name with a wildcard character. - do_arglist(eap->arg, AL_SET, 0); + do_arglist(eap->arg, AL_SET, 0, false); // Expanding wildcards may result in an empty argument list. E.g. when // editing "foo.pyc" and ".pyc" is in 'wildignore'. Assume that we diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index ca84d375ce..f928c61ea4 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -188,8 +188,8 @@ struct exarg { // used for completion on the command line struct expand { - int xp_context; // type of expansion char_u *xp_pattern; // start of item to expand + int xp_context; // type of expansion size_t xp_pattern_len; // bytes in xp_pattern before cursor char_u *xp_arg; // completion function sctx_T xp_script_ctx; // SCTX for completion function @@ -199,9 +199,9 @@ struct expand { // characters need to be escaped #endif int xp_numfiles; // number of files found by file name completion + int xp_col; // cursor position in line char_u **xp_files; // list of files char_u *xp_line; // text being completed - int xp_col; // cursor position in line }; // values for xp_backslash diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 52da7fe4f6..bc3d29a03f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2179,7 +2179,7 @@ int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only) // Set 'eventignore' to "all". Restore the // existing option value later. cmdmod.save_ei = vim_strsave(p_ei); - set_string_option_direct((char_u *)"ei", -1, + set_string_option_direct("ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); } continue; @@ -2291,9 +2291,8 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) } if (cmdmod.save_ei != NULL) { - /* Restore 'eventignore' to the value before ":noautocmd". */ - set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, - OPT_FREE, SID_NONE); + // Restore 'eventignore' to the value before ":noautocmd". + set_string_option_direct("ei", -1, cmdmod.save_ei, OPT_FREE, SID_NONE); free_string_option(cmdmod.save_ei); } @@ -3513,13 +3512,20 @@ const char * set_one_cmd_context( xp->xp_context = EXPAND_BUFFERS; xp->xp_pattern = (char_u *)arg; break; + case CMD_diffget: + case CMD_diffput: + // If current buffer is in diff mode, complete buffer names + // which are in diff mode, and different than current buffer. + xp->xp_context = EXPAND_DIFF_BUFFERS; + xp->xp_pattern = (char_u *)arg; + break; case CMD_USER: case CMD_USER_BUF: if (context != EXPAND_NOTHING) { // EX_XFILE: file names are handled above. if (!(ea.argt & EX_XFILE)) { if (context == EXPAND_MENUS) { - return (const char *)set_context_in_menu_cmd(xp, (char_u *)cmd, + return (const char *)set_context_in_menu_cmd(xp, cmd, (char_u *)arg, forceit); } else if (context == EXPAND_COMMANDS) { return arg; @@ -3599,7 +3605,7 @@ const char * set_one_cmd_context( case CMD_tmenu: case CMD_tunmenu: case CMD_popup: case CMD_emenu: return (const char *)set_context_in_menu_cmd( - xp, (char_u *)cmd, (char_u *)arg, forceit); + xp, cmd, (char_u *)arg, forceit); case CMD_colorscheme: xp->xp_context = EXPAND_COLORS; @@ -5175,6 +5181,7 @@ static const char *command_complete[] = [EXPAND_CSCOPE] = "cscope", [EXPAND_USER_DEFINED] = "custom", [EXPAND_USER_LIST] = "customlist", + [EXPAND_DIFF_BUFFERS] = "diff_buffer", [EXPAND_DIRECTORIES] = "dir", [EXPAND_ENV_VARS] = "environment", [EXPAND_EVENTS] = "event", @@ -6275,14 +6282,14 @@ int parse_compl_arg(const char_u *value, int vallen, int *complp, return OK; } -int cmdcomplete_str_to_type(char_u *complete_str) +int cmdcomplete_str_to_type(const char *complete_str) { for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) { char *cmd_compl = get_command_complete(i); if (cmd_compl == NULL) { continue; } - if (STRCMP(complete_str, command_complete[i]) == 0) { + if (strcmp(complete_str, command_complete[i]) == 0) { return i; } } @@ -7272,9 +7279,7 @@ static void ex_find(exarg_T *eap) } } -/* - * ":edit", ":badd", ":visual". - */ +/// ":edit", ":badd", ":balt", ":visual". static void ex_edit(exarg_T *eap) { do_exedit(eap, NULL); @@ -7348,7 +7353,9 @@ do_exedit( else if (eap->cmdidx == CMD_enew) readonlymode = FALSE; /* 'readonly' doesn't make sense in an empty buffer */ - setpcmark(); + if (eap->cmdidx != CMD_balt && eap->cmdidx != CMD_badd) { + setpcmark(); + } if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), NULL, eap, eap->do_ecmd_lnum, (buf_hide(curbuf) ? ECMD_HIDE : 0) @@ -7356,6 +7363,7 @@ do_exedit( // After a split we can use an existing buffer. + (old_curwin != NULL ? ECMD_OLDBUF : 0) + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0) + + (eap->cmdidx == CMD_balt ? ECMD_ALTBUF : 0) , old_curwin == NULL ? curwin : NULL) == FAIL) { // Editing the file failed. If the window was split, close it. if (old_curwin != NULL) { @@ -7756,6 +7764,11 @@ static void do_exmap(exarg_T *eap, int isabbrev) static void ex_winsize(exarg_T *eap) { char_u *arg = eap->arg; + + if (!ascii_isdigit(*arg)) { + EMSG2(_(e_invarg2), arg); + return; + } int w = getdigits_int(&arg, false, 10); arg = skipwhite(arg); char_u *p = arg; @@ -8721,7 +8734,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) * Evaluate cmdline variables. * * change '%' to curbuf->b_ffname - * '#' to curwin->w_altfile + * '#' to curwin->w_alt_fnum * '<cword>' to word under the cursor * '<cWORD>' to WORD under the cursor * '<cexpr>' to C-expression under the cursor diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index cf6bb6d492..5979f4d3a0 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -886,7 +886,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) need_wait_return = false; } - set_string_option_direct((char_u *)"icm", -1, s->save_p_icm, OPT_FREE, + set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE, SID_NONE); State = s->save_State; setmouse(); @@ -935,7 +935,7 @@ static int command_line_execute(VimState *state, int key) if (s->c == K_EVENT || s->c == K_COMMAND) { if (s->c == K_EVENT) { - multiqueue_process_events(main_loop.events); + state_handle_k_event(); } else { do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); } @@ -5084,9 +5084,13 @@ ExpandFromContext ( } if (xp->xp_context == EXPAND_BUFFERS) return ExpandBufnames(pat, num_file, file, options); + if (xp->xp_context == EXPAND_DIFF_BUFFERS) { + return ExpandBufnames(pat, num_file, file, options | BUF_DIFF_FILTER); + } if (xp->xp_context == EXPAND_TAGS - || xp->xp_context == EXPAND_TAGS_LISTFILES) + || xp->xp_context == EXPAND_TAGS_LISTFILES) { return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + } if (xp->xp_context == EXPAND_COLORS) { char *directories[] = { "colors", NULL }; return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, directories); @@ -6441,7 +6445,7 @@ static int open_cmdwin(void) cmdwin_level = ccline.level; // Create empty command-line buffer. - buf_open_scratch(0, "[Command Line]"); + buf_open_scratch(0, _("[Command Line]")); // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer. set_option_value("bh", 0L, "wipe", OPT_LOCAL); curwin->w_p_rl = cmdmsg_rl; @@ -6770,6 +6774,6 @@ static void set_search_match(pos_T *t) t->col = search_match_endcol; if (t->lnum > curbuf->b_ml.ml_line_count) { t->lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index dc4395e081..3727aa5e62 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -32,6 +32,7 @@ #define WILD_IGNORE_COMPLETESLASH 0x400 #define WILD_NOERROR 0x800 // sets EW_NOERROR #define WILD_BUFLASTUSED 0x1000 +#define BUF_DIFF_FILTER 0x2000 /// Present history tables typedef enum { diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 14dac9a126..63789b3981 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -384,6 +384,18 @@ static int put_view( xfree(fname_esc); } + if (wp->w_alt_fnum) { + buf_T *const alt = buflist_findnr(wp->w_alt_fnum); + + // Set the alternate file. + if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL + && *alt->b_fname != NUL + && (fputs("balt ", fd) < 0 + || ses_fname(fd, alt, flagp, true) == FAIL)) { + return FAIL; + } + } + // // Local mappings and abbreviations. // @@ -438,9 +450,9 @@ static int put_view( "let s:l = %" PRId64 " - ((%" PRId64 " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" "if s:l < 1 | let s:l = 1 | endif\n" - "exe s:l\n" + "keepjumps exe s:l\n" "normal! zt\n" - "%" PRId64 "\n", + "keepjumps %" PRId64 "\n", (int64_t)wp->w_cursor.lnum, (int64_t)(wp->w_cursor.lnum - wp->w_topline), (int64_t)(wp->w_height_inner / 2), diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index a542bb19dd..714bbb5780 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1644,8 +1644,7 @@ failed: save_file_ff(curbuf); // If editing a new file: set 'fenc' for the current buffer. // Also for ":read ++edit file". - set_string_option_direct((char_u *)"fenc", -1, fenc, - OPT_FREE | OPT_LOCAL, 0); + set_string_option_direct("fenc", -1, fenc, OPT_FREE | OPT_LOCAL, 0); } if (fenc_alloced) xfree(fenc); @@ -2002,7 +2001,7 @@ void set_forced_fenc(exarg_T *eap) { if (eap->force_enc != 0) { char_u *fenc = enc_canonize(eap->cmd + eap->force_enc); - set_string_option_direct((char_u *)"fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); + set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); xfree(fenc); } } @@ -5077,7 +5076,8 @@ void buf_reload(buf_T *buf, int orig_mode) // Mark all undo states as changed. u_unchanged(curbuf); } - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, true); + curbuf->b_mod_set = true; } } xfree(ea.cmd); diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 0593c16999..5032646d7e 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2999,7 +2999,6 @@ static void foldlevelDiff(fline_T *flp) static void foldlevelExpr(fline_T *flp) { win_T *win; - int n; int c; linenr_T lnum = flp->lnum + flp->off; @@ -3017,7 +3016,7 @@ static void foldlevelExpr(fline_T *flp) /* KeyTyped may be reset to 0 when calling a function which invokes * do_cmdline(). To make 'foldopen' work correctly restore KeyTyped. */ const bool save_keytyped = KeyTyped; - n = (int)eval_foldexpr(flp->wp->w_p_fde, &c); + const int n = eval_foldexpr(flp->wp->w_p_fde, &c); KeyTyped = save_keytyped; switch (c) { @@ -3202,8 +3201,10 @@ int put_folds(FILE *fd, win_T *wp) { if (foldmethodIsManual(wp)) { if (put_line(fd, "silent! normal! zE") == FAIL - || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL) + || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL + || put_line(fd, "let &fdl = &fdl") == FAIL) { return FAIL; + } } /* If some folds are manually opened/closed, need to restore that. */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 22f06941aa..7b8e809de7 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -180,6 +180,11 @@ EXTERN int compl_cont_status INIT(= 0); # define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local // expansion, (eg use complete=.) +EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode +EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode +EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode +EXTERN hlf_T edit_submode_highl; // highl. method for extra info + // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left EXTERN int msg_col; @@ -328,9 +333,10 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_ENV -4 // for sourcing environment variable #define SID_ERROR -5 // option was reset because of an error #define SID_NONE -6 // don't set scriptID -#define SID_LUA -7 // for Lua scripts/chunks -#define SID_API_CLIENT -8 // for API clients -#define SID_STR -9 // for sourcing a string +#define SID_WINLAYOUT -7 // changing window size +#define SID_LUA -8 // for Lua scripts/chunks +#define SID_API_CLIENT -9 // for API clients +#define SID_STR -10 // for sourcing a string // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); @@ -639,10 +645,6 @@ EXTERN int arrow_used; // Normally false, set to true after // to call u_sync() EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when // restarting edit after CTRL-O -EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode -EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode -EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode -EXTERN hlf_T edit_submode_highl; // highl. method for extra info EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 2dad8fb781..31615e744a 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1865,7 +1865,7 @@ static void cs_release_csp(size_t i, bool freefnpp) alive = false; // cscope process no longer exists break; } - os_delay(50L, false); // sleep 50ms + os_delay(50L, false); // sleep 50 ms } } if (alive) diff --git a/src/nvim/log.h b/src/nvim/log.h index f2e74df031..17d754c033 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -17,10 +17,11 @@ #endif -#define DEBUG_LOG_LEVEL 0 -#define INFO_LOG_LEVEL 1 -#define WARN_LOG_LEVEL 2 -#define ERROR_LOG_LEVEL 3 +#define TRACE_LOG_LEVEL 0 +#define DEBUG_LOG_LEVEL 1 +#define INFO_LOG_LEVEL 2 +#define WARN_LOG_LEVEL 3 +#define ERROR_LOG_LEVEL 4 #define DLOG(...) #define DLOGN(...) diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index fd53bffe94..eb54ff28ee 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -39,6 +39,16 @@ assert(vim) vim.inspect = package.loaded['vim.inspect'] assert(vim.inspect) +vim.log = { + levels = { + TRACE = 0; + DEBUG = 1; + INFO = 2; + WARN = 3; + ERROR = 4; + } +} + -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} @@ -485,6 +495,23 @@ function vim.defer_fn(fn, timeout) return timer end + +--- Notification provider +--- without a runtime, writes to :Messages +-- see :help nvim_notify +--@param msg Content of the notification to show to the user +--@param log_level Optional log level +--@param opts Dictionary with optional options (timeout, etc) +function vim.notify(msg, log_level, _opts) + + if log_level == vim.log.levels.ERROR then + vim.api.nvim_err_writeln(msg) + else + vim.api.nvim_echo({{msg}}, true, {}) + end +end + + local on_keystroke_callbacks = {} --- Register a lua {fn} with an {id} to be run after every keystroke. diff --git a/src/nvim/main.c b/src/nvim/main.c index 9f71df3a46..7064f2a068 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1404,9 +1404,9 @@ static void load_plugins(void) static void handle_quickfix(mparm_T *paramp) { if (paramp->edit_type == EDIT_QF) { - if (paramp->use_ef != NULL) - set_string_option_direct((char_u *)"ef", -1, - paramp->use_ef, OPT_FREE, SID_CARG); + if (paramp->use_ef != NULL) { + set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); + } vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 31dc6b3649..293a4d01db 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -4142,7 +4142,7 @@ void goto_byte(long cnt) if (lnum < 1) { // past the end curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; curwin->w_curswant = MAXCOL; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } else { curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (colnr_T)boff; diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index 9a6f29a908..dc4755f83d 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -45,16 +45,16 @@ typedef struct memline { memfile_T *ml_mfp; // pointer to associated memfile + infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs) + int ml_stack_top; // current top of ml_stack + int ml_stack_size; // total number of entries in ml_stack + #define ML_EMPTY 1 // empty buffer #define ML_LINE_DIRTY 2 // cached line was changed and allocated #define ML_LOCKED_DIRTY 4 // ml_locked was changed #define ML_LOCKED_POS 8 // ml_locked needs positive block number int ml_flags; - infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs) - int ml_stack_top; // current top of ml_stack - int ml_stack_size; // total number of entries in ml_stack - linenr_T ml_line_lnum; // line number of cached line, 0 if not valid char_u *ml_line_ptr; // pointer to cached line size_t ml_line_offset; // cached byte offset of ml_line_lnum diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 7094d3be90..ac3b7768e6 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -81,7 +81,7 @@ ex_menu(exarg_T *eap) // kFalse for "menu disable vimmenu_T menuarg; - modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu); + modes = get_menu_cmd_modes((char *)eap->cmd, eap->forceit, &noremap, &unmenu); arg = eap->arg; for (;; ) { @@ -912,7 +912,9 @@ static int expand_emenu; /* TRUE for ":emenu" command */ /* * Work out what to complete when doing command line completion of menu names. */ -char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forceit) +char_u *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char_u *arg, + bool forceit) + FUNC_ATTR_NONNULL_ALL { char_u *after_dot; char_u *p; @@ -1178,7 +1180,7 @@ static bool menu_namecmp(const char_u *const name, const char_u *const mname) /// to whether the command is an "unmenu" command. int get_menu_cmd_modes( - const char_u * cmd, + const char *cmd, bool forceit, int *noremap, int *unmenu diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index fcffe64595..34c43da0f7 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -412,7 +412,7 @@ int plines_win_nofold(win_T *wp, linenr_T lnum) s = ml_get_buf(wp->w_buffer, lnum, FALSE); if (*s == NUL) /* empty line */ return 1; - col = win_linetabsize(wp, s, (colnr_T)MAXCOL); + col = win_linetabsize(wp, s, MAXCOL); // If list mode is on, then the '$' at the end of the line may take up one // extra column. diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index ff471ea978..f28658aa29 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -72,9 +72,7 @@ int jump_to_mouse(int flags, int row = mouse_row; int col = mouse_col; int grid = mouse_grid; - int mouse_char; int fdc = 0; - ScreenGrid *gp = &default_grid; mouse_past_bottom = false; mouse_past_eol = false; @@ -303,25 +301,6 @@ retnomove: } } - // Remember the character under the mouse, might be one of foldclose or - // foldopen fillchars in the fold column. - if (ui_has(kUIMultigrid)) { - gp = &curwin->w_grid; - } - if (row >= 0 && row < Rows && col >= 0 && col <= Columns - && gp->chars != NULL) { - mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row] - + (unsigned)col]); - } else { - mouse_char = ' '; - } - - // Check for position outside of the fold column. - if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc : - col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { - mouse_char = ' '; - } - // compute the position in the buffer line from the posn on the screen if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) { mouse_past_bottom = true; @@ -362,11 +341,7 @@ retnomove: count |= CURSOR_MOVED; // Cursor has moved } - if (mouse_char == curwin->w_p_fcs_chars.foldclosed) { - count |= MOUSE_FOLD_OPEN; - } else if (mouse_char != ' ') { - count |= MOUSE_FOLD_CLOSE; - } + count |= mouse_check_fold(); return count; } @@ -738,3 +713,47 @@ static int mouse_adjust_click(win_T *wp, int row, int col) return col + nudge; } + +// Check clicked cell is foldcolumn +int mouse_check_fold(void) +{ + int grid = mouse_grid; + int row = mouse_row; + int col = mouse_col; + int mouse_char = ' '; + + win_T *wp; + + wp = mouse_find_win(&grid, &row, &col); + + if (wp && mouse_row >= 0 && mouse_row < Rows + && mouse_col >= 0 && mouse_col <= Columns) { + int multigrid = ui_has(kUIMultigrid); + ScreenGrid *gp = multigrid ? &wp->w_grid : &default_grid; + int fdc = win_fdccol_count(wp); + + row = multigrid && mouse_grid == 0 ? row : mouse_row; + col = multigrid && mouse_grid == 0 ? col : mouse_col; + + // Remember the character under the mouse, might be one of foldclose or + // foldopen fillchars in the fold column. + if (gp->chars != NULL) { + mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row] + + (unsigned)col]); + } + + // Check for position outside of the fold column. + if (wp->w_p_rl ? col < wp->w_width_inner - fdc : + col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { + mouse_char = ' '; + } + } + + if (mouse_char == wp->w_p_fcs_chars.foldclosed) { + return MOUSE_FOLD_OPEN; + } else if (mouse_char != ' ') { + return MOUSE_FOLD_CLOSE; + } + + return 0; +} diff --git a/src/nvim/move.c b/src/nvim/move.c index a6afdc27d9..1210a3365a 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -888,11 +888,11 @@ void curs_columns( } else { n = plines; } - if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width) { + if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) { extra += 2; } - if (extra == 3 || plines < so * 2) { + if (extra == 3 || plines <= so * 2) { // not enough room for 'scrolloff', put cursor in the middle n = wp->w_virtcol / width; if (n > wp->w_height_inner / 2) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8f22243348..0b4e2e1f23 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2404,8 +2404,8 @@ do_mouse ( start_visual.lnum = 0; - /* Check for clicking in the tab page line. */ - if (mouse_row == 0 && firstwin->w_winrow > 0) { + // Check for clicking in the tab page line. + if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { if (is_drag) { if (in_tab_line) { move_tab_to_mouse(); @@ -5225,16 +5225,13 @@ static void nv_left(cmdarg_T *cap) * 'h' wraps to previous line if 'whichwrap' has 'h'. * CURS_LEFT wraps to previous line if 'whichwrap' has '<'. */ - if ( (((cap->cmdchar == K_BS - || cap->cmdchar == Ctrl_H) - && vim_strchr(p_ww, 'b') != NULL) - || (cap->cmdchar == 'h' - && vim_strchr(p_ww, 'h') != NULL) - || (cap->cmdchar == K_LEFT - && vim_strchr(p_ww, '<') != NULL)) - && curwin->w_cursor.lnum > 1) { - --(curwin->w_cursor.lnum); - coladvance((colnr_T)MAXCOL); + if ((((cap->cmdchar == K_BS || cap->cmdchar == Ctrl_H) + && vim_strchr(p_ww, 'b') != NULL) + || (cap->cmdchar == 'h' && vim_strchr(p_ww, 'h') != NULL) + || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL)) + && curwin->w_cursor.lnum > 1) { + curwin->w_cursor.lnum--; + coladvance(MAXCOL); curwin->w_set_curswant = true; // When the NL before the first char has to be deleted we @@ -5792,16 +5789,20 @@ static void nv_percent(cmdarg_T *cap) } else { cap->oap->motion_type = kMTLineWise; setpcmark(); - /* Round up, so CTRL-G will give same value. Watch out for a - * large line count, the line number must not go negative! */ - if (curbuf->b_ml.ml_line_count > 1000000) + // Round up, so 'normal 100%' always jumps at the line line. + // Beyond 21474836 lines, (ml_line_count * 100 + 99) would + // overflow on 32-bits, so use a formula with less accuracy + // to avoid overflows. + if (curbuf->b_ml.ml_line_count >= 21474836) { curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count + 99L) / 100L * cap->count0; - else + } else { curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count * cap->count0 + 99L) / 100L; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + } + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } beginline(BL_SOL | BL_FIX); } } else { // "%" : go to matching paren @@ -6467,7 +6468,7 @@ static void nv_visual(cmdarg_T *cap) } if (resel_VIsual_vcol == MAXCOL) { curwin->w_curswant = MAXCOL; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } else if (VIsual_mode == Ctrl_V) { validate_virtcol(); assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); @@ -7570,6 +7571,12 @@ static void nv_esc(cmdarg_T *cap) got_int = false; /* don't stop executing autocommands et al. */ return; } + } else if (cmdwin_type != 0 && ex_normal_busy) { + // When :normal runs out of characters while in the command line window + // vgetorpeek() will return ESC. Exit the cmdline window to break the + // loop. + cmdwin_result = K_IGNORE; + return; } if (VIsual_active) { @@ -7600,7 +7607,7 @@ void set_cursor_for_append_to_line(void) // Pretend Insert mode here to allow the cursor on the // character past the end of the line State = INSERT; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); State = save_State; } else { curwin->w_cursor.col += (colnr_T)STRLEN(get_cursor_pos_ptr()); @@ -7988,7 +7995,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) * line. */ if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } } auto_format(false, true); @@ -8096,7 +8103,7 @@ static void nv_event(cmdarg_T *cap) // lists or dicts being used. may_garbage_collect = false; bool may_restart = (restart_edit != 0); - multiqueue_process_events(main_loop.events); + state_handle_k_event(); finish_op = false; if (may_restart) { // Tricky: if restart_edit was set before the handler we are in ctrl-o mode, diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 87d092281a..3038fad894 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3422,15 +3422,11 @@ error: if (dir == FORWARD) curbuf->b_op_start.lnum++; } - // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. - if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count) { - ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT)) - ? kExtmarkUndo : kExtmarkNOOP; - mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), - (linenr_T)MAXLNUM, nr_lines, 0L, kind); - } + + ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT)) + ? kExtmarkUndo : kExtmarkNOOP; + mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), + (linenr_T)MAXLNUM, nr_lines, 0L, kind); // note changed text for displaying and folding if (y_type == kMTCharWise) { @@ -4309,11 +4305,12 @@ format_lines( * tabs and spaces, according to current options */ (void)set_indent(get_indent(), SIN_CHANGED); - /* put cursor on last non-space */ - State = NORMAL; /* don't go past end-of-line */ - coladvance((colnr_T)MAXCOL); - while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) + // put cursor on last non-space + State = NORMAL; // don't go past end-of-line + coladvance(MAXCOL); + while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { dec_cursor(); + } /* do the formatting, without 'showmode' */ State = INSERT; /* for open_line() */ diff --git a/src/nvim/option.c b/src/nvim/option.c index 74bf6f0590..ac25c86b5f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1942,6 +1942,7 @@ static void didset_options(void) (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); + (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); @@ -2119,9 +2120,9 @@ static int shada_idx = -1; // "set_sid". void set_string_option_direct( - char_u *name, + const char *name, int opt_idx, - char_u *val, + const char_u *val, int opt_flags, // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL int set_sid ) @@ -2132,7 +2133,7 @@ set_string_option_direct( int idx = opt_idx; if (idx == -1) { // Use name. - idx = findoption((const char *)name); + idx = findoption(name); if (idx < 0) { // Not found (should not happen). internal_error("set_string_option_direct()"); IEMSG2(_("For option %s"), name); @@ -3077,6 +3078,10 @@ ambw_end: if (!parse_winhl_opt(curwin)) { errmsg = e_invarg; } + } else if (varp == &p_tpf) { + if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { + errmsg = e_invarg; + } } else { // Options that are a list of flags. p = NULL; @@ -3786,7 +3791,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, if (p_terse && p == NULL) { STRCPY(IObuff, p_shm); STRCAT(IObuff, "s"); - set_string_option_direct((char_u *)"shm", -1, IObuff, OPT_FREE, 0); + set_string_option_direct("shm", -1, IObuff, OPT_FREE, 0); } else if (!p_terse && p != NULL) { // remove 's' from p_shm STRMOVE(p, p + 1); } @@ -4526,7 +4531,7 @@ bool is_tty_option(const char *name) #define TCO_BUFFER_SIZE 8 /// @param name TUI-related option /// @param[out,allocated] value option string value -bool get_tty_option(char *name, char **value) +bool get_tty_option(const char *name, char **value) { if (strequal(name, "t_Co")) { if (value) { @@ -4592,6 +4597,7 @@ bool set_tty_option(const char *name, char *value) /// /// @return Option index or -1 if option was not found. static int findoption(const char *const arg) + FUNC_ATTR_NONNULL_ALL { return findoption_len(arg, strlen(arg)); } @@ -4605,17 +4611,17 @@ static int findoption(const char *const arg) /// hidden String option: -2. /// unknown option: -3. int get_option_value( - char_u *name, + const char *name, long *numval, char_u **stringval, ///< NULL when only checking existence int opt_flags ) { - if (get_tty_option((char *)name, (char **)stringval)) { + if (get_tty_option(name, (char **)stringval)) { return 0; } - int opt_idx = findoption((const char *)name); + int opt_idx = findoption(name); if (opt_idx < 0) { // Unknown option. return -3; } @@ -7049,7 +7055,7 @@ void set_fileformat(int eol_style, int opt_flags) // p is NULL if "eol_style" is EOL_UNKNOWN. if (p != NULL) { - set_string_option_direct((char_u *)"ff", + set_string_option_direct("ff", -1, (char_u *)p, OPT_FREE | opt_flags, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 683afc670e..43b0107800 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -621,6 +621,19 @@ EXTERN int p_sta; // 'smarttab' EXTERN int p_sb; // 'splitbelow' EXTERN long p_tpm; // 'tabpagemax' EXTERN char_u *p_tal; // 'tabline' +EXTERN char_u *p_tpf; // 'termpastefilter' +EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter' +#ifdef IN_OPTION_C +static char *(p_tpf_values[]) = + { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; +#endif +# define TPF_BS 0x001 +# define TPF_HT 0x002 +# define TPF_FF 0x004 +# define TPF_ESC 0x008 +# define TPF_DEL 0x010 +# define TPF_C0 0x020 +# define TPF_C1 0x040 EXTERN char_u *p_sps; // 'spellsuggest' EXTERN int p_spr; // 'splitright' EXTERN int p_sol; // 'startofline' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index df2bfbce34..fe108ef1cc 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2821,6 +2821,14 @@ return { defaults={if_true={vi=false}} }, { + full_name='termpastefilter', abbreviation='tpf', + type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, + vim=true, + varname='p_tpf', + defaults={if_true={vi="", vim="BS,HT,ESC,DEL"}} + }, + { full_name='terse', short_desc=N_("hides notification of search wrap"), type='bool', scope={'global'}, diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 9d6518841a..eca245650a 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -159,16 +159,28 @@ bool os_char_avail(void) return inbuf_poll(0, NULL) == kInputAvail; } -// Check for CTRL-C typed by reading all available characters. +/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed. +/// +/// This invokes a full libuv loop iteration which can be quite costly. +/// Prefer `line_breakcheck()` if called in a busy inner loop. +/// +/// Caller must at least check `got_int` before calling this function again. +/// checking for other low-level input state like `input_available()` might +/// also be relevant (i e to throttle idle processing when user input is +/// available) void os_breakcheck(void) { + if (got_int) { + return; + } + int save_us = updating_screen; // We do not want screen_resize() to redraw here. + // TODO(bfredl): we are already special casing redraw events, is this + // hack still needed? updating_screen++; - if (!got_int) { - loop_poll_events(&main_loop, 0); - } + loop_poll_events(&main_loop, 0); updating_screen = save_us; } diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 348a139e79..d794969ab5 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -182,8 +182,6 @@ static void init_child(PtyProcess *ptyproc) char *prog = ptyproc->process.argv[0]; assert(proc->env); - tv_dict_add_str(proc->env, S_LEN("TERM"), - ptyproc->term_name ? ptyproc->term_name : "ansi"); environ = tv_dict_to_env(proc->env); execvp(prog, proc->argv); ELOG("execvp failed: %s: %s", strerror(errno), prog); diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h index f7c57b3839..8c822eafad 100644 --- a/src/nvim/os/pty_process_unix.h +++ b/src/nvim/os/pty_process_unix.h @@ -17,7 +17,6 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; rv.process = process_init(loop, kProcessTypePty, data); - rv.term_name = NULL; rv.width = 80; rv.height = 24; rv.tty_fd = -1; diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 8ad5ba7286..f8ec79a3d6 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -37,7 +37,6 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; rv.process = process_init(loop, kProcessTypePty, data); - rv.term_name = NULL; rv.width = 80; rv.height = 24; rv.object.winpty = NULL; diff --git a/src/nvim/path.c b/src/nvim/path.c index 2de7e00ddb..3e1713fbdd 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -342,7 +342,7 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, p1 += utfc_ptr2len((const char_u *)p1); p2 += utfc_ptr2len((const char_u *)p2); } - return c1 - c2; + return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2; #else if (p_fic) { return mb_strnicmp((const char_u *)fname1, (const char_u *)fname2, len); diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 8e86ea08c5..b7c4b6ef92 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -1,6 +1,9 @@ #ifndef NVIM_POS_H #define NVIM_POS_H +// for INT_MAX, LONG_MAX et al. +#include <limits.h> + typedef long linenr_T; // line number type /// Format used to print values which have linenr_T type #define PRIdLINENR "ld" @@ -12,8 +15,8 @@ typedef int colnr_T; /// Maximal (invalid) line number enum { MAXLNUM = 0x7fffffff }; -/// Maximal column number, 31 bits -enum { MAXCOL = 0x7fffffff }; +/// Maximal column number +enum { MAXCOL = INT_MAX }; // Minimum line number enum { MINLNUM = 1 }; // minimum column number diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index e074f73d04..dfd38a6eca 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3665,7 +3665,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) static void qf_set_title_var(qf_list_T *qfl) { if (qfl->qf_title != NULL) { - set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title); + set_internal_string_var("w:quickfix_title", qfl->qf_title); } } @@ -4951,7 +4951,7 @@ void ex_cfile(exarg_T *eap) } } if (*eap->arg != NUL) { - set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + set_string_option_direct("ef", -1, eap->arg, OPT_FREE, 0); } char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a78f905a70..20e3cc0a2e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -143,7 +143,7 @@ long tab_page_click_defs_size = 0; // for line_putchar. Contains the state that needs to be remembered from // putting one character to the next. typedef struct { - const char_u *p; + const char *p; int prev_c; // previous Arabic character int prev_c1; // first composing char for prev_c } LineState; @@ -1857,7 +1857,7 @@ static int compute_foldcolumn(win_T *wp, int col) /// Handles composing chars and arabic shaping state. static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl) { - const char_u *p = s->p; + const char_u *p = (char_u *)s->p; int cells = utf_ptr2cells(p); int c_len = utfc_ptr2len(p); int u8c, u8cc[MAX_MCO]; @@ -2082,7 +2082,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int line_attr_lowprio = 0; // low-priority attribute for the 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 + bool shl_flag; // flag to indicate whether search_hl // has been processed or not bool prevcol_hl_flag; // flag to indicate whether prevcol // equals startcol of search_hl or one @@ -2870,6 +2870,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vcol >= (long)wp->w_virtcol) || (number_only && draw_state > WL_NR)) && filler_todo <= 0) { + draw_virt_text(buf, &col, grid->Columns); grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); // Pretend we have finished updating the window. Except when @@ -2950,16 +2951,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, */ v = (long)(ptr - line); cur = wp->w_match_head; - shl_flag = FALSE; - while (cur != NULL || shl_flag == FALSE) { - if (shl_flag == FALSE - && ((cur != NULL - && cur->priority > SEARCH_HL_PRIORITY) - || cur == NULL)) { + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = &search_hl; - shl_flag = TRUE; - } else + shl_flag = true; + } else { shl = &cur->hl; + } if (cur != NULL) { cur->pos.cur = 0; } @@ -2984,7 +2984,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, has_match_conc = v == (long)shl->startcol ? 2 : 1; match_conc = cur->conceal_char; } else { - has_match_conc = match_conc = 0; + has_match_conc = 0; } } else if (v == (long)shl->endcol) { shl->attr_cur = 0; @@ -3026,16 +3026,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, search_attr_from_match = false; search_attr = search_hl.attr_cur; cur = wp->w_match_head; - shl_flag = FALSE; - while (cur != NULL || shl_flag == FALSE) { - if (shl_flag == FALSE - && ((cur != NULL - && cur->priority > SEARCH_HL_PRIORITY) - || cur == NULL)) { + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = &search_hl; - shl_flag = TRUE; - } else + shl_flag = true; + } else { shl = &cur->hl; + } if (shl->attr_cur != 0) { search_attr = shl->attr_cur; search_attr_from_match = shl != &search_hl; @@ -3048,6 +3047,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && (wp->w_p_list && lcs_eol_one == -1)) { search_attr = 0; } + + // Do not allow a conceal over EOL otherwise EOL will be missed + // and bad things happen. + if (*ptr == NUL) { + has_match_conc = 0; + } } if (diff_hlf != (hlf_T)0) { @@ -3393,7 +3398,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } if (has_decor && v > 0) { - int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, off, &decor_state); if (extmark_attr != 0) { if (!attr_pri) { @@ -3672,12 +3677,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { char_attr = conceal_attr; if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1) - && (syn_get_sub_char() != NUL || match_conc + && (syn_get_sub_char() != NUL + || (has_match_conc && match_conc) || wp->w_p_cole == 1) && wp->w_p_cole != 3) { // First time at this concealed item: display one // character. - if (match_conc) { + if (has_match_conc && match_conc) { c = match_conc; } else if (syn_get_sub_char() != NUL) { c = syn_get_sub_char(); @@ -3837,16 +3843,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // 'search_hl' and the match list. char_attr = search_hl.attr; cur = wp->w_match_head; - shl_flag = FALSE; - while (cur != NULL || shl_flag == FALSE) { - if (shl_flag == FALSE - && ((cur != NULL - && cur->priority > SEARCH_HL_PRIORITY) - || cur == NULL)) { + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = &search_hl; - shl_flag = TRUE; - } else + shl_flag = true; + } else { shl = &cur->hl; + } if ((ptr - line) - 1 == (long)shl->startcol && (shl == &search_hl || !shl->is_addpos)) { char_attr = shl->attr; @@ -3915,7 +3920,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int i; size_t virt_pos = 0; - LineState s = LINE_STATE((char_u *)""); + LineState s = LINE_STATE(""); int virt_attr = 0; // Make sure alignment is the same regardless @@ -3959,7 +3964,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, if (do_virttext && !delay_virttext) { if (*s.p == NUL) { if (virt_pos < virt_text.size) { - s.p = (char_u *)kv_A(virt_text, virt_pos).text; + s.p = kv_A(virt_text, virt_pos).text; int hl_id = kv_A(virt_text, virt_pos).hl_id; virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; virt_pos++; @@ -4025,6 +4030,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, col += n; } } + + draw_virt_text(buf, &col, grid->Columns); grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); row++; @@ -4243,7 +4250,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && (grid->Columns == Columns // Window spans the width of the screen, || ui_has(kUIMultigrid)) // or has dedicated grid. && !wp->w_p_rl; // Not right-to-left. - grid_put_linebuf(grid, row, 0, col - boguscols, grid->Columns, wp->w_p_rl, + + int draw_col = col - boguscols; + draw_virt_text(buf, &draw_col, grid->Columns); + grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, wrap); if (wrap) { ScreenGrid *current_grid = grid; @@ -4319,6 +4329,43 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, return row; } +void draw_virt_text(buf_T *buf, int *end_col, int max_col) +{ + DecorState *state = &decor_state; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange *item = &kv_A(state->active, i); + if (item->start_row == state->row && item->virt_text + && item->virt_text_pos == kVTOverlay + && item->virt_col >= 0) { + VirtText vt = *item->virt_text; + LineState s = LINE_STATE(""); + int virt_attr = 0; + int col = item->virt_col; + size_t virt_pos = 0; + item->virt_col = -2; // deactivate + + while (col < max_col) { + if (!*s.p) { + if (virt_pos == kv_size(vt)) { + break; + } + s.p = kv_A(vt, virt_pos).text; + int hl_id = kv_A(vt, virt_pos).hl_id; + virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; + virt_pos++; + continue; + } + int cells = line_putchar(&s, &linebuf_char[col], 2, false); + linebuf_attr[col++] = virt_attr; + if (cells > 1) { + linebuf_attr[col++] = virt_attr; + } + } + *end_col = MAX(*end_col, col); + } + } +} + /// Determine if dedicated window grid should be used or the default_grid /// /// If UI did not request multigrid support, draw all windows on the @@ -5092,9 +5139,9 @@ static void redraw_custom_statusline(win_T *wp) // When there is an error disable the statusline, otherwise the // display is messed up with errors and a redraw triggers the problem // again and again. - set_string_option_direct((char_u *)"statusline", -1, - (char_u *)"", OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); + set_string_option_direct("statusline", -1, (char_u *)"", + OPT_FREE | (*wp->w_p_stl != NUL + ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); } did_emsg |= saved_did_emsg; entered = false; @@ -5175,7 +5222,7 @@ get_keymap_str ( static void win_redr_custom ( win_T *wp, - int draw_ruler /* TRUE or FALSE */ + bool draw_ruler ) { static int entered = FALSE; @@ -6852,7 +6899,7 @@ void draw_tabline(void) did_emsg = false; win_redr_custom(NULL, false); if (did_emsg) { - set_string_option_direct((char_u *)"tabline", -1, + set_string_option_direct("tabline", -1, (char_u *)"", OPT_FREE, SID_ERROR); } did_emsg |= saved_did_emsg; @@ -7105,20 +7152,23 @@ static void win_redr_ruler(win_T *wp, int always) if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) return; - /* Don't draw the ruler while doing insert-completion, it might overwrite - * the (long) mode message. */ - if (wp == lastwin && lastwin->w_status_height == 0) - if (edit_submode != NULL) + // Don't draw the ruler while doing insert-completion, it might overwrite + // the (long) mode message. + if (wp == lastwin && lastwin->w_status_height == 0) { + if (edit_submode != NULL) { return; + } + } if (*p_ruf) { int save_called_emsg = called_emsg; - called_emsg = FALSE; - win_redr_custom(wp, TRUE); - if (called_emsg) - set_string_option_direct((char_u *)"rulerformat", -1, - (char_u *)"", OPT_FREE, SID_ERROR); + called_emsg = false; + win_redr_custom(wp, true); + if (called_emsg) { + set_string_option_direct("rulerformat", -1, (char_u *)"", + OPT_FREE, SID_ERROR); + } called_emsg |= save_called_emsg; return; } diff --git a/src/nvim/search.c b/src/nvim/search.c index 2802da6f7f..84b71d56a0 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2685,8 +2685,9 @@ fwd_word( while (--count >= 0) { /* When inside a range of folded lines, move to the last char of the * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) - coladvance((colnr_T)MAXCOL); + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } sclass = cls(); /* @@ -2803,8 +2804,9 @@ int end_word(long count, int bigword, int stop, int empty) while (--count >= 0) { /* When inside a range of folded lines, move to the last char of the * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) - coladvance((colnr_T)MAXCOL); + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } sclass = cls(); if (inc_cursor() == -1) return FAIL; diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index c898dba890..19c0263cf1 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -24,11 +24,11 @@ typedef struct signlist signlist_T; struct signlist { int id; // unique identifier for each placed sign - linenr_T lnum; // line number which has this sign int typenr; // typenr of sign + int priority; // priority for highlighting bool has_text_or_icon; // has text or icon + linenr_T lnum; // line number which has this sign signgroup_T *group; // sign group - int priority; // priority for highlighting signlist_T *next; // next signlist entry signlist_T *prev; // previous entry -- for easy reordering }; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 6425c9fed5..55f9594de2 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -112,6 +112,7 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/undo.h" +#include "nvim/ui.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -2889,8 +2890,14 @@ void spell_suggest(int count) msg_col = 0; // Ask for choice. selected = prompt_for_number(&mouse_used); - if (mouse_used) + + if (ui_has(kUIMessages)) { + ui_call_msg_clear(); + } + + if (mouse_used) { selected -= lines_left; + } lines_left = Rows; // avoid more prompt // don't delay for 'smd' in normal_cmd() msg_scroll = msg_scroll_save; @@ -6624,7 +6631,7 @@ void ex_spelldump(exarg_T *eap) if (no_spell_checking(curwin)) { return; } - get_option_value((char_u *)"spl", &dummy, &spl, OPT_LOCAL); + get_option_value("spl", &dummy, &spl, OPT_LOCAL); // Create a new empty buffer in a new window. do_cmdline_cmd("new"); diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 90af010164..3c125959a9 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -5387,7 +5387,8 @@ spell_add_word ( len, word, NameBuff); } } - if (fseek(fd, fpos_next, SEEK_SET) <= 0) { + if (fseek(fd, fpos_next, SEEK_SET) != 0) { + PERROR(_("Seek error in spellfile")); break; } } diff --git a/src/nvim/state.c b/src/nvim/state.c index b195c1d96b..a3c74789d1 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -75,6 +75,34 @@ getkey: } } +/// process events on main_loop, but interrupt if input is available +/// +/// This should be used to handle K_EVENT in states accepting input +/// otherwise bursts of events can block break checking indefinitely. +void state_handle_k_event(void) +{ + while (true) { + Event event = multiqueue_get(main_loop.events); + if (event.handler) { + event.handler(event.argv); + } + + if (multiqueue_empty(main_loop.events)) { + // don't breakcheck before return, caller should return to main-loop + // and handle input already. + return; + } + + // TODO(bfredl): as an further micro-optimization, we could check whether + // event.handler already checked input. + os_breakcheck(); + if (input_available() || got_int) { + return; + } + } +} + + /// Return true if in the current mode we need to use virtual. bool virtual_active(void) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f99eca7953..547d953be9 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3419,7 +3419,7 @@ static void syn_cmd_on(exarg_T *eap, int syncing) */ static void syn_cmd_enable(exarg_T *eap, int syncing) { - set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable"); + set_internal_string_var("syntax_cmd", (char_u *)"enable"); syn_cmd_onoff(eap, "syntax"); do_unlet(S_LEN("g:syntax_cmd"), true); } @@ -3432,7 +3432,7 @@ static void syn_cmd_reset(exarg_T *eap, int syncing) { eap->nextcmd = check_nextcmd(eap->arg); if (!eap->skip) { - set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset"); + set_internal_string_var("syntax_cmd", (char_u *)"reset"); do_cmdline_cmd("runtime! syntax/syncolor.vim"); do_unlet(S_LEN("g:syntax_cmd"), true); } @@ -5614,14 +5614,14 @@ void ex_ownsyntax(exarg_T *eap) // Move value of b:current_syntax to w:current_syntax. new_value = get_var_value("b:current_syntax"); if (new_value != NULL) { - set_internal_string_var((char_u *)"w:current_syntax", new_value); + set_internal_string_var("w:current_syntax", new_value); } // Restore value of b:current_syntax. if (old_value == NULL) { do_unlet(S_LEN("b:current_syntax"), true); } else { - set_internal_string_var((char_u *)"b:current_syntax", old_value); + set_internal_string_var("b:current_syntax", old_value); xfree(old_value); } } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 84ca240734..4ea298fba9 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -3152,7 +3152,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) bool is_static; ret = find_tags(pat, &num_matches, &matches, - TAG_REGEXP | TAG_NOIC, (int)MAXCOL, buf_fname); + TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; ++i) { int parse_result = parse_match(matches[i], &tp); diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 39e2ca6171..f6995cddb6 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -457,7 +457,7 @@ static int terminal_execute(VimState *state, int key) case K_EVENT: // We cannot let an event free the terminal yet. It is still needed. s->term->refcount++; - multiqueue_process_events(main_loop.events); + state_handle_k_event(); s->term->refcount--; if (s->term->buf_handle == 0) { s->close = true; @@ -535,8 +535,44 @@ void terminal_send(Terminal *term, char *data, size_t size) term->opts.write_cb(data, size, term->opts.data); } +static bool is_filter_char(int c) +{ + unsigned int flag = 0; + switch (c) { + case 0x08: + flag = TPF_BS; + break; + case 0x09: + flag = TPF_HT; + break; + case 0x0A: + case 0x0D: + break; + case 0x0C: + flag = TPF_FF; + break; + case 0x1b: + flag = TPF_ESC; + break; + case 0x7F: + flag = TPF_DEL; + break; + default: + if (c < ' ') { + flag = TPF_C0; + } else if (c >= 0x80 && c <= 0x9F) { + flag = TPF_C1; + } + } + return !!(tpf_flags & flag); +} + void terminal_paste(long count, char_u **y_array, size_t y_size) { + vterm_keyboard_start_paste(curbuf->terminal->vt); + terminal_flush_output(curbuf->terminal); + size_t buff_len = STRLEN(y_array[0]); + char_u *buff = xmalloc(buff_len); for (int i = 0; i < count; i++) { // -V756 // feed the lines to the terminal for (size_t j = 0; j < y_size; j++) { @@ -544,9 +580,28 @@ void terminal_paste(long count, char_u **y_array, size_t y_size) // terminate the previous line terminal_send(curbuf->terminal, "\n", 1); } - terminal_send(curbuf->terminal, (char *)y_array[j], STRLEN(y_array[j])); + size_t len = STRLEN(y_array[j]); + if (len > buff_len) { + buff = xrealloc(buff, len); + buff_len = len; + } + char_u *dst = buff; + char_u *src = y_array[j]; + while (*src != '\0') { + len = (size_t)utf_ptr2len(src); + int c = utf_ptr2char(src); + if (!is_filter_char(c)) { + memcpy(dst, src, len); + dst += len; + } + src += len; + } + terminal_send(curbuf->terminal, (char *)buff, (size_t)(dst - buff)); } } + xfree(buff); + vterm_keyboard_end_paste(curbuf->terminal->vt); + terminal_flush_output(curbuf->terminal); } void terminal_flush_output(Terminal *term) diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 4f056abdc0..daf3c9c110 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -37,7 +37,6 @@ source test_recover.vim source test_scroll_opt.vim source test_sort.vim source test_sha256.vim -source test_statusline.vim source test_suspend.vim source test_syn_attr.vim source test_tabline.vim diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 92fedf9bfb..a1ef8325ec 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -80,6 +80,24 @@ func Test_argadd() call assert_equal(0, len(argv())) endfunc +func Test_argadd_empty_curbuf() + new + let curbuf = bufnr('%') + call writefile(['test', 'Xargadd'], 'Xargadd') + " must not re-use the current buffer. + argadd Xargadd + call assert_equal(curbuf, bufnr('%')) + call assert_equal('', bufname('%')) + call assert_equal(1, line('$')) + rew + call assert_notequal(curbuf, bufnr('%')) + call assert_equal('Xargadd', bufname('%')) + call assert_equal(2, line('$')) + + %argd + bwipe! +endfunc + func Init_abc() args a b c next diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 374ad65aa9..1f3a45a9ab 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1939,4 +1939,15 @@ func Test_autocmd_window() %bw! endfunc +func Test_autocmd_closes_window() + au BufNew,BufWinLeave * e %e + file yyy + au BufNew,BufWinLeave * ball + call assert_fails('n xxx', 'E143:') + + bwipe % + au! BufNew + au! BufWinLeave +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index a4c1f62a43..d53acb77d7 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -353,14 +353,19 @@ func Test_breakindent19_sbr_nextpage() " Scroll down one screen line setl scrolloff=5 norm! 5gj - redraw! let lines = s:screen_lines(1, 20) let expect = [ - \ "> aaaaaaaaaaaaaaaaaa", + \ "aaaaaaaaaaaaaaaaaaaa", \ "> aaaaaaaaaaaaaaaaaa", \ "> aaaaaaaaaaaaaaaaaa", \ ] call s:compare_lines(expect, lines) + redraw! + " moving the cursor doesn't change the text offset + norm! l + redraw! + let lines = s:screen_lines(1, 20) + call s:compare_lines(expect, lines) setl breakindent briopt=min:18 sbr=> norm! 5gj diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim new file mode 100644 index 0000000000..40111fdf06 --- /dev/null +++ b/src/nvim/testdir/test_buffer.vim @@ -0,0 +1,33 @@ +" Tests for Vim buffer + +func Test_buffer_error() + new foo1 + new foo2 + + call assert_fails('buffer foo', 'E93:') + call assert_fails('buffer bar', 'E94:') + call assert_fails('buffer 0', 'E939:') + + %bwipe +endfunc + +func Test_badd_options() + new SomeNewBuffer + setlocal numberwidth=3 + wincmd p + badd +1 SomeNewBuffer + new SomeNewBuffer + call assert_equal(3, &numberwidth) + close + close + bwipe! SomeNewBuffer +endfunc + +func Test_balt() + new SomeNewBuffer + balt +3 OtherBuffer + e # + call assert_equal('OtherBuffer', bufname()) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index 076f03fdd8..e038bce08e 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -112,6 +112,17 @@ func Test_deletebufline() call assert_equal(0, deletebufline(b, 1)) call assert_equal(['b', 'c'], getbufline(b, 1, 2)) exe "bwipe! " . b + + edit XbufOne + let one = bufnr() + call setline(1, ['a', 'b', 'c']) + setlocal nomodifiable + split XbufTwo + let two = bufnr() + call assert_fails('call deletebufline(one, 1)', 'E21:') + call assert_equal(two, bufnr()) + bwipe! XbufTwo + bwipe! XbufOne endfunc func Test_appendbufline_redraw() diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 39f865144a..a66aee5e02 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -442,6 +442,7 @@ func Test_getcompletion() set tags& call assert_fails('call getcompletion("", "burp")', 'E475:') + call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc func Test_shellcmd_completion() diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim index 7262789ab4..55b230373f 100644 --- a/src/nvim/testdir/test_command_count.vim +++ b/src/nvim/testdir/test_command_count.vim @@ -158,7 +158,9 @@ endfunc func Test_command_count_4() %argd let bufnr = bufnr('$') - arga aa bb cc dd ee ff + next aa bb cc dd ee ff + call assert_equal(bufnr, bufnr('%')) + 3argu let args = [] .,$-argdo call add(args, expand('%')) diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim new file mode 100644 index 0000000000..1306dbe5cf --- /dev/null +++ b/src/nvim/testdir/test_conceal.vim @@ -0,0 +1,282 @@ +" Tests for 'conceal'. + +source check.vim +CheckFeature conceal + +source screendump.vim +" CheckScreendump + +func Test_conceal_two_windows() + CheckScreendump + let code =<< trim [CODE] + let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"] + call setline(1, lines) + syntax match test /|hidden|/ conceal + set conceallevel=2 + set concealcursor= + exe "normal /here\r" + new + call setline(1, lines) + call setline(4, "Second window") + syntax match test /|hidden|/ conceal + set conceallevel=2 + set concealcursor=nc + exe "normal /here\r" + [CODE] + + call writefile(code, 'XTest_conceal') + " Check that cursor line is concealed + let buf = RunVimInTerminal('-S XTest_conceal', {}) + call VerifyScreenDump(buf, 'Test_conceal_two_windows_01', {}) + + " Check that with concealed text vertical cursor movement is correct. + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_02', {}) + + " Check that with cursor line is not concealed + call term_sendkeys(buf, "j") + call term_sendkeys(buf, ":set concealcursor=\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_03', {}) + + " Check that with cursor line is not concealed when moving cursor down + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_04', {}) + + " Check that with cursor line is not concealed when switching windows + call term_sendkeys(buf, "\<C-W>\<C-W>") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_05', {}) + + " Check that with cursor line is only concealed in Normal mode + call term_sendkeys(buf, ":set concealcursor=n\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check that with cursor line is only concealed in Insert mode + call term_sendkeys(buf, ":set concealcursor=i\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check that with cursor line is only concealed in Command mode + call term_sendkeys(buf, ":set concealcursor=c\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check that with cursor line is only concealed in Visual mode + call term_sendkeys(buf, ":set concealcursor=v\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check moving the cursor while in insert mode. + call term_sendkeys(buf, ":set concealcursor=\r") + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_10', {}) + call term_sendkeys(buf, "\<Down>") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_11', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check the "o" command + call VerifyScreenDump(buf, 'Test_conceal_two_windows_12', {}) + call term_sendkeys(buf, "o") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_13', {}) + call term_sendkeys(buf, "\<Esc>") + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_conceal') +endfunc + +func Test_conceal_with_cursorline() + CheckScreendump + " Opens a help window, where 'conceal' is set, switches to the other window + " where 'cursorline' needs to be updated when the cursor moves. + let code =<< trim [CODE] + set cursorline + normal othis is a test + new + call setline(1, ["one", "two", "three", "four", "five"]) + set ft=help + normal M + [CODE] + + call writefile(code, 'XTest_conceal_cul') + let buf = RunVimInTerminal('-S XTest_conceal_cul', {}) + call VerifyScreenDump(buf, 'Test_conceal_cul_01', {}) + + call term_sendkeys(buf, ":wincmd w\r") + call VerifyScreenDump(buf, 'Test_conceal_cul_02', {}) + + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_conceal_cul_03', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_conceal_cul') +endfunc + +func Test_conceal_resize_term() + CheckScreendump + let code =<< trim [CODE] + call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed') + setl cocu=n cole=3 + syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends + normal fb + [CODE] + call writefile(code, 'XTest_conceal_resize') + let buf = RunVimInTerminal('-S XTest_conceal_resize', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_conceal_resize_01', {}) + + call win_execute(buf->win_findbuf()[0], 'wincmd +') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_conceal_resize_02', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_conceal_resize') +endfunc + +" Tests for correct display (cursor column position) with +conceal and +" tabulators. Need to run this test in a separate Vim instance. Otherwise the +" screen is not updated (lazy redraw) and the cursor position is wrong. +func Test_conceal_cursor_pos() + let code =<< trim [CODE] + :let l = ['start:', '.concealed. text', "|concealed|\ttext"] + :let l += ['', "\t.concealed.\ttext", "\t|concealed|\ttext", ''] + :let l += [".a.\t.b.\t.c.\t.d.", "|a|\t|b|\t|c|\t|d|"] + :call append(0, l) + :call cursor(1, 1) + :" Conceal settings. + :set conceallevel=2 + :set concealcursor=nc + :syntax match test /|/ conceal + :" Save current cursor position. Only works in <expr> mode, can't be used + :" with :normal because it moves the cursor to the command line. Thanks + :" to ZyX <zyx.vim@gmail.com> for the idea to use an <expr> mapping. + :let curpos = [] + :nnoremap <expr> GG ":let curpos += ['".screenrow().":".screencol()."']\n" + :normal ztj + GGk + :" We should end up in the same column when running these commands on the + :" two lines. + :normal ft + GGk + :normal $ + GGk + :normal 0j + GGk + :normal ft + GGk + :normal $ + GGk + :normal 0j0j + GGk + :" Same for next test block. + :normal ft + GGk + :normal $ + GGk + :normal 0j + GGk + :normal ft + GGk + :normal $ + GGk + :normal 0j0j + GGk + :" And check W with multiple tabs and conceals in a line. + :normal W + GGk + :normal W + GGk + :normal W + GGk + :normal $ + GGk + :normal 0j + GGk + :normal W + GGk + :normal W + GGk + :normal W + GGk + :normal $ + GGk + :set lbr + :normal $ + GGk + :set list listchars=tab:>- + :normal 0 + GGk + :normal W + GGk + :normal W + GGk + :normal W + GGk + :normal $ + GGk + :call writefile(curpos, 'Xconceal_curpos.out') + :q! + + [CODE] + call writefile(code, 'XTest_conceal_curpos') + + if RunVim([], [], '-s XTest_conceal_curpos') + call assert_equal([ + \ '2:1', '2:17', '2:20', '3:1', '3:17', '3:20', '5:8', '5:25', + \ '5:28', '6:8', '6:25', '6:28', '8:1', '8:9', '8:17', '8:25', + \ '8:27', '9:1', '9:9', '9:17', '9:25', '9:26', '9:26', '9:1', + \ '9:9', '9:17', '9:25', '9:26'], readfile('Xconceal_curpos.out')) + endif + + call delete('Xconceal_curpos.out') + call delete('XTest_conceal_curpos') +endfunc + +func Test_conceal_eol() + new! + setlocal concealcursor=n conceallevel=1 + call setline(1, ["x", ""]) + call matchaddpos('Conceal', [[2, 1, 1]], 2, -1, {'conceal': 1}) + redraw! + + call assert_notequal(screenchar(1, 1), screenchar(2, 2)) + call assert_equal(screenattr(1, 1), screenattr(1, 2)) + call assert_equal(screenattr(1, 2), screenattr(2, 2)) + call assert_equal(screenattr(2, 1), screenattr(2, 2)) + + set list + redraw! + + call assert_equal(screenattr(1, 1), screenattr(2, 2)) + call assert_notequal(screenattr(1, 1), screenattr(1, 2)) + call assert_notequal(screenattr(1, 2), screenattr(2, 1)) + + set nolist +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index f09a64c329..21c1f98283 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -242,6 +242,63 @@ func Test_diffput_two() bwipe! b endfunc +" :diffput and :diffget completes names of buffers which +" are in diff mode and which are different then current buffer. +" No completion when the current window is not in diff mode. +func Test_diffget_diffput_completion() + e Xdiff1 | diffthis + botright new Xdiff2 + botright new Xdiff3 | split | diffthis + botright new Xdiff4 | diffthis + + wincmd t + call assert_equal('Xdiff1', bufname('%')) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput Xdiff3 Xdiff4', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget Xdiff3 Xdiff4', @:) + call assert_equal(['Xdiff3', 'Xdiff4'], getcompletion('', 'diff_buffer')) + + " Xdiff2 is not in diff mode, so no completion for :diffput, :diffget + wincmd j + call assert_equal('Xdiff2', bufname('%')) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput ', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget ', @:) + call assert_equal([], getcompletion('', 'diff_buffer')) + + " Xdiff3 is split in 2 windows, only the top one is in diff mode. + " So completion of :diffput :diffget only happens in the top window. + wincmd j + call assert_equal('Xdiff3', bufname('%')) + call assert_equal(1, &diff) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput Xdiff1 Xdiff4', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget Xdiff1 Xdiff4', @:) + call assert_equal(['Xdiff1', 'Xdiff4'], getcompletion('', 'diff_buffer')) + + wincmd j + call assert_equal('Xdiff3', bufname('%')) + call assert_equal(0, &diff) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput ', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget ', @:) + call assert_equal([], getcompletion('', 'diff_buffer')) + + wincmd j + call assert_equal('Xdiff4', bufname('%')) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput Xdiff1 Xdiff3', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget Xdiff1 Xdiff3', @:) + call assert_equal(['Xdiff1', 'Xdiff3'], getcompletion('', 'diff_buffer')) + + %bwipe +endfunc + func Test_dp_do_buffer() e! one let bn1=bufnr('%') @@ -964,6 +1021,21 @@ func Test_diff_closeoff() enew! endfunc +func Test_diff_followwrap() + new + set diffopt+=followwrap + set wrap + diffthis + call assert_equal(1, &wrap) + diffoff + set nowrap + diffthis + call assert_equal(0, &wrap) + diffoff + set diffopt& + bwipe! +endfunc + func Test_diff_rnu() CheckScreendump @@ -992,6 +1064,18 @@ func Test_diff_rnu() call delete('Xtest_diff_rnu') endfunc +func Test_diff_multilineconceal() + new + diffthis + + new + call matchadd('Conceal', 'a\nb', 9, -1, {'conceal': 'Y'}) + set cole=2 cocu=n + call setline(1, ["a", "b"]) + diffthis + redraw +endfunc + func Test_diff_and_scroll() " this was causing an ml_get error set ls=2 diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index b6d9687560..d23748a3e3 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -211,6 +211,8 @@ func Test_digraphs() call Put_Dig("00") call Put_Dig("el") call assert_equal(['␀', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) + call assert_fails('digraph xy z', 'E39:') + call assert_fails('digraph x', 'E474:') bw! endfunc diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 20508b12d3..98a3e60368 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -131,3 +131,11 @@ func Test_confirm_cmd_cancel() \ term_getline(buf, 20))}, 1000) call StopVimInTerminal(buf) endfunc + +" Test for the :winsize command +func Test_winsize_cmd() + call assert_fails('winsize 1', 'E465:') + call assert_fails('winsize 1 x', 'E465:') + call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)') + " Actually changing the window size would be flaky. +endfunc diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index 99a401d4a4..bd3e9eb4d4 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -81,3 +81,32 @@ func Test_exiting() endif call delete('Xtestout') endfunc + +" Test for getting the Vim exit code from v:exiting +func Test_exit_code() + call assert_equal(v:null, v:exiting) + + let before =<< trim [CODE] + au QuitPre * call writefile(['qp = ' .. v:exiting], 'Xtestout', 'a') + au ExitPre * call writefile(['ep = ' .. v:exiting], 'Xtestout', 'a') + au VimLeavePre * call writefile(['lp = ' .. v:exiting], 'Xtestout', 'a') + au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout', 'a') + [CODE] + + if RunVim(before, ['quit'], '') + call assert_equal(['qp = null', 'ep = null', 'lp = 0', 'l = 0'], readfile('Xtestout')) + endif + call delete('Xtestout') + + if RunVim(before, ['cquit'], '') + call assert_equal(['lp = 1', 'l = 1'], readfile('Xtestout')) + endif + call delete('Xtestout') + + if RunVim(before, ['cquit 4'], '') + call assert_equal(['lp = 4', 'l = 4'], readfile('Xtestout')) + endif + call delete('Xtestout') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 7b90ba56e0..09d79979ce 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -399,7 +399,11 @@ function Test_printf_errors() call assert_fails('echo printf("%d", [])', 'E745:') call assert_fails('echo printf("%d", 1, 2)', 'E767:') call assert_fails('echo printf("%*d", 1)', 'E766:') - call assert_fails('echo printf("%d", 1.2)', 'E805:') + call assert_fails('echo printf("%s")', 'E766:') + if has('float') + call assert_fails('echo printf("%d", 1.2)', 'E805:') + call assert_fails('echo printf("%f")') + endif endfunc function Test_max_min_errors() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index f9f0ade1f6..6bc5fba5db 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -79,6 +79,7 @@ let s:filename_checks = { \ 'bc': ['file.bc'], \ 'bdf': ['file.bdf'], \ 'bib': ['file.bib'], + \ 'beancount': ['file.beancount'], \ 'bindzone': ['named.root'], \ 'blank': ['file.bl'], \ 'bsdl': ['file.bsd', 'file.bsdl'], @@ -427,6 +428,7 @@ let s:filename_checks = { \ 'slrnrc': ['.slrnrc'], \ 'slrnsc': ['file.score'], \ 'sm': ['sendmail.cf'], + \ 'svelte': ['file.svelte'], \ 'smarty': ['file.tpl'], \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'], \ 'smith': ['file.smt', 'file.smith'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 917a5e8eca..5dae8d681a 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -833,6 +833,31 @@ func Test_byte2line_line2byte() bw! endfunc +" Test for charidx() +func Test_charidx() + let a = 'xáb́y' + call assert_equal(0, charidx(a, 0)) + call assert_equal(1, charidx(a, 3)) + call assert_equal(2, charidx(a, 4)) + call assert_equal(3, charidx(a, 7)) + call assert_equal(-1, charidx(a, 8)) + call assert_equal(-1, charidx('', 0)) + + " count composing characters + call assert_equal(0, charidx(a, 0, 1)) + call assert_equal(2, charidx(a, 2, 1)) + call assert_equal(3, charidx(a, 4, 1)) + call assert_equal(5, charidx(a, 7, 1)) + call assert_equal(-1, charidx(a, 8, 1)) + call assert_equal(-1, charidx('', 0, 1)) + + call assert_fails('let x = charidx([], 1)', 'E474:') + call assert_fails('let x = charidx("abc", [])', 'E474:') + call assert_fails('let x = charidx("abc", 1, [])', 'E474:') + call assert_fails('let x = charidx("abc", 1, -1)', 'E474:') + call assert_fails('let x = charidx("abc", 1, 2)', 'E474:') +endfunc + func Test_count() let l = ['a', 'a', 'A', 'b'] call assert_equal(2, count(l, 'a')) @@ -930,10 +955,20 @@ func Test_Executable() " get "cat" path and remove the leading / let catcmd = exepath('cat')[1:] new + " check that the relative path works in / lcd / call assert_equal(1, executable(catcmd)) - call assert_equal('/' .. catcmd, exepath(catcmd)) + " let result = catcmd->exepath() + let result = exepath(catcmd) + " when using chroot looking for sbin/cat can return bin/cat, that is OK + if catcmd =~ '\<sbin\>' && result =~ '\<bin\>' + call assert_equal('/' .. substitute(catcmd, '\<sbin\>', 'bin', ''), result) + else + call assert_equal('/' .. catcmd, result) + endif bwipe + else + throw 'Skipped: does not work on this platform' endif endfunc diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index f9e40a9b43..2cbaf5cb76 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -59,9 +59,9 @@ func Test_matchadd_and_conceallevel_3() setlocal filetype=conf syntax on - 1put='# This is a Test' - " 1234567890123456 - let expect = '#ThisisaTest' + 1put='# This is a Test $' + " 1234567890123 + let expect = '#ThisisaTest$' call cursor(1, 1) call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'}) @@ -69,22 +69,25 @@ func Test_matchadd_and_conceallevel_3() let lnum = 2 call assert_equal(expect, Screenline(lnum)) call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2)) - call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) - call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10)) - call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 10)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 13)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 14)) call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 16)) " more matchadd() - " 1234567890123456 - let expect = '#Thisisa Test' + " 12345678901234 + let expect = '#Thisisa Test$' call matchadd('ErrorMsg', '\%2l Test', 20, -1, {'conceal': 'X'}) redraw! call assert_equal(expect, Screenline(lnum)) call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 2)) - call assert_equal(screenattr(lnum, 2) , screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7)) call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 10)) - call assert_equal(screenattr(lnum, 10), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 10), screenattr(lnum, 13)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 14)) call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16)) call assert_notequal(screenattr(lnum, 10), screenattr(lnum, 16)) @@ -132,15 +135,29 @@ func Test_syn_and_match_conceal() new setlocal concealcursor=n conceallevel=1 - 1put='# This is a Test' - " 1234567890123456 - let expect = '#ZThisZisZaZTest' + 1put='# This is a Test ' + let lnum = 2 call cursor(1, 1) + + " 123456789012345678 + let expect = '#ZThisZisZaZTestZZ' call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'}) + syntax match MyConceal /\%2l / conceal containedin=ALL + hi MyConceal ctermbg=4 ctermfg=2 + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) + + syntax clear MyConceal syntax match MyConceal /\%2l / conceal containedin=ALL cchar=* redraw! - let lnum = 2 + call assert_equal(expect, Screenline(lnum)) call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) @@ -148,8 +165,8 @@ func Test_syn_and_match_conceal() call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) - " 1234567890123456 - let expect = '#*This*is*a*Test' + " 123456789012345678 + let expect = '#*This*is*a*Test**' call clearmatches() redraw! @@ -160,6 +177,48 @@ func Test_syn_and_match_conceal() call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) + " 123456789012345678 + let expect = '#*ThisXis*a*Test**' + call matchadd('Conceal', '\%2l\%7c ', 10, -1, {'conceal': 'X'}) + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) + + " 123456789012345678 + let expect = '#*ThisXis*a*Test**' + call matchadd('ErrorMsg', '\%2l Test', 20, -1) + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13)) + call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18)) + call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19)) + + " 123456789012345678 + let expect = '# ThisXis a Test' + syntax clear MyConceal + syntax match MyConceal /\%2l / conceal containedin=ALL + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 12)) + call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13)) + call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18)) + call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19)) + syntax off quit! endfunc diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 30239a90c2..3ebd048f46 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -31,6 +31,8 @@ func Test_messages() finally let &more = oldmore endtry + + call assert_fails('message 1', 'E474:') endfunc " Patch 7.4.1696 defined the "clearmode()" command for clearing the mode diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index f71da92bf8..7c7804212b 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -291,6 +291,46 @@ endfunc endif +func Test_mkview_open_folds() + enew! + + call append(0, ['a', 'b', 'c']) + 1,3fold + " zR affects 'foldlevel', make sure the option is applied after the folds + " have been recreated. + normal zR + write! Xtestfile + + call assert_equal(-1, foldclosed(1)) + call assert_equal(-1, foldclosed(2)) + call assert_equal(-1, foldclosed(3)) + + mkview! Xtestview + source Xtestview + + call assert_equal(-1, foldclosed(1)) + call assert_equal(-1, foldclosed(2)) + call assert_equal(-1, foldclosed(3)) + + call delete('Xtestview') + call delete('Xtestfile') + %bwipe +endfunc + +func Test_mkview_no_balt() + edit Xtestfile1 + edit Xtestfile2 + + mkview! Xtestview + bdelete Xtestfile1 + + source Xtestview + call assert_equal(0, buflisted('Xtestfile1')) + + call delete('Xtestview') + %bwipe +endfunc + " Test :mkview with a file argument. func Test_mkview_file() " Create a view with line number and a fold. @@ -373,6 +413,58 @@ func Test_mkview_no_file_name() %bwipe endfunc +func Test_mkview_loadview_jumplist() + set viewdir=Xviewdir + au BufWinLeave * silent mkview + " au BufWinEnter * silent loadview + + edit Xfile1 + call setline(1, ['a', 'bbbbbbb', 'c']) + normal j3l + call assert_equal([2, 4], getcurpos()[1:2]) + write + + edit Xfile2 + call setline(1, ['d', 'eeeeeee', 'f']) + normal j5l + call assert_equal([2, 6], getcurpos()[1:2]) + write + + edit Xfile3 + call setline(1, ['g', 'h', 'iiiii']) + normal jj3l + call assert_equal([3, 4], getcurpos()[1:2]) + write + + " The commented :au above was moved here so that :mkview (on BufWinLeave) can + " run before :loadview. This is needed because Nvim's :loadview raises E484 if + " the view can't be opened, while Vim's silently fails instead. + au BufWinEnter * silent loadview + + edit Xfile1 + call assert_equal([2, 4], getcurpos()[1:2]) + edit Xfile2 + call assert_equal([2, 6], getcurpos()[1:2]) + edit Xfile3 + call assert_equal([3, 4], getcurpos()[1:2]) + + exe "normal \<C-O>" + call assert_equal('Xfile2', expand('%')) + call assert_equal([2, 6], getcurpos()[1:2]) + exe "normal \<C-O>" + call assert_equal('Xfile1', expand('%')) + call assert_equal([2, 4], getcurpos()[1:2]) + + au! BufWinLeave + au! BufWinEnter + bwipe! + call delete('Xviewdir', 'rf') + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') + set viewdir& +endfunc + " A clean session (one empty buffer, one window, and one tab) should not " set any error messages when sourced because no commands should fail. func Test_mksession_no_errmsg() @@ -663,4 +755,27 @@ func Test_scrolloff() set sessionoptions& endfunc +func Test_altfile() + edit Xone + split Xtwo + edit Xtwoalt + edit # + wincmd w + edit Xonealt + edit # + mksession! Xtest_altfile + only + bwipe Xonealt + bwipe Xtwoalt + bwipe! + source Xtest_altfile + call assert_equal('Xone', bufname()) + call assert_equal('Xonealt', bufname('#')) + wincmd w + call assert_equal('Xtwo', bufname()) + call assert_equal('Xtwoalt', bufname('#')) + only + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 84d99ebb74..1202b842fd 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -1,5 +1,7 @@ " Test for options +source check.vim + func Test_whichwrap() set whichwrap=b,s call assert_equal('b,s', &whichwrap) @@ -330,12 +332,10 @@ func Test_set_ttytype() set ttytype=xterm call assert_equal('xterm', &ttytype) call assert_equal(&ttytype, &term) - " "set ttytype=" gives E522 instead of E529 - " in travis on some builds. Why? Catch both for now try set ttytype= call assert_report('set ttytype= did not fail') - catch /E529\|E522/ + catch /E529/ endtry " Some systems accept any terminal name and return dumb settings, @@ -606,6 +606,27 @@ func Test_opt_boolean() set number& endfunc +func Test_opt_winminheight_term() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + + " The tabline should be taken into account. + let lines =<< trim END + set wmh=0 stal=2 + below sp | wincmd _ + below sp | wincmd _ + below sp | wincmd _ + below sp + END + call writefile(lines, 'Xwinminheight') + let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11}) + call term_sendkeys(buf, ":set wmh=1\n") + call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))}) + + call StopVimInTerminal(buf) + call delete('Xwinminheight') +endfunc + " Test for setting option value containing spaces with isfname+=32 func Test_isfname_with_options() set isfname+=32 @@ -615,4 +636,23 @@ func Test_isfname_with_options() setlocal keywordprg& endfunc +" Test that resetting laststatus does change scroll option +func Test_opt_reset_scroll() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + let vimrc =<< trim [CODE] + set scroll=2 + set laststatus=2 + [CODE] + call writefile(vimrc, 'Xscroll') + let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45}) + call term_sendkeys(buf, ":verbose set scroll?\n") + call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))}) + call assert_match('^\s*scroll=7$', term_getline(buf, 14)) + call StopVimInTerminal(buf) + + " clean up + call delete('Xscroll') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 4e38f7ebd8..ce2ef4dcd8 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -1,12 +1,12 @@ " Test 'statusline' " " Not tested yet: -" %a " %N " %T " %X source view_util.vim +source check.vim source term_util.vim func s:get_statusline() @@ -61,7 +61,19 @@ func Test_statusline_will_be_disabled_with_error() endfunc func Test_statusline() - new Xstatusline + CheckFeature quickfix + + " %a: Argument list ({current} of {max}) + set statusline=%a + call assert_match('^\s*$', s:get_statusline()) + arglocal a1 a2 + rewind + call assert_match('^ (1 of 2)\s*$', s:get_statusline()) + next + call assert_match('^ (2 of 2)\s*$', s:get_statusline()) + e Xstatusline + call assert_match('^ ((2) of 2)\s*$', s:get_statusline()) + only set laststatus=2 set splitbelow diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index 67701ee3ca..e9e181ce3d 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -95,6 +95,59 @@ func Test_user_func() enew! endfunc +func Log(val, base = 10) + return log(a:val) / log(a:base) +endfunc + +func Args(mandatory, optional = v:null, ...) + return deepcopy(a:) +endfunc + +func Args2(a = 1, b = 2, c = 3) + return deepcopy(a:) +endfunc + +func MakeBadFunc() + func s:fcn(a, b=1, c) + endfunc +endfunc + +func Test_default_arg() + call assert_equal(1.0, Log(10)) + call assert_equal(log(10), Log(10, exp(1))) + call assert_fails("call Log(1,2,3)", 'E118') + + let res = Args(1) + call assert_equal(res.mandatory, 1) + call assert_equal(res.optional, v:null) + call assert_equal(res['0'], 0) + + let res = Args(1,2) + call assert_equal(res.mandatory, 1) + call assert_equal(res.optional, 2) + call assert_equal(res['0'], 0) + + let res = Args(1,2,3) + call assert_equal(res.mandatory, 1) + call assert_equal(res.optional, 2) + call assert_equal(res['0'], 1) + + call assert_fails("call MakeBadFunc()", 'E989') + call assert_fails("fu F(a=1 ,) | endf", 'E475') + + " Since neovim does not have v:none, the ability to use the default + " argument with the intermediate argument set to v:none has been omitted. + " Therefore, this test is not performed. + " let d = Args2(7, v:none, 9) + " call assert_equal([7, 2, 9], [d.a, d.b, d.c]) + + call assert_equal("\n" + \ .. " function Args2(a = 1, b = 2, c = 3)\n" + \ .. "1 return deepcopy(a:)\n" + \ .. " endfunction", + \ execute('func Args2')) +endfunc + func Test_failed_call_in_try() try | call UnknownFunc() | catch | endtry endfunc diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim index 46cf34979f..5fd38f7cdc 100644 --- a/src/nvim/testdir/test_version.vim +++ b/src/nvim/testdir/test_version.vim @@ -1,5 +1,8 @@ " Test :version Ex command +so check.vim +so shared.vim + func Test_version() " version should always return the same string. let v1 = execute('version') @@ -9,4 +12,15 @@ func Test_version() call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1) endfunc +func Test_version_redirect() + CheckNotGui + CheckCanRunGui + CheckUnix + + call RunVim([], [], '--clean -g --version >Xversion 2>&1') + call assert_match('Features included', readfile('Xversion')->join()) + + call delete('Xversion') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 3e683a4926..6705ab98c2 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -557,8 +557,8 @@ HandleState ut_handle_background_color(TermInput *input) static void handle_raw_buffer(TermInput *input, bool force) { - HandleState is_paste; - HandleState is_bc; + HandleState is_paste = kNotApplicable; + HandleState is_bc = kNotApplicable; do { if (!force diff --git a/src/nvim/undo.c b/src/nvim/undo.c index da464c56dc..f52850f6f3 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -580,6 +580,10 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) uep->ue_array = NULL; uep->ue_next = curbuf->b_u_newhead->uh_entry; curbuf->b_u_newhead->uh_entry = uep; + if (reload) { + // buffer was reloaded, notify text change subscribers + curbuf->b_u_newhead->uh_flags |= UH_RELOAD; + } curbuf->b_u_synced = false; undo_undoes = false; @@ -590,13 +594,20 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) } -# define UF_START_MAGIC "Vim\237UnDo\345" /* magic at start of undofile */ +// magic at start of undofile +# define UF_START_MAGIC "Vim\237UnDo\345" # define UF_START_MAGIC_LEN 9 -# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */ -# define UF_HEADER_END_MAGIC 0xe7aa /* magic after last header */ -# define UF_ENTRY_MAGIC 0xf518 /* magic at start of entry */ -# define UF_ENTRY_END_MAGIC 0x3581 /* magic after last entry */ -# define UF_VERSION 2 /* 2-byte undofile version number */ +// magic at start of header +# define UF_HEADER_MAGIC 0x5fd0 +// magic after last header +# define UF_HEADER_END_MAGIC 0xe7aa +// magic at start of entry +# define UF_ENTRY_MAGIC 0xf518 +// magic after last entry +# define UF_ENTRY_END_MAGIC 0x3581 + +// 2-byte undofile version number +# define UF_VERSION 3 /* extra fields for header */ # define UF_LAST_SAVE_NR 1 @@ -843,6 +854,15 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) } } undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + + // Write all extmark undo objects + for (size_t i = 0; i < kv_size(uhp->uh_extmark); i++) { + if (!serialize_extmark(bi, kv_A(uhp->uh_extmark, i))) { + return false; + } + } + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + return true; } @@ -924,9 +944,95 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, return NULL; } + // Unserialize all extmark undo information + ExtmarkUndoObject *extup; + kv_init(uhp->uh_extmark); + + while ((c = undo_read_2c(bi)) == UF_ENTRY_MAGIC) { + bool error = false; + extup = unserialize_extmark(bi, &error, file_name); + if (error) { + kv_destroy(uhp->uh_extmark); + xfree(extup); + return NULL; + } + kv_push(uhp->uh_extmark, *extup); + xfree(extup); + } + if (c != UF_ENTRY_END_MAGIC) { + corruption_error("entry end", file_name); + u_free_uhp(uhp); + return NULL; + } + return uhp; } +static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup) +{ + if (extup.type == kExtmarkSplice) { + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + undo_write_bytes(bi, (uintmax_t)extup.type, 4); + if (!undo_write(bi, (uint8_t *)&(extup.data.splice), + sizeof(ExtmarkSplice))) { + return false; + } + } else if (extup.type == kExtmarkMove) { + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + undo_write_bytes(bi, (uintmax_t)extup.type, 4); + if (!undo_write(bi, (uint8_t *)&(extup.data.move), sizeof(ExtmarkMove))) { + return false; + } + } + // Note: We do not serialize ExtmarkSavePos information, since + // buffer marktrees are not retained when closing/reopening a file + return true; +} + +static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error, + const char *filename) +{ + UndoObjectType type; + uint8_t *buf = NULL; + size_t n_elems; + + ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject)); + + type = (UndoObjectType)undo_read_4c(bi); + extup->type = type; + if (type == kExtmarkSplice) { + n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); + buf = xcalloc(sizeof(uint8_t), n_elems); + if (!undo_read(bi, buf, n_elems)) { + goto error; + } + extup->data.splice = *(ExtmarkSplice *)buf; + } else if (type == kExtmarkMove) { + n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); + buf = xcalloc(sizeof(uint8_t), n_elems); + if (!undo_read(bi, buf, n_elems)) { + goto error; + } + extup->data.move = *(ExtmarkMove *)buf; + } else { + goto error; + } + + if (buf) { + xfree(buf); + } + + return extup; + +error: + xfree(extup); + if (buf) { + xfree(buf); + } + *error = true; + return NULL; +} + /// Serializes "uep". /// /// @param bi The buffer information @@ -2157,8 +2263,9 @@ static void u_undoredo(int undo, bool do_buf_event) u_check(FALSE); #endif old_flags = curhead->uh_flags; - new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + - ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); + new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0) + | (old_flags & UH_RELOAD); setpcmark(); /* @@ -2299,6 +2406,11 @@ static void u_undoredo(int undo, bool do_buf_event) extmark_apply_undo(undo_info, undo); } } + if (curhead->uh_flags & UH_RELOAD) { + // TODO(bfredl): this is a bit crude. When 'undoreload' is used we + // should have all info to send a buffer-reloaing on_lines/on_bytes event + buf_updates_unload(curbuf, true); + } // finish Adjusting extmarks diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index cc2c39a711..b46295a15d 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -69,9 +69,10 @@ struct u_header { #endif }; -/* values for uh_flags */ -#define UH_CHANGED 0x01 /* b_changed flag before undo/after redo */ -#define UH_EMPTYBUF 0x02 /* buffer was empty */ +// values for uh_flags +#define UH_CHANGED 0x01 // b_changed flag before undo/after redo +#define UH_EMPTYBUF 0x02 // buffer was empty +#define UH_RELOAD 0x04 // buffer was reloaded /// Structure passed around between undofile functions. typedef struct { diff --git a/src/nvim/vim.h b/src/nvim/vim.h index e70749795b..0245c472ef 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -158,6 +158,7 @@ enum { EXPAND_MESSAGES, EXPAND_MAPCLEAR, EXPAND_ARGLIST, + EXPAND_DIFF_BUFFERS, EXPAND_CHECKHEALTH, EXPAND_LUA, }; diff --git a/src/nvim/window.c b/src/nvim/window.c index 00f49724b6..0f717a2f90 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5501,7 +5501,7 @@ void win_setminheight(void) // loop until there is a 'winminheight' that is possible while (p_wmh > 0) { - const int room = Rows - p_ch; + const int room = Rows - p_ch - tabline_height(); const int needed = frame_minheight(topframe, NULL); if (room >= needed) { break; @@ -5960,9 +5960,17 @@ void win_new_width(win_T *wp, int width) void win_comp_scroll(win_T *wp) { + const long old_w_p_scr = wp->w_p_scr; + wp->w_p_scr = wp->w_height / 2; - if (wp->w_p_scr == 0) + if (wp->w_p_scr == 0) { wp->w_p_scr = 1; + } + if (wp->w_p_scr != old_w_p_scr) { + // Used by "verbose set scroll". + wp->w_p_script_ctx[WV_SCROLL].script_ctx.sc_sid = SID_WINLAYOUT; + wp->w_p_script_ctx[WV_SCROLL].script_ctx.sc_lnum = 0; + } } /* |