diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2024-05-24 19:18:11 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2024-05-24 19:18:11 +0000 |
commit | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (patch) | |
tree | 729bbcb92231538fa61dab6c3d890b025484b7f5 /src/nvim/eval.c | |
parent | 376914f419eb08fdf4c1a63a77e1f035898a0f10 (diff) | |
parent | 28c04948a1c887a1cc0cb64de79fa32631700466 (diff) | |
download | rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.gz rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.bz2 rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.zip |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r-- | src/nvim/eval.c | 467 |
1 files changed, 275 insertions, 192 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7e3060100c..64883e69a2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -17,6 +17,7 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand_defs.h" @@ -763,8 +764,8 @@ void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip) return; } - if (getline_equal(eap->getline, eap->cookie, getsourceline)) { - evalarg->eval_getline = eap->getline; + if (getline_equal(eap->ea_getline, eap->cookie, getsourceline)) { + evalarg->eval_getline = eap->ea_getline; evalarg->eval_cookie = eap->cookie; } } @@ -968,12 +969,12 @@ int skip_expr(char **pp, evalarg_T *const evalarg) /// Convert "tv" to a string. /// -/// @param convert when true convert a List into a sequence of lines. +/// @param join_list when true convert a List into a sequence of lines. /// /// @return an allocated string. -static char *typval2string(typval_T *tv, bool convert) +static char *typval2string(typval_T *tv, bool join_list) { - if (convert && tv->v_type == VAR_LIST) { + if (join_list && tv->v_type == VAR_LIST) { garray_T ga; ga_init(&ga, (int)sizeof(char), 80); if (tv->vval.v_list != NULL) { @@ -984,24 +985,28 @@ static char *typval2string(typval_T *tv, bool convert) } ga_append(&ga, NUL); return (char *)ga.ga_data; + } else if (tv->v_type == VAR_LIST || tv->v_type == VAR_DICT) { + return encode_tv2string(tv, NULL); } return xstrdup(tv_get_string(tv)); } /// Top level evaluation function, returning a string. /// -/// @param convert when true convert a List into a sequence of lines. +/// @param join_list when true convert a List into a sequence of lines. /// /// @return pointer to allocated memory, or NULL for failure. -char *eval_to_string(char *arg, bool convert) +char *eval_to_string_eap(char *arg, bool join_list, exarg_T *eap) { typval_T tv; char *retval; - if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip); + if (eval0(arg, &tv, NULL, &evalarg) == FAIL) { retval = NULL; } else { - retval = typval2string(&tv, convert); + retval = typval2string(&tv, join_list); tv_clear(&tv); } clear_evalarg(&EVALARG_EVALUATE, NULL); @@ -1009,6 +1014,11 @@ char *eval_to_string(char *arg, bool convert) return retval; } +char *eval_to_string(char *arg, bool join_list) +{ + return eval_to_string_eap(arg, join_list, NULL); +} + /// Call eval_to_string() without using current local variables and using /// textlock. /// @@ -1372,112 +1382,24 @@ Object eval_foldtext(win_T *wp) return retval; } -/// Get an lvalue +/// Get the lval of a list/dict/blob subitem starting at "p". Loop +/// until no more [idx] or .key is following. /// -/// Lvalue may be -/// - variable: "name", "na{me}" -/// - dictionary item: "dict.key", "dict['key']" -/// - list item: "list[expr]" -/// - list slice: "list[expr:expr]" -/// -/// Indexing only works if trying to use it with an existing List or Dictionary. -/// -/// @param[in] name Name to parse. -/// @param rettv Pointer to the value to be assigned or NULL. -/// @param[out] lp Lvalue definition. When evaluation errors occur `->ll_name` -/// is NULL. -/// @param[in] unlet True if using `:unlet`. This results in slightly -/// different behaviour when something is wrong; must end in -/// space or cmd separator. -/// @param[in] skip True when skipping. /// @param[in] flags @see GetLvalFlags. -/// @param[in] fne_flags Flags for find_name_end(). /// -/// @return A pointer to just after the name, including indexes. Returns NULL -/// for a parsing error, but it is still needed to free items in lp. -char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const bool unlet, - const bool skip, const int flags, const int fne_flags) - FUNC_ATTR_NONNULL_ARG(1, 3) +/// @return A pointer to the character after the subscript on success or NULL on +/// failure. +static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv, hashtab_T *ht, + dictitem_T *v, int unlet, int flags) { - bool empty1 = false; int quiet = flags & GLV_QUIET; - - // Clear everything in "lp". - CLEAR_POINTER(lp); - - if (skip) { - // When skipping just find the end of the name. - lp->ll_name = name; - return (char *)find_name_end(name, NULL, NULL, FNE_INCL_BR | fne_flags); - } - - // Find the end of the name. - char *expr_start; - char *expr_end; - char *p = (char *)find_name_end(name, (const char **)&expr_start, - (const char **)&expr_end, - fne_flags); - if (expr_start != NULL) { - // Don't expand the name when we already know there is an error. - if (unlet && !ascii_iswhite(*p) && !ends_excmd(*p) - && *p != '[' && *p != '.') { - semsg(_(e_trailing_arg), p); - return NULL; - } - - lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p); - lp->ll_name = lp->ll_exp_name; - if (lp->ll_exp_name == NULL) { - // Report an invalid expression in braces, unless the - // expression evaluation has been cancelled due to an - // aborting error, an interrupt, or an exception. - if (!aborting() && !quiet) { - emsg_severe = true; - semsg(_(e_invarg2), name); - return NULL; - } - lp->ll_name_len = 0; - } else { - lp->ll_name_len = strlen(lp->ll_name); - } - } else { - lp->ll_name = name; - lp->ll_name_len = (size_t)(p - lp->ll_name); - } - - // Without [idx] or .key we are done. - if ((*p != '[' && *p != '.') || lp->ll_name == NULL) { - return p; - } - - hashtab_T *ht = NULL; - - // Only pass &ht when we would write to the variable, it prevents autoload - // as well. - dictitem_T *v = find_var(lp->ll_name, lp->ll_name_len, - (flags & GLV_READ_ONLY) ? NULL : &ht, - flags & GLV_NO_AUTOLOAD); - if (v == NULL && !quiet) { - semsg(_("E121: Undefined variable: %.*s"), - (int)lp->ll_name_len, lp->ll_name); - } - if (v == NULL) { - return NULL; - } - - lp->ll_tv = &v->di_tv; - - if (tv_is_luafunc(lp->ll_tv)) { - // For v:lua just return a pointer to the "." after the "v:lua". - // If the caller is trans_function_name() it will check for a Lua function name. - return p; - } - - // Loop until no more [idx] or .key is following. typval_T var1; var1.v_type = VAR_UNKNOWN; typval_T var2; var2.v_type = VAR_UNKNOWN; + bool empty1 = false; + + // Loop until no more [idx] or .key is following. while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) { if (*p == '.' && lp->ll_tv->v_type != VAR_DICT) { if (!quiet) { @@ -1512,6 +1434,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const char *key = NULL; if (*p == '.') { key = p + 1; + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) {} if (len == 0) { if (!quiet) { @@ -1730,6 +1653,116 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return p; } +/// Get an lvalue +/// +/// Lvalue may be +/// - variable: "name", "na{me}" +/// - dictionary item: "dict.key", "dict['key']" +/// - list item: "list[expr]" +/// - list slice: "list[expr:expr]" +/// +/// Indexing only works if trying to use it with an existing List or Dictionary. +/// +/// @param[in] name Name to parse. +/// @param rettv Pointer to the value to be assigned or NULL. +/// @param[out] lp Lvalue definition. When evaluation errors occur `->ll_name` +/// is NULL. +/// @param[in] unlet True if using `:unlet`. This results in slightly +/// different behaviour when something is wrong; must end in +/// space or cmd separator. +/// @param[in] skip True when skipping. +/// @param[in] flags @see GetLvalFlags. +/// @param[in] fne_flags Flags for find_name_end(). +/// +/// @return A pointer to just after the name, including indexes. Returns NULL +/// for a parsing error, but it is still needed to free items in lp. +char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const bool unlet, + const bool skip, const int flags, const int fne_flags) + FUNC_ATTR_NONNULL_ARG(1, 3) +{ + int quiet = flags & GLV_QUIET; + + // Clear everything in "lp". + CLEAR_POINTER(lp); + + if (skip) { + // When skipping just find the end of the name. + lp->ll_name = name; + return (char *)find_name_end(name, NULL, NULL, FNE_INCL_BR | fne_flags); + } + + // Find the end of the name. + char *expr_start; + char *expr_end; + char *p = (char *)find_name_end(name, (const char **)&expr_start, + (const char **)&expr_end, + fne_flags); + if (expr_start != NULL) { + // Don't expand the name when we already know there is an error. + if (unlet && !ascii_iswhite(*p) && !ends_excmd(*p) + && *p != '[' && *p != '.') { + semsg(_(e_trailing_arg), p); + return NULL; + } + + lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p); + lp->ll_name = lp->ll_exp_name; + if (lp->ll_exp_name == NULL) { + // Report an invalid expression in braces, unless the + // expression evaluation has been cancelled due to an + // aborting error, an interrupt, or an exception. + if (!aborting() && !quiet) { + emsg_severe = true; + semsg(_(e_invarg2), name); + return NULL; + } + lp->ll_name_len = 0; + } else { + lp->ll_name_len = strlen(lp->ll_name); + } + } else { + lp->ll_name = name; + lp->ll_name_len = (size_t)(p - lp->ll_name); + } + + // Without [idx] or .key we are done. + if ((*p != '[' && *p != '.') || lp->ll_name == NULL) { + return p; + } + + hashtab_T *ht = NULL; + + // Only pass &ht when we would write to the variable, it prevents autoload + // as well. + dictitem_T *v = find_var(lp->ll_name, lp->ll_name_len, + (flags & GLV_READ_ONLY) ? NULL : &ht, + flags & GLV_NO_AUTOLOAD); + if (v == NULL && !quiet) { + semsg(_("E121: Undefined variable: %.*s"), + (int)lp->ll_name_len, lp->ll_name); + } + if (v == NULL) { + return NULL; + } + + lp->ll_tv = &v->di_tv; + + if (tv_is_luafunc(lp->ll_tv)) { + // For v:lua just return a pointer to the "." after the "v:lua". + // If the caller is trans_function_name() it will check for a Lua function name. + return p; + } + + // If the next character is a "." or a "[", then process the subitem. + p = get_lval_subscript(lp, p, name, rettv, ht, v, unlet, flags); + if (p == NULL) { + return NULL; + } + + lp->ll_name_len = (size_t)(p - lp->ll_name); + return p; +} + /// Clear lval "lp" that was filled by get_lval(). void clear_lval(lval_T *lp) { @@ -1892,7 +1925,7 @@ void *eval_for_line(const char *arg, bool *errp, exarg_T *eap, evalarg_T *const *errp = true; // Default: there is an error. - const char *expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon); + const char *expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon, false); if (expr == NULL) { return fi; } @@ -4766,6 +4799,88 @@ bool set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack) return abort; } +/// Mark the dict "dd" with "copyID". +/// Also see set_ref_in_item(). +static bool set_ref_in_item_dict(dict_T *dd, int copyID, ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (dd == NULL || dd->dv_copyID == copyID) { + return false; + } + + // Didn't see this dict yet. + dd->dv_copyID = copyID; + if (ht_stack == NULL) { + return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); + } + + ht_stack_T *const newitem = xmalloc(sizeof(ht_stack_T)); + newitem->ht = &dd->dv_hashtab; + newitem->prev = *ht_stack; + *ht_stack = newitem; + + QUEUE *w = NULL; + DictWatcher *watcher = NULL; + QUEUE_FOREACH(w, &dd->watchers, { + watcher = tv_dict_watcher_node_data(w); + set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack); + }) + + return false; +} + +/// Mark the list "ll" with "copyID". +/// Also see set_ref_in_item(). +static bool set_ref_in_item_list(list_T *ll, int copyID, ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (ll == NULL || ll->lv_copyID == copyID) { + return false; + } + + // Didn't see this list yet. + ll->lv_copyID = copyID; + if (list_stack == NULL) { + return set_ref_in_list_items(ll, copyID, ht_stack); + } + + list_stack_T *const newitem = xmalloc(sizeof(list_stack_T)); + newitem->list = ll; + newitem->prev = *list_stack; + *list_stack = newitem; + + return false; +} + +/// Mark the partial "pt" with "copyID". +/// Also see set_ref_in_item(). +static bool set_ref_in_item_partial(partial_T *pt, int copyID, ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (pt == NULL || pt->pt_copyID == copyID) { + return false; + } + + // Didn't see this partial yet. + pt->pt_copyID = copyID; + + bool abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); + + if (pt->pt_dict != NULL) { + typval_T dtv; + + dtv.v_type = VAR_DICT; + dtv.vval.v_dict = pt->pt_dict; + abort = abort || set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + for (int i = 0; i < pt->pt_argc; i++) { + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, ht_stack, list_stack); + } + + return abort; +} + /// Mark all lists and dicts referenced through typval "tv" with "copyID". /// /// @param tv Typval content will be marked. @@ -4780,71 +4895,15 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack bool abort = false; switch (tv->v_type) { - case VAR_DICT: { - dict_T *dd = tv->vval.v_dict; - if (dd != NULL && dd->dv_copyID != copyID) { - // Didn't see this dict yet. - dd->dv_copyID = copyID; - if (ht_stack == NULL) { - abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); - } else { - ht_stack_T *const newitem = xmalloc(sizeof(ht_stack_T)); - newitem->ht = &dd->dv_hashtab; - newitem->prev = *ht_stack; - *ht_stack = newitem; - } - - QUEUE *w = NULL; - DictWatcher *watcher = NULL; - QUEUE_FOREACH(w, &dd->watchers, { - watcher = tv_dict_watcher_node_data(w); - set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack); - }) - } - break; - } - - case VAR_LIST: { - list_T *ll = tv->vval.v_list; - if (ll != NULL && ll->lv_copyID != copyID) { - // Didn't see this list yet. - ll->lv_copyID = copyID; - if (list_stack == NULL) { - abort = set_ref_in_list_items(ll, copyID, ht_stack); - } else { - list_stack_T *const newitem = xmalloc(sizeof(list_stack_T)); - newitem->list = ll; - newitem->prev = *list_stack; - *list_stack = newitem; - } - } - break; - } - - case VAR_PARTIAL: { - partial_T *pt = tv->vval.v_partial; - - // A partial does not have a copyID, because it cannot contain itself. - if (pt != NULL) { - abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); - if (pt->pt_dict != NULL) { - typval_T dtv; - - dtv.v_type = VAR_DICT; - dtv.vval.v_dict = pt->pt_dict; - abort = abort || set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - - for (int i = 0; i < pt->pt_argc; i++) { - abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, - ht_stack, list_stack); - } - } - break; - } + case VAR_DICT: + return set_ref_in_item_dict(tv->vval.v_dict, copyID, ht_stack, list_stack); + case VAR_LIST: + return set_ref_in_item_list(tv->vval.v_list, copyID, ht_stack, list_stack); case VAR_FUNC: abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); break; + case VAR_PARTIAL: + return set_ref_in_item_partial(tv->vval.v_partial, copyID, ht_stack, list_stack); case VAR_UNKNOWN: case VAR_BOOL: case VAR_SPECIAL: @@ -6697,7 +6756,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (charcol) { len = mb_charlen(ml_get(pos.lnum)); } else { - len = (int)strlen(ml_get(pos.lnum)); + len = ml_get_len(pos.lnum); } // We accept "$" for the column number: last column. @@ -6787,7 +6846,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (charcol) { pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); } else { - pos.col = (colnr_T)strlen(get_cursor_line_ptr()); + pos.col = get_cursor_line_len(); } } return &pos; @@ -7517,29 +7576,44 @@ int check_luafunc_name(const char *const str, const bool paren) return (int)(p - str); } -/// Return the character "str[index]" where "index" is the character index. If -/// "index" is out of range NULL is returned. +/// Return the character "str[index]" where "index" is the character index, +/// including composing characters. +/// If "index" is out of range NULL is returned. char *char_from_string(const char *str, varnumber_T index) { - size_t nbyte = 0; varnumber_T nchar = index; - if (str == NULL || index < 0) { + if (str == NULL) { return NULL; } size_t slen = strlen(str); - while (nchar > 0 && nbyte < slen) { - nbyte += (size_t)utf_ptr2len(str + nbyte); - nchar--; + + // do the same as for a list: a negative index counts from the end + if (index < 0) { + int clen = 0; + + for (size_t nbyte = 0; nbyte < slen; clen++) { + nbyte += (size_t)utfc_ptr2len(str + nbyte); + } + nchar = clen + index; + if (nchar < 0) { + // unlike list: index out of range results in empty string + return NULL; + } + } + + size_t nbyte = 0; + for (; nchar > 0 && nbyte < slen; nchar--) { + nbyte += (size_t)utfc_ptr2len(str + nbyte); } if (nbyte >= slen) { return NULL; } - return xmemdupz(str + nbyte, (size_t)utf_ptr2len(str + nbyte)); + return xmemdupz(str + nbyte, (size_t)utfc_ptr2len(str + nbyte)); } /// Get the byte index for character index "idx" in string "str" with length -/// "str_len". +/// "str_len". Composing characters are included. /// If going over the end return "str_len". /// If "idx" is negative count from the end, -1 is the last character. /// When going over the start return -1. @@ -7550,7 +7624,7 @@ static ssize_t char_idx2byte(const char *str, size_t str_len, varnumber_T idx) if (nchar >= 0) { while (nchar > 0 && nbyte < str_len) { - nbyte += (size_t)utf_ptr2len(str + nbyte); + nbyte += (size_t)utfc_ptr2len(str + nbyte); nchar--; } } else { @@ -7567,7 +7641,8 @@ static ssize_t char_idx2byte(const char *str, size_t str_len, varnumber_T idx) return (ssize_t)nbyte; } -/// Return the slice "str[first:last]" using character indexes. +/// Return the slice "str[first : last]" using character indexes. Composing +/// characters are included. /// /// @param exclusive true for slice(). /// @@ -7589,7 +7664,7 @@ char *string_slice(const char *str, varnumber_T first, varnumber_T last, bool ex end_byte = char_idx2byte(str, slen, last); if (!exclusive && end_byte >= 0 && end_byte < (ssize_t)slen) { // end index is inclusive - end_byte += utf_ptr2len(str + end_byte); + end_byte += utfc_ptr2len(str + end_byte); } } @@ -7876,8 +7951,8 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char .channel_id = LUA_INTERNAL_CALL, }; bool should_free; - // should_free is ignored as script_sctx will be resolved to a fnmae - // & new_script_item will consume it. + // should_free is ignored as script_ctx will be resolved to a fname + // and new_script_item() will consume it. char *sc_name = get_scriptname(last_set, &should_free); new_script_item(sc_name, ¤t_sctx.sc_sid); } @@ -8083,10 +8158,12 @@ void ex_echo(exarg_T *eap) // Call msg_start() after eval1(), evaluating the expression // may cause a message to appear. if (eap->cmdidx == CMD_echo) { - // Mark the saved text as finishing the line, so that what - // follows is displayed on a new line when scrolling back - // at the more prompt. - msg_sb_eol(); + if (!msg_didout) { + // Mark the saved text as finishing the line, so that what + // follows is displayed on a new line when scrolling back + // at the more prompt. + msg_sb_eol(); + } msg_start(); } } else if (eap->cmdidx == CMD_echo) { @@ -8182,7 +8259,7 @@ void ex_execute(exarg_T *eap) did_emsg = save_did_emsg; } } else if (eap->cmdidx == CMD_execute) { - do_cmdline(ga.ga_data, eap->getline, eap->cookie, DOCMD_NOWAIT|DOCMD_VERBOSE); + do_cmdline(ga.ga_data, eap->ea_getline, eap->cookie, DOCMD_NOWAIT|DOCMD_VERBOSE); } } @@ -8752,7 +8829,7 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) /// an empty typval_T. typval_T eval_call_provider(char *provider, char *method, list_T *arguments, bool discard) { - if (!eval_has_provider(provider)) { + if (!eval_has_provider(provider, false)) { semsg("E319: No \"%s\" provider found. Run \":checkhealth provider\"", provider); return (typval_T){ @@ -8810,7 +8887,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo } /// Checks if provider for feature `feat` is enabled. -bool eval_has_provider(const char *feat) +bool eval_has_provider(const char *feat, bool throw_if_fast) { if (!strequal(feat, "clipboard") && !strequal(feat, "python3") @@ -8823,6 +8900,11 @@ bool eval_has_provider(const char *feat) return false; } + if (throw_if_fast && !nlua_is_deferred_safe()) { + semsg(e_luv_api_disabled, "Vimscript function"); + return false; + } + char name[32]; // Normalized: "python3_compiled" => "python3". snprintf(name, sizeof(name), "%s", feat); strchrsub(name, '_', '\0'); // Chop any "_xx" suffix. @@ -8885,6 +8967,7 @@ void invoke_prompt_callback(void) // Add a new line for the prompt before invoking the callback, so that // text can always be inserted above the last line. ml_append(lnum, "", 0, false); + appended_lines_mark(lnum, 1); curwin->w_cursor.lnum = lnum + 1; curwin->w_cursor.col = 0; |