view src/evalfunc.c @ 15300:890203d87ee5 v8.1.0658

patch 8.1.0658: deleting signs and completion for :sign is insufficient commit https://github.com/vim/vim/commit/7d83bf4f2b785b46d87c7bc376fc9d0a862af782 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Dec 29 18:53:55 2018 +0100 patch 8.1.0658: deleting signs and completion for :sign is insufficient Problem: Deleting signs and completion for :sign is insufficient. Solution: Add deleting signs in a specified or any group from the current cursor location. Add group and priority to sign command completion. Add tests for different sign unplace commands. Update help text. Add tests for sign jump with group. Update help for sign jump. (Yegappan Lakshmanan, closes #3731)
author Bram Moolenaar <Bram@vim.org>
date Sat, 29 Dec 2018 19:00:08 +0100
parents 54457fc4af0b
children 5bfdce4de97f
line wrap: on
line source

/* vi:set ts=8 sts=4 sw=4 noet:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

/*
 * evalfunc.c: Builtin functions
 */
#define USING_FLOAT_STUFF

#include "vim.h"

#if defined(FEAT_EVAL) || defined(PROTO)

#ifdef AMIGA
# include <time.h>	/* for strftime() */
#endif

#ifdef VMS
# include <float.h>
#endif

#ifdef MACOS_X
# include <time.h>	/* for time_t */
#endif

static char *e_listarg = N_("E686: Argument of %s must be a List");
static char *e_stringreq = N_("E928: String required");

#ifdef FEAT_FLOAT
static void f_abs(typval_T *argvars, typval_T *rettv);
static void f_acos(typval_T *argvars, typval_T *rettv);
#endif
static void f_add(typval_T *argvars, typval_T *rettv);
static void f_and(typval_T *argvars, typval_T *rettv);
static void f_append(typval_T *argvars, typval_T *rettv);
static void f_appendbufline(typval_T *argvars, typval_T *rettv);
static void f_argc(typval_T *argvars, typval_T *rettv);
static void f_argidx(typval_T *argvars, typval_T *rettv);
static void f_arglistid(typval_T *argvars, typval_T *rettv);
static void f_argv(typval_T *argvars, typval_T *rettv);
static void f_assert_beeps(typval_T *argvars, typval_T *rettv);
static void f_assert_equal(typval_T *argvars, typval_T *rettv);
static void f_assert_equalfile(typval_T *argvars, typval_T *rettv);
static void f_assert_exception(typval_T *argvars, typval_T *rettv);
static void f_assert_fails(typval_T *argvars, typval_T *rettv);
static void f_assert_false(typval_T *argvars, typval_T *rettv);
static void f_assert_inrange(typval_T *argvars, typval_T *rettv);
static void f_assert_match(typval_T *argvars, typval_T *rettv);
static void f_assert_notequal(typval_T *argvars, typval_T *rettv);
static void f_assert_notmatch(typval_T *argvars, typval_T *rettv);
static void f_assert_report(typval_T *argvars, typval_T *rettv);
static void f_assert_true(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_asin(typval_T *argvars, typval_T *rettv);
static void f_atan(typval_T *argvars, typval_T *rettv);
static void f_atan2(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_BEVAL
static void f_balloon_show(typval_T *argvars, typval_T *rettv);
# if defined(FEAT_BEVAL_TERM)
static void f_balloon_split(typval_T *argvars, typval_T *rettv);
# endif
#endif
static void f_browse(typval_T *argvars, typval_T *rettv);
static void f_browsedir(typval_T *argvars, typval_T *rettv);
static void f_bufexists(typval_T *argvars, typval_T *rettv);
static void f_buflisted(typval_T *argvars, typval_T *rettv);
static void f_bufloaded(typval_T *argvars, typval_T *rettv);
static void f_bufname(typval_T *argvars, typval_T *rettv);
static void f_bufnr(typval_T *argvars, typval_T *rettv);
static void f_bufwinid(typval_T *argvars, typval_T *rettv);
static void f_bufwinnr(typval_T *argvars, typval_T *rettv);
static void f_byte2line(typval_T *argvars, typval_T *rettv);
static void byteidx(typval_T *argvars, typval_T *rettv, int comp);
static void f_byteidx(typval_T *argvars, typval_T *rettv);
static void f_byteidxcomp(typval_T *argvars, typval_T *rettv);
static void f_call(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_ceil(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_JOB_CHANNEL
static void f_ch_canread(typval_T *argvars, typval_T *rettv);
static void f_ch_close(typval_T *argvars, typval_T *rettv);
static void f_ch_close_in(typval_T *argvars, typval_T *rettv);
static void f_ch_evalexpr(typval_T *argvars, typval_T *rettv);
static void f_ch_evalraw(typval_T *argvars, typval_T *rettv);
static void f_ch_getbufnr(typval_T *argvars, typval_T *rettv);
static void f_ch_getjob(typval_T *argvars, typval_T *rettv);
static void f_ch_info(typval_T *argvars, typval_T *rettv);
static void f_ch_log(typval_T *argvars, typval_T *rettv);
static void f_ch_logfile(typval_T *argvars, typval_T *rettv);
static void f_ch_open(typval_T *argvars, typval_T *rettv);
static void f_ch_read(typval_T *argvars, typval_T *rettv);
static void f_ch_readraw(typval_T *argvars, typval_T *rettv);
static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv);
static void f_ch_sendraw(typval_T *argvars, typval_T *rettv);
static void f_ch_setoptions(typval_T *argvars, typval_T *rettv);
static void f_ch_status(typval_T *argvars, typval_T *rettv);
#endif
static void f_changenr(typval_T *argvars, typval_T *rettv);
static void f_char2nr(typval_T *argvars, typval_T *rettv);
static void f_cindent(typval_T *argvars, typval_T *rettv);
static void f_clearmatches(typval_T *argvars, typval_T *rettv);
static void f_col(typval_T *argvars, typval_T *rettv);
#if defined(FEAT_INS_EXPAND)
static void f_complete(typval_T *argvars, typval_T *rettv);
static void f_complete_add(typval_T *argvars, typval_T *rettv);
static void f_complete_check(typval_T *argvars, typval_T *rettv);
#endif
static void f_confirm(typval_T *argvars, typval_T *rettv);
static void f_copy(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_cos(typval_T *argvars, typval_T *rettv);
static void f_cosh(typval_T *argvars, typval_T *rettv);
#endif
static void f_count(typval_T *argvars, typval_T *rettv);
static void f_cscope_connection(typval_T *argvars, typval_T *rettv);
static void f_cursor(typval_T *argsvars, typval_T *rettv);
#ifdef WIN3264
static void f_debugbreak(typval_T *argvars, typval_T *rettv);
#endif
static void f_deepcopy(typval_T *argvars, typval_T *rettv);
static void f_delete(typval_T *argvars, typval_T *rettv);
static void f_deletebufline(typval_T *argvars, typval_T *rettv);
static void f_did_filetype(typval_T *argvars, typval_T *rettv);
static void f_diff_filler(typval_T *argvars, typval_T *rettv);
static void f_diff_hlID(typval_T *argvars, typval_T *rettv);
static void f_empty(typval_T *argvars, typval_T *rettv);
static void f_escape(typval_T *argvars, typval_T *rettv);
static void f_eval(typval_T *argvars, typval_T *rettv);
static void f_eventhandler(typval_T *argvars, typval_T *rettv);
static void f_executable(typval_T *argvars, typval_T *rettv);
static void f_execute(typval_T *argvars, typval_T *rettv);
static void f_exepath(typval_T *argvars, typval_T *rettv);
static void f_exists(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_exp(typval_T *argvars, typval_T *rettv);
#endif
static void f_expand(typval_T *argvars, typval_T *rettv);
static void f_extend(typval_T *argvars, typval_T *rettv);
static void f_feedkeys(typval_T *argvars, typval_T *rettv);
static void f_filereadable(typval_T *argvars, typval_T *rettv);
static void f_filewritable(typval_T *argvars, typval_T *rettv);
static void f_filter(typval_T *argvars, typval_T *rettv);
static void f_finddir(typval_T *argvars, typval_T *rettv);
static void f_findfile(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_float2nr(typval_T *argvars, typval_T *rettv);
static void f_floor(typval_T *argvars, typval_T *rettv);
static void f_fmod(typval_T *argvars, typval_T *rettv);
#endif
static void f_fnameescape(typval_T *argvars, typval_T *rettv);
static void f_fnamemodify(typval_T *argvars, typval_T *rettv);
static void f_foldclosed(typval_T *argvars, typval_T *rettv);
static void f_foldclosedend(typval_T *argvars, typval_T *rettv);
static void f_foldlevel(typval_T *argvars, typval_T *rettv);
static void f_foldtext(typval_T *argvars, typval_T *rettv);
static void f_foldtextresult(typval_T *argvars, typval_T *rettv);
static void f_foreground(typval_T *argvars, typval_T *rettv);
static void f_funcref(typval_T *argvars, typval_T *rettv);
static void f_function(typval_T *argvars, typval_T *rettv);
static void f_garbagecollect(typval_T *argvars, typval_T *rettv);
static void f_get(typval_T *argvars, typval_T *rettv);
static void f_getbufinfo(typval_T *argvars, typval_T *rettv);
static void f_getbufline(typval_T *argvars, typval_T *rettv);
static void f_getbufvar(typval_T *argvars, typval_T *rettv);
static void f_getchangelist(typval_T *argvars, typval_T *rettv);
static void f_getchar(typval_T *argvars, typval_T *rettv);
static void f_getcharmod(typval_T *argvars, typval_T *rettv);
static void f_getcharsearch(typval_T *argvars, typval_T *rettv);
static void f_getcmdline(typval_T *argvars, typval_T *rettv);
#if defined(FEAT_CMDL_COMPL)
static void f_getcompletion(typval_T *argvars, typval_T *rettv);
#endif
static void f_getcmdpos(typval_T *argvars, typval_T *rettv);
static void f_getcmdtype(typval_T *argvars, typval_T *rettv);
static void f_getcmdwintype(typval_T *argvars, typval_T *rettv);
static void f_getcwd(typval_T *argvars, typval_T *rettv);
static void f_getfontname(typval_T *argvars, typval_T *rettv);
static void f_getfperm(typval_T *argvars, typval_T *rettv);
static void f_getfsize(typval_T *argvars, typval_T *rettv);
static void f_getftime(typval_T *argvars, typval_T *rettv);
static void f_getftype(typval_T *argvars, typval_T *rettv);
static void f_getjumplist(typval_T *argvars, typval_T *rettv);
static void f_getline(typval_T *argvars, typval_T *rettv);
static void f_getloclist(typval_T *argvars UNUSED, typval_T *rettv UNUSED);
static void f_getmatches(typval_T *argvars, typval_T *rettv);
static void f_getpid(typval_T *argvars, typval_T *rettv);
static void f_getcurpos(typval_T *argvars, typval_T *rettv);
static void f_getpos(typval_T *argvars, typval_T *rettv);
static void f_getqflist(typval_T *argvars, typval_T *rettv);
static void f_getreg(typval_T *argvars, typval_T *rettv);
static void f_getregtype(typval_T *argvars, typval_T *rettv);
static void f_gettabinfo(typval_T *argvars, typval_T *rettv);
static void f_gettabvar(typval_T *argvars, typval_T *rettv);
static void f_gettabwinvar(typval_T *argvars, typval_T *rettv);
static void f_gettagstack(typval_T *argvars, typval_T *rettv);
static void f_getwininfo(typval_T *argvars, typval_T *rettv);
static void f_getwinpos(typval_T *argvars, typval_T *rettv);
static void f_getwinposx(typval_T *argvars, typval_T *rettv);
static void f_getwinposy(typval_T *argvars, typval_T *rettv);
static void f_getwinvar(typval_T *argvars, typval_T *rettv);
static void f_glob(typval_T *argvars, typval_T *rettv);
static void f_globpath(typval_T *argvars, typval_T *rettv);
static void f_glob2regpat(typval_T *argvars, typval_T *rettv);
static void f_has(typval_T *argvars, typval_T *rettv);
static void f_has_key(typval_T *argvars, typval_T *rettv);
static void f_haslocaldir(typval_T *argvars, typval_T *rettv);
static void f_hasmapto(typval_T *argvars, typval_T *rettv);
static void f_histadd(typval_T *argvars, typval_T *rettv);
static void f_histdel(typval_T *argvars, typval_T *rettv);
static void f_histget(typval_T *argvars, typval_T *rettv);
static void f_histnr(typval_T *argvars, typval_T *rettv);
static void f_hlID(typval_T *argvars, typval_T *rettv);
static void f_hlexists(typval_T *argvars, typval_T *rettv);
static void f_hostname(typval_T *argvars, typval_T *rettv);
static void f_iconv(typval_T *argvars, typval_T *rettv);
static void f_indent(typval_T *argvars, typval_T *rettv);
static void f_index(typval_T *argvars, typval_T *rettv);
static void f_input(typval_T *argvars, typval_T *rettv);
static void f_inputdialog(typval_T *argvars, typval_T *rettv);
static void f_inputlist(typval_T *argvars, typval_T *rettv);
static void f_inputrestore(typval_T *argvars, typval_T *rettv);
static void f_inputsave(typval_T *argvars, typval_T *rettv);
static void f_inputsecret(typval_T *argvars, typval_T *rettv);
static void f_insert(typval_T *argvars, typval_T *rettv);
static void f_invert(typval_T *argvars, typval_T *rettv);
static void f_isdirectory(typval_T *argvars, typval_T *rettv);
static void f_islocked(typval_T *argvars, typval_T *rettv);
#if defined(FEAT_FLOAT) && defined(HAVE_MATH_H)
static void f_isnan(typval_T *argvars, typval_T *rettv);
#endif
static void f_items(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_JOB_CHANNEL
static void f_job_getchannel(typval_T *argvars, typval_T *rettv);
static void f_job_info(typval_T *argvars, typval_T *rettv);
static void f_job_setoptions(typval_T *argvars, typval_T *rettv);
static void f_job_start(typval_T *argvars, typval_T *rettv);
static void f_job_stop(typval_T *argvars, typval_T *rettv);
static void f_job_status(typval_T *argvars, typval_T *rettv);
#endif
static void f_join(typval_T *argvars, typval_T *rettv);
static void f_js_decode(typval_T *argvars, typval_T *rettv);
static void f_js_encode(typval_T *argvars, typval_T *rettv);
static void f_json_decode(typval_T *argvars, typval_T *rettv);
static void f_json_encode(typval_T *argvars, typval_T *rettv);
static void f_keys(typval_T *argvars, typval_T *rettv);
static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv);
static void f_len(typval_T *argvars, typval_T *rettv);
static void f_libcall(typval_T *argvars, typval_T *rettv);
static void f_libcallnr(typval_T *argvars, typval_T *rettv);
static void f_line(typval_T *argvars, typval_T *rettv);
static void f_line2byte(typval_T *argvars, typval_T *rettv);
static void f_lispindent(typval_T *argvars, typval_T *rettv);
static void f_localtime(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_log(typval_T *argvars, typval_T *rettv);
static void f_log10(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_LUA
static void f_luaeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_map(typval_T *argvars, typval_T *rettv);
static void f_maparg(typval_T *argvars, typval_T *rettv);
static void f_mapcheck(typval_T *argvars, typval_T *rettv);
static void f_match(typval_T *argvars, typval_T *rettv);
static void f_matchadd(typval_T *argvars, typval_T *rettv);
static void f_matchaddpos(typval_T *argvars, typval_T *rettv);
static void f_matcharg(typval_T *argvars, typval_T *rettv);
static void f_matchdelete(typval_T *argvars, typval_T *rettv);
static void f_matchend(typval_T *argvars, typval_T *rettv);
static void f_matchlist(typval_T *argvars, typval_T *rettv);
static void f_matchstr(typval_T *argvars, typval_T *rettv);
static void f_matchstrpos(typval_T *argvars, typval_T *rettv);
static void f_max(typval_T *argvars, typval_T *rettv);
static void f_min(typval_T *argvars, typval_T *rettv);
#ifdef vim_mkdir
static void f_mkdir(typval_T *argvars, typval_T *rettv);
#endif
static void f_mode(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_MZSCHEME
static void f_mzeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_nextnonblank(typval_T *argvars, typval_T *rettv);
static void f_nr2char(typval_T *argvars, typval_T *rettv);
static void f_or(typval_T *argvars, typval_T *rettv);
static void f_pathshorten(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_PERL
static void f_perleval(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_FLOAT
static void f_pow(typval_T *argvars, typval_T *rettv);
#endif
static void f_prevnonblank(typval_T *argvars, typval_T *rettv);
static void f_printf(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_JOB_CHANNEL
static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv);
static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv);
static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv);
#endif
static void f_pumvisible(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_PYTHON3
static void f_py3eval(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_PYTHON
static void f_pyeval(typval_T *argvars, typval_T *rettv);
#endif
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
static void f_pyxeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_range(typval_T *argvars, typval_T *rettv);
static void f_readfile(typval_T *argvars, typval_T *rettv);
static void f_reg_executing(typval_T *argvars, typval_T *rettv);
static void f_reg_recording(typval_T *argvars, typval_T *rettv);
static void f_reltime(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_reltimefloat(typval_T *argvars, typval_T *rettv);
#endif
static void f_reltimestr(typval_T *argvars, typval_T *rettv);
static void f_remote_expr(typval_T *argvars, typval_T *rettv);
static void f_remote_foreground(typval_T *argvars, typval_T *rettv);
static void f_remote_peek(typval_T *argvars, typval_T *rettv);
static void f_remote_read(typval_T *argvars, typval_T *rettv);
static void f_remote_send(typval_T *argvars, typval_T *rettv);
static void f_remote_startserver(typval_T *argvars, typval_T *rettv);
static void f_remove(typval_T *argvars, typval_T *rettv);
static void f_rename(typval_T *argvars, typval_T *rettv);
static void f_repeat(typval_T *argvars, typval_T *rettv);
static void f_resolve(typval_T *argvars, typval_T *rettv);
static void f_reverse(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_round(typval_T *argvars, typval_T *rettv);
#endif
static void f_screenattr(typval_T *argvars, typval_T *rettv);
static void f_screenchar(typval_T *argvars, typval_T *rettv);
static void f_screencol(typval_T *argvars, typval_T *rettv);
static void f_screenrow(typval_T *argvars, typval_T *rettv);
static void f_search(typval_T *argvars, typval_T *rettv);
static void f_searchdecl(typval_T *argvars, typval_T *rettv);
static void f_searchpair(typval_T *argvars, typval_T *rettv);
static void f_searchpairpos(typval_T *argvars, typval_T *rettv);
static void f_searchpos(typval_T *argvars, typval_T *rettv);
static void f_server2client(typval_T *argvars, typval_T *rettv);
static void f_serverlist(typval_T *argvars, typval_T *rettv);
static void f_setbufline(typval_T *argvars, typval_T *rettv);
static void f_setbufvar(typval_T *argvars, typval_T *rettv);
static void f_setcharsearch(typval_T *argvars, typval_T *rettv);
static void f_setcmdpos(typval_T *argvars, typval_T *rettv);
static void f_setfperm(typval_T *argvars, typval_T *rettv);
static void f_setline(typval_T *argvars, typval_T *rettv);
static void f_setloclist(typval_T *argvars, typval_T *rettv);
static void f_setmatches(typval_T *argvars, typval_T *rettv);
static void f_setpos(typval_T *argvars, typval_T *rettv);
static void f_setqflist(typval_T *argvars, typval_T *rettv);
static void f_setreg(typval_T *argvars, typval_T *rettv);
static void f_settabvar(typval_T *argvars, typval_T *rettv);
static void f_settabwinvar(typval_T *argvars, typval_T *rettv);
static void f_settagstack(typval_T *argvars, typval_T *rettv);
static void f_setwinvar(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_CRYPT
static void f_sha256(typval_T *argvars, typval_T *rettv);
#endif /* FEAT_CRYPT */
static void f_shellescape(typval_T *argvars, typval_T *rettv);
static void f_shiftwidth(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_SIGNS
static void f_sign_define(typval_T *argvars, typval_T *rettv);
static void f_sign_getdefined(typval_T *argvars, typval_T *rettv);
static void f_sign_getplaced(typval_T *argvars, typval_T *rettv);
static void f_sign_place(typval_T *argvars, typval_T *rettv);
static void f_sign_undefine(typval_T *argvars, typval_T *rettv);
static void f_sign_unplace(typval_T *argvars, typval_T *rettv);
#endif
static void f_simplify(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_sin(typval_T *argvars, typval_T *rettv);
static void f_sinh(typval_T *argvars, typval_T *rettv);
#endif
static void f_sort(typval_T *argvars, typval_T *rettv);
static void f_soundfold(typval_T *argvars, typval_T *rettv);
static void f_spellbadword(typval_T *argvars, typval_T *rettv);
static void f_spellsuggest(typval_T *argvars, typval_T *rettv);
static void f_split(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_sqrt(typval_T *argvars, typval_T *rettv);
static void f_str2float(typval_T *argvars, typval_T *rettv);
#endif
static void f_str2nr(typval_T *argvars, typval_T *rettv);
static void f_strchars(typval_T *argvars, typval_T *rettv);
#ifdef HAVE_STRFTIME
static void f_strftime(typval_T *argvars, typval_T *rettv);
#endif
static void f_strgetchar(typval_T *argvars, typval_T *rettv);
static void f_stridx(typval_T *argvars, typval_T *rettv);
static void f_strlen(typval_T *argvars, typval_T *rettv);
static void f_strcharpart(typval_T *argvars, typval_T *rettv);
static void f_strpart(typval_T *argvars, typval_T *rettv);
static void f_strridx(typval_T *argvars, typval_T *rettv);
static void f_strtrans(typval_T *argvars, typval_T *rettv);
static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv);
static void f_strwidth(typval_T *argvars, typval_T *rettv);
static void f_submatch(typval_T *argvars, typval_T *rettv);
static void f_substitute(typval_T *argvars, typval_T *rettv);
static void f_swapinfo(typval_T *argvars, typval_T *rettv);
static void f_swapname(typval_T *argvars, typval_T *rettv);
static void f_synID(typval_T *argvars, typval_T *rettv);
static void f_synIDattr(typval_T *argvars, typval_T *rettv);
static void f_synIDtrans(typval_T *argvars, typval_T *rettv);
static void f_synstack(typval_T *argvars, typval_T *rettv);
static void f_synconcealed(typval_T *argvars, typval_T *rettv);
static void f_system(typval_T *argvars, typval_T *rettv);
static void f_systemlist(typval_T *argvars, typval_T *rettv);
static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv);
static void f_tabpagenr(typval_T *argvars, typval_T *rettv);
static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv);
static void f_taglist(typval_T *argvars, typval_T *rettv);
static void f_tagfiles(typval_T *argvars, typval_T *rettv);
static void f_tempname(typval_T *argvars, typval_T *rettv);
static void f_test_alloc_fail(typval_T *argvars, typval_T *rettv);
static void f_test_autochdir(typval_T *argvars, typval_T *rettv);
static void f_test_feedinput(typval_T *argvars, typval_T *rettv);
static void f_test_option_not_set(typval_T *argvars, typval_T *rettv);
static void f_test_override(typval_T *argvars, typval_T *rettv);
static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv);
static void f_test_ignore_error(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_JOB_CHANNEL
static void f_test_null_channel(typval_T *argvars, typval_T *rettv);
#endif
static void f_test_null_dict(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_JOB_CHANNEL
static void f_test_null_job(typval_T *argvars, typval_T *rettv);
#endif
static void f_test_null_list(typval_T *argvars, typval_T *rettv);
static void f_test_null_partial(typval_T *argvars, typval_T *rettv);
static void f_test_null_string(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_GUI
static void f_test_scrollbar(typval_T *argvars, typval_T *rettv);
#endif
static void f_test_settime(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_tan(typval_T *argvars, typval_T *rettv);
static void f_tanh(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_TIMERS
static void f_timer_info(typval_T *argvars, typval_T *rettv);
static void f_timer_pause(typval_T *argvars, typval_T *rettv);
static void f_timer_start(typval_T *argvars, typval_T *rettv);
static void f_timer_stop(typval_T *argvars, typval_T *rettv);
static void f_timer_stopall(typval_T *argvars, typval_T *rettv);
#endif
static void f_tolower(typval_T *argvars, typval_T *rettv);
static void f_toupper(typval_T *argvars, typval_T *rettv);
static void f_tr(typval_T *argvars, typval_T *rettv);
static void f_trim(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_FLOAT
static void f_trunc(typval_T *argvars, typval_T *rettv);
#endif
static void f_type(typval_T *argvars, typval_T *rettv);
static void f_undofile(typval_T *argvars, typval_T *rettv);
static void f_undotree(typval_T *argvars, typval_T *rettv);
static void f_uniq(typval_T *argvars, typval_T *rettv);
static void f_values(typval_T *argvars, typval_T *rettv);
static void f_virtcol(typval_T *argvars, typval_T *rettv);
static void f_visualmode(typval_T *argvars, typval_T *rettv);
static void f_wildmenumode(typval_T *argvars, typval_T *rettv);
static void f_win_findbuf(typval_T *argvars, typval_T *rettv);
static void f_win_getid(typval_T *argvars, typval_T *rettv);
static void f_win_gotoid(typval_T *argvars, typval_T *rettv);
static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv);
static void f_win_id2win(typval_T *argvars, typval_T *rettv);
static void f_win_screenpos(typval_T *argvars, typval_T *rettv);
static void f_winbufnr(typval_T *argvars, typval_T *rettv);
static void f_wincol(typval_T *argvars, typval_T *rettv);
static void f_winheight(typval_T *argvars, typval_T *rettv);
static void f_winlayout(typval_T *argvars, typval_T *rettv);
static void f_winline(typval_T *argvars, typval_T *rettv);
static void f_winnr(typval_T *argvars, typval_T *rettv);
static void f_winrestcmd(typval_T *argvars, typval_T *rettv);
static void f_winrestview(typval_T *argvars, typval_T *rettv);
static void f_winsaveview(typval_T *argvars, typval_T *rettv);
static void f_winwidth(typval_T *argvars, typval_T *rettv);
static void f_writefile(typval_T *argvars, typval_T *rettv);
static void f_wordcount(typval_T *argvars, typval_T *rettv);
static void f_xor(typval_T *argvars, typval_T *rettv);

/*
 * Array with names and number of arguments of all internal functions
 * MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
 */
static struct fst
{
    char	*f_name;	/* function name */
    char	f_min_argc;	/* minimal number of arguments */
    char	f_max_argc;	/* maximal number of arguments */
    void	(*f_func)(typval_T *args, typval_T *rvar);
				/* implementation of function */
} functions[] =
{
#ifdef FEAT_FLOAT
    {"abs",		1, 1, f_abs},
    {"acos",		1, 1, f_acos},	/* WJMc */
#endif
    {"add",		2, 2, f_add},
    {"and",		2, 2, f_and},
    {"append",		2, 2, f_append},
    {"appendbufline",	3, 3, f_appendbufline},
    {"argc",		0, 1, f_argc},
    {"argidx",		0, 0, f_argidx},
    {"arglistid",	0, 2, f_arglistid},
    {"argv",		0, 2, f_argv},
#ifdef FEAT_FLOAT
    {"asin",		1, 1, f_asin},	/* WJMc */
#endif
    {"assert_beeps",	1, 2, f_assert_beeps},
    {"assert_equal",	2, 3, f_assert_equal},
    {"assert_equalfile", 2, 2, f_assert_equalfile},
    {"assert_exception", 1, 2, f_assert_exception},
    {"assert_fails",	1, 3, f_assert_fails},
    {"assert_false",	1, 2, f_assert_false},
    {"assert_inrange",	3, 4, f_assert_inrange},
    {"assert_match",	2, 3, f_assert_match},
    {"assert_notequal",	2, 3, f_assert_notequal},
    {"assert_notmatch",	2, 3, f_assert_notmatch},
    {"assert_report",	1, 1, f_assert_report},
    {"assert_true",	1, 2, f_assert_true},
#ifdef FEAT_FLOAT
    {"atan",		1, 1, f_atan},
    {"atan2",		2, 2, f_atan2},
#endif
#ifdef FEAT_BEVAL
    {"balloon_show",	1, 1, f_balloon_show},
# if defined(FEAT_BEVAL_TERM)
    {"balloon_split",	1, 1, f_balloon_split},
# endif
#endif
    {"browse",		4, 4, f_browse},
    {"browsedir",	2, 2, f_browsedir},
    {"bufexists",	1, 1, f_bufexists},
    {"buffer_exists",	1, 1, f_bufexists},	/* obsolete */
    {"buffer_name",	1, 1, f_bufname},	/* obsolete */
    {"buffer_number",	1, 1, f_bufnr},		/* obsolete */
    {"buflisted",	1, 1, f_buflisted},
    {"bufloaded",	1, 1, f_bufloaded},
    {"bufname",		1, 1, f_bufname},
    {"bufnr",		1, 2, f_bufnr},
    {"bufwinid",	1, 1, f_bufwinid},
    {"bufwinnr",	1, 1, f_bufwinnr},
    {"byte2line",	1, 1, f_byte2line},
    {"byteidx",		2, 2, f_byteidx},
    {"byteidxcomp",	2, 2, f_byteidxcomp},
    {"call",		2, 3, f_call},
#ifdef FEAT_FLOAT
    {"ceil",		1, 1, f_ceil},
#endif
#ifdef FEAT_JOB_CHANNEL
    {"ch_canread",	1, 1, f_ch_canread},
    {"ch_close",	1, 1, f_ch_close},
    {"ch_close_in",	1, 1, f_ch_close_in},
    {"ch_evalexpr",	2, 3, f_ch_evalexpr},
    {"ch_evalraw",	2, 3, f_ch_evalraw},
    {"ch_getbufnr",	2, 2, f_ch_getbufnr},
    {"ch_getjob",	1, 1, f_ch_getjob},
    {"ch_info",		1, 1, f_ch_info},
    {"ch_log",		1, 2, f_ch_log},
    {"ch_logfile",	1, 2, f_ch_logfile},
    {"ch_open",		1, 2, f_ch_open},
    {"ch_read",		1, 2, f_ch_read},
    {"ch_readraw",	1, 2, f_ch_readraw},
    {"ch_sendexpr",	2, 3, f_ch_sendexpr},
    {"ch_sendraw",	2, 3, f_ch_sendraw},
    {"ch_setoptions",	2, 2, f_ch_setoptions},
    {"ch_status",	1, 2, f_ch_status},
#endif
    {"changenr",	0, 0, f_changenr},
    {"char2nr",		1, 2, f_char2nr},
    {"cindent",		1, 1, f_cindent},
    {"clearmatches",	0, 0, f_clearmatches},
    {"col",		1, 1, f_col},
#if defined(FEAT_INS_EXPAND)
    {"complete",	2, 2, f_complete},
    {"complete_add",	1, 1, f_complete_add},
    {"complete_check",	0, 0, f_complete_check},
#endif
    {"confirm",		1, 4, f_confirm},
    {"copy",		1, 1, f_copy},
#ifdef FEAT_FLOAT
    {"cos",		1, 1, f_cos},
    {"cosh",		1, 1, f_cosh},
#endif
    {"count",		2, 4, f_count},
    {"cscope_connection",0,3, f_cscope_connection},
    {"cursor",		1, 3, f_cursor},
#ifdef WIN3264
    {"debugbreak",	1, 1, f_debugbreak},
#endif
    {"deepcopy",	1, 2, f_deepcopy},
    {"delete",		1, 2, f_delete},
    {"deletebufline",	2, 3, f_deletebufline},
    {"did_filetype",	0, 0, f_did_filetype},
    {"diff_filler",	1, 1, f_diff_filler},
    {"diff_hlID",	2, 2, f_diff_hlID},
    {"empty",		1, 1, f_empty},
    {"escape",		2, 2, f_escape},
    {"eval",		1, 1, f_eval},
    {"eventhandler",	0, 0, f_eventhandler},
    {"executable",	1, 1, f_executable},
    {"execute",		1, 2, f_execute},
    {"exepath",		1, 1, f_exepath},
    {"exists",		1, 1, f_exists},
#ifdef FEAT_FLOAT
    {"exp",		1, 1, f_exp},
#endif
    {"expand",		1, 3, f_expand},
    {"extend",		2, 3, f_extend},
    {"feedkeys",	1, 2, f_feedkeys},
    {"file_readable",	1, 1, f_filereadable},	/* obsolete */
    {"filereadable",	1, 1, f_filereadable},
    {"filewritable",	1, 1, f_filewritable},
    {"filter",		2, 2, f_filter},
    {"finddir",		1, 3, f_finddir},
    {"findfile",	1, 3, f_findfile},
#ifdef FEAT_FLOAT
    {"float2nr",	1, 1, f_float2nr},
    {"floor",		1, 1, f_floor},
    {"fmod",		2, 2, f_fmod},
#endif
    {"fnameescape",	1, 1, f_fnameescape},
    {"fnamemodify",	2, 2, f_fnamemodify},
    {"foldclosed",	1, 1, f_foldclosed},
    {"foldclosedend",	1, 1, f_foldclosedend},
    {"foldlevel",	1, 1, f_foldlevel},
    {"foldtext",	0, 0, f_foldtext},
    {"foldtextresult",	1, 1, f_foldtextresult},
    {"foreground",	0, 0, f_foreground},
    {"funcref",		1, 3, f_funcref},
    {"function",	1, 3, f_function},
    {"garbagecollect",	0, 1, f_garbagecollect},
    {"get",		2, 3, f_get},
    {"getbufinfo",	0, 1, f_getbufinfo},
    {"getbufline",	2, 3, f_getbufline},
    {"getbufvar",	2, 3, f_getbufvar},
    {"getchangelist",	1, 1, f_getchangelist},
    {"getchar",		0, 1, f_getchar},
    {"getcharmod",	0, 0, f_getcharmod},
    {"getcharsearch",	0, 0, f_getcharsearch},
    {"getcmdline",	0, 0, f_getcmdline},
    {"getcmdpos",	0, 0, f_getcmdpos},
    {"getcmdtype",	0, 0, f_getcmdtype},
    {"getcmdwintype",	0, 0, f_getcmdwintype},
#if defined(FEAT_CMDL_COMPL)
    {"getcompletion",	2, 3, f_getcompletion},
#endif
    {"getcurpos",	0, 0, f_getcurpos},
    {"getcwd",		0, 2, f_getcwd},
    {"getfontname",	0, 1, f_getfontname},
    {"getfperm",	1, 1, f_getfperm},
    {"getfsize",	1, 1, f_getfsize},
    {"getftime",	1, 1, f_getftime},
    {"getftype",	1, 1, f_getftype},
    {"getjumplist",	0, 2, f_getjumplist},
    {"getline",		1, 2, f_getline},
    {"getloclist",	1, 2, f_getloclist},
    {"getmatches",	0, 0, f_getmatches},
    {"getpid",		0, 0, f_getpid},
    {"getpos",		1, 1, f_getpos},
    {"getqflist",	0, 1, f_getqflist},
    {"getreg",		0, 3, f_getreg},
    {"getregtype",	0, 1, f_getregtype},
    {"gettabinfo",	0, 1, f_gettabinfo},
    {"gettabvar",	2, 3, f_gettabvar},
    {"gettabwinvar",	3, 4, f_gettabwinvar},
    {"gettagstack",	0, 1, f_gettagstack},
    {"getwininfo",	0, 1, f_getwininfo},
    {"getwinpos",	0, 1, f_getwinpos},
    {"getwinposx",	0, 0, f_getwinposx},
    {"getwinposy",	0, 0, f_getwinposy},
    {"getwinvar",	2, 3, f_getwinvar},
    {"glob",		1, 4, f_glob},
    {"glob2regpat",	1, 1, f_glob2regpat},
    {"globpath",	2, 5, f_globpath},
    {"has",		1, 1, f_has},
    {"has_key",		2, 2, f_has_key},
    {"haslocaldir",	0, 2, f_haslocaldir},
    {"hasmapto",	1, 3, f_hasmapto},
    {"highlightID",	1, 1, f_hlID},		/* obsolete */
    {"highlight_exists",1, 1, f_hlexists},	/* obsolete */
    {"histadd",		2, 2, f_histadd},
    {"histdel",		1, 2, f_histdel},
    {"histget",		1, 2, f_histget},
    {"histnr",		1, 1, f_histnr},
    {"hlID",		1, 1, f_hlID},
    {"hlexists",	1, 1, f_hlexists},
    {"hostname",	0, 0, f_hostname},
    {"iconv",		3, 3, f_iconv},
    {"indent",		1, 1, f_indent},
    {"index",		2, 4, f_index},
    {"input",		1, 3, f_input},
    {"inputdialog",	1, 3, f_inputdialog},
    {"inputlist",	1, 1, f_inputlist},
    {"inputrestore",	0, 0, f_inputrestore},
    {"inputsave",	0, 0, f_inputsave},
    {"inputsecret",	1, 2, f_inputsecret},
    {"insert",		2, 3, f_insert},
    {"invert",		1, 1, f_invert},
    {"isdirectory",	1, 1, f_isdirectory},
    {"islocked",	1, 1, f_islocked},
#if defined(FEAT_FLOAT) && defined(HAVE_MATH_H)
    {"isnan",		1, 1, f_isnan},
#endif
    {"items",		1, 1, f_items},
#ifdef FEAT_JOB_CHANNEL
    {"job_getchannel",	1, 1, f_job_getchannel},
    {"job_info",	0, 1, f_job_info},
    {"job_setoptions",	2, 2, f_job_setoptions},
    {"job_start",	1, 2, f_job_start},
    {"job_status",	1, 1, f_job_status},
    {"job_stop",	1, 2, f_job_stop},
#endif
    {"join",		1, 2, f_join},
    {"js_decode",	1, 1, f_js_decode},
    {"js_encode",	1, 1, f_js_encode},
    {"json_decode",	1, 1, f_json_decode},
    {"json_encode",	1, 1, f_json_encode},
    {"keys",		1, 1, f_keys},
    {"last_buffer_nr",	0, 0, f_last_buffer_nr},/* obsolete */
    {"len",		1, 1, f_len},
    {"libcall",		3, 3, f_libcall},
    {"libcallnr",	3, 3, f_libcallnr},
    {"line",		1, 1, f_line},
    {"line2byte",	1, 1, f_line2byte},
    {"lispindent",	1, 1, f_lispindent},
    {"localtime",	0, 0, f_localtime},
#ifdef FEAT_FLOAT
    {"log",		1, 1, f_log},
    {"log10",		1, 1, f_log10},
#endif
#ifdef FEAT_LUA
    {"luaeval",		1, 2, f_luaeval},
#endif
    {"map",		2, 2, f_map},
    {"maparg",		1, 4, f_maparg},
    {"mapcheck",	1, 3, f_mapcheck},
    {"match",		2, 4, f_match},
    {"matchadd",	2, 5, f_matchadd},
    {"matchaddpos",	2, 5, f_matchaddpos},
    {"matcharg",	1, 1, f_matcharg},
    {"matchdelete",	1, 1, f_matchdelete},
    {"matchend",	2, 4, f_matchend},
    {"matchlist",	2, 4, f_matchlist},
    {"matchstr",	2, 4, f_matchstr},
    {"matchstrpos",	2, 4, f_matchstrpos},
    {"max",		1, 1, f_max},
    {"min",		1, 1, f_min},
#ifdef vim_mkdir
    {"mkdir",		1, 3, f_mkdir},
#endif
    {"mode",		0, 1, f_mode},
#ifdef FEAT_MZSCHEME
    {"mzeval",		1, 1, f_mzeval},
#endif
    {"nextnonblank",	1, 1, f_nextnonblank},
    {"nr2char",		1, 2, f_nr2char},
    {"or",		2, 2, f_or},
    {"pathshorten",	1, 1, f_pathshorten},
#ifdef FEAT_PERL
    {"perleval",	1, 1, f_perleval},
#endif
#ifdef FEAT_FLOAT
    {"pow",		2, 2, f_pow},
#endif
    {"prevnonblank",	1, 1, f_prevnonblank},
    {"printf",		1, 19, f_printf},
#ifdef FEAT_JOB_CHANNEL
    {"prompt_setcallback", 2, 2, f_prompt_setcallback},
    {"prompt_setinterrupt", 2, 2, f_prompt_setinterrupt},
    {"prompt_setprompt", 2, 2, f_prompt_setprompt},
#endif
#ifdef FEAT_TEXT_PROP
    {"prop_add",	3, 3, f_prop_add},
    {"prop_clear",	1, 3, f_prop_clear},
    {"prop_list",	1, 2, f_prop_list},
    {"prop_remove",	2, 3, f_prop_remove},
    {"prop_type_add",	2, 2, f_prop_type_add},
    {"prop_type_change", 2, 2, f_prop_type_change},
    {"prop_type_delete", 1, 2, f_prop_type_delete},
    {"prop_type_get",	1, 2, f_prop_type_get},
    {"prop_type_list",	0, 1, f_prop_type_list},
#endif
    {"pumvisible",	0, 0, f_pumvisible},
#ifdef FEAT_PYTHON3
    {"py3eval",		1, 1, f_py3eval},
#endif
#ifdef FEAT_PYTHON
    {"pyeval",		1, 1, f_pyeval},
#endif
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
    {"pyxeval",		1, 1, f_pyxeval},
#endif
    {"range",		1, 3, f_range},
    {"readfile",	1, 3, f_readfile},
    {"reg_executing",	0, 0, f_reg_executing},
    {"reg_recording",	0, 0, f_reg_recording},
    {"reltime",		0, 2, f_reltime},
#ifdef FEAT_FLOAT
    {"reltimefloat",	1, 1, f_reltimefloat},
#endif
    {"reltimestr",	1, 1, f_reltimestr},
    {"remote_expr",	2, 4, f_remote_expr},
    {"remote_foreground", 1, 1, f_remote_foreground},
    {"remote_peek",	1, 2, f_remote_peek},
    {"remote_read",	1, 2, f_remote_read},
    {"remote_send",	2, 3, f_remote_send},
    {"remote_startserver", 1, 1, f_remote_startserver},
    {"remove",		2, 3, f_remove},
    {"rename",		2, 2, f_rename},
    {"repeat",		2, 2, f_repeat},
    {"resolve",		1, 1, f_resolve},
    {"reverse",		1, 1, f_reverse},
#ifdef FEAT_FLOAT
    {"round",		1, 1, f_round},
#endif
    {"screenattr",	2, 2, f_screenattr},
    {"screenchar",	2, 2, f_screenchar},
    {"screencol",	0, 0, f_screencol},
    {"screenrow",	0, 0, f_screenrow},
    {"search",		1, 4, f_search},
    {"searchdecl",	1, 3, f_searchdecl},
    {"searchpair",	3, 7, f_searchpair},
    {"searchpairpos",	3, 7, f_searchpairpos},
    {"searchpos",	1, 4, f_searchpos},
    {"server2client",	2, 2, f_server2client},
    {"serverlist",	0, 0, f_serverlist},
    {"setbufline",	3, 3, f_setbufline},
    {"setbufvar",	3, 3, f_setbufvar},
    {"setcharsearch",	1, 1, f_setcharsearch},
    {"setcmdpos",	1, 1, f_setcmdpos},
    {"setfperm",	2, 2, f_setfperm},
    {"setline",		2, 2, f_setline},
    {"setloclist",	2, 4, f_setloclist},
    {"setmatches",	1, 1, f_setmatches},
    {"setpos",		2, 2, f_setpos},
    {"setqflist",	1, 3, f_setqflist},
    {"setreg",		2, 3, f_setreg},
    {"settabvar",	3, 3, f_settabvar},
    {"settabwinvar",	4, 4, f_settabwinvar},
    {"settagstack",	2, 3, f_settagstack},
    {"setwinvar",	3, 3, f_setwinvar},
#ifdef FEAT_CRYPT
    {"sha256",		1, 1, f_sha256},
#endif
    {"shellescape",	1, 2, f_shellescape},
    {"shiftwidth",	0, 1, f_shiftwidth},
#ifdef FEAT_SIGNS
    {"sign_define",	1, 2, f_sign_define},
    {"sign_getdefined",	0, 1, f_sign_getdefined},
    {"sign_getplaced",	0, 2, f_sign_getplaced},
    {"sign_place",	4, 5, f_sign_place},
    {"sign_undefine",	0, 1, f_sign_undefine},
    {"sign_unplace",	1, 2, f_sign_unplace},
#endif
    {"simplify",	1, 1, f_simplify},
#ifdef FEAT_FLOAT
    {"sin",		1, 1, f_sin},
    {"sinh",		1, 1, f_sinh},
#endif
    {"sort",		1, 3, f_sort},
    {"soundfold",	1, 1, f_soundfold},
    {"spellbadword",	0, 1, f_spellbadword},
    {"spellsuggest",	1, 3, f_spellsuggest},
    {"split",		1, 3, f_split},
#ifdef FEAT_FLOAT
    {"sqrt",		1, 1, f_sqrt},
    {"str2float",	1, 1, f_str2float},
#endif
    {"str2nr",		1, 2, f_str2nr},
    {"strcharpart",	2, 3, f_strcharpart},
    {"strchars",	1, 2, f_strchars},
    {"strdisplaywidth",	1, 2, f_strdisplaywidth},
#ifdef HAVE_STRFTIME
    {"strftime",	1, 2, f_strftime},
#endif
    {"strgetchar",	2, 2, f_strgetchar},
    {"stridx",		2, 3, f_stridx},
    {"string",		1, 1, f_string},
    {"strlen",		1, 1, f_strlen},
    {"strpart",		2, 3, f_strpart},
    {"strridx",		2, 3, f_strridx},
    {"strtrans",	1, 1, f_strtrans},
    {"strwidth",	1, 1, f_strwidth},
    {"submatch",	1, 2, f_submatch},
    {"substitute",	4, 4, f_substitute},
    {"swapinfo",	1, 1, f_swapinfo},
    {"swapname",	1, 1, f_swapname},
    {"synID",		3, 3, f_synID},
    {"synIDattr",	2, 3, f_synIDattr},
    {"synIDtrans",	1, 1, f_synIDtrans},
    {"synconcealed",	2, 2, f_synconcealed},
    {"synstack",	2, 2, f_synstack},
    {"system",		1, 2, f_system},
    {"systemlist",	1, 2, f_systemlist},
    {"tabpagebuflist",	0, 1, f_tabpagebuflist},
    {"tabpagenr",	0, 1, f_tabpagenr},
    {"tabpagewinnr",	1, 2, f_tabpagewinnr},
    {"tagfiles",	0, 0, f_tagfiles},
    {"taglist",		1, 2, f_taglist},
#ifdef FEAT_FLOAT
    {"tan",		1, 1, f_tan},
    {"tanh",		1, 1, f_tanh},
#endif
    {"tempname",	0, 0, f_tempname},
#ifdef FEAT_TERMINAL
    {"term_dumpdiff",	2, 3, f_term_dumpdiff},
    {"term_dumpload",	1, 2, f_term_dumpload},
    {"term_dumpwrite",	2, 3, f_term_dumpwrite},
    {"term_getaltscreen", 1, 1, f_term_getaltscreen},
# if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
    {"term_getansicolors", 1, 1, f_term_getansicolors},
# endif
    {"term_getattr",	2, 2, f_term_getattr},
    {"term_getcursor",	1, 1, f_term_getcursor},
    {"term_getjob",	1, 1, f_term_getjob},
    {"term_getline",	2, 2, f_term_getline},
    {"term_getscrolled", 1, 1, f_term_getscrolled},
    {"term_getsize",	1, 1, f_term_getsize},
    {"term_getstatus",	1, 1, f_term_getstatus},
    {"term_gettitle",	1, 1, f_term_gettitle},
    {"term_gettty",	1, 2, f_term_gettty},
    {"term_list",	0, 0, f_term_list},
    {"term_scrape",	2, 2, f_term_scrape},
    {"term_sendkeys",	2, 2, f_term_sendkeys},
# if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
    {"term_setansicolors", 2, 2, f_term_setansicolors},
# endif
    {"term_setkill",	2, 2, f_term_setkill},
    {"term_setrestore",	2, 2, f_term_setrestore},
    {"term_setsize",	3, 3, f_term_setsize},
    {"term_start",	1, 2, f_term_start},
    {"term_wait",	1, 2, f_term_wait},
#endif
    {"test_alloc_fail",	3, 3, f_test_alloc_fail},
    {"test_autochdir",	0, 0, f_test_autochdir},
    {"test_feedinput",	1, 1, f_test_feedinput},
    {"test_garbagecollect_now",	0, 0, f_test_garbagecollect_now},
    {"test_ignore_error",	1, 1, f_test_ignore_error},
#ifdef FEAT_JOB_CHANNEL
    {"test_null_channel", 0, 0, f_test_null_channel},
#endif
    {"test_null_dict",	0, 0, f_test_null_dict},
#ifdef FEAT_JOB_CHANNEL
    {"test_null_job",	0, 0, f_test_null_job},
#endif
    {"test_null_list",	0, 0, f_test_null_list},
    {"test_null_partial", 0, 0, f_test_null_partial},
    {"test_null_string", 0, 0, f_test_null_string},
    {"test_option_not_set", 1, 1, f_test_option_not_set},
    {"test_override",    2, 2, f_test_override},
#ifdef FEAT_GUI
    {"test_scrollbar",	3, 3, f_test_scrollbar},
#endif
    {"test_settime",	1, 1, f_test_settime},
#ifdef FEAT_TIMERS
    {"timer_info",	0, 1, f_timer_info},
    {"timer_pause",	2, 2, f_timer_pause},
    {"timer_start",	2, 3, f_timer_start},
    {"timer_stop",	1, 1, f_timer_stop},
    {"timer_stopall",	0, 0, f_timer_stopall},
#endif
    {"tolower",		1, 1, f_tolower},
    {"toupper",		1, 1, f_toupper},
    {"tr",		3, 3, f_tr},
    {"trim",		1, 2, f_trim},
#ifdef FEAT_FLOAT
    {"trunc",		1, 1, f_trunc},
#endif
    {"type",		1, 1, f_type},
    {"undofile",	1, 1, f_undofile},
    {"undotree",	0, 0, f_undotree},
    {"uniq",		1, 3, f_uniq},
    {"values",		1, 1, f_values},
    {"virtcol",		1, 1, f_virtcol},
    {"visualmode",	0, 1, f_visualmode},
    {"wildmenumode",	0, 0, f_wildmenumode},
    {"win_findbuf",	1, 1, f_win_findbuf},
    {"win_getid",	0, 2, f_win_getid},
    {"win_gotoid",	1, 1, f_win_gotoid},
    {"win_id2tabwin",	1, 1, f_win_id2tabwin},
    {"win_id2win",	1, 1, f_win_id2win},
    {"win_screenpos",	1, 1, f_win_screenpos},
    {"winbufnr",	1, 1, f_winbufnr},
    {"wincol",		0, 0, f_wincol},
    {"winheight",	1, 1, f_winheight},
    {"winlayout",	0, 1, f_winlayout},
    {"winline",		0, 0, f_winline},
    {"winnr",		0, 1, f_winnr},
    {"winrestcmd",	0, 0, f_winrestcmd},
    {"winrestview",	1, 1, f_winrestview},
    {"winsaveview",	0, 0, f_winsaveview},
    {"winwidth",	1, 1, f_winwidth},
    {"wordcount",	0, 0, f_wordcount},
    {"writefile",	2, 3, f_writefile},
    {"xor",		2, 2, f_xor},
};

#if defined(FEAT_CMDL_COMPL) || defined(PROTO)

/*
 * Function given to ExpandGeneric() to obtain the list of internal
 * or user defined function names.
 */
    char_u *
get_function_name(expand_T *xp, int idx)
{
    static int	intidx = -1;
    char_u	*name;

    if (idx == 0)
	intidx = -1;
    if (intidx < 0)
    {
	name = get_user_func_name(xp, idx);
	if (name != NULL)
	    return name;
    }
    if (++intidx < (int)(sizeof(functions) / sizeof(struct fst)))
    {
	STRCPY(IObuff, functions[intidx].f_name);
	STRCAT(IObuff, "(");
	if (functions[intidx].f_max_argc == 0)
	    STRCAT(IObuff, ")");
	return IObuff;
    }

    return NULL;
}

/*
 * Function given to ExpandGeneric() to obtain the list of internal or
 * user defined variable or function names.
 */
    char_u *
get_expr_name(expand_T *xp, int idx)
{
    static int	intidx = -1;
    char_u	*name;

    if (idx == 0)
	intidx = -1;
    if (intidx < 0)
    {
	name = get_function_name(xp, idx);
	if (name != NULL)
	    return name;
    }
    return get_user_var_name(xp, ++intidx);
}

#endif /* FEAT_CMDL_COMPL */

/*
 * Find internal function in table above.
 * Return index, or -1 if not found
 */
    int
find_internal_func(
    char_u	*name)		/* name of the function */
{
    int		first = 0;
    int		last = (int)(sizeof(functions) / sizeof(struct fst)) - 1;
    int		cmp;
    int		x;

    /*
     * Find the function name in the table. Binary search.
     */
    while (first <= last)
    {
	x = first + ((unsigned)(last - first) >> 1);
	cmp = STRCMP(name, functions[x].f_name);
	if (cmp < 0)
	    last = x - 1;
	else if (cmp > 0)
	    first = x + 1;
	else
	    return x;
    }
    return -1;
}

    int
call_internal_func(
	char_u	    *name,
	int	    argcount,
	typval_T    *argvars,
	typval_T    *rettv)
{
    int i;

    i = find_internal_func(name);
    if (i < 0)
	return ERROR_UNKNOWN;
    if (argcount < functions[i].f_min_argc)
	return ERROR_TOOFEW;
    if (argcount > functions[i].f_max_argc)
	return ERROR_TOOMANY;
    argvars[argcount].v_type = VAR_UNKNOWN;
    functions[i].f_func(argvars, rettv);
    return ERROR_NONE;
}

/*
 * Return TRUE for a non-zero Number and a non-empty String.
 */
    static int
non_zero_arg(typval_T *argvars)
{
    return ((argvars[0].v_type == VAR_NUMBER
		&& argvars[0].vval.v_number != 0)
	    || (argvars[0].v_type == VAR_SPECIAL
		&& argvars[0].vval.v_number == VVAL_TRUE)
	    || (argvars[0].v_type == VAR_STRING
		&& argvars[0].vval.v_string != NULL
		&& *argvars[0].vval.v_string != NUL));
}

/*
 * Get the lnum from the first argument.
 * Also accepts ".", "$", etc., but that only works for the current buffer.
 * Returns -1 on error.
 */
    static linenr_T
tv_get_lnum(typval_T *argvars)
{
    typval_T	rettv;
    linenr_T	lnum;

    lnum = (linenr_T)tv_get_number_chk(&argvars[0], NULL);
    if (lnum == 0)  /* no valid number, try using line() */
    {
	rettv.v_type = VAR_NUMBER;
	f_line(argvars, &rettv);
	lnum = (linenr_T)rettv.vval.v_number;
	clear_tv(&rettv);
    }
    return lnum;
}

/*
 * Get the lnum from the first argument.
 * Also accepts "$", then "buf" is used.
 * Returns 0 on error.
 */
    static linenr_T
tv_get_lnum_buf(typval_T *argvars, buf_T *buf)
{
    if (argvars[0].v_type == VAR_STRING
	    && argvars[0].vval.v_string != NULL
	    && argvars[0].vval.v_string[0] == '$'
	    && buf != NULL)
	return buf->b_ml.ml_line_count;
    return (linenr_T)tv_get_number_chk(&argvars[0], NULL);
}

#ifdef FEAT_FLOAT
/*
 * Get the float value of "argvars[0]" into "f".
 * Returns FAIL when the argument is not a Number or Float.
 */
    static int
get_float_arg(typval_T *argvars, float_T *f)
{
    if (argvars[0].v_type == VAR_FLOAT)
    {
	*f = argvars[0].vval.v_float;
	return OK;
    }
    if (argvars[0].v_type == VAR_NUMBER)
    {
	*f = (float_T)argvars[0].vval.v_number;
	return OK;
    }
    EMSG(_("E808: Number or Float required"));
    return FAIL;
}

/*
 * "abs(expr)" function
 */
    static void
f_abs(typval_T *argvars, typval_T *rettv)
{
    if (argvars[0].v_type == VAR_FLOAT)
    {
	rettv->v_type = VAR_FLOAT;
	rettv->vval.v_float = fabs(argvars[0].vval.v_float);
    }
    else
    {
	varnumber_T	n;
	int		error = FALSE;

	n = tv_get_number_chk(&argvars[0], &error);
	if (error)
	    rettv->vval.v_number = -1;
	else if (n > 0)
	    rettv->vval.v_number = n;
	else
	    rettv->vval.v_number = -n;
    }
}

/*
 * "acos()" function
 */
    static void
f_acos(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = acos(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "add(list, item)" function
 */
    static void
f_add(typval_T *argvars, typval_T *rettv)
{
    list_T	*l;

    rettv->vval.v_number = 1; /* Default: Failed */
    if (argvars[0].v_type == VAR_LIST)
    {
	if ((l = argvars[0].vval.v_list) != NULL
		&& !tv_check_lock(l->lv_lock,
					 (char_u *)N_("add() argument"), TRUE)
		&& list_append_tv(l, &argvars[1]) == OK)
	    copy_tv(&argvars[0], rettv);
    }
    else
	EMSG(_(e_listreq));
}

/*
 * "and(expr, expr)" function
 */
    static void
f_and(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
					& tv_get_number_chk(&argvars[1], NULL);
}

/*
 * If there is a window for "curbuf", make it the current window.
 */
    static void
find_win_for_curbuf(void)
{
    wininfo_T *wip;

    for (wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next)
    {
	if (wip->wi_win != NULL)
	{
	    curwin = wip->wi_win;
	    break;
	}
    }
}

/*
 * Set line or list of lines in buffer "buf".
 */
    static void
set_buffer_lines(
	buf_T	    *buf,
	linenr_T    lnum_arg,
	int	    append,
	typval_T    *lines,
	typval_T    *rettv)
{
    linenr_T    lnum = lnum_arg + (append ? 1 : 0);
    char_u	*line = NULL;
    list_T	*l = NULL;
    listitem_T	*li = NULL;
    long	added = 0;
    linenr_T	append_lnum;
    buf_T	*curbuf_save = NULL;
    win_T	*curwin_save = NULL;
    int		is_curbuf = buf == curbuf;

    /* When using the current buffer ml_mfp will be set if needed.  Useful when
     * setline() is used on startup.  For other buffers the buffer must be
     * loaded. */
    if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1)
    {
	rettv->vval.v_number = 1;	/* FAIL */
	return;
    }

    if (!is_curbuf)
    {
	curbuf_save = curbuf;
	curwin_save = curwin;
	curbuf = buf;
	find_win_for_curbuf();
    }

    if (append)
	// appendbufline() uses the line number below which we insert
	append_lnum = lnum - 1;
    else
	// setbufline() uses the line number above which we insert, we only
	// append if it's below the last line
	append_lnum = curbuf->b_ml.ml_line_count;

    if (lines->v_type == VAR_LIST)
    {
	l = lines->vval.v_list;
	li = l->lv_first;
    }
    else
	line = tv_get_string_chk(lines);

    /* default result is zero == OK */
    for (;;)
    {
	if (l != NULL)
	{
	    /* list argument, get next string */
	    if (li == NULL)
		break;
	    line = tv_get_string_chk(&li->li_tv);
	    li = li->li_next;
	}

	rettv->vval.v_number = 1;	/* FAIL */
	if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1)
	    break;

	/* When coming here from Insert mode, sync undo, so that this can be
	 * undone separately from what was previously inserted. */
	if (u_sync_once == 2)
	{
	    u_sync_once = 1; /* notify that u_sync() was called */
	    u_sync(TRUE);
	}

	if (!append && lnum <= curbuf->b_ml.ml_line_count)
	{
	    /* existing line, replace it */
	    if (u_savesub(lnum) == OK && ml_replace(lnum, line, TRUE) == OK)
	    {
		changed_bytes(lnum, 0);
		if (is_curbuf && lnum == curwin->w_cursor.lnum)
		    check_cursor_col();
		rettv->vval.v_number = 0;	/* OK */
	    }
	}
	else if (added > 0 || u_save(lnum - 1, lnum) == OK)
	{
	    /* append the line */
	    ++added;
	    if (ml_append(lnum - 1, line, (colnr_T)0, FALSE) == OK)
		rettv->vval.v_number = 0;	/* OK */
	}

	if (l == NULL)			/* only one string argument */
	    break;
	++lnum;
    }

    if (added > 0)
    {
	win_T	    *wp;
	tabpage_T   *tp;

	appended_lines_mark(append_lnum, added);
	FOR_ALL_TAB_WINDOWS(tp, wp)
	    if (wp->w_buffer == buf && wp->w_cursor.lnum > append_lnum)
		wp->w_cursor.lnum += added;
	check_cursor_col();

#ifdef FEAT_JOB_CHANNEL
	if (bt_prompt(curbuf) && (State & INSERT))
	    // show the line with the prompt
	    update_topline();
#endif
    }

    if (!is_curbuf)
    {
	curbuf = curbuf_save;
	curwin = curwin_save;
    }
}

/*
 * "append(lnum, string/list)" function
 */
    static void
f_append(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum = tv_get_lnum(&argvars[0]);

    set_buffer_lines(curbuf, lnum, TRUE, &argvars[1], rettv);
}

/*
 * "appendbufline(buf, lnum, string/list)" function
 */
    static void
f_appendbufline(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;
    buf_T	*buf;

    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL)
	rettv->vval.v_number = 1; /* FAIL */
    else
    {
	lnum = tv_get_lnum_buf(&argvars[1], buf);
	set_buffer_lines(buf, lnum, TRUE, &argvars[2], rettv);
    }
}

/*
 * "argc([window id])" function
 */
    static void
f_argc(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    if (argvars[0].v_type == VAR_UNKNOWN)
	// use the current window
	rettv->vval.v_number = ARGCOUNT;
    else if (argvars[0].v_type == VAR_NUMBER
					   && tv_get_number(&argvars[0]) == -1)
	// use the global argument list
	rettv->vval.v_number = GARGCOUNT;
    else
    {
	// use the argument list of the specified window
	wp = find_win_by_nr_or_id(&argvars[0]);
	if (wp != NULL)
	    rettv->vval.v_number = WARGCOUNT(wp);
	else
	    rettv->vval.v_number = -1;
    }
}

/*
 * "argidx()" function
 */
    static void
f_argidx(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = curwin->w_arg_idx;
}

/*
 * "arglistid()" function
 */
    static void
f_arglistid(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    rettv->vval.v_number = -1;
    wp = find_tabwin(&argvars[0], &argvars[1]);
    if (wp != NULL)
	rettv->vval.v_number = wp->w_alist->id;
}

/*
 * Get the argument list for a given window
 */
    static void
get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv)
{
    int		idx;

    if (rettv_list_alloc(rettv) == OK && arglist != NULL)
	for (idx = 0; idx < argcount; ++idx)
	    list_append_string(rettv->vval.v_list,
						alist_name(&arglist[idx]), -1);
}

/*
 * "argv(nr)" function
 */
    static void
f_argv(typval_T *argvars, typval_T *rettv)
{
    int		idx;
    aentry_T	*arglist = NULL;
    int		argcount = -1;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	if (argvars[1].v_type == VAR_UNKNOWN)
	{
	    arglist = ARGLIST;
	    argcount = ARGCOUNT;
	}
	else if (argvars[1].v_type == VAR_NUMBER
					   && tv_get_number(&argvars[1]) == -1)
	{
	    arglist = GARGLIST;
	    argcount = GARGCOUNT;
	}
	else
	{
	    win_T	*wp = find_win_by_nr_or_id(&argvars[1]);

	    if (wp != NULL)
	    {
		/* Use the argument list of the specified window */
		arglist = WARGLIST(wp);
		argcount = WARGCOUNT(wp);
	    }
	}

	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = NULL;
	idx = tv_get_number_chk(&argvars[0], NULL);
	if (arglist != NULL && idx >= 0 && idx < argcount)
	    rettv->vval.v_string = vim_strsave(alist_name(&arglist[idx]));
	else if (idx == -1)
	    get_arglist_as_rettv(arglist, argcount, rettv);
    }
    else
	get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
}

/*
 * "assert_beeps(cmd [, error])" function
 */
    static void
f_assert_beeps(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_beeps(argvars);
}

/*
 * "assert_equal(expected, actual[, msg])" function
 */
    static void
f_assert_equal(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
}

/*
 * "assert_equalfile(fname-one, fname-two)" function
 */
    static void
f_assert_equalfile(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_equalfile(argvars);
}

/*
 * "assert_notequal(expected, actual[, msg])" function
 */
    static void
f_assert_notequal(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
}

/*
 * "assert_exception(string[, msg])" function
 */
    static void
f_assert_exception(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_exception(argvars);
}

/*
 * "assert_fails(cmd [, error[, msg]])" function
 */
    static void
f_assert_fails(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_fails(argvars);
}

/*
 * "assert_false(actual[, msg])" function
 */
    static void
f_assert_false(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_bool(argvars, FALSE);
}

/*
 * "assert_inrange(lower, upper[, msg])" function
 */
    static void
f_assert_inrange(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_inrange(argvars);
}

/*
 * "assert_match(pattern, actual[, msg])" function
 */
    static void
f_assert_match(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
}

/*
 * "assert_notmatch(pattern, actual[, msg])" function
 */
    static void
f_assert_notmatch(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
}

/*
 * "assert_report(msg)" function
 */
    static void
f_assert_report(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_report(argvars);
}

/*
 * "assert_true(actual[, msg])" function
 */
    static void
f_assert_true(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_bool(argvars, TRUE);
}

#ifdef FEAT_FLOAT
/*
 * "asin()" function
 */
    static void
f_asin(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = asin(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "atan()" function
 */
    static void
f_atan(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = atan(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "atan2()" function
 */
    static void
f_atan2(typval_T *argvars, typval_T *rettv)
{
    float_T	fx = 0.0, fy = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &fx) == OK
				     && get_float_arg(&argvars[1], &fy) == OK)
	rettv->vval.v_float = atan2(fx, fy);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "balloon_show()" function
 */
#ifdef FEAT_BEVAL
    static void
f_balloon_show(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (balloonEval != NULL)
    {
	if (argvars[0].v_type == VAR_LIST
# ifdef FEAT_GUI
		&& !gui.in_use
# endif
	   )
	    post_balloon(balloonEval, NULL, argvars[0].vval.v_list);
	else
	    post_balloon(balloonEval, tv_get_string_chk(&argvars[0]), NULL);
    }
}

# if defined(FEAT_BEVAL_TERM)
    static void
f_balloon_split(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (rettv_list_alloc(rettv) == OK)
    {
	char_u *msg = tv_get_string_chk(&argvars[0]);

	if (msg != NULL)
	{
	    pumitem_T	*array;
	    int		size = split_message(msg, &array);
	    int		i;

	    /* Skip the first and last item, they are always empty. */
	    for (i = 1; i < size - 1; ++i)
		list_append_string(rettv->vval.v_list, array[i].pum_text, -1);
	    while (size > 0)
		vim_free(array[--size].pum_text);
	    vim_free(array);
	}
    }
}
# endif
#endif

/*
 * "browse(save, title, initdir, default)" function
 */
    static void
f_browse(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_BROWSE
    int		save;
    char_u	*title;
    char_u	*initdir;
    char_u	*defname;
    char_u	buf[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    int		error = FALSE;

    save = (int)tv_get_number_chk(&argvars[0], &error);
    title = tv_get_string_chk(&argvars[1]);
    initdir = tv_get_string_buf_chk(&argvars[2], buf);
    defname = tv_get_string_buf_chk(&argvars[3], buf2);

    if (error || title == NULL || initdir == NULL || defname == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string =
		 do_browse(save ? BROWSE_SAVE : 0,
				 title, defname, NULL, initdir, NULL, curbuf);
#else
    rettv->vval.v_string = NULL;
#endif
    rettv->v_type = VAR_STRING;
}

/*
 * "browsedir(title, initdir)" function
 */
    static void
f_browsedir(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_BROWSE
    char_u	*title;
    char_u	*initdir;
    char_u	buf[NUMBUFLEN];

    title = tv_get_string_chk(&argvars[0]);
    initdir = tv_get_string_buf_chk(&argvars[1], buf);

    if (title == NULL || initdir == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = do_browse(BROWSE_DIR,
				    title, NULL, NULL, initdir, NULL, curbuf);
#else
    rettv->vval.v_string = NULL;
#endif
    rettv->v_type = VAR_STRING;
}

/*
 * Find a buffer by number or exact name.
 */
    static buf_T *
find_buffer(typval_T *avar)
{
    buf_T	*buf = NULL;

    if (avar->v_type == VAR_NUMBER)
	buf = buflist_findnr((int)avar->vval.v_number);
    else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL)
    {
	buf = buflist_findname_exp(avar->vval.v_string);
	if (buf == NULL)
	{
	    /* No full path name match, try a match with a URL or a "nofile"
	     * buffer, these don't use the full path. */
	    FOR_ALL_BUFFERS(buf)
		if (buf->b_fname != NULL
			&& (path_with_url(buf->b_fname)
#ifdef FEAT_QUICKFIX
			    || bt_nofile(buf)
#endif
			   )
			&& STRCMP(buf->b_fname, avar->vval.v_string) == 0)
		    break;
	}
    }
    return buf;
}

/*
 * "bufexists(expr)" function
 */
    static void
f_bufexists(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
}

/*
 * "buflisted(expr)" function
 */
    static void
f_buflisted(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;

    buf = find_buffer(&argvars[0]);
    rettv->vval.v_number = (buf != NULL && buf->b_p_bl);
}

/*
 * "bufloaded(expr)" function
 */
    static void
f_bufloaded(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;

    buf = find_buffer(&argvars[0]);
    rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
}

    buf_T *
buflist_find_by_name(char_u *name, int curtab_only)
{
    int		save_magic;
    char_u	*save_cpo;
    buf_T	*buf;

    /* Ignore 'magic' and 'cpoptions' here to make scripts portable */
    save_magic = p_magic;
    p_magic = TRUE;
    save_cpo = p_cpo;
    p_cpo = (char_u *)"";

    buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name),
						    TRUE, FALSE, curtab_only));

    p_magic = save_magic;
    p_cpo = save_cpo;
    return buf;
}

/*
 * Get buffer by number or pattern.
 */
    buf_T *
get_buf_tv(typval_T *tv, int curtab_only)
{
    char_u	*name = tv->vval.v_string;
    buf_T	*buf;

    if (tv->v_type == VAR_NUMBER)
	return buflist_findnr((int)tv->vval.v_number);
    if (tv->v_type != VAR_STRING)
	return NULL;
    if (name == NULL || *name == NUL)
	return curbuf;
    if (name[0] == '$' && name[1] == NUL)
	return lastbuf;

    buf = buflist_find_by_name(name, curtab_only);

    /* If not found, try expanding the name, like done for bufexists(). */
    if (buf == NULL)
	buf = find_buffer(tv);

    return buf;
}

/*
 * "bufname(expr)" function
 */
    static void
f_bufname(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;

    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    ++emsg_off;
    buf = get_buf_tv(&argvars[0], FALSE);
    rettv->v_type = VAR_STRING;
    if (buf != NULL && buf->b_fname != NULL)
	rettv->vval.v_string = vim_strsave(buf->b_fname);
    else
	rettv->vval.v_string = NULL;
    --emsg_off;
}

/*
 * "bufnr(expr)" function
 */
    static void
f_bufnr(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;
    int		error = FALSE;
    char_u	*name;

    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    ++emsg_off;
    buf = get_buf_tv(&argvars[0], FALSE);
    --emsg_off;

    /* If the buffer isn't found and the second argument is not zero create a
     * new buffer. */
    if (buf == NULL
	    && argvars[1].v_type != VAR_UNKNOWN
	    && tv_get_number_chk(&argvars[1], &error) != 0
	    && !error
	    && (name = tv_get_string_chk(&argvars[0])) != NULL
	    && !error)
	buf = buflist_new(name, NULL, (linenr_T)1, 0);

    if (buf != NULL)
	rettv->vval.v_number = buf->b_fnum;
    else
	rettv->vval.v_number = -1;
}

    static void
buf_win_common(typval_T *argvars, typval_T *rettv, int get_nr)
{
    win_T	*wp;
    int		winnr = 0;
    buf_T	*buf;

    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    ++emsg_off;
    buf = get_buf_tv(&argvars[0], TRUE);
    FOR_ALL_WINDOWS(wp)
    {
	++winnr;
	if (wp->w_buffer == buf)
	    break;
    }
    rettv->vval.v_number = (wp != NULL ? (get_nr ? winnr : wp->w_id) : -1);
    --emsg_off;
}

/*
 * "bufwinid(nr)" function
 */
    static void
f_bufwinid(typval_T *argvars, typval_T *rettv)
{
    buf_win_common(argvars, rettv, FALSE);
}

/*
 * "bufwinnr(nr)" function
 */
    static void
f_bufwinnr(typval_T *argvars, typval_T *rettv)
{
    buf_win_common(argvars, rettv, TRUE);
}

/*
 * "byte2line(byte)" function
 */
    static void
f_byte2line(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifndef FEAT_BYTEOFF
    rettv->vval.v_number = -1;
#else
    long	boff = 0;

    boff = tv_get_number(&argvars[0]) - 1;  /* boff gets -1 on type error */
    if (boff < 0)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = ml_find_line_or_offset(curbuf,
							  (linenr_T)0, &boff);
#endif
}

    static void
byteidx(typval_T *argvars, typval_T *rettv, int comp UNUSED)
{
#ifdef FEAT_MBYTE
    char_u	*t;
#endif
    char_u	*str;
    varnumber_T	idx;

    str = tv_get_string_chk(&argvars[0]);
    idx = tv_get_number_chk(&argvars[1], NULL);
    rettv->vval.v_number = -1;
    if (str == NULL || idx < 0)
	return;

#ifdef FEAT_MBYTE
    t = str;
    for ( ; idx > 0; idx--)
    {
	if (*t == NUL)		/* EOL reached */
	    return;
	if (enc_utf8 && comp)
	    t += utf_ptr2len(t);
	else
	    t += (*mb_ptr2len)(t);
    }
    rettv->vval.v_number = (varnumber_T)(t - str);
#else
    if ((size_t)idx <= STRLEN(str))
	rettv->vval.v_number = idx;
#endif
}

/*
 * "byteidx()" function
 */
    static void
f_byteidx(typval_T *argvars, typval_T *rettv)
{
    byteidx(argvars, rettv, FALSE);
}

/*
 * "byteidxcomp()" function
 */
    static void
f_byteidxcomp(typval_T *argvars, typval_T *rettv)
{
    byteidx(argvars, rettv, TRUE);
}

/*
 * "call(func, arglist [, dict])" function
 */
    static void
f_call(typval_T *argvars, typval_T *rettv)
{
    char_u	*func;
    partial_T   *partial = NULL;
    dict_T	*selfdict = NULL;

    if (argvars[1].v_type != VAR_LIST)
    {
	EMSG(_(e_listreq));
	return;
    }
    if (argvars[1].vval.v_list == NULL)
	return;

    if (argvars[0].v_type == VAR_FUNC)
	func = argvars[0].vval.v_string;
    else if (argvars[0].v_type == VAR_PARTIAL)
    {
	partial = argvars[0].vval.v_partial;
	func = partial_name(partial);
    }
    else
	func = tv_get_string(&argvars[0]);
    if (*func == NUL)
	return;		/* type error or empty name */

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	if (argvars[2].v_type != VAR_DICT)
	{
	    EMSG(_(e_dictreq));
	    return;
	}
	selfdict = argvars[2].vval.v_dict;
    }

    (void)func_call(func, &argvars[1], partial, selfdict, rettv);
}

#ifdef FEAT_FLOAT
/*
 * "ceil({float})" function
 */
    static void
f_ceil(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = ceil(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

#ifdef FEAT_JOB_CHANNEL
/*
 * "ch_canread()" function
 */
    static void
f_ch_canread(typval_T *argvars, typval_T *rettv)
{
    channel_T *channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    rettv->vval.v_number = 0;
    if (channel != NULL)
	rettv->vval.v_number = channel_has_readahead(channel, PART_SOCK)
			    || channel_has_readahead(channel, PART_OUT)
			    || channel_has_readahead(channel, PART_ERR);
}

/*
 * "ch_close()" function
 */
    static void
f_ch_close(typval_T *argvars, typval_T *rettv UNUSED)
{
    channel_T *channel = get_channel_arg(&argvars[0], TRUE, FALSE, 0);

    if (channel != NULL)
    {
	channel_close(channel, FALSE);
	channel_clear(channel);
    }
}

/*
 * "ch_close()" function
 */
    static void
f_ch_close_in(typval_T *argvars, typval_T *rettv UNUSED)
{
    channel_T *channel = get_channel_arg(&argvars[0], TRUE, FALSE, 0);

    if (channel != NULL)
	channel_close_in(channel);
}

/*
 * "ch_getbufnr()" function
 */
    static void
f_ch_getbufnr(typval_T *argvars, typval_T *rettv)
{
    channel_T *channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    rettv->vval.v_number = -1;
    if (channel != NULL)
    {
	char_u	*what = tv_get_string(&argvars[1]);
	int	part;

	if (STRCMP(what, "err") == 0)
	    part = PART_ERR;
	else if (STRCMP(what, "out") == 0)
	    part = PART_OUT;
	else if (STRCMP(what, "in") == 0)
	    part = PART_IN;
	else
	    part = PART_SOCK;
	if (channel->ch_part[part].ch_bufref.br_buf != NULL)
	    rettv->vval.v_number =
			      channel->ch_part[part].ch_bufref.br_buf->b_fnum;
    }
}

/*
 * "ch_getjob()" function
 */
    static void
f_ch_getjob(typval_T *argvars, typval_T *rettv)
{
    channel_T *channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    if (channel != NULL)
    {
	rettv->v_type = VAR_JOB;
	rettv->vval.v_job = channel->ch_job;
	if (channel->ch_job != NULL)
	    ++channel->ch_job->jv_refcount;
    }
}

/*
 * "ch_info()" function
 */
    static void
f_ch_info(typval_T *argvars, typval_T *rettv UNUSED)
{
    channel_T *channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    if (channel != NULL && rettv_dict_alloc(rettv) != FAIL)
	channel_info(channel, rettv->vval.v_dict);
}

/*
 * "ch_log()" function
 */
    static void
f_ch_log(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u	*msg = tv_get_string(&argvars[0]);
    channel_T	*channel = NULL;

    if (argvars[1].v_type != VAR_UNKNOWN)
	channel = get_channel_arg(&argvars[1], FALSE, FALSE, 0);

    ch_log(channel, "%s", msg);
}

/*
 * "ch_logfile()" function
 */
    static void
f_ch_logfile(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *fname;
    char_u *opt = (char_u *)"";
    char_u buf[NUMBUFLEN];

    /* Don't open a file in restricted mode. */
    if (check_restricted() || check_secure())
	return;
    fname = tv_get_string(&argvars[0]);
    if (argvars[1].v_type == VAR_STRING)
	opt = tv_get_string_buf(&argvars[1], buf);
    ch_logfile(fname, opt);
}

/*
 * "ch_open()" function
 */
    static void
f_ch_open(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_CHANNEL;
    if (check_restricted() || check_secure())
	return;
    rettv->vval.v_channel = channel_open_func(argvars);
}

/*
 * "ch_read()" function
 */
    static void
f_ch_read(typval_T *argvars, typval_T *rettv)
{
    common_channel_read(argvars, rettv, FALSE);
}

/*
 * "ch_readraw()" function
 */
    static void
f_ch_readraw(typval_T *argvars, typval_T *rettv)
{
    common_channel_read(argvars, rettv, TRUE);
}

/*
 * "ch_evalexpr()" function
 */
    static void
f_ch_evalexpr(typval_T *argvars, typval_T *rettv)
{
    ch_expr_common(argvars, rettv, TRUE);
}

/*
 * "ch_sendexpr()" function
 */
    static void
f_ch_sendexpr(typval_T *argvars, typval_T *rettv)
{
    ch_expr_common(argvars, rettv, FALSE);
}

/*
 * "ch_evalraw()" function
 */
    static void
f_ch_evalraw(typval_T *argvars, typval_T *rettv)
{
    ch_raw_common(argvars, rettv, TRUE);
}

/*
 * "ch_sendraw()" function
 */
    static void
f_ch_sendraw(typval_T *argvars, typval_T *rettv)
{
    ch_raw_common(argvars, rettv, FALSE);
}

/*
 * "ch_setoptions()" function
 */
    static void
f_ch_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
{
    channel_T	*channel;
    jobopt_T	opt;

    channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);
    if (channel == NULL)
	return;
    clear_job_options(&opt);
    if (get_job_options(&argvars[1], &opt,
			    JO_CB_ALL + JO_TIMEOUT_ALL + JO_MODE_ALL, 0) == OK)
	channel_set_options(channel, &opt);
    free_job_options(&opt);
}

/*
 * "ch_status()" function
 */
    static void
f_ch_status(typval_T *argvars, typval_T *rettv)
{
    channel_T	*channel;
    jobopt_T	opt;
    int		part = -1;

    /* return an empty string by default */
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	clear_job_options(&opt);
	if (get_job_options(&argvars[1], &opt, JO_PART, 0) == OK
						     && (opt.jo_set & JO_PART))
	    part = opt.jo_part;
    }

    rettv->vval.v_string = vim_strsave((char_u *)channel_status(channel, part));
}
#endif

/*
 * "changenr()" function
 */
    static void
f_changenr(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = curbuf->b_u_seq_cur;
}

/*
 * "char2nr(string)" function
 */
    static void
f_char2nr(typval_T *argvars, typval_T *rettv)
{
#ifdef FEAT_MBYTE
    if (has_mbyte)
    {
	int	utf8 = 0;

	if (argvars[1].v_type != VAR_UNKNOWN)
	    utf8 = (int)tv_get_number_chk(&argvars[1], NULL);

	if (utf8)
	    rettv->vval.v_number = (*utf_ptr2char)(tv_get_string(&argvars[0]));
	else
	    rettv->vval.v_number = (*mb_ptr2char)(tv_get_string(&argvars[0]));
    }
    else
#endif
    rettv->vval.v_number = tv_get_string(&argvars[0])[0];
}

/*
 * "cindent(lnum)" function
 */
    static void
f_cindent(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_CINDENT
    pos_T	pos;
    linenr_T	lnum;

    pos = curwin->w_cursor;
    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
    {
	curwin->w_cursor.lnum = lnum;
	rettv->vval.v_number = get_c_indent();
	curwin->w_cursor = pos;
    }
    else
#endif
	rettv->vval.v_number = -1;
}

/*
 * "clearmatches()" function
 */
    static void
f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    clear_matches(curwin);
#endif
}

/*
 * "col(string)" function
 */
    static void
f_col(typval_T *argvars, typval_T *rettv)
{
    colnr_T	col = 0;
    pos_T	*fp;
    int		fnum = curbuf->b_fnum;

    fp = var2fpos(&argvars[0], FALSE, &fnum);
    if (fp != NULL && fnum == curbuf->b_fnum)
    {
	if (fp->col == MAXCOL)
	{
	    /* '> can be MAXCOL, get the length of the line then */
	    if (fp->lnum <= curbuf->b_ml.ml_line_count)
		col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
	    else
		col = MAXCOL;
	}
	else
	{
	    col = fp->col + 1;
#ifdef FEAT_VIRTUALEDIT
	    /* col(".") when the cursor is on the NUL at the end of the line
	     * because of "coladd" can be seen as an extra column. */
	    if (virtual_active() && fp == &curwin->w_cursor)
	    {
		char_u	*p = ml_get_cursor();

		if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p,
				 curwin->w_virtcol - curwin->w_cursor.coladd))
		{
# ifdef FEAT_MBYTE
		    int		l;

		    if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL)
			col += l;
# else
		    if (*p != NUL && p[1] == NUL)
			++col;
# endif
		}
	    }
#endif
	}
    }
    rettv->vval.v_number = col;
}

#if defined(FEAT_INS_EXPAND)
/*
 * "complete()" function
 */
    static void
f_complete(typval_T *argvars, typval_T *rettv UNUSED)
{
    int	    startcol;

    if ((State & INSERT) == 0)
    {
	EMSG(_("E785: complete() can only be used in Insert mode"));
	return;
    }

    /* Check for undo allowed here, because if something was already inserted
     * the line was already saved for undo and this check isn't done. */
    if (!undo_allowed())
	return;

    if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
    {
	EMSG(_(e_invarg));
	return;
    }

    startcol = (int)tv_get_number_chk(&argvars[0], NULL);
    if (startcol <= 0)
	return;

    set_completion(startcol - 1, argvars[1].vval.v_list);
}

/*
 * "complete_add()" function
 */
    static void
f_complete_add(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0);
}

/*
 * "complete_check()" function
 */
    static void
f_complete_check(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		saved = RedrawingDisabled;

    RedrawingDisabled = 0;
    ins_compl_check_keys(0, TRUE);
    rettv->vval.v_number = compl_interrupted;
    RedrawingDisabled = saved;
}
#endif

/*
 * "confirm(message, buttons[, default [, type]])" function
 */
    static void
f_confirm(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
    char_u	*message;
    char_u	*buttons = NULL;
    char_u	buf[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    int		def = 1;
    int		type = VIM_GENERIC;
    char_u	*typestr;
    int		error = FALSE;

    message = tv_get_string_chk(&argvars[0]);
    if (message == NULL)
	error = TRUE;
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	buttons = tv_get_string_buf_chk(&argvars[1], buf);
	if (buttons == NULL)
	    error = TRUE;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    def = (int)tv_get_number_chk(&argvars[2], &error);
	    if (argvars[3].v_type != VAR_UNKNOWN)
	    {
		typestr = tv_get_string_buf_chk(&argvars[3], buf2);
		if (typestr == NULL)
		    error = TRUE;
		else
		{
		    switch (TOUPPER_ASC(*typestr))
		    {
			case 'E': type = VIM_ERROR; break;
			case 'Q': type = VIM_QUESTION; break;
			case 'I': type = VIM_INFO; break;
			case 'W': type = VIM_WARNING; break;
			case 'G': type = VIM_GENERIC; break;
		    }
		}
	    }
	}
    }

    if (buttons == NULL || *buttons == NUL)
	buttons = (char_u *)_("&Ok");

    if (!error)
	rettv->vval.v_number = do_dialog(type, NULL, message, buttons,
							    def, NULL, FALSE);
#endif
}

/*
 * "copy()" function
 */
    static void
f_copy(typval_T *argvars, typval_T *rettv)
{
    item_copy(&argvars[0], rettv, FALSE, 0);
}

#ifdef FEAT_FLOAT
/*
 * "cos()" function
 */
    static void
f_cos(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = cos(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "cosh()" function
 */
    static void
f_cosh(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = cosh(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "count()" function
 */
    static void
f_count(typval_T *argvars, typval_T *rettv)
{
    long	n = 0;
    int		ic = FALSE;
    int		error = FALSE;

    if (argvars[2].v_type != VAR_UNKNOWN)
	ic = (int)tv_get_number_chk(&argvars[2], &error);

    if (argvars[0].v_type == VAR_STRING)
    {
	char_u *expr = tv_get_string_chk(&argvars[1]);
	char_u *p = argvars[0].vval.v_string;
	char_u *next;

	if (!error && expr != NULL && *expr != NUL && p != NULL)
	{
	    if (ic)
	    {
		size_t len = STRLEN(expr);

		while (*p != NUL)
		{
		    if (MB_STRNICMP(p, expr, len) == 0)
		    {
			++n;
			p += len;
		    }
		    else
			MB_PTR_ADV(p);
		}
	    }
	    else
		while ((next = (char_u *)strstr((char *)p, (char *)expr))
								       != NULL)
		{
		    ++n;
		    p = next + STRLEN(expr);
		}
	}

    }
    else if (argvars[0].v_type == VAR_LIST)
    {
	listitem_T	*li;
	list_T		*l;
	long		idx;

	if ((l = argvars[0].vval.v_list) != NULL)
	{
	    li = l->lv_first;
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		if (argvars[3].v_type != VAR_UNKNOWN)
		{
		    idx = (long)tv_get_number_chk(&argvars[3], &error);
		    if (!error)
		    {
			li = list_find(l, idx);
			if (li == NULL)
			    EMSGN(_(e_listidx), idx);
		    }
		}
		if (error)
		    li = NULL;
	    }

	    for ( ; li != NULL; li = li->li_next)
		if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE))
		    ++n;
	}
    }
    else if (argvars[0].v_type == VAR_DICT)
    {
	int		todo;
	dict_T		*d;
	hashitem_T	*hi;

	if ((d = argvars[0].vval.v_dict) != NULL)
	{
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		if (argvars[3].v_type != VAR_UNKNOWN)
		    EMSG(_(e_invarg));
	    }

	    todo = error ? 0 : (int)d->dv_hashtab.ht_used;
	    for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
	    {
		if (!HASHITEM_EMPTY(hi))
		{
		    --todo;
		    if (tv_equal(&HI2DI(hi)->di_tv, &argvars[1], ic, FALSE))
			++n;
		}
	    }
	}
    }
    else
	EMSG2(_(e_listdictarg), "count()");
    rettv->vval.v_number = n;
}

/*
 * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
 *
 * Checks the existence of a cscope connection.
 */
    static void
f_cscope_connection(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_CSCOPE
    int		num = 0;
    char_u	*dbpath = NULL;
    char_u	*prepend = NULL;
    char_u	buf[NUMBUFLEN];

    if (argvars[0].v_type != VAR_UNKNOWN
	    && argvars[1].v_type != VAR_UNKNOWN)
    {
	num = (int)tv_get_number(&argvars[0]);
	dbpath = tv_get_string(&argvars[1]);
	if (argvars[2].v_type != VAR_UNKNOWN)
	    prepend = tv_get_string_buf(&argvars[2], buf);
    }

    rettv->vval.v_number = cs_connection(num, dbpath, prepend);
#endif
}

/*
 * "cursor(lnum, col)" function, or
 * "cursor(list)"
 *
 * Moves the cursor to the specified line and column.
 * Returns 0 when the position could be set, -1 otherwise.
 */
    static void
f_cursor(typval_T *argvars, typval_T *rettv)
{
    long	line, col;
#ifdef FEAT_VIRTUALEDIT
    long	coladd = 0;
#endif
    int		set_curswant = TRUE;

    rettv->vval.v_number = -1;
    if (argvars[1].v_type == VAR_UNKNOWN)
    {
	pos_T	    pos;
	colnr_T	    curswant = -1;

	if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL)
	{
	    EMSG(_(e_invarg));
	    return;
	}
	line = pos.lnum;
	col = pos.col;
#ifdef FEAT_VIRTUALEDIT
	coladd = pos.coladd;
#endif
	if (curswant >= 0)
	{
	    curwin->w_curswant = curswant - 1;
	    set_curswant = FALSE;
	}
    }
    else
    {
	line = tv_get_lnum(argvars);
	col = (long)tv_get_number_chk(&argvars[1], NULL);
#ifdef FEAT_VIRTUALEDIT
	if (argvars[2].v_type != VAR_UNKNOWN)
	    coladd = (long)tv_get_number_chk(&argvars[2], NULL);
#endif
    }
    if (line < 0 || col < 0
#ifdef FEAT_VIRTUALEDIT
			    || coladd < 0
#endif
	    )
	return;		/* type error; errmsg already given */
    if (line > 0)
	curwin->w_cursor.lnum = line;
    if (col > 0)
	curwin->w_cursor.col = col - 1;
#ifdef FEAT_VIRTUALEDIT
    curwin->w_cursor.coladd = coladd;
#endif

    /* Make sure the cursor is in a valid position. */
    check_cursor();
#ifdef FEAT_MBYTE
    /* Correct cursor for multi-byte character. */
    if (has_mbyte)
	mb_adjust_cursor();
#endif

    curwin->w_set_curswant = set_curswant;
    rettv->vval.v_number = 0;
}

#ifdef WIN3264
/*
 * "debugbreak()" function
 */
    static void
f_debugbreak(typval_T *argvars, typval_T *rettv)
{
    int		pid;

    rettv->vval.v_number = FAIL;
    pid = (int)tv_get_number(&argvars[0]);
    if (pid == 0)
	EMSG(_(e_invarg));
    else
    {
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);

	if (hProcess != NULL)
	{
	    DebugBreakProcess(hProcess);
	    CloseHandle(hProcess);
	    rettv->vval.v_number = OK;
	}
    }
}
#endif

/*
 * "deepcopy()" function
 */
    static void
f_deepcopy(typval_T *argvars, typval_T *rettv)
{
    int		noref = 0;
    int		copyID;

    if (argvars[1].v_type != VAR_UNKNOWN)
	noref = (int)tv_get_number_chk(&argvars[1], NULL);
    if (noref < 0 || noref > 1)
	EMSG(_(e_invarg));
    else
    {
	copyID = get_copyID();
	item_copy(&argvars[0], rettv, TRUE, noref == 0 ? copyID : 0);
    }
}

/*
 * "delete()" function
 */
    static void
f_delete(typval_T *argvars, typval_T *rettv)
{
    char_u	nbuf[NUMBUFLEN];
    char_u	*name;
    char_u	*flags;

    rettv->vval.v_number = -1;
    if (check_restricted() || check_secure())
	return;

    name = tv_get_string(&argvars[0]);
    if (name == NULL || *name == NUL)
    {
	EMSG(_(e_invarg));
	return;
    }

    if (argvars[1].v_type != VAR_UNKNOWN)
	flags = tv_get_string_buf(&argvars[1], nbuf);
    else
	flags = (char_u *)"";

    if (*flags == NUL)
	/* delete a file */
	rettv->vval.v_number = mch_remove(name) == 0 ? 0 : -1;
    else if (STRCMP(flags, "d") == 0)
	/* delete an empty directory */
	rettv->vval.v_number = mch_rmdir(name) == 0 ? 0 : -1;
    else if (STRCMP(flags, "rf") == 0)
	/* delete a directory recursively */
	rettv->vval.v_number = delete_recursive(name);
    else
	EMSG2(_(e_invexpr2), flags);
}

/*
 * "deletebufline()" function
 */
    static void
f_deletebufline(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;
    linenr_T	first, last;
    linenr_T	lnum;
    long	count;
    int		is_curbuf;
    buf_T	*curbuf_save = NULL;
    win_T	*curwin_save = NULL;
    tabpage_T	*tp;
    win_T	*wp;

    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL)
    {
	rettv->vval.v_number = 1; /* FAIL */
	return;
    }
    is_curbuf = buf == curbuf;

    first = tv_get_lnum_buf(&argvars[1], buf);
    if (argvars[2].v_type != VAR_UNKNOWN)
	last = tv_get_lnum_buf(&argvars[2], buf);
    else
	last = first;

    if (buf->b_ml.ml_mfp == NULL || first < 1
			   || first > buf->b_ml.ml_line_count || last < first)
    {
	rettv->vval.v_number = 1;	/* FAIL */
	return;
    }

    if (!is_curbuf)
    {
	curbuf_save = curbuf;
	curwin_save = curwin;
	curbuf = buf;
	find_win_for_curbuf();
    }
    if (last > curbuf->b_ml.ml_line_count)
	last = curbuf->b_ml.ml_line_count;
    count = last - first + 1;

    // When coming here from Insert mode, sync undo, so that this can be
    // undone separately from what was previously inserted.
    if (u_sync_once == 2)
    {
	u_sync_once = 1; // notify that u_sync() was called
	u_sync(TRUE);
    }

    if (u_save(first - 1, last + 1) == FAIL)
    {
	rettv->vval.v_number = 1;	/* FAIL */
	return;
    }

    for (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;
	}
    check_cursor_col();
    deleted_lines_mark(first, count);

    if (!is_curbuf)
    {
	curbuf = curbuf_save;
	curwin = curwin_save;
    }
}

/*
 * "did_filetype()" function
 */
    static void
f_did_filetype(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    rettv->vval.v_number = did_filetype;
}

/*
 * "diff_filler()" function
 */
    static void
f_diff_filler(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_DIFF
    rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
#endif
}

/*
 * "diff_hlID()" function
 */
    static void
f_diff_hlID(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_DIFF
    linenr_T		lnum = tv_get_lnum(argvars);
    static linenr_T	prev_lnum = 0;
    static varnumber_T	changedtick = 0;
    static int		fnum = 0;
    static int		change_start = 0;
    static int		change_end = 0;
    static hlf_T	hlID = (hlf_T)0;
    int			filler_lines;
    int			col;

    if (lnum < 0)	/* ignore type error in {lnum} arg */
	lnum = 0;
    if (lnum != prev_lnum
	    || changedtick != CHANGEDTICK(curbuf)
	    || fnum != curbuf->b_fnum)
    {
	/* New line, buffer, change: need to get the values. */
	filler_lines = diff_check(curwin, lnum);
	if (filler_lines < 0)
	{
	    if (filler_lines == -1)
	    {
		change_start = MAXCOL;
		change_end = -1;
		if (diff_find_change(curwin, lnum, &change_start, &change_end))
		    hlID = HLF_ADD;	/* added line */
		else
		    hlID = HLF_CHD;	/* changed line */
	    }
	    else
		hlID = HLF_ADD;	/* added line */
	}
	else
	    hlID = (hlf_T)0;
	prev_lnum = lnum;
	changedtick = CHANGEDTICK(curbuf);
	fnum = curbuf->b_fnum;
    }

    if (hlID == HLF_CHD || hlID == HLF_TXD)
    {
	col = tv_get_number(&argvars[1]) - 1; /* ignore type error in {col} */
	if (col >= change_start && col <= change_end)
	    hlID = HLF_TXD;			/* changed text */
	else
	    hlID = HLF_CHD;			/* changed line */
    }
    rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)hlID;
#endif
}

/*
 * "empty({expr})" function
 */
    static void
f_empty(typval_T *argvars, typval_T *rettv)
{
    int		n = FALSE;

    switch (argvars[0].v_type)
    {
	case VAR_STRING:
	case VAR_FUNC:
	    n = argvars[0].vval.v_string == NULL
					  || *argvars[0].vval.v_string == NUL;
	    break;
	case VAR_PARTIAL:
	    n = FALSE;
	    break;
	case VAR_NUMBER:
	    n = argvars[0].vval.v_number == 0;
	    break;
	case VAR_FLOAT:
#ifdef FEAT_FLOAT
	    n = argvars[0].vval.v_float == 0.0;
	    break;
#endif
	case VAR_LIST:
	    n = argvars[0].vval.v_list == NULL
				  || argvars[0].vval.v_list->lv_first == NULL;
	    break;
	case VAR_DICT:
	    n = argvars[0].vval.v_dict == NULL
			|| argvars[0].vval.v_dict->dv_hashtab.ht_used == 0;
	    break;
	case VAR_SPECIAL:
	    n = argvars[0].vval.v_number != VVAL_TRUE;
	    break;

	case VAR_JOB:
#ifdef FEAT_JOB_CHANNEL
	    n = argvars[0].vval.v_job == NULL
			   || argvars[0].vval.v_job->jv_status != JOB_STARTED;
	    break;
#endif
	case VAR_CHANNEL:
#ifdef FEAT_JOB_CHANNEL
	    n = argvars[0].vval.v_channel == NULL
			       || !channel_is_open(argvars[0].vval.v_channel);
	    break;
#endif
	case VAR_UNKNOWN:
	    internal_error("f_empty(UNKNOWN)");
	    n = TRUE;
	    break;
    }

    rettv->vval.v_number = n;
}

/*
 * "escape({string}, {chars})" function
 */
    static void
f_escape(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];

    rettv->vval.v_string = vim_strsave_escaped(tv_get_string(&argvars[0]),
					 tv_get_string_buf(&argvars[1], buf));
    rettv->v_type = VAR_STRING;
}

/*
 * "eval()" function
 */
    static void
f_eval(typval_T *argvars, typval_T *rettv)
{
    char_u	*s, *p;

    s = tv_get_string_chk(&argvars[0]);
    if (s != NULL)
	s = skipwhite(s);

    p = s;
    if (s == NULL || eval1(&s, rettv, TRUE) == FAIL)
    {
	if (p != NULL && !aborting())
	    EMSG2(_(e_invexpr2), p);
	need_clr_eos = FALSE;
	rettv->v_type = VAR_NUMBER;
	rettv->vval.v_number = 0;
    }
    else if (*s != NUL)
	EMSG(_(e_trailing));
}

/*
 * "eventhandler()" function
 */
    static void
f_eventhandler(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = vgetc_busy;
}

/*
 * "executable()" function
 */
    static void
f_executable(typval_T *argvars, typval_T *rettv)
{
    char_u *name = tv_get_string(&argvars[0]);

    /* Check in $PATH and also check directly if there is a directory name. */
    rettv->vval.v_number = mch_can_exe(name, NULL, TRUE)
		 || (gettail(name) != name && mch_can_exe(name, NULL, FALSE));
}

static garray_T	redir_execute_ga;

/*
 * Append "value[value_len]" to the execute() output.
 */
    void
execute_redir_str(char_u *value, int value_len)
{
    int		len;

    if (value_len == -1)
	len = (int)STRLEN(value);	/* Append the entire string */
    else
	len = value_len;		/* Append only "value_len" characters */
    if (ga_grow(&redir_execute_ga, len) == OK)
    {
	mch_memmove((char *)redir_execute_ga.ga_data
				       + redir_execute_ga.ga_len, value, len);
	redir_execute_ga.ga_len += len;
    }
}

/*
 * Get next line from a list.
 * Called by do_cmdline() to get the next line.
 * Returns allocated string, or NULL for end of function.
 */

    static char_u *
get_list_line(
    int	    c UNUSED,
    void    *cookie,
    int	    indent UNUSED)
{
    listitem_T **p = (listitem_T **)cookie;
    listitem_T *item = *p;
    char_u	buf[NUMBUFLEN];
    char_u	*s;

    if (item == NULL)
	return NULL;
    s = tv_get_string_buf_chk(&item->li_tv, buf);
    *p = item->li_next;
    return s == NULL ? NULL : vim_strsave(s);
}

/*
 * "execute()" function
 */
    static void
f_execute(typval_T *argvars, typval_T *rettv)
{
    char_u	*cmd = NULL;
    list_T	*list = NULL;
    int		save_msg_silent = msg_silent;
    int		save_emsg_silent = emsg_silent;
    int		save_emsg_noredir = emsg_noredir;
    int		save_redir_execute = redir_execute;
    int		save_redir_off = redir_off;
    garray_T	save_ga;
    int		save_msg_col = msg_col;
    int		echo_output = FALSE;

    rettv->vval.v_string = NULL;
    rettv->v_type = VAR_STRING;

    if (argvars[0].v_type == VAR_LIST)
    {
	list = argvars[0].vval.v_list;
	if (list == NULL || list->lv_first == NULL)
	    /* empty list, no commands, empty output */
	    return;
	++list->lv_refcount;
    }
    else
    {
	cmd = tv_get_string_chk(&argvars[0]);
	if (cmd == NULL)
	    return;
    }

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	char_u	buf[NUMBUFLEN];
	char_u  *s = tv_get_string_buf_chk(&argvars[1], buf);

	if (s == NULL)
	    return;
	if (*s == NUL)
	    echo_output = TRUE;
	if (STRNCMP(s, "silent", 6) == 0)
	    ++msg_silent;
	if (STRCMP(s, "silent!") == 0)
	{
	    emsg_silent = TRUE;
	    emsg_noredir = TRUE;
	}
    }
    else
	++msg_silent;

    if (redir_execute)
	save_ga = redir_execute_ga;
    ga_init2(&redir_execute_ga, (int)sizeof(char), 500);
    redir_execute = TRUE;
    redir_off = FALSE;
    if (!echo_output)
	msg_col = 0;  // prevent leading spaces

    if (cmd != NULL)
	do_cmdline_cmd(cmd);
    else
    {
	listitem_T	*item = list->lv_first;

	do_cmdline(NULL, get_list_line, (void *)&item,
		      DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
	--list->lv_refcount;
    }

    /* Need to append a NUL to the result. */
    if (ga_grow(&redir_execute_ga, 1) == OK)
    {
	((char *)redir_execute_ga.ga_data)[redir_execute_ga.ga_len] = NUL;
	rettv->vval.v_string = redir_execute_ga.ga_data;
    }
    else
    {
	ga_clear(&redir_execute_ga);
	rettv->vval.v_string = NULL;
    }
    msg_silent = save_msg_silent;
    emsg_silent = save_emsg_silent;
    emsg_noredir = save_emsg_noredir;

    redir_execute = save_redir_execute;
    if (redir_execute)
	redir_execute_ga = save_ga;
    redir_off = save_redir_off;

    // "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
    if (echo_output)
	// When not working silently: put it in column zero.  A following
	// "echon" will overwrite the message, unavoidably.
	msg_col = 0;
    else
	// When working silently: Put it back where it was, since nothing
	// should have been written.
	msg_col = save_msg_col;
}

/*
 * "exepath()" function
 */
    static void
f_exepath(typval_T *argvars, typval_T *rettv)
{
    char_u *p = NULL;

    (void)mch_can_exe(tv_get_string(&argvars[0]), &p, TRUE);
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = p;
}

/*
 * "exists()" function
 */
    static void
f_exists(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;
    int		n = FALSE;

    p = tv_get_string(&argvars[0]);
    if (*p == '$')			/* environment variable */
    {
	/* first try "normal" environment variables (fast) */
	if (mch_getenv(p + 1) != NULL)
	    n = TRUE;
	else
	{
	    /* try expanding things like $VIM and ${HOME} */
	    p = expand_env_save(p);
	    if (p != NULL && *p != '$')
		n = TRUE;
	    vim_free(p);
	}
    }
    else if (*p == '&' || *p == '+')			/* option */
    {
	n = (get_option_tv(&p, NULL, TRUE) == OK);
	if (*skipwhite(p) != NUL)
	    n = FALSE;			/* trailing garbage */
    }
    else if (*p == '*')			/* internal or user defined function */
    {
	n = function_exists(p + 1, FALSE);
    }
    else if (*p == ':')
    {
	n = cmd_exists(p + 1);
    }
    else if (*p == '#')
    {
	if (p[1] == '#')
	    n = autocmd_supported(p + 2);
	else
	    n = au_exists(p + 1);
    }
    else				/* internal variable */
    {
	n = var_exists(p);
    }

    rettv->vval.v_number = n;
}

#ifdef FEAT_FLOAT
/*
 * "exp()" function
 */
    static void
f_exp(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = exp(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "expand()" function
 */
    static void
f_expand(typval_T *argvars, typval_T *rettv)
{
    char_u	*s;
    int		len;
    char_u	*errormsg;
    int		options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
    expand_T	xpc;
    int		error = FALSE;
    char_u	*result;

    rettv->v_type = VAR_STRING;
    if (argvars[1].v_type != VAR_UNKNOWN
	    && argvars[2].v_type != VAR_UNKNOWN
	    && tv_get_number_chk(&argvars[2], &error)
	    && !error)
    {
	rettv_list_set(rettv, NULL);
    }

    s = tv_get_string(&argvars[0]);
    if (*s == '%' || *s == '#' || *s == '<')
    {
	++emsg_off;
	result = eval_vars(s, s, &len, NULL, &errormsg, NULL);
	--emsg_off;
	if (rettv->v_type == VAR_LIST)
	{
	    if (rettv_list_alloc(rettv) != FAIL && result != NULL)
		list_append_string(rettv->vval.v_list, result, -1);
	    else
		vim_free(result);
	}
	else
	    rettv->vval.v_string = result;
    }
    else
    {
	/* When the optional second argument is non-zero, don't remove matches
	 * for 'wildignore' and don't put matches for 'suffixes' at the end. */
	if (argvars[1].v_type != VAR_UNKNOWN
				    && tv_get_number_chk(&argvars[1], &error))
	    options |= WILD_KEEP_ALL;
	if (!error)
	{
	    ExpandInit(&xpc);
	    xpc.xp_context = EXPAND_FILES;
	    if (p_wic)
		options += WILD_ICASE;
	    if (rettv->v_type == VAR_STRING)
		rettv->vval.v_string = ExpandOne(&xpc, s, NULL,
							   options, WILD_ALL);
	    else if (rettv_list_alloc(rettv) != FAIL)
	    {
		int i;

		ExpandOne(&xpc, s, NULL, options, WILD_ALL_KEEP);
		for (i = 0; i < xpc.xp_numfiles; i++)
		    list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
		ExpandCleanup(&xpc);
	    }
	}
	else
	    rettv->vval.v_string = NULL;
    }
}

/*
 * "extend(list, list [, idx])" function
 * "extend(dict, dict [, action])" function
 */
    static void
f_extend(typval_T *argvars, typval_T *rettv)
{
    char_u      *arg_errmsg = (char_u *)N_("extend() argument");

    if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST)
    {
	list_T		*l1, *l2;
	listitem_T	*item;
	long		before;
	int		error = FALSE;

	l1 = argvars[0].vval.v_list;
	l2 = argvars[1].vval.v_list;
	if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, TRUE)
		&& l2 != NULL)
	{
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		before = (long)tv_get_number_chk(&argvars[2], &error);
		if (error)
		    return;		/* type error; errmsg already given */

		if (before == l1->lv_len)
		    item = NULL;
		else
		{
		    item = list_find(l1, before);
		    if (item == NULL)
		    {
			EMSGN(_(e_listidx), before);
			return;
		    }
		}
	    }
	    else
		item = NULL;
	    list_extend(l1, l2, item);

	    copy_tv(&argvars[0], rettv);
	}
    }
    else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT)
    {
	dict_T	*d1, *d2;
	char_u	*action;
	int	i;

	d1 = argvars[0].vval.v_dict;
	d2 = argvars[1].vval.v_dict;
	if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, TRUE)
		&& d2 != NULL)
	{
	    /* Check the third argument. */
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		static char *(av[]) = {"keep", "force", "error"};

		action = tv_get_string_chk(&argvars[2]);
		if (action == NULL)
		    return;		/* type error; errmsg already given */
		for (i = 0; i < 3; ++i)
		    if (STRCMP(action, av[i]) == 0)
			break;
		if (i == 3)
		{
		    EMSG2(_(e_invarg2), action);
		    return;
		}
	    }
	    else
		action = (char_u *)"force";

	    dict_extend(d1, d2, action);

	    copy_tv(&argvars[0], rettv);
	}
    }
    else
	EMSG2(_(e_listdictarg), "extend()");
}

/*
 * "feedkeys()" function
 */
    static void
f_feedkeys(typval_T *argvars, typval_T *rettv UNUSED)
{
    int		remap = TRUE;
    int		insert = FALSE;
    char_u	*keys, *flags;
    char_u	nbuf[NUMBUFLEN];
    int		typed = FALSE;
    int		execute = FALSE;
    int		dangerous = FALSE;
    char_u	*keys_esc;

    /* This is not allowed in the sandbox.  If the commands would still be
     * executed in the sandbox it would be OK, but it probably happens later,
     * when "sandbox" is no longer set. */
    if (check_secure())
	return;

    keys = tv_get_string(&argvars[0]);

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	flags = tv_get_string_buf(&argvars[1], nbuf);
	for ( ; *flags != NUL; ++flags)
	{
	    switch (*flags)
	    {
		case 'n': remap = FALSE; break;
		case 'm': remap = TRUE; break;
		case 't': typed = TRUE; break;
		case 'i': insert = TRUE; break;
		case 'x': execute = TRUE; break;
		case '!': dangerous = TRUE; break;
	    }
	}
    }

    if (*keys != NUL || execute)
    {
	/* Need to escape K_SPECIAL and CSI before putting the string in the
	 * typeahead buffer. */
	keys_esc = vim_strsave_escape_csi(keys);
	if (keys_esc != NULL)
	{
	    ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
				  insert ? 0 : typebuf.tb_len, !typed, FALSE);
	    vim_free(keys_esc);
	    if (vgetc_busy
#ifdef FEAT_TIMERS
		    || timer_busy
#endif
		    )
		typebuf_was_filled = TRUE;
	    if (execute)
	    {
		int save_msg_scroll = msg_scroll;

		/* Avoid a 1 second delay when the keys start Insert mode. */
		msg_scroll = FALSE;

		if (!dangerous)
		    ++ex_normal_busy;
		exec_normal(TRUE, FALSE, TRUE);
		if (!dangerous)
		    --ex_normal_busy;

		msg_scroll |= save_msg_scroll;
	    }
	}
    }
}

/*
 * "filereadable()" function
 */
    static void
f_filereadable(typval_T *argvars, typval_T *rettv)
{
    int		fd;
    char_u	*p;
    int		n;

#ifndef O_NONBLOCK
# define O_NONBLOCK 0
#endif
    p = tv_get_string(&argvars[0]);
    if (*p && !mch_isdir(p) && (fd = mch_open((char *)p,
					      O_RDONLY | O_NONBLOCK, 0)) >= 0)
    {
	n = TRUE;
	close(fd);
    }
    else
	n = FALSE;

    rettv->vval.v_number = n;
}

/*
 * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
 * rights to write into.
 */
    static void
f_filewritable(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = filewritable(tv_get_string(&argvars[0]));
}

    static void
findfilendir(
    typval_T	*argvars UNUSED,
    typval_T	*rettv,
    int		find_what UNUSED)
{
#ifdef FEAT_SEARCHPATH
    char_u	*fname;
    char_u	*fresult = NULL;
    char_u	*path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
    char_u	*p;
    char_u	pathbuf[NUMBUFLEN];
    int		count = 1;
    int		first = TRUE;
    int		error = FALSE;
#endif

    rettv->vval.v_string = NULL;
    rettv->v_type = VAR_STRING;

#ifdef FEAT_SEARCHPATH
    fname = tv_get_string(&argvars[0]);

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	p = tv_get_string_buf_chk(&argvars[1], pathbuf);
	if (p == NULL)
	    error = TRUE;
	else
	{
	    if (*p != NUL)
		path = p;

	    if (argvars[2].v_type != VAR_UNKNOWN)
		count = (int)tv_get_number_chk(&argvars[2], &error);
	}
    }

    if (count < 0 && rettv_list_alloc(rettv) == FAIL)
	error = TRUE;

    if (*fname != NUL && !error)
    {
	do
	{
	    if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
		vim_free(fresult);
	    fresult = find_file_in_path_option(first ? fname : NULL,
					       first ? (int)STRLEN(fname) : 0,
					0, first, path,
					find_what,
					curbuf->b_ffname,
					find_what == FINDFILE_DIR
					    ? (char_u *)"" : curbuf->b_p_sua);
	    first = FALSE;

	    if (fresult != NULL && rettv->v_type == VAR_LIST)
		list_append_string(rettv->vval.v_list, fresult, -1);

	} while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
    }

    if (rettv->v_type == VAR_STRING)
	rettv->vval.v_string = fresult;
#endif
}

/*
 * "filter()" function
 */
    static void
f_filter(typval_T *argvars, typval_T *rettv)
{
    filter_map(argvars, rettv, FALSE);
}

/*
 * "finddir({fname}[, {path}[, {count}]])" function
 */
    static void
f_finddir(typval_T *argvars, typval_T *rettv)
{
    findfilendir(argvars, rettv, FINDFILE_DIR);
}

/*
 * "findfile({fname}[, {path}[, {count}]])" function
 */
    static void
f_findfile(typval_T *argvars, typval_T *rettv)
{
    findfilendir(argvars, rettv, FINDFILE_FILE);
}

#ifdef FEAT_FLOAT
/*
 * "float2nr({float})" function
 */
    static void
f_float2nr(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    if (get_float_arg(argvars, &f) == OK)
    {
	if (f <= -VARNUM_MAX + DBL_EPSILON)
	    rettv->vval.v_number = -VARNUM_MAX;
	else if (f >= VARNUM_MAX - DBL_EPSILON)
	    rettv->vval.v_number = VARNUM_MAX;
	else
	    rettv->vval.v_number = (varnumber_T)f;
    }
}

/*
 * "floor({float})" function
 */
    static void
f_floor(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = floor(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "fmod()" function
 */
    static void
f_fmod(typval_T *argvars, typval_T *rettv)
{
    float_T	fx = 0.0, fy = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &fx) == OK
				     && get_float_arg(&argvars[1], &fy) == OK)
	rettv->vval.v_float = fmod(fx, fy);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "fnameescape({string})" function
 */
    static void
f_fnameescape(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_string = vim_strsave_fnameescape(
					   tv_get_string(&argvars[0]), FALSE);
    rettv->v_type = VAR_STRING;
}

/*
 * "fnamemodify({fname}, {mods})" function
 */
    static void
f_fnamemodify(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    char_u	*mods;
    int		usedlen = 0;
    int		len;
    char_u	*fbuf = NULL;
    char_u	buf[NUMBUFLEN];

    fname = tv_get_string_chk(&argvars[0]);
    mods = tv_get_string_buf_chk(&argvars[1], buf);
    if (fname == NULL || mods == NULL)
	fname = NULL;
    else
    {
	len = (int)STRLEN(fname);
	(void)modify_fname(mods, FALSE, &usedlen, &fname, &fbuf, &len);
    }

    rettv->v_type = VAR_STRING;
    if (fname == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = vim_strnsave(fname, len);
    vim_free(fbuf);
}

/*
 * "foldclosed()" function
 */
    static void
foldclosed_both(
    typval_T	*argvars UNUSED,
    typval_T	*rettv,
    int		end UNUSED)
{
#ifdef FEAT_FOLDING
    linenr_T	lnum;
    linenr_T	first, last;

    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
    {
	if (hasFoldingWin(curwin, lnum, &first, &last, FALSE, NULL))
	{
	    if (end)
		rettv->vval.v_number = (varnumber_T)last;
	    else
		rettv->vval.v_number = (varnumber_T)first;
	    return;
	}
    }
#endif
    rettv->vval.v_number = -1;
}

/*
 * "foldclosed()" function
 */
    static void
f_foldclosed(typval_T *argvars, typval_T *rettv)
{
    foldclosed_both(argvars, rettv, FALSE);
}

/*
 * "foldclosedend()" function
 */
    static void
f_foldclosedend(typval_T *argvars, typval_T *rettv)
{
    foldclosed_both(argvars, rettv, TRUE);
}

/*
 * "foldlevel()" function
 */
    static void
f_foldlevel(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_FOLDING
    linenr_T	lnum;

    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
	rettv->vval.v_number = foldLevel(lnum);
#endif
}

/*
 * "foldtext()" function
 */
    static void
f_foldtext(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_FOLDING
    linenr_T	foldstart;
    linenr_T	foldend;
    char_u	*dashes;
    linenr_T	lnum;
    char_u	*s;
    char_u	*r;
    int		len;
    char	*txt;
    long	count;
#endif

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_FOLDING
    foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
    foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
    dashes = get_vim_var_str(VV_FOLDDASHES);
    if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count
	    && dashes != NULL)
    {
	/* Find first non-empty line in the fold. */
	for (lnum = foldstart; lnum < foldend; ++lnum)
	    if (!linewhite(lnum))
		break;

	/* Find interesting text in this line. */
	s = skipwhite(ml_get(lnum));
	/* skip C comment-start */
	if (s[0] == '/' && (s[1] == '*' || s[1] == '/'))
	{
	    s = skipwhite(s + 2);
	    if (*skipwhite(s) == NUL
			    && lnum + 1 < (linenr_T)get_vim_var_nr(VV_FOLDEND))
	    {
		s = skipwhite(ml_get(lnum + 1));
		if (*s == '*')
		    s = skipwhite(s + 1);
	    }
	}
	count = (long)(foldend - foldstart + 1);
	txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
	r = alloc((unsigned)(STRLEN(txt)
		    + STRLEN(dashes)	    /* for %s */
		    + 20		    /* for %3ld */
		    + STRLEN(s)));	    /* concatenated */
	if (r != NULL)
	{
	    sprintf((char *)r, txt, dashes, count);
	    len = (int)STRLEN(r);
	    STRCAT(r, s);
	    /* remove 'foldmarker' and 'commentstring' */
	    foldtext_cleanup(r + len);
	    rettv->vval.v_string = r;
	}
    }
#endif
}

/*
 * "foldtextresult(lnum)" function
 */
    static void
f_foldtextresult(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_FOLDING
    linenr_T	lnum;
    char_u	*text;
    char_u	buf[FOLD_TEXT_LEN];
    foldinfo_T  foldinfo;
    int		fold_count;
    static int	entered = FALSE;
#endif

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_FOLDING
    if (entered)
	return; /* reject recursive use */
    entered = TRUE;

    lnum = tv_get_lnum(argvars);
    /* treat illegal types and illegal string values for {lnum} the same */
    if (lnum < 0)
	lnum = 0;
    fold_count = foldedCount(curwin, lnum, &foldinfo);
    if (fold_count > 0)
    {
	text = get_foldtext(curwin, lnum, lnum + fold_count - 1,
							       &foldinfo, buf);
	if (text == buf)
	    text = vim_strsave(text);
	rettv->vval.v_string = text;
    }

    entered = FALSE;
#endif
}

/*
 * "foreground()" function
 */
    static void
f_foreground(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_GUI
    if (gui.in_use)
	gui_mch_set_foreground();
#else
# ifdef WIN32
    win32_set_foreground();
# endif
#endif
}

    static void
common_function(typval_T *argvars, typval_T *rettv, int is_funcref)
{
    char_u	*s;
    char_u	*name;
    int		use_string = FALSE;
    partial_T   *arg_pt = NULL;
    char_u	*trans_name = NULL;

    if (argvars[0].v_type == VAR_FUNC)
    {
	/* function(MyFunc, [arg], dict) */
	s = argvars[0].vval.v_string;
    }
    else if (argvars[0].v_type == VAR_PARTIAL
					 && argvars[0].vval.v_partial != NULL)
    {
	/* function(dict.MyFunc, [arg]) */
	arg_pt = argvars[0].vval.v_partial;
	s = partial_name(arg_pt);
    }
    else
    {
	/* function('MyFunc', [arg], dict) */
	s = tv_get_string(&argvars[0]);
	use_string = TRUE;
    }

    if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref)
    {
	name = s;
	trans_name = trans_function_name(&name, FALSE,
	     TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL);
	if (*name != NUL)
	    s = NULL;
    }

    if (s == NULL || *s == NUL || (use_string && VIM_ISDIGIT(*s))
					 || (is_funcref && trans_name == NULL))
	EMSG2(_(e_invarg2), use_string ? tv_get_string(&argvars[0]) : s);
    /* Don't check an autoload name for existence here. */
    else if (trans_name != NULL && (is_funcref
				? find_func(trans_name) == NULL
				: !translated_function_exists(trans_name)))
	EMSG2(_("E700: Unknown function: %s"), s);
    else
    {
	int	dict_idx = 0;
	int	arg_idx = 0;
	list_T	*list = NULL;

	if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "<SID>", 5) == 0)
	{
	    char	sid_buf[25];
	    int		off = *s == 's' ? 2 : 5;

	    /* Expand s: and <SID> into <SNR>nr_, so that the function can
	     * also be called from another script. Using trans_function_name()
	     * would also work, but some plugins depend on the name being
	     * printable text. */
	    sprintf(sid_buf, "<SNR>%ld_", (long)current_sctx.sc_sid);
	    name = alloc((int)(STRLEN(sid_buf) + STRLEN(s + off) + 1));
	    if (name != NULL)
	    {
		STRCPY(name, sid_buf);
		STRCAT(name, s + off);
	    }
	}
	else
	    name = vim_strsave(s);

	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		/* function(name, [args], dict) */
		arg_idx = 1;
		dict_idx = 2;
	    }
	    else if (argvars[1].v_type == VAR_DICT)
		/* function(name, dict) */
		dict_idx = 1;
	    else
		/* function(name, [args]) */
		arg_idx = 1;
	    if (dict_idx > 0)
	    {
		if (argvars[dict_idx].v_type != VAR_DICT)
		{
		    EMSG(_("E922: expected a dict"));
		    vim_free(name);
		    goto theend;
		}
		if (argvars[dict_idx].vval.v_dict == NULL)
		    dict_idx = 0;
	    }
	    if (arg_idx > 0)
	    {
		if (argvars[arg_idx].v_type != VAR_LIST)
		{
		    EMSG(_("E923: Second argument of function() must be a list or a dict"));
		    vim_free(name);
		    goto theend;
		}
		list = argvars[arg_idx].vval.v_list;
		if (list == NULL || list->lv_len == 0)
		    arg_idx = 0;
	    }
	}
	if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref)
	{
	    partial_T	*pt = (partial_T *)alloc_clear(sizeof(partial_T));

	    /* result is a VAR_PARTIAL */
	    if (pt == NULL)
		vim_free(name);
	    else
	    {
		if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0))
		{
		    listitem_T	*li;
		    int		i = 0;
		    int		arg_len = 0;
		    int		lv_len = 0;

		    if (arg_pt != NULL)
			arg_len = arg_pt->pt_argc;
		    if (list != NULL)
			lv_len = list->lv_len;
		    pt->pt_argc = arg_len + lv_len;
		    pt->pt_argv = (typval_T *)alloc(
					      sizeof(typval_T) * pt->pt_argc);
		    if (pt->pt_argv == NULL)
		    {
			vim_free(pt);
			vim_free(name);
			goto theend;
		    }
		    for (i = 0; i < arg_len; i++)
			copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
		    if (lv_len > 0)
			for (li = list->lv_first; li != NULL;
							 li = li->li_next)
			    copy_tv(&li->li_tv, &pt->pt_argv[i++]);
		}

		/* For "function(dict.func, [], dict)" and "func" is a partial
		 * use "dict".  That is backwards compatible. */
		if (dict_idx > 0)
		{
		    /* The dict is bound explicitly, pt_auto is FALSE. */
		    pt->pt_dict = argvars[dict_idx].vval.v_dict;
		    ++pt->pt_dict->dv_refcount;
		}
		else if (arg_pt != NULL)
		{
		    /* If the dict was bound automatically the result is also
		     * bound automatically. */
		    pt->pt_dict = arg_pt->pt_dict;
		    pt->pt_auto = arg_pt->pt_auto;
		    if (pt->pt_dict != NULL)
			++pt->pt_dict->dv_refcount;
		}

		pt->pt_refcount = 1;
		if (arg_pt != NULL && arg_pt->pt_func != NULL)
		{
		    pt->pt_func = arg_pt->pt_func;
		    func_ptr_ref(pt->pt_func);
		    vim_free(name);
		}
		else if (is_funcref)
		{
		    pt->pt_func = find_func(trans_name);
		    func_ptr_ref(pt->pt_func);
		    vim_free(name);
		}
		else
		{
		    pt->pt_name = name;
		    func_ref(name);
		}
	    }
	    rettv->v_type = VAR_PARTIAL;
	    rettv->vval.v_partial = pt;
	}
	else
	{
	    /* result is a VAR_FUNC */
	    rettv->v_type = VAR_FUNC;
	    rettv->vval.v_string = name;
	    func_ref(name);
	}
    }
theend:
    vim_free(trans_name);
}

/*
 * "funcref()" function
 */
    static void
f_funcref(typval_T *argvars, typval_T *rettv)
{
    common_function(argvars, rettv, TRUE);
}

/*
 * "function()" function
 */
    static void
f_function(typval_T *argvars, typval_T *rettv)
{
    common_function(argvars, rettv, FALSE);
}

/*
 * "garbagecollect()" function
 */
    static void
f_garbagecollect(typval_T *argvars, typval_T *rettv UNUSED)
{
    /* This is postponed until we are back at the toplevel, because we may be
     * using Lists and Dicts internally.  E.g.: ":echo [garbagecollect()]". */
    want_garbage_collect = TRUE;

    if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1)
	garbage_collect_at_exit = TRUE;
}

/*
 * "get()" function
 */
    static void
f_get(typval_T *argvars, typval_T *rettv)
{
    listitem_T	*li;
    list_T	*l;
    dictitem_T	*di;
    dict_T	*d;
    typval_T	*tv = NULL;

    if (argvars[0].v_type == VAR_LIST)
    {
	if ((l = argvars[0].vval.v_list) != NULL)
	{
	    int		error = FALSE;

	    li = list_find(l, (long)tv_get_number_chk(&argvars[1], &error));
	    if (!error && li != NULL)
		tv = &li->li_tv;
	}
    }
    else if (argvars[0].v_type == VAR_DICT)
    {
	if ((d = argvars[0].vval.v_dict) != NULL)
	{
	    di = dict_find(d, tv_get_string(&argvars[1]), -1);
	    if (di != NULL)
		tv = &di->di_tv;
	}
    }
    else if (argvars[0].v_type == VAR_PARTIAL || argvars[0].v_type == VAR_FUNC)
    {
	partial_T	*pt;
	partial_T	fref_pt;

	if (argvars[0].v_type == VAR_PARTIAL)
	    pt = argvars[0].vval.v_partial;
	else
	{
	    vim_memset(&fref_pt, 0, sizeof(fref_pt));
	    fref_pt.pt_name = argvars[0].vval.v_string;
	    pt = &fref_pt;
	}

	if (pt != NULL)
	{
	    char_u *what = tv_get_string(&argvars[1]);
	    char_u *n;

	    if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0)
	    {
		rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
		n = partial_name(pt);
		if (n == NULL)
		    rettv->vval.v_string = NULL;
		else
		{
		    rettv->vval.v_string = vim_strsave(n);
		    if (rettv->v_type == VAR_FUNC)
			func_ref(rettv->vval.v_string);
		}
	    }
	    else if (STRCMP(what, "dict") == 0)
		rettv_dict_set(rettv, pt->pt_dict);
	    else if (STRCMP(what, "args") == 0)
	    {
		rettv->v_type = VAR_LIST;
		if (rettv_list_alloc(rettv) == OK)
		{
		    int i;

		    for (i = 0; i < pt->pt_argc; ++i)
			list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
		}
	    }
	    else
		EMSG2(_(e_invarg2), what);
	    return;
	}
    }
    else
	EMSG2(_(e_listdictarg), "get()");

    if (tv == NULL)
    {
	if (argvars[2].v_type != VAR_UNKNOWN)
	    copy_tv(&argvars[2], rettv);
    }
    else
	copy_tv(tv, rettv);
}

#ifdef FEAT_SIGNS
/*
 * Returns information about signs placed in a buffer as list of dicts.
 */
    static void
get_buffer_signs(buf_T *buf, list_T *l)
{
    signlist_T	*sign;
    dict_T	*d;

    FOR_ALL_SIGNS_IN_BUF(buf, sign)
    {
	if ((d = sign_get_info(sign)) != NULL)
	    list_append_dict(l, d);
    }
}
#endif

/*
 * Returns buffer options, variables and other attributes in a dictionary.
 */
    static dict_T *
get_buffer_info(buf_T *buf)
{
    dict_T	*dict;
    tabpage_T	*tp;
    win_T	*wp;
    list_T	*windows;

    dict = dict_alloc();
    if (dict == NULL)
	return NULL;

    dict_add_number(dict, "bufnr", buf->b_fnum);
    dict_add_string(dict, "name", buf->b_ffname);
    dict_add_number(dict, "lnum", buf == curbuf ? curwin->w_cursor.lnum
						     : buflist_findlnum(buf));
    dict_add_number(dict, "loaded", buf->b_ml.ml_mfp != NULL);
    dict_add_number(dict, "listed", buf->b_p_bl);
    dict_add_number(dict, "changed", bufIsChanged(buf));
    dict_add_number(dict, "changedtick", CHANGEDTICK(buf));
    dict_add_number(dict, "hidden",
			    buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0);

    /* Get a reference to buffer variables */
    dict_add_dict(dict, "variables", buf->b_vars);

    /* List of windows displaying this buffer */
    windows = list_alloc();
    if (windows != NULL)
    {
	FOR_ALL_TAB_WINDOWS(tp, wp)
	    if (wp->w_buffer == buf)
		list_append_number(windows, (varnumber_T)wp->w_id);
	dict_add_list(dict, "windows", windows);
    }

#ifdef FEAT_SIGNS
    if (buf->b_signlist != NULL)
    {
	/* List of signs placed in this buffer */
	list_T	*signs = list_alloc();
	if (signs != NULL)
	{
	    get_buffer_signs(buf, signs);
	    dict_add_list(dict, "signs", signs);
	}
    }
#endif

    return dict;
}

/*
 * "getbufinfo()" function
 */
    static void
f_getbufinfo(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf = NULL;
    buf_T	*argbuf = NULL;
    dict_T	*d;
    int		filtered = FALSE;
    int		sel_buflisted = FALSE;
    int		sel_bufloaded = FALSE;
    int		sel_bufmodified = FALSE;

    if (rettv_list_alloc(rettv) != OK)
	return;

    /* List of all the buffers or selected buffers */
    if (argvars[0].v_type == VAR_DICT)
    {
	dict_T	*sel_d = argvars[0].vval.v_dict;

	if (sel_d != NULL)
	{
	    dictitem_T	*di;

	    filtered = TRUE;

	    di = dict_find(sel_d, (char_u *)"buflisted", -1);
	    if (di != NULL && tv_get_number(&di->di_tv))
		sel_buflisted = TRUE;

	    di = dict_find(sel_d, (char_u *)"bufloaded", -1);
	    if (di != NULL && tv_get_number(&di->di_tv))
		sel_bufloaded = TRUE;

	    di = dict_find(sel_d, (char_u *)"bufmodified", -1);
	    if (di != NULL && tv_get_number(&di->di_tv))
		sel_bufmodified = TRUE;
	}
    }
    else if (argvars[0].v_type != VAR_UNKNOWN)
    {
	/* Information about one buffer.  Argument specifies the buffer */
	(void)tv_get_number(&argvars[0]);   /* issue errmsg if type error */
	++emsg_off;
	argbuf = get_buf_tv(&argvars[0], FALSE);
	--emsg_off;
	if (argbuf == NULL)
	    return;
    }

    /* Return information about all the buffers or a specified buffer */
    FOR_ALL_BUFFERS(buf)
    {
	if (argbuf != NULL && argbuf != buf)
	    continue;
	if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
			|| (sel_buflisted && !buf->b_p_bl)
			|| (sel_bufmodified && !buf->b_changed)))
	    continue;

	d = get_buffer_info(buf);
	if (d != NULL)
	    list_append_dict(rettv->vval.v_list, d);
	if (argbuf != NULL)
	    return;
    }
}

/*
 * Get line or list of lines from buffer "buf" into "rettv".
 * Return a range (from start to end) of lines in rettv from the specified
 * buffer.
 * If 'retlist' is TRUE, then the lines are returned as a Vim List.
 */
    static void
get_buffer_lines(
    buf_T	*buf,
    linenr_T	start,
    linenr_T	end,
    int		retlist,
    typval_T	*rettv)
{
    char_u	*p;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
    if (retlist && rettv_list_alloc(rettv) == FAIL)
	return;

    if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0)
	return;

    if (!retlist)
    {
	if (start >= 1 && start <= buf->b_ml.ml_line_count)
	    p = ml_get_buf(buf, start, FALSE);
	else
	    p = (char_u *)"";
	rettv->vval.v_string = vim_strsave(p);
    }
    else
    {
	if (end < start)
	    return;

	if (start < 1)
	    start = 1;
	if (end > buf->b_ml.ml_line_count)
	    end = buf->b_ml.ml_line_count;
	while (start <= end)
	    if (list_append_string(rettv->vval.v_list,
				 ml_get_buf(buf, start++, FALSE), -1) == FAIL)
		break;
    }
}

/*
 * "getbufline()" function
 */
    static void
f_getbufline(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;
    linenr_T	end;
    buf_T	*buf;

    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    ++emsg_off;
    buf = get_buf_tv(&argvars[0], FALSE);
    --emsg_off;

    lnum = tv_get_lnum_buf(&argvars[1], buf);
    if (argvars[2].v_type == VAR_UNKNOWN)
	end = lnum;
    else
	end = tv_get_lnum_buf(&argvars[2], buf);

    get_buffer_lines(buf, lnum, end, TRUE, rettv);
}

/*
 * "getbufvar()" function
 */
    static void
f_getbufvar(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;
    buf_T	*save_curbuf;
    char_u	*varname;
    dictitem_T	*v;
    int		done = FALSE;

    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    varname = tv_get_string_chk(&argvars[1]);
    ++emsg_off;
    buf = get_buf_tv(&argvars[0], FALSE);

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (buf != NULL && varname != NULL)
    {
	/* set curbuf to be our buf, temporarily */
	save_curbuf = curbuf;
	curbuf = buf;

	if (*varname == '&')
	{
	    if (varname[1] == NUL)
	    {
		/* get all buffer-local options in a dict */
		dict_T	*opts = get_winbuf_options(TRUE);

		if (opts != NULL)
		{
		    rettv_dict_set(rettv, opts);
		    done = TRUE;
		}
	    }
	    else if (get_option_tv(&varname, rettv, TRUE) == OK)
		/* buffer-local-option */
		done = TRUE;
	}
	else
	{
	    /* Look up the variable. */
	    /* Let getbufvar({nr}, "") return the "b:" dictionary. */
	    v = find_var_in_ht(&curbuf->b_vars->dv_hashtab,
							 'b', varname, FALSE);
	    if (v != NULL)
	    {
		copy_tv(&v->di_tv, rettv);
		done = TRUE;
	    }
	}

	/* restore previous notion of curbuf */
	curbuf = save_curbuf;
    }

    if (!done && argvars[2].v_type != VAR_UNKNOWN)
	/* use the default value */
	copy_tv(&argvars[2], rettv);

    --emsg_off;
}

/*
 * "getchangelist()" function
 */
    static void
f_getchangelist(typval_T *argvars, typval_T *rettv)
{
#ifdef FEAT_JUMPLIST
    buf_T	*buf;
    int		i;
    list_T	*l;
    dict_T	*d;
#endif

    if (rettv_list_alloc(rettv) != OK)
	return;

#ifdef FEAT_JUMPLIST
    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    ++emsg_off;
    buf = get_buf_tv(&argvars[0], FALSE);
    --emsg_off;
    if (buf == NULL)
	return;

    l = list_alloc();
    if (l == NULL)
	return;

    if (list_append_list(rettv->vval.v_list, l) == FAIL)
	return;
    /*
     * The current window change list index tracks only the position in the
     * current buffer change list. For other buffers, use the change list
     * length as the current index.
     */
    list_append_number(rettv->vval.v_list,
	    (varnumber_T)((buf == curwin->w_buffer)
		? curwin->w_changelistidx : buf->b_changelistlen));

    for (i = 0; i < buf->b_changelistlen; ++i)
    {
	if (buf->b_changelist[i].lnum == 0)
	    continue;
	if ((d = dict_alloc()) == NULL)
	    return;
	if (list_append_dict(l, d) == FAIL)
	    return;
	dict_add_number(d, "lnum", (long)buf->b_changelist[i].lnum);
	dict_add_number(d, "col", (long)buf->b_changelist[i].col);
# ifdef FEAT_VIRTUALEDIT
	dict_add_number(d, "coladd", (long)buf->b_changelist[i].coladd);
# endif
    }
#endif
}
/*
 * "getchar()" function
 */
    static void
f_getchar(typval_T *argvars, typval_T *rettv)
{
    varnumber_T		n;
    int			error = FALSE;

#ifdef MESSAGE_QUEUE
    // vpeekc() used to check for messages, but that caused problems, invoking
    // a callback where it was not expected.  Some plugins use getchar(1) in a
    // loop to await a message, therefore make sure we check for messages here.
    parse_queued_messages();
#endif

    /* Position the cursor.  Needed after a message that ends in a space. */
    windgoto(msg_row, msg_col);

    ++no_mapping;
    ++allow_keys;
    for (;;)
    {
	if (argvars[0].v_type == VAR_UNKNOWN)
	    /* getchar(): blocking wait. */
	    n = plain_vgetc();
	else if (tv_get_number_chk(&argvars[0], &error) == 1)
	    /* getchar(1): only check if char avail */
	    n = vpeekc_any();
	else if (error || vpeekc_any() == NUL)
	    /* illegal argument or getchar(0) and no char avail: return zero */
	    n = 0;
	else
	    /* getchar(0) and char avail: return char */
	    n = plain_vgetc();

	if (n == K_IGNORE)
	    continue;
	break;
    }
    --no_mapping;
    --allow_keys;

    set_vim_var_nr(VV_MOUSE_WIN, 0);
    set_vim_var_nr(VV_MOUSE_WINID, 0);
    set_vim_var_nr(VV_MOUSE_LNUM, 0);
    set_vim_var_nr(VV_MOUSE_COL, 0);

    rettv->vval.v_number = n;
    if (IS_SPECIAL(n) || mod_mask != 0)
    {
	char_u		temp[10];   /* modifier: 3, mbyte-char: 6, NUL: 1 */
	int		i = 0;

	/* Turn a special key into three bytes, plus modifier. */
	if (mod_mask != 0)
	{
	    temp[i++] = K_SPECIAL;
	    temp[i++] = KS_MODIFIER;
	    temp[i++] = mod_mask;
	}
	if (IS_SPECIAL(n))
	{
	    temp[i++] = K_SPECIAL;
	    temp[i++] = K_SECOND(n);
	    temp[i++] = K_THIRD(n);
	}
#ifdef FEAT_MBYTE
	else if (has_mbyte)
	    i += (*mb_char2bytes)(n, temp + i);
#endif
	else
	    temp[i++] = n;
	temp[i++] = NUL;
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = vim_strsave(temp);

#ifdef FEAT_MOUSE
	if (is_mouse_key(n))
	{
	    int		row = mouse_row;
	    int		col = mouse_col;
	    win_T	*win;
	    linenr_T	lnum;
	    win_T	*wp;
	    int		winnr = 1;

	    if (row >= 0 && col >= 0)
	    {
		/* Find the window at the mouse coordinates and compute the
		 * text position. */
		win = mouse_find_win(&row, &col);
		if (win == NULL)
		    return;
		(void)mouse_comp_pos(win, &row, &col, &lnum);
		for (wp = firstwin; wp != win; wp = wp->w_next)
		    ++winnr;
		set_vim_var_nr(VV_MOUSE_WIN, winnr);
		set_vim_var_nr(VV_MOUSE_WINID, win->w_id);
		set_vim_var_nr(VV_MOUSE_LNUM, lnum);
		set_vim_var_nr(VV_MOUSE_COL, col + 1);
	    }
	}
#endif
    }
}

/*
 * "getcharmod()" function
 */
    static void
f_getcharmod(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = mod_mask;
}

/*
 * "getcharsearch()" function
 */
    static void
f_getcharsearch(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) != FAIL)
    {
	dict_T *dict = rettv->vval.v_dict;

	dict_add_string(dict, "char", last_csearch());
	dict_add_number(dict, "forward", last_csearch_forward());
	dict_add_number(dict, "until", last_csearch_until());
    }
}

/*
 * "getcmdline()" function
 */
    static void
f_getcmdline(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = get_cmdline_str();
}

/*
 * "getcmdpos()" function
 */
    static void
f_getcmdpos(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = get_cmdline_pos() + 1;
}

/*
 * "getcmdtype()" function
 */
    static void
f_getcmdtype(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = alloc(2);
    if (rettv->vval.v_string != NULL)
    {
	rettv->vval.v_string[0] = get_cmdline_type();
	rettv->vval.v_string[1] = NUL;
    }
}

/*
 * "getcmdwintype()" function
 */
    static void
f_getcmdwintype(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_CMDWIN
    rettv->vval.v_string = alloc(2);
    if (rettv->vval.v_string != NULL)
    {
	rettv->vval.v_string[0] = cmdwin_type;
	rettv->vval.v_string[1] = NUL;
    }
#endif
}

#if defined(FEAT_CMDL_COMPL)
/*
 * "getcompletion()" function
 */
    static void
f_getcompletion(typval_T *argvars, typval_T *rettv)
{
    char_u	*pat;
    expand_T	xpc;
    int		filtered = FALSE;
    int		options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
					| WILD_NO_BEEP;

    if (argvars[2].v_type != VAR_UNKNOWN)
	filtered = tv_get_number_chk(&argvars[2], NULL);

    if (p_wic)
	options |= WILD_ICASE;

    /* For filtered results, 'wildignore' is used */
    if (!filtered)
	options |= WILD_KEEP_ALL;

    ExpandInit(&xpc);
    xpc.xp_pattern = tv_get_string(&argvars[0]);
    xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern);
    xpc.xp_context = cmdcomplete_str_to_type(tv_get_string(&argvars[1]));
    if (xpc.xp_context == EXPAND_NOTHING)
    {
	if (argvars[1].v_type == VAR_STRING)
	    EMSG2(_(e_invarg2), argvars[1].vval.v_string);
	else
	    EMSG(_(e_invarg));
	return;
    }

# if defined(FEAT_MENU)
    if (xpc.xp_context == EXPAND_MENUS)
    {
	set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, FALSE);
	xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern);
    }
# endif
#ifdef FEAT_CSCOPE
    if (xpc.xp_context == EXPAND_CSCOPE)
    {
	set_context_in_cscope_cmd(&xpc, xpc.xp_pattern, CMD_cscope);
	xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern);
    }
#endif
#ifdef FEAT_SIGNS
    if (xpc.xp_context == EXPAND_SIGN)
    {
	set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
	xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern);
    }
#endif

    pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context);
    if ((rettv_list_alloc(rettv) != FAIL) && (pat != NULL))
    {
	int	i;

	ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP);

	for (i = 0; i < xpc.xp_numfiles; i++)
	    list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
    }
    vim_free(pat);
    ExpandCleanup(&xpc);
}
#endif

/*
 * "getcwd()" function
 */
    static void
f_getcwd(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp = NULL;
    char_u	*cwd;
    int		global = FALSE;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (argvars[0].v_type == VAR_NUMBER && argvars[0].vval.v_number == -1)
	global = TRUE;
    else
	wp = find_tabwin(&argvars[0], &argvars[1]);

    if (wp != NULL && wp->w_localdir != NULL)
	rettv->vval.v_string = vim_strsave(wp->w_localdir);
    else if (wp != NULL || global)
    {
	if (globaldir != NULL)
	    rettv->vval.v_string = vim_strsave(globaldir);
	else
	{
	    cwd = alloc(MAXPATHL);
	    if (cwd != NULL)
	    {
		if (mch_dirname(cwd, MAXPATHL) != FAIL)
		    rettv->vval.v_string = vim_strsave(cwd);
		vim_free(cwd);
	    }
	}
    }
#ifdef BACKSLASH_IN_FILENAME
    if (rettv->vval.v_string != NULL)
	slash_adjust(rettv->vval.v_string);
#endif
}

/*
 * "getfontname()" function
 */
    static void
f_getfontname(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_GUI
    if (gui.in_use)
    {
	GuiFont font;
	char_u	*name = NULL;

	if (argvars[0].v_type == VAR_UNKNOWN)
	{
	    /* Get the "Normal" font.  Either the name saved by
	     * hl_set_font_name() or from the font ID. */
	    font = gui.norm_font;
	    name = hl_get_font_name();
	}
	else
	{
	    name = tv_get_string(&argvars[0]);
	    if (STRCMP(name, "*") == 0)	    /* don't use font dialog */
		return;
	    font = gui_mch_get_font(name, FALSE);
	    if (font == NOFONT)
		return;	    /* Invalid font name, return empty string. */
	}
	rettv->vval.v_string = gui_mch_get_fontname(font, name);
	if (argvars[0].v_type != VAR_UNKNOWN)
	    gui_mch_free_font(font);
    }
#endif
}

/*
 * "getfperm({fname})" function
 */
    static void
f_getfperm(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    stat_T	st;
    char_u	*perm = NULL;
    char_u	flags[] = "rwx";
    int		i;

    fname = tv_get_string(&argvars[0]);

    rettv->v_type = VAR_STRING;
    if (mch_stat((char *)fname, &st) >= 0)
    {
	perm = vim_strsave((char_u *)"---------");
	if (perm != NULL)
	{
	    for (i = 0; i < 9; i++)
	    {
		if (st.st_mode & (1 << (8 - i)))
		    perm[i] = flags[i % 3];
	    }
	}
    }
    rettv->vval.v_string = perm;
}

/*
 * "getfsize({fname})" function
 */
    static void
f_getfsize(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    stat_T	st;

    fname = tv_get_string(&argvars[0]);

    rettv->v_type = VAR_NUMBER;

    if (mch_stat((char *)fname, &st) >= 0)
    {
	if (mch_isdir(fname))
	    rettv->vval.v_number = 0;
	else
	{
	    rettv->vval.v_number = (varnumber_T)st.st_size;

	    /* non-perfect check for overflow */
	    if ((off_T)rettv->vval.v_number != (off_T)st.st_size)
		rettv->vval.v_number = -2;
	}
    }
    else
	  rettv->vval.v_number = -1;
}

/*
 * "getftime({fname})" function
 */
    static void
f_getftime(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    stat_T	st;

    fname = tv_get_string(&argvars[0]);

    if (mch_stat((char *)fname, &st) >= 0)
	rettv->vval.v_number = (varnumber_T)st.st_mtime;
    else
	rettv->vval.v_number = -1;
}

/*
 * "getftype({fname})" function
 */
    static void
f_getftype(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    stat_T	st;
    char_u	*type = NULL;
    char	*t;

    fname = tv_get_string(&argvars[0]);

    rettv->v_type = VAR_STRING;
    if (mch_lstat((char *)fname, &st) >= 0)
    {
	if (S_ISREG(st.st_mode))
	    t = "file";
	else if (S_ISDIR(st.st_mode))
	    t = "dir";
	else if (S_ISLNK(st.st_mode))
	    t = "link";
	else if (S_ISBLK(st.st_mode))
	    t = "bdev";
	else if (S_ISCHR(st.st_mode))
	    t = "cdev";
	else if (S_ISFIFO(st.st_mode))
	    t = "fifo";
	else if (S_ISSOCK(st.st_mode))
	    t = "socket";
	else
	    t = "other";
	type = vim_strsave((char_u *)t);
    }
    rettv->vval.v_string = type;
}

/*
 * "getjumplist()" function
 */
    static void
f_getjumplist(typval_T *argvars, typval_T *rettv)
{
#ifdef FEAT_JUMPLIST
    win_T	*wp;
    int		i;
    list_T	*l;
    dict_T	*d;
#endif

    if (rettv_list_alloc(rettv) != OK)
	return;

#ifdef FEAT_JUMPLIST
    wp = find_tabwin(&argvars[0], &argvars[1]);
    if (wp == NULL)
	return;

    l = list_alloc();
    if (l == NULL)
	return;

    if (list_append_list(rettv->vval.v_list, l) == FAIL)
	return;
    list_append_number(rettv->vval.v_list, (varnumber_T)wp->w_jumplistidx);

    cleanup_jumplist(wp, TRUE);

    for (i = 0; i < wp->w_jumplistlen; ++i)
    {
	if (wp->w_jumplist[i].fmark.mark.lnum == 0)
	    continue;
	if ((d = dict_alloc()) == NULL)
	    return;
	if (list_append_dict(l, d) == FAIL)
	    return;
	dict_add_number(d, "lnum", (long)wp->w_jumplist[i].fmark.mark.lnum);
	dict_add_number(d, "col", (long)wp->w_jumplist[i].fmark.mark.col);
# ifdef FEAT_VIRTUALEDIT
	dict_add_number(d, "coladd", (long)wp->w_jumplist[i].fmark.mark.coladd);
# endif
	dict_add_number(d, "bufnr", (long)wp->w_jumplist[i].fmark.fnum);
	if (wp->w_jumplist[i].fname != NULL)
	    dict_add_string(d, "filename", wp->w_jumplist[i].fname);
    }
#endif
}

/*
 * "getline(lnum, [end])" function
 */
    static void
f_getline(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;
    linenr_T	end;
    int		retlist;

    lnum = tv_get_lnum(argvars);
    if (argvars[1].v_type == VAR_UNKNOWN)
    {
	end = 0;
	retlist = FALSE;
    }
    else
    {
	end = tv_get_lnum(&argvars[1]);
	retlist = TRUE;
    }

    get_buffer_lines(curbuf, lnum, end, retlist, rettv);
}

#ifdef FEAT_QUICKFIX
    static void
get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv)
{
    if (what_arg->v_type == VAR_UNKNOWN)
    {
	if (rettv_list_alloc(rettv) == OK)
	    if (is_qf || wp != NULL)
		(void)get_errorlist(NULL, wp, -1, rettv->vval.v_list);
    }
    else
    {
	if (rettv_dict_alloc(rettv) == OK)
	    if (is_qf || (wp != NULL))
	    {
		if (what_arg->v_type == VAR_DICT)
		{
		    dict_T	*d = what_arg->vval.v_dict;

		    if (d != NULL)
			qf_get_properties(wp, d, rettv->vval.v_dict);
		}
		else
		    EMSG(_(e_dictreq));
	    }
    }
}
#endif

/*
 * "getloclist()" function
 */
    static void
f_getloclist(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_QUICKFIX
    win_T	*wp;

    wp = find_win_by_nr_or_id(&argvars[0]);
    get_qf_loc_list(FALSE, wp, &argvars[1], rettv);
#endif
}

/*
 * "getmatches()" function
 */
    static void
f_getmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    dict_T	*dict;
    matchitem_T	*cur = curwin->w_match_head;
    int		i;

    if (rettv_list_alloc(rettv) == OK)
    {
	while (cur != NULL)
	{
	    dict = dict_alloc();
	    if (dict == NULL)
		return;
	    if (cur->match.regprog == NULL)
	    {
		/* match added with matchaddpos() */
		for (i = 0; i < MAXPOSMATCH; ++i)
		{
		    llpos_T	*llpos;
		    char	buf[6];
		    list_T	*l;

		    llpos = &cur->pos.pos[i];
		    if (llpos->lnum == 0)
			break;
		    l = list_alloc();
		    if (l == NULL)
			break;
		    list_append_number(l, (varnumber_T)llpos->lnum);
		    if (llpos->col > 0)
		    {
			list_append_number(l, (varnumber_T)llpos->col);
			list_append_number(l, (varnumber_T)llpos->len);
		    }
		    sprintf(buf, "pos%d", i + 1);
		    dict_add_list(dict, buf, l);
		}
	    }
	    else
	    {
		dict_add_string(dict, "pattern", cur->pattern);
	    }
	    dict_add_string(dict, "group", syn_id2name(cur->hlg_id));
	    dict_add_number(dict, "priority", (long)cur->priority);
	    dict_add_number(dict, "id", (long)cur->id);
# if defined(FEAT_CONCEAL) && defined(FEAT_MBYTE)
	    if (cur->conceal_char)
	    {
		char_u buf[MB_MAXBYTES + 1];

		buf[(*mb_char2bytes)((int)cur->conceal_char, buf)] = NUL;
		dict_add_string(dict, "conceal", (char_u *)&buf);
	    }
# endif
	    list_append_dict(rettv->vval.v_list, dict);
	    cur = cur->next;
	}
    }
#endif
}

/*
 * "getpid()" function
 */
    static void
f_getpid(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = mch_get_pid();
}

    static void
getpos_both(
    typval_T	*argvars,
    typval_T	*rettv,
    int		getcurpos)
{
    pos_T	*fp;
    list_T	*l;
    int		fnum = -1;

    if (rettv_list_alloc(rettv) == OK)
    {
	l = rettv->vval.v_list;
	if (getcurpos)
	    fp = &curwin->w_cursor;
	else
	    fp = var2fpos(&argvars[0], TRUE, &fnum);
	if (fnum != -1)
	    list_append_number(l, (varnumber_T)fnum);
	else
	    list_append_number(l, (varnumber_T)0);
	list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum
							    : (varnumber_T)0);
	list_append_number(l, (fp != NULL)
		     ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
							    : (varnumber_T)0);
	list_append_number(l,
#ifdef FEAT_VIRTUALEDIT
				(fp != NULL) ? (varnumber_T)fp->coladd :
#endif
							      (varnumber_T)0);
	if (getcurpos)
	{
	    update_curswant();
	    list_append_number(l, curwin->w_curswant == MAXCOL ?
		    (varnumber_T)MAXCOL : (varnumber_T)curwin->w_curswant + 1);
	}
    }
    else
	rettv->vval.v_number = FALSE;
}

/*
 * "getcurpos()" function
 */
    static void
f_getcurpos(typval_T *argvars, typval_T *rettv)
{
    getpos_both(argvars, rettv, TRUE);
}

/*
 * "getpos(string)" function
 */
    static void
f_getpos(typval_T *argvars, typval_T *rettv)
{
    getpos_both(argvars, rettv, FALSE);
}

/*
 * "getqflist()" function
 */
    static void
f_getqflist(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_QUICKFIX
    get_qf_loc_list(TRUE, NULL, &argvars[0], rettv);
#endif
}

/*
 * "getreg()" function
 */
    static void
f_getreg(typval_T *argvars, typval_T *rettv)
{
    char_u	*strregname;
    int		regname;
    int		arg2 = FALSE;
    int		return_list = FALSE;
    int		error = FALSE;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	strregname = tv_get_string_chk(&argvars[0]);
	error = strregname == NULL;
	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    arg2 = (int)tv_get_number_chk(&argvars[1], &error);
	    if (!error && argvars[2].v_type != VAR_UNKNOWN)
		return_list = (int)tv_get_number_chk(&argvars[2], &error);
	}
    }
    else
	strregname = get_vim_var_str(VV_REG);

    if (error)
	return;

    regname = (strregname == NULL ? '"' : *strregname);
    if (regname == 0)
	regname = '"';

    if (return_list)
    {
	rettv->v_type = VAR_LIST;
	rettv->vval.v_list = (list_T *)get_reg_contents(regname,
				      (arg2 ? GREG_EXPR_SRC : 0) | GREG_LIST);
	if (rettv->vval.v_list == NULL)
	    (void)rettv_list_alloc(rettv);
	else
	    ++rettv->vval.v_list->lv_refcount;
    }
    else
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = get_reg_contents(regname,
						    arg2 ? GREG_EXPR_SRC : 0);
    }
}

/*
 * "getregtype()" function
 */
    static void
f_getregtype(typval_T *argvars, typval_T *rettv)
{
    char_u	*strregname;
    int		regname;
    char_u	buf[NUMBUFLEN + 2];
    long	reglen = 0;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	strregname = tv_get_string_chk(&argvars[0]);
	if (strregname == NULL)	    /* type error; errmsg already given */
	{
	    rettv->v_type = VAR_STRING;
	    rettv->vval.v_string = NULL;
	    return;
	}
    }
    else
	/* Default to v:register */
	strregname = get_vim_var_str(VV_REG);

    regname = (strregname == NULL ? '"' : *strregname);
    if (regname == 0)
	regname = '"';

    buf[0] = NUL;
    buf[1] = NUL;
    switch (get_reg_type(regname, &reglen))
    {
	case MLINE: buf[0] = 'V'; break;
	case MCHAR: buf[0] = 'v'; break;
	case MBLOCK:
		buf[0] = Ctrl_V;
		sprintf((char *)buf + 1, "%ld", reglen + 1);
		break;
    }
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * Returns information (variables, options, etc.) about a tab page
 * as a dictionary.
 */
    static dict_T *
get_tabpage_info(tabpage_T *tp, int tp_idx)
{
    win_T	*wp;
    dict_T	*dict;
    list_T	*l;

    dict = dict_alloc();
    if (dict == NULL)
	return NULL;

    dict_add_number(dict, "tabnr", tp_idx);

    l = list_alloc();
    if (l != NULL)
    {
	for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
		wp; wp = wp->w_next)
	    list_append_number(l, (varnumber_T)wp->w_id);
	dict_add_list(dict, "windows", l);
    }

    /* Make a reference to tabpage variables */
    dict_add_dict(dict, "variables", tp->tp_vars);

    return dict;
}

/*
 * "gettabinfo()" function
 */
    static void
f_gettabinfo(typval_T *argvars, typval_T *rettv)
{
    tabpage_T	*tp, *tparg = NULL;
    dict_T	*d;
    int		tpnr = 0;

    if (rettv_list_alloc(rettv) != OK)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	/* Information about one tab page */
	tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
	if (tparg == NULL)
	    return;
    }

    /* Get information about a specific tab page or all tab pages */
    FOR_ALL_TABPAGES(tp)
    {
	tpnr++;
	if (tparg != NULL && tp != tparg)
	    continue;
	d = get_tabpage_info(tp, tpnr);
	if (d != NULL)
	    list_append_dict(rettv->vval.v_list, d);
	if (tparg != NULL)
	    return;
    }
}

/*
 * "gettabvar()" function
 */
    static void
f_gettabvar(typval_T *argvars, typval_T *rettv)
{
    win_T	*oldcurwin;
    tabpage_T	*tp, *oldtabpage;
    dictitem_T	*v;
    char_u	*varname;
    int		done = FALSE;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    varname = tv_get_string_chk(&argvars[1]);
    tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
    if (tp != NULL && varname != NULL)
    {
	/* Set tp to be our tabpage, temporarily.  Also set the window to the
	 * first window in the tabpage, otherwise the window is not valid. */
	if (switch_win(&oldcurwin, &oldtabpage,
		tp == curtab || tp->tp_firstwin == NULL ? firstwin
					    : tp->tp_firstwin, tp, TRUE) == OK)
	{
	    /* look up the variable */
	    /* Let gettabvar({nr}, "") return the "t:" dictionary. */
	    v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', varname, FALSE);
	    if (v != NULL)
	    {
		copy_tv(&v->di_tv, rettv);
		done = TRUE;
	    }
	}

	/* restore previous notion of curwin */
	restore_win(oldcurwin, oldtabpage, TRUE);
    }

    if (!done && argvars[2].v_type != VAR_UNKNOWN)
	copy_tv(&argvars[2], rettv);
}

/*
 * "gettabwinvar()" function
 */
    static void
f_gettabwinvar(typval_T *argvars, typval_T *rettv)
{
    getwinvar(argvars, rettv, 1);
}

/*
 * "gettagstack()" function
 */
    static void
f_gettagstack(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp = curwin;			// default is current window

    if (rettv_dict_alloc(rettv) != OK)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	wp = find_win_by_nr_or_id(&argvars[0]);
	if (wp == NULL)
	    return;
    }

    get_tagstack(wp, rettv->vval.v_dict);
}

/*
 * Returns information about a window as a dictionary.
 */
    static dict_T *
get_win_info(win_T *wp, short tpnr, short winnr)
{
    dict_T	*dict;

    dict = dict_alloc();
    if (dict == NULL)
	return NULL;

    dict_add_number(dict, "tabnr", tpnr);
    dict_add_number(dict, "winnr", winnr);
    dict_add_number(dict, "winid", wp->w_id);
    dict_add_number(dict, "height", wp->w_height);
    dict_add_number(dict, "winrow", wp->w_winrow + 1);
#ifdef FEAT_MENU
    dict_add_number(dict, "winbar", wp->w_winbar_height);
#endif
    dict_add_number(dict, "width", wp->w_width);
    dict_add_number(dict, "wincol", wp->w_wincol + 1);
    dict_add_number(dict, "bufnr", wp->w_buffer->b_fnum);

#ifdef FEAT_TERMINAL
    dict_add_number(dict, "terminal", bt_terminal(wp->w_buffer));
#endif
#ifdef FEAT_QUICKFIX
    dict_add_number(dict, "quickfix", bt_quickfix(wp->w_buffer));
    dict_add_number(dict, "loclist",
		      (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL));
#endif

    /* Add a reference to window variables */
    dict_add_dict(dict, "variables", wp->w_vars);

    return dict;
}

/*
 * "getwininfo()" function
 */
    static void
f_getwininfo(typval_T *argvars, typval_T *rettv)
{
    tabpage_T	*tp;
    win_T	*wp = NULL, *wparg = NULL;
    dict_T	*d;
    short	tabnr = 0, winnr;

    if (rettv_list_alloc(rettv) != OK)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	wparg = win_id2wp(argvars);
	if (wparg == NULL)
	    return;
    }

    /* Collect information about either all the windows across all the tab
     * pages or one particular window.
     */
    FOR_ALL_TABPAGES(tp)
    {
	tabnr++;
	winnr = 0;
	FOR_ALL_WINDOWS_IN_TAB(tp, wp)
	{
	    winnr++;
	    if (wparg != NULL && wp != wparg)
		continue;
	    d = get_win_info(wp, tabnr, winnr);
	    if (d != NULL)
		list_append_dict(rettv->vval.v_list, d);
	    if (wparg != NULL)
		/* found information about a specific window */
		return;
	}
    }
}

/*
 * "win_findbuf()" function
 */
    static void
f_win_findbuf(typval_T *argvars, typval_T *rettv)
{
    if (rettv_list_alloc(rettv) != FAIL)
	win_findbuf(argvars, rettv->vval.v_list);
}

/*
 * "win_getid()" function
 */
    static void
f_win_getid(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = win_getid(argvars);
}

/*
 * "win_gotoid()" function
 */
    static void
f_win_gotoid(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = win_gotoid(argvars);
}

/*
 * "win_id2tabwin()" function
 */
    static void
f_win_id2tabwin(typval_T *argvars, typval_T *rettv)
{
    if (rettv_list_alloc(rettv) != FAIL)
	win_id2tabwin(argvars, rettv->vval.v_list);
}

/*
 * "win_id2win()" function
 */
    static void
f_win_id2win(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = win_id2win(argvars);
}

/*
 * "win_screenpos()" function
 */
    static void
f_win_screenpos(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    wp = find_win_by_nr_or_id(&argvars[0]);
    list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1);
    list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
}

/*
 * "getwinpos({timeout})" function
 */
    static void
f_getwinpos(typval_T *argvars UNUSED, typval_T *rettv)
{
    int x = -1;
    int y = -1;

    if (rettv_list_alloc(rettv) == FAIL)
	return;
#ifdef FEAT_GUI
    if (gui.in_use)
	(void)gui_mch_get_winpos(&x, &y);
# if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)
    else
# endif
#endif
#if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)
    {
	varnumber_T timeout = 100;

	if (argvars[0].v_type != VAR_UNKNOWN)
	    timeout = tv_get_number(&argvars[0]);
	term_get_winpos(&x, &y, timeout);
    }
#endif
    list_append_number(rettv->vval.v_list, (varnumber_T)x);
    list_append_number(rettv->vval.v_list, (varnumber_T)y);
}


/*
 * "getwinposx()" function
 */
    static void
f_getwinposx(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = -1;
#ifdef FEAT_GUI
    if (gui.in_use)
    {
	int	    x, y;

	if (gui_mch_get_winpos(&x, &y) == OK)
	    rettv->vval.v_number = x;
	return;
    }
#endif
#if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)
    {
	int	    x, y;

	if (term_get_winpos(&x, &y, (varnumber_T)100) == OK)
	    rettv->vval.v_number = x;
    }
#endif
}

/*
 * "getwinposy()" function
 */
    static void
f_getwinposy(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = -1;
#ifdef FEAT_GUI
    if (gui.in_use)
    {
	int	    x, y;

	if (gui_mch_get_winpos(&x, &y) == OK)
	    rettv->vval.v_number = y;
	return;
    }
#endif
#if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)
    {
	int	    x, y;

	if (term_get_winpos(&x, &y, (varnumber_T)100) == OK)
	    rettv->vval.v_number = y;
    }
#endif
}

/*
 * "getwinvar()" function
 */
    static void
f_getwinvar(typval_T *argvars, typval_T *rettv)
{
    getwinvar(argvars, rettv, 0);
}

/*
 * "glob()" function
 */
    static void
f_glob(typval_T *argvars, typval_T *rettv)
{
    int		options = WILD_SILENT|WILD_USE_NL;
    expand_T	xpc;
    int		error = FALSE;

    /* When the optional second argument is non-zero, don't remove matches
     * for 'wildignore' and don't put matches for 'suffixes' at the end. */
    rettv->v_type = VAR_STRING;
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	if (tv_get_number_chk(&argvars[1], &error))
	    options |= WILD_KEEP_ALL;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    if (tv_get_number_chk(&argvars[2], &error))
	    {
		rettv_list_set(rettv, NULL);
	    }
	    if (argvars[3].v_type != VAR_UNKNOWN
				    && tv_get_number_chk(&argvars[3], &error))
		options |= WILD_ALLLINKS;
	}
    }
    if (!error)
    {
	ExpandInit(&xpc);
	xpc.xp_context = EXPAND_FILES;
	if (p_wic)
	    options += WILD_ICASE;
	if (rettv->v_type == VAR_STRING)
	    rettv->vval.v_string = ExpandOne(&xpc, tv_get_string(&argvars[0]),
						     NULL, options, WILD_ALL);
	else if (rettv_list_alloc(rettv) != FAIL)
	{
	  int i;

	  ExpandOne(&xpc, tv_get_string(&argvars[0]),
						NULL, options, WILD_ALL_KEEP);
	  for (i = 0; i < xpc.xp_numfiles; i++)
	      list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);

	  ExpandCleanup(&xpc);
	}
    }
    else
	rettv->vval.v_string = NULL;
}

/*
 * "globpath()" function
 */
    static void
f_globpath(typval_T *argvars, typval_T *rettv)
{
    int		flags = 0;
    char_u	buf1[NUMBUFLEN];
    char_u	*file = tv_get_string_buf_chk(&argvars[1], buf1);
    int		error = FALSE;
    garray_T	ga;
    int		i;

    /* When the optional second argument is non-zero, don't remove matches
    * for 'wildignore' and don't put matches for 'suffixes' at the end. */
    rettv->v_type = VAR_STRING;
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	if (tv_get_number_chk(&argvars[2], &error))
	    flags |= WILD_KEEP_ALL;
	if (argvars[3].v_type != VAR_UNKNOWN)
	{
	    if (tv_get_number_chk(&argvars[3], &error))
	    {
		rettv_list_set(rettv, NULL);
	    }
	    if (argvars[4].v_type != VAR_UNKNOWN
				    && tv_get_number_chk(&argvars[4], &error))
		flags |= WILD_ALLLINKS;
	}
    }
    if (file != NULL && !error)
    {
	ga_init2(&ga, (int)sizeof(char_u *), 10);
	globpath(tv_get_string(&argvars[0]), file, &ga, flags);
	if (rettv->v_type == VAR_STRING)
	    rettv->vval.v_string = ga_concat_strings(&ga, "\n");
	else if (rettv_list_alloc(rettv) != FAIL)
	    for (i = 0; i < ga.ga_len; ++i)
		list_append_string(rettv->vval.v_list,
					    ((char_u **)(ga.ga_data))[i], -1);
	ga_clear_strings(&ga);
    }
    else
	rettv->vval.v_string = NULL;
}

/*
 * "glob2regpat()" function
 */
    static void
f_glob2regpat(typval_T *argvars, typval_T *rettv)
{
    char_u	*pat = tv_get_string_chk(&argvars[0]);

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = (pat == NULL)
			 ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, FALSE);
}

/* for VIM_VERSION_ defines */
#include "version.h"

/*
 * "has()" function
 */
    static void
f_has(typval_T *argvars, typval_T *rettv)
{
    int		i;
    char_u	*name;
    int		n = FALSE;
    static char	*(has_list[]) =
    {
#ifdef AMIGA
	"amiga",
# ifdef FEAT_ARP
	"arp",
# endif
#endif
#ifdef __BEOS__
	"beos",
#endif
#ifdef MACOS_X
	"mac",		/* Mac OS X (and, once, Mac OS Classic) */
	"osx",		/* Mac OS X */
# ifdef MACOS_X_DARWIN
	"macunix",	/* Mac OS X, with the darwin feature */
	"osxdarwin",	/* synonym for macunix */
# endif
#endif
#ifdef __QNX__
	"qnx",
#endif
#ifdef UNIX
	"unix",
#endif
#ifdef VMS
	"vms",
#endif
#ifdef WIN32
	"win32",
#endif
#if defined(UNIX) && (defined(__CYGWIN32__) || defined(__CYGWIN__))
	"win32unix",
#endif
#if defined(WIN64) || defined(_WIN64)
	"win64",
#endif
#ifdef EBCDIC
	"ebcdic",
#endif
#ifndef CASE_INSENSITIVE_FILENAME
	"fname_case",
#endif
#ifdef HAVE_ACL
	"acl",
#endif
#ifdef FEAT_ARABIC
	"arabic",
#endif
	"autocmd",
#ifdef FEAT_AUTOCHDIR
       "autochdir",
#endif
#ifdef FEAT_AUTOSERVERNAME
	"autoservername",
#endif
#ifdef FEAT_BEVAL_GUI
	"balloon_eval",
# ifndef FEAT_GUI_W32 /* other GUIs always have multiline balloons */
	"balloon_multiline",
# endif
#endif
#ifdef FEAT_BEVAL_TERM
	"balloon_eval_term",
#endif
#if defined(SOME_BUILTIN_TCAPS) || defined(ALL_BUILTIN_TCAPS)
	"builtin_terms",
# ifdef ALL_BUILTIN_TCAPS
	"all_builtin_terms",
# endif
#endif
#if defined(FEAT_BROWSE) && (defined(USE_FILE_CHOOSER) \
	|| defined(FEAT_GUI_W32) \
	|| defined(FEAT_GUI_MOTIF))
	"browsefilter",
#endif
#ifdef FEAT_BYTEOFF
	"byte_offset",
#endif
#ifdef FEAT_JOB_CHANNEL
	"channel",
#endif
#ifdef FEAT_CINDENT
	"cindent",
#endif
#ifdef FEAT_CLIENTSERVER
	"clientserver",
#endif
#ifdef FEAT_CLIPBOARD
	"clipboard",
#endif
#ifdef FEAT_CMDL_COMPL
	"cmdline_compl",
#endif
#ifdef FEAT_CMDHIST
	"cmdline_hist",
#endif
#ifdef FEAT_COMMENTS
	"comments",
#endif
#ifdef FEAT_CONCEAL
	"conceal",
#endif
#ifdef FEAT_CRYPT
	"cryptv",
	"crypt-blowfish",
	"crypt-blowfish2",
#endif
#ifdef FEAT_CSCOPE
	"cscope",
#endif
	"cursorbind",
#ifdef CURSOR_SHAPE
	"cursorshape",
#endif
#ifdef DEBUG
	"debug",
#endif
#ifdef FEAT_CON_DIALOG
	"dialog_con",
#endif
#ifdef FEAT_GUI_DIALOG
	"dialog_gui",
#endif
#ifdef FEAT_DIFF
	"diff",
#endif
#ifdef FEAT_DIGRAPHS
	"digraphs",
#endif
#ifdef FEAT_DIRECTX
	"directx",
#endif
#ifdef FEAT_DND
	"dnd",
#endif
#ifdef FEAT_EMACS_TAGS
	"emacs_tags",
#endif
	"eval",	    /* always present, of course! */
	"ex_extra", /* graduated feature */
#ifdef FEAT_SEARCH_EXTRA
	"extra_search",
#endif
#ifdef FEAT_FKMAP
	"farsi",
#endif
#ifdef FEAT_SEARCHPATH
	"file_in_path",
#endif
#ifdef FEAT_FILTERPIPE
	"filterpipe",
#endif
#ifdef FEAT_FIND_ID
	"find_in_path",
#endif
#ifdef FEAT_FLOAT
	"float",
#endif
#ifdef FEAT_FOLDING
	"folding",
#endif
#ifdef FEAT_FOOTER
	"footer",
#endif
#if !defined(USE_SYSTEM) && defined(UNIX)
	"fork",
#endif
#ifdef FEAT_GETTEXT
	"gettext",
#endif
#ifdef FEAT_GUI
	"gui",
#endif
#ifdef FEAT_GUI_ATHENA
# ifdef FEAT_GUI_NEXTAW
	"gui_neXtaw",
# else
	"gui_athena",
# endif
#endif
#ifdef FEAT_GUI_GTK
	"gui_gtk",
# ifdef USE_GTK3
	"gui_gtk3",
# else
	"gui_gtk2",
# endif
#endif
#ifdef FEAT_GUI_GNOME
	"gui_gnome",
#endif
#ifdef FEAT_GUI_MAC
	"gui_mac",
#endif
#ifdef FEAT_GUI_MOTIF
	"gui_motif",
#endif
#ifdef FEAT_GUI_PHOTON
	"gui_photon",
#endif
#ifdef FEAT_GUI_W32
	"gui_win32",
#endif
#ifdef FEAT_HANGULIN
	"hangul_input",
#endif
#if defined(HAVE_ICONV_H) && defined(USE_ICONV)
	"iconv",
#endif
#ifdef FEAT_INS_EXPAND
	"insert_expand",
#endif
#ifdef FEAT_JOB_CHANNEL
	"job",
#endif
#ifdef FEAT_JUMPLIST
	"jumplist",
#endif
#ifdef FEAT_KEYMAP
	"keymap",
#endif
	"lambda", /* always with FEAT_EVAL, since 7.4.2120 with closure */
#ifdef FEAT_LANGMAP
	"langmap",
#endif
#ifdef FEAT_LIBCALL
	"libcall",
#endif
#ifdef FEAT_LINEBREAK
	"linebreak",
#endif
#ifdef FEAT_LISP
	"lispindent",
#endif
	"listcmds",
#ifdef FEAT_LOCALMAP
	"localmap",
#endif
#ifdef FEAT_LUA
# ifndef DYNAMIC_LUA
	"lua",
# endif
#endif
#ifdef FEAT_MENU
	"menu",
#endif
#ifdef FEAT_SESSION
	"mksession",
#endif
#ifdef FEAT_MODIFY_FNAME
	"modify_fname",
#endif
#ifdef FEAT_MOUSE
	"mouse",
#endif
#ifdef FEAT_MOUSESHAPE
	"mouseshape",
#endif
#if defined(UNIX) || defined(VMS)
# ifdef FEAT_MOUSE_DEC
	"mouse_dec",
# endif
# ifdef FEAT_MOUSE_GPM
	"mouse_gpm",
# endif
# ifdef FEAT_MOUSE_JSB
	"mouse_jsbterm",
# endif
# ifdef FEAT_MOUSE_NET
	"mouse_netterm",
# endif
# ifdef FEAT_MOUSE_PTERM
	"mouse_pterm",
# endif
# ifdef FEAT_MOUSE_SGR
	"mouse_sgr",
# endif
# ifdef FEAT_SYSMOUSE
	"mouse_sysmouse",
# endif
# ifdef FEAT_MOUSE_URXVT
	"mouse_urxvt",
# endif
# ifdef FEAT_MOUSE_XTERM
	"mouse_xterm",
# endif
#endif
#ifdef FEAT_MBYTE
	"multi_byte",
#endif
#ifdef FEAT_MBYTE_IME
	"multi_byte_ime",
#endif
#ifdef FEAT_MULTI_LANG
	"multi_lang",
#endif
#ifdef FEAT_MZSCHEME
#ifndef DYNAMIC_MZSCHEME
	"mzscheme",
#endif
#endif
#ifdef FEAT_NUM64
	"num64",
#endif
#ifdef FEAT_OLE
	"ole",
#endif
#ifdef FEAT_EVAL
	"packages",
#endif
#ifdef FEAT_PATH_EXTRA
	"path_extra",
#endif
#ifdef FEAT_PERL
#ifndef DYNAMIC_PERL
	"perl",
#endif
#endif
#ifdef FEAT_PERSISTENT_UNDO
	"persistent_undo",
#endif
#if defined(FEAT_PYTHON)
	"python_compiled",
# if defined(DYNAMIC_PYTHON)
	"python_dynamic",
# else
	"python",
	"pythonx",
# endif
#endif
#if defined(FEAT_PYTHON3)
	"python3_compiled",
# if defined(DYNAMIC_PYTHON3)
	"python3_dynamic",
# else
	"python3",
	"pythonx",
# endif
#endif
#ifdef FEAT_POSTSCRIPT
	"postscript",
#endif
#ifdef FEAT_PRINTER
	"printer",
#endif
#ifdef FEAT_PROFILE
	"profile",
#endif
#ifdef FEAT_RELTIME
	"reltime",
#endif
#ifdef FEAT_QUICKFIX
	"quickfix",
#endif
#ifdef FEAT_RIGHTLEFT
	"rightleft",
#endif
#if defined(FEAT_RUBY) && !defined(DYNAMIC_RUBY)
	"ruby",
#endif
	"scrollbind",
#ifdef FEAT_CMDL_INFO
	"showcmd",
	"cmdline_info",
#endif
#ifdef FEAT_SIGNS
	"signs",
#endif
#ifdef FEAT_SMARTINDENT
	"smartindent",
#endif
#ifdef STARTUPTIME
	"startuptime",
#endif
#ifdef FEAT_STL_OPT
	"statusline",
#endif
#ifdef FEAT_SUN_WORKSHOP
	"sun_workshop",
#endif
#ifdef FEAT_NETBEANS_INTG
	"netbeans_intg",
#endif
#ifdef FEAT_SPELL
	"spell",
#endif
#ifdef FEAT_SYN_HL
	"syntax",
#endif
#if defined(USE_SYSTEM) || !defined(UNIX)
	"system",
#endif
#ifdef FEAT_TAG_BINS
	"tag_binary",
#endif
#ifdef FEAT_TAG_OLDSTATIC
	"tag_old_static",
#endif
#ifdef FEAT_TAG_ANYWHITE
	"tag_any_white",
#endif
#ifdef FEAT_TCL
# ifndef DYNAMIC_TCL
	"tcl",
# endif
#endif
#ifdef FEAT_TERMGUICOLORS
	"termguicolors",
#endif
#if defined(FEAT_TERMINAL) && !defined(WIN3264)
	"terminal",
#endif
#ifdef TERMINFO
	"terminfo",
#endif
#ifdef FEAT_TERMRESPONSE
	"termresponse",
#endif
#ifdef FEAT_TEXTOBJ
	"textobjects",
#endif
#ifdef FEAT_TEXT_PROP
	"textprop",
#endif
#ifdef HAVE_TGETENT
	"tgetent",
#endif
#ifdef FEAT_TIMERS
	"timers",
#endif
#ifdef FEAT_TITLE
	"title",
#endif
#ifdef FEAT_TOOLBAR
	"toolbar",
#endif
#if defined(FEAT_CLIPBOARD) && defined(FEAT_X11)
	"unnamedplus",
#endif
#ifdef FEAT_USR_CMDS
	"user-commands",    /* was accidentally included in 5.4 */
	"user_commands",
#endif
#ifdef FEAT_VARTABS
	"vartabs",
#endif
#ifdef FEAT_VIMINFO
	"viminfo",
#endif
	"vertsplit",
#ifdef FEAT_VIRTUALEDIT
	"virtualedit",
#endif
	"visual",
#ifdef FEAT_VISUALEXTRA
	"visualextra",
#endif
	"vreplace",
#ifdef FEAT_VTP
	"vtp",
#endif
#ifdef FEAT_WILDIGN
	"wildignore",
#endif
#ifdef FEAT_WILDMENU
	"wildmenu",
#endif
	"windows",
#ifdef FEAT_WAK
	"winaltkeys",
#endif
#ifdef FEAT_WRITEBACKUP
	"writebackup",
#endif
#ifdef FEAT_XIM
	"xim",
#endif
#ifdef FEAT_XFONTSET
	"xfontset",
#endif
#ifdef FEAT_XPM_W32
	"xpm",
	"xpm_w32",	/* for backward compatibility */
#else
# if defined(HAVE_XPM)
	"xpm",
# endif
#endif
#ifdef USE_XSMP
	"xsmp",
#endif
#ifdef USE_XSMP_INTERACT
	"xsmp_interact",
#endif
#ifdef FEAT_XCLIPBOARD
	"xterm_clipboard",
#endif
#ifdef FEAT_XTERM_SAVE
	"xterm_save",
#endif
#if defined(UNIX) && defined(FEAT_X11)
	"X11",
#endif
	NULL
    };

    name = tv_get_string(&argvars[0]);
    for (i = 0; has_list[i] != NULL; ++i)
	if (STRICMP(name, has_list[i]) == 0)
	{
	    n = TRUE;
	    break;
	}

    if (n == FALSE)
    {
	if (STRNICMP(name, "patch", 5) == 0)
	{
	    if (name[5] == '-'
		    && STRLEN(name) >= 11
		    && vim_isdigit(name[6])
		    && vim_isdigit(name[8])
		    && vim_isdigit(name[10]))
	    {
		int major = atoi((char *)name + 6);
		int minor = atoi((char *)name + 8);

		/* Expect "patch-9.9.01234". */
		n = (major < VIM_VERSION_MAJOR
		     || (major == VIM_VERSION_MAJOR
			 && (minor < VIM_VERSION_MINOR
			     || (minor == VIM_VERSION_MINOR
				 && has_patch(atoi((char *)name + 10))))));
	    }
	    else
		n = has_patch(atoi((char *)name + 5));
	}
	else if (STRICMP(name, "vim_starting") == 0)
	    n = (starting != 0);
	else if (STRICMP(name, "ttyin") == 0)
	    n = mch_input_isatty();
	else if (STRICMP(name, "ttyout") == 0)
	    n = stdout_isatty;
#ifdef FEAT_MBYTE
	else if (STRICMP(name, "multi_byte_encoding") == 0)
	    n = has_mbyte;
#endif
#if defined(FEAT_BEVAL) && defined(FEAT_GUI_W32)
	else if (STRICMP(name, "balloon_multiline") == 0)
	    n = multiline_balloon_available();
#endif
#ifdef DYNAMIC_TCL
	else if (STRICMP(name, "tcl") == 0)
	    n = tcl_enabled(FALSE);
#endif
#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
	else if (STRICMP(name, "iconv") == 0)
	    n = iconv_enabled(FALSE);
#endif
#ifdef DYNAMIC_LUA
	else if (STRICMP(name, "lua") == 0)
	    n = lua_enabled(FALSE);
#endif
#ifdef DYNAMIC_MZSCHEME
	else if (STRICMP(name, "mzscheme") == 0)
	    n = mzscheme_enabled(FALSE);
#endif
#ifdef DYNAMIC_RUBY
	else if (STRICMP(name, "ruby") == 0)
	    n = ruby_enabled(FALSE);
#endif
#ifdef DYNAMIC_PYTHON
	else if (STRICMP(name, "python") == 0)
	    n = python_enabled(FALSE);
#endif
#ifdef DYNAMIC_PYTHON3
	else if (STRICMP(name, "python3") == 0)
	    n = python3_enabled(FALSE);
#endif
#if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3)
	else if (STRICMP(name, "pythonx") == 0)
	{
# if defined(DYNAMIC_PYTHON) && defined(DYNAMIC_PYTHON3)
	    if (p_pyx == 0)
		n = python3_enabled(FALSE) || python_enabled(FALSE);
	    else if (p_pyx == 3)
		n = python3_enabled(FALSE);
	    else if (p_pyx == 2)
		n = python_enabled(FALSE);
# elif defined(DYNAMIC_PYTHON)
	    n = python_enabled(FALSE);
# elif defined(DYNAMIC_PYTHON3)
	    n = python3_enabled(FALSE);
# endif
	}
#endif
#ifdef DYNAMIC_PERL
	else if (STRICMP(name, "perl") == 0)
	    n = perl_enabled(FALSE);
#endif
#ifdef FEAT_GUI
	else if (STRICMP(name, "gui_running") == 0)
	    n = (gui.in_use || gui.starting);
# ifdef FEAT_BROWSE
	else if (STRICMP(name, "browse") == 0)
	    n = gui.in_use;	/* gui_mch_browse() works when GUI is running */
# endif
#endif
#ifdef FEAT_SYN_HL
	else if (STRICMP(name, "syntax_items") == 0)
	    n = syntax_present(curwin);
#endif
#ifdef FEAT_VTP
	else if (STRICMP(name, "vcon") == 0)
	    n = is_term_win32() && has_vtp_working();
#endif
#ifdef FEAT_NETBEANS_INTG
	else if (STRICMP(name, "netbeans_enabled") == 0)
	    n = netbeans_active();
#endif
#if defined(FEAT_TERMINAL) && defined(WIN3264)
	else if (STRICMP(name, "terminal") == 0)
	    n = terminal_enabled();
#endif
    }

    rettv->vval.v_number = n;
}

/*
 * "has_key()" function
 */
    static void
f_has_key(typval_T *argvars, typval_T *rettv)
{
    if (argvars[0].v_type != VAR_DICT)
    {
	EMSG(_(e_dictreq));
	return;
    }
    if (argvars[0].vval.v_dict == NULL)
	return;

    rettv->vval.v_number = dict_find(argvars[0].vval.v_dict,
				      tv_get_string(&argvars[1]), -1) != NULL;
}

/*
 * "haslocaldir()" function
 */
    static void
f_haslocaldir(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp = NULL;

    wp = find_tabwin(&argvars[0], &argvars[1]);
    rettv->vval.v_number = (wp != NULL && wp->w_localdir != NULL);
}

/*
 * "hasmapto()" function
 */
    static void
f_hasmapto(typval_T *argvars, typval_T *rettv)
{
    char_u	*name;
    char_u	*mode;
    char_u	buf[NUMBUFLEN];
    int		abbr = FALSE;

    name = tv_get_string(&argvars[0]);
    if (argvars[1].v_type == VAR_UNKNOWN)
	mode = (char_u *)"nvo";
    else
    {
	mode = tv_get_string_buf(&argvars[1], buf);
	if (argvars[2].v_type != VAR_UNKNOWN)
	    abbr = (int)tv_get_number(&argvars[2]);
    }

    if (map_to_exists(name, mode, abbr))
	rettv->vval.v_number = TRUE;
    else
	rettv->vval.v_number = FALSE;
}

/*
 * "histadd()" function
 */
    static void
f_histadd(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_CMDHIST
    int		histype;
    char_u	*str;
    char_u	buf[NUMBUFLEN];
#endif

    rettv->vval.v_number = FALSE;
    if (check_restricted() || check_secure())
	return;
#ifdef FEAT_CMDHIST
    str = tv_get_string_chk(&argvars[0]);	/* NULL on type error */
    histype = str != NULL ? get_histtype(str) : -1;
    if (histype >= 0)
    {
	str = tv_get_string_buf(&argvars[1], buf);
	if (*str != NUL)
	{
	    init_history();
	    add_to_history(histype, str, FALSE, NUL);
	    rettv->vval.v_number = TRUE;
	    return;
	}
    }
#endif
}

/*
 * "histdel()" function
 */
    static void
f_histdel(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_CMDHIST
    int		n;
    char_u	buf[NUMBUFLEN];
    char_u	*str;

    str = tv_get_string_chk(&argvars[0]);	/* NULL on type error */
    if (str == NULL)
	n = 0;
    else if (argvars[1].v_type == VAR_UNKNOWN)
	/* only one argument: clear entire history */
	n = clr_history(get_histtype(str));
    else if (argvars[1].v_type == VAR_NUMBER)
	/* index given: remove that entry */
	n = del_history_idx(get_histtype(str),
					  (int)tv_get_number(&argvars[1]));
    else
	/* string given: remove all matching entries */
	n = del_history_entry(get_histtype(str),
				      tv_get_string_buf(&argvars[1], buf));
    rettv->vval.v_number = n;
#endif
}

/*
 * "histget()" function
 */
    static void
f_histget(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_CMDHIST
    int		type;
    int		idx;
    char_u	*str;

    str = tv_get_string_chk(&argvars[0]);	/* NULL on type error */
    if (str == NULL)
	rettv->vval.v_string = NULL;
    else
    {
	type = get_histtype(str);
	if (argvars[1].v_type == VAR_UNKNOWN)
	    idx = get_history_idx(type);
	else
	    idx = (int)tv_get_number_chk(&argvars[1], NULL);
						    /* -1 on type error */
	rettv->vval.v_string = vim_strsave(get_history_entry(type, idx));
    }
#else
    rettv->vval.v_string = NULL;
#endif
    rettv->v_type = VAR_STRING;
}

/*
 * "histnr()" function
 */
    static void
f_histnr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		i;

#ifdef FEAT_CMDHIST
    char_u	*history = tv_get_string_chk(&argvars[0]);

    i = history == NULL ? HIST_CMD - 1 : get_histtype(history);
    if (i >= HIST_CMD && i < HIST_COUNT)
	i = get_history_idx(i);
    else
#endif
	i = -1;
    rettv->vval.v_number = i;
}

/*
 * "highlightID(name)" function
 */
    static void
f_hlID(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0]));
}

/*
 * "highlight_exists()" function
 */
    static void
f_hlexists(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0]));
}

/*
 * "hostname()" function
 */
    static void
f_hostname(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u hostname[256];

    mch_get_host_name(hostname, 256);
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(hostname);
}

/*
 * iconv() function
 */
    static void
f_iconv(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_MBYTE
    char_u	buf1[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    char_u	*from, *to, *str;
    vimconv_T	vimconv;
#endif

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

#ifdef FEAT_MBYTE
    str = tv_get_string(&argvars[0]);
    from = enc_canonize(enc_skip(tv_get_string_buf(&argvars[1], buf1)));
    to = enc_canonize(enc_skip(tv_get_string_buf(&argvars[2], buf2)));
    vimconv.vc_type = CONV_NONE;
    convert_setup(&vimconv, from, to);

    /* If the encodings are equal, no conversion needed. */
    if (vimconv.vc_type == CONV_NONE)
	rettv->vval.v_string = vim_strsave(str);
    else
	rettv->vval.v_string = string_convert(&vimconv, str, NULL);

    convert_setup(&vimconv, NULL, NULL);
    vim_free(from);
    vim_free(to);
#endif
}

/*
 * "indent()" function
 */
    static void
f_indent(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;

    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
	rettv->vval.v_number = get_indent_lnum(lnum);
    else
	rettv->vval.v_number = -1;
}

/*
 * "index()" function
 */
    static void
f_index(typval_T *argvars, typval_T *rettv)
{
    list_T	*l;
    listitem_T	*item;
    long	idx = 0;
    int		ic = FALSE;

    rettv->vval.v_number = -1;
    if (argvars[0].v_type != VAR_LIST)
    {
	EMSG(_(e_listreq));
	return;
    }
    l = argvars[0].vval.v_list;
    if (l != NULL)
    {
	item = l->lv_first;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    int		error = FALSE;

	    /* Start at specified item.  Use the cached index that list_find()
	     * sets, so that a negative number also works. */
	    item = list_find(l, (long)tv_get_number_chk(&argvars[2], &error));
	    idx = l->lv_idx;
	    if (argvars[3].v_type != VAR_UNKNOWN)
		ic = (int)tv_get_number_chk(&argvars[3], &error);
	    if (error)
		item = NULL;
	}

	for ( ; item != NULL; item = item->li_next, ++idx)
	    if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE))
	    {
		rettv->vval.v_number = idx;
		break;
	    }
    }
}

static int inputsecret_flag = 0;

/*
 * "input()" function
 *     Also handles inputsecret() when inputsecret is set.
 */
    static void
f_input(typval_T *argvars, typval_T *rettv)
{
    get_user_input(argvars, rettv, FALSE, inputsecret_flag);
}

/*
 * "inputdialog()" function
 */
    static void
f_inputdialog(typval_T *argvars, typval_T *rettv)
{
#if defined(FEAT_GUI_TEXTDIALOG)
    /* Use a GUI dialog if the GUI is running and 'c' is not in 'guioptions' */
    if (gui.in_use && vim_strchr(p_go, GO_CONDIALOG) == NULL)
    {
	char_u	*message;
	char_u	buf[NUMBUFLEN];
	char_u	*defstr = (char_u *)"";

	message = tv_get_string_chk(&argvars[0]);
	if (argvars[1].v_type != VAR_UNKNOWN
		&& (defstr = tv_get_string_buf_chk(&argvars[1], buf)) != NULL)
	    vim_strncpy(IObuff, defstr, IOSIZE - 1);
	else
	    IObuff[0] = NUL;
	if (message != NULL && defstr != NULL
		&& do_dialog(VIM_QUESTION, NULL, message,
			  (char_u *)_("&OK\n&Cancel"), 1, IObuff, FALSE) == 1)
	    rettv->vval.v_string = vim_strsave(IObuff);
	else
	{
	    if (message != NULL && defstr != NULL
					&& argvars[1].v_type != VAR_UNKNOWN
					&& argvars[2].v_type != VAR_UNKNOWN)
		rettv->vval.v_string = vim_strsave(
				      tv_get_string_buf(&argvars[2], buf));
	    else
		rettv->vval.v_string = NULL;
	}
	rettv->v_type = VAR_STRING;
    }
    else
#endif
	get_user_input(argvars, rettv, TRUE, inputsecret_flag);
}

/*
 * "inputlist()" function
 */
    static void
f_inputlist(typval_T *argvars, typval_T *rettv)
{
    listitem_T	*li;
    int		selected;
    int		mouse_used;

#ifdef NO_CONSOLE_INPUT
    /* While starting up, there is no place to enter text. When running tests
     * with --not-a-term we assume feedkeys() will be used. */
    if (no_console_input() && !is_not_a_term())
	return;
#endif
    if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
    {
	EMSG2(_(e_listarg), "inputlist()");
	return;
    }

    msg_start();
    msg_row = Rows - 1;	/* for when 'cmdheight' > 1 */
    lines_left = Rows;	/* avoid more prompt */
    msg_scroll = TRUE;
    msg_clr_eos();

    for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next)
    {
	msg_puts(tv_get_string(&li->li_tv));
	msg_putchar('\n');
    }

    /* Ask for choice. */
    selected = prompt_for_number(&mouse_used);
    if (mouse_used)
	selected -= lines_left;

    rettv->vval.v_number = selected;
}

static garray_T	    ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL};

/*
 * "inputrestore()" function
 */
    static void
f_inputrestore(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (ga_userinput.ga_len > 0)
    {
	--ga_userinput.ga_len;
	restore_typeahead((tasave_T *)(ga_userinput.ga_data)
						       + ga_userinput.ga_len);
	/* default return is zero == OK */
    }
    else if (p_verbose > 1)
    {
	verb_msg((char_u *)_("called inputrestore() more often than inputsave()"));
	rettv->vval.v_number = 1; /* Failed */
    }
}

/*
 * "inputsave()" function
 */
    static void
f_inputsave(typval_T *argvars UNUSED, typval_T *rettv)
{
    /* Add an entry to the stack of typeahead storage. */
    if (ga_grow(&ga_userinput, 1) == OK)
    {
	save_typeahead((tasave_T *)(ga_userinput.ga_data)
						       + ga_userinput.ga_len);
	++ga_userinput.ga_len;
	/* default return is zero == OK */
    }
    else
	rettv->vval.v_number = 1; /* Failed */
}

/*
 * "inputsecret()" function
 */
    static void
f_inputsecret(typval_T *argvars, typval_T *rettv)
{
    ++cmdline_star;
    ++inputsecret_flag;
    f_input(argvars, rettv);
    --cmdline_star;
    --inputsecret_flag;
}

/*
 * "insert()" function
 */
    static void
f_insert(typval_T *argvars, typval_T *rettv)
{
    long	before = 0;
    listitem_T	*item;
    list_T	*l;
    int		error = FALSE;

    if (argvars[0].v_type != VAR_LIST)
	EMSG2(_(e_listarg), "insert()");
    else if ((l = argvars[0].vval.v_list) != NULL
	    && !tv_check_lock(l->lv_lock, (char_u *)N_("insert() argument"), TRUE))
    {
	if (argvars[2].v_type != VAR_UNKNOWN)
	    before = (long)tv_get_number_chk(&argvars[2], &error);
	if (error)
	    return;		/* type error; errmsg already given */

	if (before == l->lv_len)
	    item = NULL;
	else
	{
	    item = list_find(l, before);
	    if (item == NULL)
	    {
		EMSGN(_(e_listidx), before);
		l = NULL;
	    }
	}
	if (l != NULL)
	{
	    list_insert_tv(l, &argvars[1], item);
	    copy_tv(&argvars[0], rettv);
	}
    }
}

/*
 * "invert(expr)" function
 */
    static void
f_invert(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
}

/*
 * "isdirectory()" function
 */
    static void
f_isdirectory(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = mch_isdir(tv_get_string(&argvars[0]));
}

/*
 * Return TRUE if typeval "tv" is locked: Either that value is locked itself
 * or it refers to a List or Dictionary that is locked.
 */
    static int
tv_islocked(typval_T *tv)
{
    return (tv->v_lock & VAR_LOCKED)
	|| (tv->v_type == VAR_LIST
		&& tv->vval.v_list != NULL
		&& (tv->vval.v_list->lv_lock & VAR_LOCKED))
	|| (tv->v_type == VAR_DICT
		&& tv->vval.v_dict != NULL
		&& (tv->vval.v_dict->dv_lock & VAR_LOCKED));
}

/*
 * "islocked()" function
 */
    static void
f_islocked(typval_T *argvars, typval_T *rettv)
{
    lval_T	lv;
    char_u	*end;
    dictitem_T	*di;

    rettv->vval.v_number = -1;
    end = get_lval(tv_get_string(&argvars[0]), NULL, &lv, FALSE, FALSE,
			     GLV_NO_AUTOLOAD | GLV_READ_ONLY, FNE_CHECK_START);
    if (end != NULL && lv.ll_name != NULL)
    {
	if (*end != NUL)
	    EMSG(_(e_trailing));
	else
	{
	    if (lv.ll_tv == NULL)
	    {
		di = find_var(lv.ll_name, NULL, TRUE);
		if (di != NULL)
		{
		    /* Consider a variable locked when:
		     * 1. the variable itself is locked
		     * 2. the value of the variable is locked.
		     * 3. the List or Dict value is locked.
		     */
		    rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
						   || tv_islocked(&di->di_tv));
		}
	    }
	    else if (lv.ll_range)
		EMSG(_("E786: Range not allowed"));
	    else if (lv.ll_newkey != NULL)
		EMSG2(_(e_dictkey), lv.ll_newkey);
	    else if (lv.ll_list != NULL)
		/* List item. */
		rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv);
	    else
		/* Dictionary item. */
		rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
	}
    }

    clear_lval(&lv);
}

#if defined(FEAT_FLOAT) && defined(HAVE_MATH_H)
/*
 * "isnan()" function
 */
    static void
f_isnan(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT
					    && isnan(argvars[0].vval.v_float);
}
#endif

/*
 * "items(dict)" function
 */
    static void
f_items(typval_T *argvars, typval_T *rettv)
{
    dict_list(argvars, rettv, 2);
}

#if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
/*
 * Get the job from the argument.
 * Returns NULL if the job is invalid.
 */
    static job_T *
get_job_arg(typval_T *tv)
{
    job_T *job;

    if (tv->v_type != VAR_JOB)
    {
	EMSG2(_(e_invarg2), tv_get_string(tv));
	return NULL;
    }
    job = tv->vval.v_job;

    if (job == NULL)
	EMSG(_("E916: not a valid job"));
    return job;
}

/*
 * "job_getchannel()" function
 */
    static void
f_job_getchannel(typval_T *argvars, typval_T *rettv)
{
    job_T	*job = get_job_arg(&argvars[0]);

    if (job != NULL)
    {
	rettv->v_type = VAR_CHANNEL;
	rettv->vval.v_channel = job->jv_channel;
	if (job->jv_channel != NULL)
	    ++job->jv_channel->ch_refcount;
    }
}

/*
 * "job_info()" function
 */
    static void
f_job_info(typval_T *argvars, typval_T *rettv)
{
    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	job_T	*job = get_job_arg(&argvars[0]);

	if (job != NULL && rettv_dict_alloc(rettv) != FAIL)
	    job_info(job, rettv->vval.v_dict);
    }
    else if (rettv_list_alloc(rettv) == OK)
	job_info_all(rettv->vval.v_list);
}

/*
 * "job_setoptions()" function
 */
    static void
f_job_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
{
    job_T	*job = get_job_arg(&argvars[0]);
    jobopt_T	opt;

    if (job == NULL)
	return;
    clear_job_options(&opt);
    if (get_job_options(&argvars[1], &opt, JO_STOPONEXIT + JO_EXIT_CB, 0) == OK)
	job_set_options(job, &opt);
    free_job_options(&opt);
}

/*
 * "job_start()" function
 */
    static void
f_job_start(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_JOB;
    if (check_restricted() || check_secure())
	return;
    rettv->vval.v_job = job_start(argvars, NULL, NULL, FALSE);
}

/*
 * "job_status()" function
 */
    static void
f_job_status(typval_T *argvars, typval_T *rettv)
{
    job_T	*job = get_job_arg(&argvars[0]);

    if (job != NULL)
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = vim_strsave((char_u *)job_status(job));
    }
}

/*
 * "job_stop()" function
 */
    static void
f_job_stop(typval_T *argvars, typval_T *rettv)
{
    job_T	*job = get_job_arg(&argvars[0]);

    if (job != NULL)
	rettv->vval.v_number = job_stop(job, argvars, NULL);
}
#endif

/*
 * "join()" function
 */
    static void
f_join(typval_T *argvars, typval_T *rettv)
{
    garray_T	ga;
    char_u	*sep;

    if (argvars[0].v_type != VAR_LIST)
    {
	EMSG(_(e_listreq));
	return;
    }
    if (argvars[0].vval.v_list == NULL)
	return;
    if (argvars[1].v_type == VAR_UNKNOWN)
	sep = (char_u *)" ";
    else
	sep = tv_get_string_chk(&argvars[1]);

    rettv->v_type = VAR_STRING;

    if (sep != NULL)
    {
	ga_init2(&ga, (int)sizeof(char), 80);
	list_join(&ga, argvars[0].vval.v_list, sep, TRUE, FALSE, 0);
	ga_append(&ga, NUL);
	rettv->vval.v_string = (char_u *)ga.ga_data;
    }
    else
	rettv->vval.v_string = NULL;
}

/*
 * "js_decode()" function
 */
    static void
f_js_decode(typval_T *argvars, typval_T *rettv)
{
    js_read_T	reader;

    reader.js_buf = tv_get_string(&argvars[0]);
    reader.js_fill = NULL;
    reader.js_used = 0;
    if (json_decode_all(&reader, rettv, JSON_JS) != OK)
	EMSG(_(e_invarg));
}

/*
 * "js_encode()" function
 */
    static void
f_js_encode(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = json_encode(&argvars[0], JSON_JS);
}

/*
 * "json_decode()" function
 */
    static void
f_json_decode(typval_T *argvars, typval_T *rettv)
{
    js_read_T	reader;

    reader.js_buf = tv_get_string(&argvars[0]);
    reader.js_fill = NULL;
    reader.js_used = 0;
    json_decode_all(&reader, rettv, 0);
}

/*
 * "json_encode()" function
 */
    static void
f_json_encode(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = json_encode(&argvars[0], 0);
}

/*
 * "keys()" function
 */
    static void
f_keys(typval_T *argvars, typval_T *rettv)
{
    dict_list(argvars, rettv, 0);
}

/*
 * "last_buffer_nr()" function.
 */
    static void
f_last_buffer_nr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		n = 0;
    buf_T	*buf;

    FOR_ALL_BUFFERS(buf)
	if (n < buf->b_fnum)
	    n = buf->b_fnum;

    rettv->vval.v_number = n;
}

/*
 * "len()" function
 */
    static void
f_len(typval_T *argvars, typval_T *rettv)
{
    switch (argvars[0].v_type)
    {
	case VAR_STRING:
	case VAR_NUMBER:
	    rettv->vval.v_number = (varnumber_T)STRLEN(
					       tv_get_string(&argvars[0]));
	    break;
	case VAR_LIST:
	    rettv->vval.v_number = list_len(argvars[0].vval.v_list);
	    break;
	case VAR_DICT:
	    rettv->vval.v_number = dict_len(argvars[0].vval.v_dict);
	    break;
	case VAR_UNKNOWN:
	case VAR_SPECIAL:
	case VAR_FLOAT:
	case VAR_FUNC:
	case VAR_PARTIAL:
	case VAR_JOB:
	case VAR_CHANNEL:
	    EMSG(_("E701: Invalid type for len()"));
	    break;
    }
}

    static void
libcall_common(typval_T *argvars UNUSED, typval_T *rettv, int type)
{
#ifdef FEAT_LIBCALL
    char_u		*string_in;
    char_u		**string_result;
    int			nr_result;
#endif

    rettv->v_type = type;
    if (type != VAR_NUMBER)
	rettv->vval.v_string = NULL;

    if (check_restricted() || check_secure())
	return;

#ifdef FEAT_LIBCALL
    /* The first two args must be strings, otherwise it's meaningless */
    if (argvars[0].v_type == VAR_STRING && argvars[1].v_type == VAR_STRING)
    {
	string_in = NULL;
	if (argvars[2].v_type == VAR_STRING)
	    string_in = argvars[2].vval.v_string;
	if (type == VAR_NUMBER)
	    string_result = NULL;
	else
	    string_result = &rettv->vval.v_string;
	if (mch_libcall(argvars[0].vval.v_string,
			     argvars[1].vval.v_string,
			     string_in,
			     argvars[2].vval.v_number,
			     string_result,
			     &nr_result) == OK
		&& type == VAR_NUMBER)
	    rettv->vval.v_number = nr_result;
    }
#endif
}

/*
 * "libcall()" function
 */
    static void
f_libcall(typval_T *argvars, typval_T *rettv)
{
    libcall_common(argvars, rettv, VAR_STRING);
}

/*
 * "libcallnr()" function
 */
    static void
f_libcallnr(typval_T *argvars, typval_T *rettv)
{
    libcall_common(argvars, rettv, VAR_NUMBER);
}

/*
 * "line(string)" function
 */
    static void
f_line(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum = 0;
    pos_T	*fp;
    int		fnum;

    fp = var2fpos(&argvars[0], TRUE, &fnum);
    if (fp != NULL)
	lnum = fp->lnum;
    rettv->vval.v_number = lnum;
}

/*
 * "line2byte(lnum)" function
 */
    static void
f_line2byte(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifndef FEAT_BYTEOFF
    rettv->vval.v_number = -1;
#else
    linenr_T	lnum;

    lnum = tv_get_lnum(argvars);
    if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL);
    if (rettv->vval.v_number >= 0)
	++rettv->vval.v_number;
#endif
}

/*
 * "lispindent(lnum)" function
 */
    static void
f_lispindent(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_LISP
    pos_T	pos;
    linenr_T	lnum;

    pos = curwin->w_cursor;
    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
    {
	curwin->w_cursor.lnum = lnum;
	rettv->vval.v_number = get_lisp_indent();
	curwin->w_cursor = pos;
    }
    else
#endif
	rettv->vval.v_number = -1;
}

/*
 * "localtime()" function
 */
    static void
f_localtime(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = (varnumber_T)time(NULL);
}

    static void
get_maparg(typval_T *argvars, typval_T *rettv, int exact)
{
    char_u	*keys;
    char_u	*which;
    char_u	buf[NUMBUFLEN];
    char_u	*keys_buf = NULL;
    char_u	*rhs;
    int		mode;
    int		abbr = FALSE;
    int		get_dict = FALSE;
    mapblock_T	*mp;
    int		buffer_local;

    /* return empty string for failure */
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    keys = tv_get_string(&argvars[0]);
    if (*keys == NUL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	which = tv_get_string_buf_chk(&argvars[1], buf);
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    abbr = (int)tv_get_number(&argvars[2]);
	    if (argvars[3].v_type != VAR_UNKNOWN)
		get_dict = (int)tv_get_number(&argvars[3]);
	}
    }
    else
	which = (char_u *)"";
    if (which == NULL)
	return;

    mode = get_map_mode(&which, 0);

    keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE, FALSE);
    rhs = check_map(keys, mode, exact, FALSE, abbr, &mp, &buffer_local);
    vim_free(keys_buf);

    if (!get_dict)
    {
	/* Return a string. */
	if (rhs != NULL)
	{
	    if (*rhs == NUL)
		rettv->vval.v_string = vim_strsave((char_u *)"<Nop>");
	    else
		rettv->vval.v_string = str2special_save(rhs, FALSE);
	}

    }
    else if (rettv_dict_alloc(rettv) != FAIL && rhs != NULL)
    {
	/* Return a dictionary. */
	char_u	    *lhs = str2special_save(mp->m_keys, TRUE);
	char_u	    *mapmode = map_mode_to_chars(mp->m_mode);
	dict_T	    *dict = rettv->vval.v_dict;

	dict_add_string(dict, "lhs", lhs);
	dict_add_string(dict, "rhs", mp->m_orig_str);
	dict_add_number(dict, "noremap", mp->m_noremap ? 1L : 0L);
	dict_add_number(dict, "expr", mp->m_expr ? 1L : 0L);
	dict_add_number(dict, "silent", mp->m_silent ? 1L : 0L);
	dict_add_number(dict, "sid", (long)mp->m_script_ctx.sc_sid);
	dict_add_number(dict, "lnum", (long)mp->m_script_ctx.sc_lnum);
	dict_add_number(dict, "buffer", (long)buffer_local);
	dict_add_number(dict, "nowait", mp->m_nowait ? 1L : 0L);
	dict_add_string(dict, "mode", mapmode);

	vim_free(lhs);
	vim_free(mapmode);
    }
}

#ifdef FEAT_FLOAT
/*
 * "log()" function
 */
    static void
f_log(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = log(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "log10()" function
 */
    static void
f_log10(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = log10(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

#ifdef FEAT_LUA
/*
 * "luaeval()" function
 */
    static void
f_luaeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    str = tv_get_string_buf(&argvars[0], buf);
    do_luaeval(str, argvars + 1, rettv);
}
#endif

/*
 * "map()" function
 */
    static void
f_map(typval_T *argvars, typval_T *rettv)
{
    filter_map(argvars, rettv, TRUE);
}

/*
 * "maparg()" function
 */
    static void
f_maparg(typval_T *argvars, typval_T *rettv)
{
    get_maparg(argvars, rettv, TRUE);
}

/*
 * "mapcheck()" function
 */
    static void
f_mapcheck(typval_T *argvars, typval_T *rettv)
{
    get_maparg(argvars, rettv, FALSE);
}

typedef enum
{
    MATCH_END,	    /* matchend() */
    MATCH_MATCH,    /* match() */
    MATCH_STR,	    /* matchstr() */
    MATCH_LIST,	    /* matchlist() */
    MATCH_POS	    /* matchstrpos() */
} matchtype_T;

    static void
find_some_match(typval_T *argvars, typval_T *rettv, matchtype_T type)
{
    char_u	*str = NULL;
    long	len = 0;
    char_u	*expr = NULL;
    char_u	*pat;
    regmatch_T	regmatch;
    char_u	patbuf[NUMBUFLEN];
    char_u	strbuf[NUMBUFLEN];
    char_u	*save_cpo;
    long	start = 0;
    long	nth = 1;
    colnr_T	startcol = 0;
    int		match = 0;
    list_T	*l = NULL;
    listitem_T	*li = NULL;
    long	idx = 0;
    char_u	*tofree = NULL;

    /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
    save_cpo = p_cpo;
    p_cpo = (char_u *)"";

    rettv->vval.v_number = -1;
    if (type == MATCH_LIST || type == MATCH_POS)
    {
	/* type MATCH_LIST: return empty list when there are no matches.
	 * type MATCH_POS: return ["", -1, -1, -1] */
	if (rettv_list_alloc(rettv) == FAIL)
	    goto theend;
	if (type == MATCH_POS
		&& (list_append_string(rettv->vval.v_list,
					    (char_u *)"", 0) == FAIL
		    || list_append_number(rettv->vval.v_list,
					    (varnumber_T)-1) == FAIL
		    || list_append_number(rettv->vval.v_list,
					    (varnumber_T)-1) == FAIL
		    || list_append_number(rettv->vval.v_list,
					    (varnumber_T)-1) == FAIL))
	{
		list_free(rettv->vval.v_list);
		rettv->vval.v_list = NULL;
		goto theend;
	}
    }
    else if (type == MATCH_STR)
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = NULL;
    }

    if (argvars[0].v_type == VAR_LIST)
    {
	if ((l = argvars[0].vval.v_list) == NULL)
	    goto theend;
	li = l->lv_first;
    }
    else
    {
	expr = str = tv_get_string(&argvars[0]);
	len = (long)STRLEN(str);
    }

    pat = tv_get_string_buf_chk(&argvars[1], patbuf);
    if (pat == NULL)
	goto theend;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	int	    error = FALSE;

	start = (long)tv_get_number_chk(&argvars[2], &error);
	if (error)
	    goto theend;
	if (l != NULL)
	{
	    li = list_find(l, start);
	    if (li == NULL)
		goto theend;
	    idx = l->lv_idx;	/* use the cached index */
	}
	else
	{
	    if (start < 0)
		start = 0;
	    if (start > len)
		goto theend;
	    /* When "count" argument is there ignore matches before "start",
	     * otherwise skip part of the string.  Differs when pattern is "^"
	     * or "\<". */
	    if (argvars[3].v_type != VAR_UNKNOWN)
		startcol = start;
	    else
	    {
		str += start;
		len -= start;
	    }
	}

	if (argvars[3].v_type != VAR_UNKNOWN)
	    nth = (long)tv_get_number_chk(&argvars[3], &error);
	if (error)
	    goto theend;
    }

    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
    if (regmatch.regprog != NULL)
    {
	regmatch.rm_ic = p_ic;

	for (;;)
	{
	    if (l != NULL)
	    {
		if (li == NULL)
		{
		    match = FALSE;
		    break;
		}
		vim_free(tofree);
		expr = str = echo_string(&li->li_tv, &tofree, strbuf, 0);
		if (str == NULL)
		    break;
	    }

	    match = vim_regexec_nl(&regmatch, str, (colnr_T)startcol);

	    if (match && --nth <= 0)
		break;
	    if (l == NULL && !match)
		break;

	    /* Advance to just after the match. */
	    if (l != NULL)
	    {
		li = li->li_next;
		++idx;
	    }
	    else
	    {
#ifdef FEAT_MBYTE
		startcol = (colnr_T)(regmatch.startp[0]
				    + (*mb_ptr2len)(regmatch.startp[0]) - str);
#else
		startcol = (colnr_T)(regmatch.startp[0] + 1 - str);
#endif
		if (startcol > (colnr_T)len
				      || str + startcol <= regmatch.startp[0])
		{
		    match = FALSE;
		    break;
		}
	    }
	}

	if (match)
	{
	    if (type == MATCH_POS)
	    {
		listitem_T *li1 = rettv->vval.v_list->lv_first;
		listitem_T *li2 = li1->li_next;
		listitem_T *li3 = li2->li_next;
		listitem_T *li4 = li3->li_next;

		vim_free(li1->li_tv.vval.v_string);
		li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0],
				(int)(regmatch.endp[0] - regmatch.startp[0]));
		li3->li_tv.vval.v_number =
				      (varnumber_T)(regmatch.startp[0] - expr);
		li4->li_tv.vval.v_number =
					(varnumber_T)(regmatch.endp[0] - expr);
		if (l != NULL)
		    li2->li_tv.vval.v_number = (varnumber_T)idx;
	    }
	    else if (type == MATCH_LIST)
	    {
		int i;

		/* return list with matched string and submatches */
		for (i = 0; i < NSUBEXP; ++i)
		{
		    if (regmatch.endp[i] == NULL)
		    {
			if (list_append_string(rettv->vval.v_list,
						     (char_u *)"", 0) == FAIL)
			    break;
		    }
		    else if (list_append_string(rettv->vval.v_list,
				regmatch.startp[i],
				(int)(regmatch.endp[i] - regmatch.startp[i]))
			    == FAIL)
			break;
		}
	    }
	    else if (type == MATCH_STR)
	    {
		/* return matched string */
		if (l != NULL)
		    copy_tv(&li->li_tv, rettv);
		else
		    rettv->vval.v_string = vim_strnsave(regmatch.startp[0],
				(int)(regmatch.endp[0] - regmatch.startp[0]));
	    }
	    else if (l != NULL)
		rettv->vval.v_number = idx;
	    else
	    {
		if (type != MATCH_END)
		    rettv->vval.v_number =
				      (varnumber_T)(regmatch.startp[0] - str);
		else
		    rettv->vval.v_number =
					(varnumber_T)(regmatch.endp[0] - str);
		rettv->vval.v_number += (varnumber_T)(str - expr);
	    }
	}
	vim_regfree(regmatch.regprog);
    }

theend:
    if (type == MATCH_POS && l == NULL && rettv->vval.v_list != NULL)
	/* matchstrpos() without a list: drop the second item. */
	listitem_remove(rettv->vval.v_list,
				       rettv->vval.v_list->lv_first->li_next);
    vim_free(tofree);
    p_cpo = save_cpo;
}

/*
 * "match()" function
 */
    static void
f_match(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_MATCH);
}

#ifdef FEAT_SEARCH_EXTRA
    static int
matchadd_dict_arg(typval_T *tv, char_u **conceal_char, win_T **win)
{
    dictitem_T *di;

    if (tv->v_type != VAR_DICT)
    {
	EMSG(_(e_dictreq));
	return FAIL;
    }

    if (dict_find(tv->vval.v_dict, (char_u *)"conceal", -1) != NULL)
	*conceal_char = dict_get_string(tv->vval.v_dict,
						   (char_u *)"conceal", FALSE);

    if ((di = dict_find(tv->vval.v_dict, (char_u *)"window", -1)) != NULL)
    {
	*win = find_win_by_nr_or_id(&di->di_tv);
	if (*win == NULL)
	{
	    EMSG(_("E957: Invalid window number"));
	    return FAIL;
	}
    }

    return OK;
}
#endif

/*
 * "matchadd()" function
 */
    static void
f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    char_u	buf[NUMBUFLEN];
    char_u	*grp = tv_get_string_buf_chk(&argvars[0], buf);	/* group */
    char_u	*pat = tv_get_string_buf_chk(&argvars[1], buf);	/* pattern */
    int		prio = 10;	/* default priority */
    int		id = -1;
    int		error = FALSE;
    char_u	*conceal_char = NULL;
    win_T	*win = curwin;

    rettv->vval.v_number = -1;

    if (grp == NULL || pat == NULL)
	return;
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	prio = (int)tv_get_number_chk(&argvars[2], &error);
	if (argvars[3].v_type != VAR_UNKNOWN)
	{
	    id = (int)tv_get_number_chk(&argvars[3], &error);
	    if (argvars[4].v_type != VAR_UNKNOWN
		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
		return;
	}
    }
    if (error == TRUE)
	return;
    if (id >= 1 && id <= 3)
    {
	EMSGN(_("E798: ID is reserved for \":match\": %ld"), id);
	return;
    }

    rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL,
								conceal_char);
#endif
}

/*
 * "matchaddpos()" function
 */
    static void
f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    char_u	buf[NUMBUFLEN];
    char_u	*group;
    int		prio = 10;
    int		id = -1;
    int		error = FALSE;
    list_T	*l;
    char_u	*conceal_char = NULL;
    win_T	*win = curwin;

    rettv->vval.v_number = -1;

    group = tv_get_string_buf_chk(&argvars[0], buf);
    if (group == NULL)
	return;

    if (argvars[1].v_type != VAR_LIST)
    {
	EMSG2(_(e_listarg), "matchaddpos()");
	return;
    }
    l = argvars[1].vval.v_list;
    if (l == NULL)
	return;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	prio = (int)tv_get_number_chk(&argvars[2], &error);
	if (argvars[3].v_type != VAR_UNKNOWN)
	{
	    id = (int)tv_get_number_chk(&argvars[3], &error);

	    if (argvars[4].v_type != VAR_UNKNOWN
		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
		return;
	}
    }
    if (error == TRUE)
	return;

    /* id == 3 is ok because matchaddpos() is supposed to substitute :3match */
    if (id == 1 || id == 2)
    {
	EMSGN(_("E798: ID is reserved for \":match\": %ld"), id);
	return;
    }

    rettv->vval.v_number = match_add(win, group, NULL, prio, id, l,
								conceal_char);
#endif
}

/*
 * "matcharg()" function
 */
    static void
f_matcharg(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_list_alloc(rettv) == OK)
    {
#ifdef FEAT_SEARCH_EXTRA
	int	    id = (int)tv_get_number(&argvars[0]);
	matchitem_T *m;

	if (id >= 1 && id <= 3)
	{
	    if ((m = (matchitem_T *)get_match(curwin, id)) != NULL)
	    {
		list_append_string(rettv->vval.v_list,
						syn_id2name(m->hlg_id), -1);
		list_append_string(rettv->vval.v_list, m->pattern, -1);
	    }
	    else
	    {
		list_append_string(rettv->vval.v_list, NULL, -1);
		list_append_string(rettv->vval.v_list, NULL, -1);
	    }
	}
#endif
    }
}

/*
 * "matchdelete()" function
 */
    static void
f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    rettv->vval.v_number = match_delete(curwin,
				       (int)tv_get_number(&argvars[0]), TRUE);
#endif
}

/*
 * "matchend()" function
 */
    static void
f_matchend(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_END);
}

/*
 * "matchlist()" function
 */
    static void
f_matchlist(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_LIST);
}

/*
 * "matchstr()" function
 */
    static void
f_matchstr(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_STR);
}

/*
 * "matchstrpos()" function
 */
    static void
f_matchstrpos(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_POS);
}

    static void
max_min(typval_T *argvars, typval_T *rettv, int domax)
{
    varnumber_T	n = 0;
    varnumber_T	i;
    int		error = FALSE;

    if (argvars[0].v_type == VAR_LIST)
    {
	list_T		*l;
	listitem_T	*li;

	l = argvars[0].vval.v_list;
	if (l != NULL)
	{
	    li = l->lv_first;
	    if (li != NULL)
	    {
		n = tv_get_number_chk(&li->li_tv, &error);
		for (;;)
		{
		    li = li->li_next;
		    if (li == NULL)
			break;
		    i = tv_get_number_chk(&li->li_tv, &error);
		    if (domax ? i > n : i < n)
			n = i;
		}
	    }
	}
    }
    else if (argvars[0].v_type == VAR_DICT)
    {
	dict_T		*d;
	int		first = TRUE;
	hashitem_T	*hi;
	int		todo;

	d = argvars[0].vval.v_dict;
	if (d != NULL)
	{
	    todo = (int)d->dv_hashtab.ht_used;
	    for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
	    {
		if (!HASHITEM_EMPTY(hi))
		{
		    --todo;
		    i = tv_get_number_chk(&HI2DI(hi)->di_tv, &error);
		    if (first)
		    {
			n = i;
			first = FALSE;
		    }
		    else if (domax ? i > n : i < n)
			n = i;
		}
	    }
	}
    }
    else
	EMSG2(_(e_listdictarg), domax ? "max()" : "min()");
    rettv->vval.v_number = error ? 0 : n;
}

/*
 * "max()" function
 */
    static void
f_max(typval_T *argvars, typval_T *rettv)
{
    max_min(argvars, rettv, TRUE);
}

/*
 * "min()" function
 */
    static void
f_min(typval_T *argvars, typval_T *rettv)
{
    max_min(argvars, rettv, FALSE);
}

/*
 * Create the directory in which "dir" is located, and higher levels when
 * needed.
 * Return OK or FAIL.
 */
    static int
mkdir_recurse(char_u *dir, int prot)
{
    char_u	*p;
    char_u	*updir;
    int		r = FAIL;

    /* Get end of directory name in "dir".
     * We're done when it's "/" or "c:/". */
    p = gettail_sep(dir);
    if (p <= get_past_head(dir))
	return OK;

    /* If the directory exists we're done.  Otherwise: create it.*/
    updir = vim_strnsave(dir, (int)(p - dir));
    if (updir == NULL)
	return FAIL;
    if (mch_isdir(updir))
	r = OK;
    else if (mkdir_recurse(updir, prot) == OK)
	r = vim_mkdir_emsg(updir, prot);
    vim_free(updir);
    return r;
}

#ifdef vim_mkdir
/*
 * "mkdir()" function
 */
    static void
f_mkdir(typval_T *argvars, typval_T *rettv)
{
    char_u	*dir;
    char_u	buf[NUMBUFLEN];
    int		prot = 0755;

    rettv->vval.v_number = FAIL;
    if (check_restricted() || check_secure())
	return;

    dir = tv_get_string_buf(&argvars[0], buf);
    if (*dir == NUL)
	return;

    if (*gettail(dir) == NUL)
	/* remove trailing slashes */
	*gettail_sep(dir) = NUL;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    prot = (int)tv_get_number_chk(&argvars[2], NULL);
	    if (prot == -1)
		return;
	}
	if (STRCMP(tv_get_string(&argvars[1]), "p") == 0)
	{
	    if (mch_isdir(dir))
	    {
		/* With the "p" flag it's OK if the dir already exists. */
		rettv->vval.v_number = OK;
		return;
	    }
	    mkdir_recurse(dir, prot);
	}
    }
    rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
}
#endif

/*
 * "mode()" function
 */
    static void
f_mode(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[4];

    vim_memset(buf, 0, sizeof(buf));

    if (time_for_testing == 93784)
    {
	/* Testing the two-character code. */
	buf[0] = 'x';
	buf[1] = '!';
    }
#ifdef FEAT_TERMINAL
    else if (term_use_loop())
	buf[0] = 't';
#endif
    else if (VIsual_active)
    {
	if (VIsual_select)
	    buf[0] = VIsual_mode + 's' - 'v';
	else
	    buf[0] = VIsual_mode;
    }
    else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE
		|| State == CONFIRM)
    {
	buf[0] = 'r';
	if (State == ASKMORE)
	    buf[1] = 'm';
	else if (State == CONFIRM)
	    buf[1] = '?';
    }
    else if (State == EXTERNCMD)
	buf[0] = '!';
    else if (State & INSERT)
    {
	if (State & VREPLACE_FLAG)
	{
	    buf[0] = 'R';
	    buf[1] = 'v';
	}
	else
	{
	    if (State & REPLACE_FLAG)
		buf[0] = 'R';
	    else
		buf[0] = 'i';
#ifdef FEAT_INS_EXPAND
	    if (ins_compl_active())
		buf[1] = 'c';
	    else if (ctrl_x_mode_not_defined_yet())
		buf[1] = 'x';
#endif
	}
    }
    else if ((State & CMDLINE) || exmode_active)
    {
	buf[0] = 'c';
	if (exmode_active == EXMODE_VIM)
	    buf[1] = 'v';
	else if (exmode_active == EXMODE_NORMAL)
	    buf[1] = 'e';
    }
    else
    {
	buf[0] = 'n';
	if (finish_op)
	{
	    buf[1] = 'o';
	    // to be able to detect force-linewise/blockwise/characterwise operations
	    buf[2] = motion_force;
	}
	else if (restart_edit == 'I' || restart_edit == 'R'
							|| restart_edit == 'V')
	{
	    buf[1] = 'i';
	    buf[2] = restart_edit;
	}
    }

    /* Clear out the minor mode when the argument is not a non-zero number or
     * non-empty string.  */
    if (!non_zero_arg(&argvars[0]))
	buf[1] = NUL;

    rettv->vval.v_string = vim_strsave(buf);
    rettv->v_type = VAR_STRING;
}

#if defined(FEAT_MZSCHEME) || defined(PROTO)
/*
 * "mzeval()" function
 */
    static void
f_mzeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    str = tv_get_string_buf(&argvars[0], buf);
    do_mzeval(str, rettv);
}

    void
mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv)
{
    typval_T argvars[3];

    argvars[0].v_type = VAR_STRING;
    argvars[0].vval.v_string = name;
    copy_tv(args, &argvars[1]);
    argvars[2].v_type = VAR_UNKNOWN;
    f_call(argvars, rettv);
    clear_tv(&argvars[1]);
}
#endif

/*
 * "nextnonblank()" function
 */
    static void
f_nextnonblank(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;

    for (lnum = tv_get_lnum(argvars); ; ++lnum)
    {
	if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count)
	{
	    lnum = 0;
	    break;
	}
	if (*skipwhite(ml_get(lnum)) != NUL)
	    break;
    }
    rettv->vval.v_number = lnum;
}

/*
 * "nr2char()" function
 */
    static void
f_nr2char(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];

#ifdef FEAT_MBYTE
    if (has_mbyte)
    {
	int	utf8 = 0;

	if (argvars[1].v_type != VAR_UNKNOWN)
	    utf8 = (int)tv_get_number_chk(&argvars[1], NULL);
	if (utf8)
	    buf[(*utf_char2bytes)((int)tv_get_number(&argvars[0]), buf)] = NUL;
	else
	    buf[(*mb_char2bytes)((int)tv_get_number(&argvars[0]), buf)] = NUL;
    }
    else
#endif
    {
	buf[0] = (char_u)tv_get_number(&argvars[0]);
	buf[1] = NUL;
    }
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * "or(expr, expr)" function
 */
    static void
f_or(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
					| tv_get_number_chk(&argvars[1], NULL);
}

/*
 * "pathshorten()" function
 */
    static void
f_pathshorten(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;

    rettv->v_type = VAR_STRING;
    p = tv_get_string_chk(&argvars[0]);
    if (p == NULL)
	rettv->vval.v_string = NULL;
    else
    {
	p = vim_strsave(p);
	rettv->vval.v_string = p;
	if (p != NULL)
	    shorten_dir(p);
    }
}

#ifdef FEAT_PERL
/*
 * "perleval()" function
 */
    static void
f_perleval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    str = tv_get_string_buf(&argvars[0], buf);
    do_perleval(str, rettv);
}
#endif

#ifdef FEAT_FLOAT
/*
 * "pow()" function
 */
    static void
f_pow(typval_T *argvars, typval_T *rettv)
{
    float_T	fx = 0.0, fy = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &fx) == OK
				     && get_float_arg(&argvars[1], &fy) == OK)
	rettv->vval.v_float = pow(fx, fy);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "prevnonblank()" function
 */
    static void
f_prevnonblank(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;

    lnum = tv_get_lnum(argvars);
    if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count)
	lnum = 0;
    else
	while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL)
	    --lnum;
    rettv->vval.v_number = lnum;
}

/* This dummy va_list is here because:
 * - passing a NULL pointer doesn't work when va_list isn't a pointer
 * - locally in the function results in a "used before set" warning
 * - using va_start() to initialize it gives "function with fixed args" error */
static va_list	ap;

/*
 * "printf()" function
 */
    static void
f_printf(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];
    int		len;
    char_u	*s;
    int		saved_did_emsg = did_emsg;
    char	*fmt;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    /* Get the required length, allocate the buffer and do it for real. */
    did_emsg = FALSE;
    fmt = (char *)tv_get_string_buf(&argvars[0], buf);
    len = vim_vsnprintf_typval(NULL, 0, fmt, ap, argvars + 1);
    if (!did_emsg)
    {
	s = alloc(len + 1);
	if (s != NULL)
	{
	    rettv->vval.v_string = s;
	    (void)vim_vsnprintf_typval((char *)s, len + 1, fmt,
							      ap, argvars + 1);
	}
    }
    did_emsg |= saved_did_emsg;
}

#ifdef FEAT_JOB_CHANNEL
/*
 * "prompt_setcallback({buffer}, {callback})" function
 */
    static void
f_prompt_setcallback(typval_T *argvars, typval_T *rettv UNUSED)
{
    buf_T	*buf;
    char_u	*callback;
    partial_T	*partial;

    if (check_secure())
	return;
    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL)
	return;

    callback = get_callback(&argvars[1], &partial);
    if (callback == NULL)
	return;

    free_callback(buf->b_prompt_callback, buf->b_prompt_partial);
    if (partial == NULL)
	buf->b_prompt_callback = vim_strsave(callback);
    else
	/* pointer into the partial */
	buf->b_prompt_callback = callback;
    buf->b_prompt_partial = partial;
}

/*
 * "prompt_setinterrupt({buffer}, {callback})" function
 */
    static void
f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv UNUSED)
{
    buf_T	*buf;
    char_u	*callback;
    partial_T	*partial;

    if (check_secure())
	return;
    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL)
	return;

    callback = get_callback(&argvars[1], &partial);
    if (callback == NULL)
	return;

    free_callback(buf->b_prompt_interrupt, buf->b_prompt_int_partial);
    if (partial == NULL)
	buf->b_prompt_interrupt = vim_strsave(callback);
    else
	/* pointer into the partial */
	buf->b_prompt_interrupt = callback;
    buf->b_prompt_int_partial = partial;
}

/*
 * "prompt_setprompt({buffer}, {text})" function
 */
    static void
f_prompt_setprompt(typval_T *argvars, typval_T *rettv UNUSED)
{
    buf_T	*buf;
    char_u	*text;

    if (check_secure())
	return;
    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL)
	return;

    text = tv_get_string(&argvars[1]);
    vim_free(buf->b_prompt_text);
    buf->b_prompt_text = vim_strsave(text);
}
#endif

/*
 * "pumvisible()" function
 */
    static void
f_pumvisible(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_INS_EXPAND
    if (pum_visible())
	rettv->vval.v_number = 1;
#endif
}

#ifdef FEAT_PYTHON3
/*
 * "py3eval()" function
 */
    static void
f_py3eval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    if (p_pyx == 0)
	p_pyx = 3;

    str = tv_get_string_buf(&argvars[0], buf);
    do_py3eval(str, rettv);
}
#endif

#ifdef FEAT_PYTHON
/*
 * "pyeval()" function
 */
    static void
f_pyeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    if (p_pyx == 0)
	p_pyx = 2;

    str = tv_get_string_buf(&argvars[0], buf);
    do_pyeval(str, rettv);
}
#endif

#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
/*
 * "pyxeval()" function
 */
    static void
f_pyxeval(typval_T *argvars, typval_T *rettv)
{
# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
    init_pyxversion();
    if (p_pyx == 2)
	f_pyeval(argvars, rettv);
    else
	f_py3eval(argvars, rettv);
# elif defined(FEAT_PYTHON)
    f_pyeval(argvars, rettv);
# elif defined(FEAT_PYTHON3)
    f_py3eval(argvars, rettv);
# endif
}
#endif

/*
 * "range()" function
 */
    static void
f_range(typval_T *argvars, typval_T *rettv)
{
    varnumber_T	start;
    varnumber_T	end;
    varnumber_T	stride = 1;
    varnumber_T	i;
    int		error = FALSE;

    start = tv_get_number_chk(&argvars[0], &error);
    if (argvars[1].v_type == VAR_UNKNOWN)
    {
	end = start - 1;
	start = 0;
    }
    else
    {
	end = tv_get_number_chk(&argvars[1], &error);
	if (argvars[2].v_type != VAR_UNKNOWN)
	    stride = tv_get_number_chk(&argvars[2], &error);
    }

    if (error)
	return;		/* type error; errmsg already given */
    if (stride == 0)
	EMSG(_("E726: Stride is zero"));
    else if (stride > 0 ? end + 1 < start : end - 1 > start)
	EMSG(_("E727: Start past end"));
    else
    {
	if (rettv_list_alloc(rettv) == OK)
	    for (i = start; stride > 0 ? i <= end : i >= end; i += stride)
		if (list_append_number(rettv->vval.v_list,
						      (varnumber_T)i) == FAIL)
		    break;
    }
}

/*
 * "readfile()" function
 */
    static void
f_readfile(typval_T *argvars, typval_T *rettv)
{
    int		binary = FALSE;
    int		failed = FALSE;
    char_u	*fname;
    FILE	*fd;
    char_u	buf[(IOSIZE/256)*256];	/* rounded to avoid odd + 1 */
    int		io_size = sizeof(buf);
    int		readlen;		/* size of last fread() */
    char_u	*prev	 = NULL;	/* previously read bytes, if any */
    long	prevlen  = 0;		/* length of data in prev */
    long	prevsize = 0;		/* size of prev buffer */
    long	maxline  = MAXLNUM;
    long	cnt	 = 0;
    char_u	*p;			/* position in buf */
    char_u	*start;			/* start of current line */

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	if (STRCMP(tv_get_string(&argvars[1]), "b") == 0)
	    binary = TRUE;
	if (argvars[2].v_type != VAR_UNKNOWN)
	    maxline = (long)tv_get_number(&argvars[2]);
    }

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    /* Always open the file in binary mode, library functions have a mind of
     * their own about CR-LF conversion. */
    fname = tv_get_string(&argvars[0]);
    if (*fname == NUL || (fd = mch_fopen((char *)fname, READBIN)) == NULL)
    {
	EMSG2(_(e_notopen), *fname == NUL ? (char_u *)_("<empty>") : fname);
	return;
    }

    while (cnt < maxline || maxline < 0)
    {
	readlen = (int)fread(buf, 1, io_size, fd);

	/* This for loop processes what was read, but is also entered at end
	 * of file so that either:
	 * - an incomplete line gets written
	 * - a "binary" file gets an empty line at the end if it ends in a
	 *   newline.  */
	for (p = buf, start = buf;
		p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
		++p)
	{
	    if (*p == '\n' || readlen <= 0)
	    {
		listitem_T  *li;
		char_u	    *s	= NULL;
		long_u	    len = p - start;

		/* Finished a line.  Remove CRs before NL. */
		if (readlen > 0 && !binary)
		{
		    while (len > 0 && start[len - 1] == '\r')
			--len;
		    /* removal may cross back to the "prev" string */
		    if (len == 0)
			while (prevlen > 0 && prev[prevlen - 1] == '\r')
			    --prevlen;
		}
		if (prevlen == 0)
		    s = vim_strnsave(start, (int)len);
		else
		{
		    /* Change "prev" buffer to be the right size.  This way
		     * the bytes are only copied once, and very long lines are
		     * allocated only once.  */
		    if ((s = vim_realloc(prev, prevlen + len + 1)) != NULL)
		    {
			mch_memmove(s + prevlen, start, len);
			s[prevlen + len] = NUL;
			prev = NULL; /* the list will own the string */
			prevlen = prevsize = 0;
		    }
		}
		if (s == NULL)
		{
		    do_outofmem_msg((long_u) prevlen + len + 1);
		    failed = TRUE;
		    break;
		}

		if ((li = listitem_alloc()) == NULL)
		{
		    vim_free(s);
		    failed = TRUE;
		    break;
		}
		li->li_tv.v_type = VAR_STRING;
		li->li_tv.v_lock = 0;
		li->li_tv.vval.v_string = s;
		list_append(rettv->vval.v_list, li);

		start = p + 1; /* step over newline */
		if ((++cnt >= maxline && maxline >= 0) || readlen <= 0)
		    break;
	    }
	    else if (*p == NUL)
		*p = '\n';
#ifdef FEAT_MBYTE
	    /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF.  Do this
	     * when finding the BF and check the previous two bytes. */
	    else if (*p == 0xbf && enc_utf8 && !binary)
	    {
		/* Find the two bytes before the 0xbf.	If p is at buf, or buf
		 * + 1, these may be in the "prev" string. */
		char_u back1 = p >= buf + 1 ? p[-1]
				     : prevlen >= 1 ? prev[prevlen - 1] : NUL;
		char_u back2 = p >= buf + 2 ? p[-2]
			  : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
			  : prevlen >= 2 ? prev[prevlen - 2] : NUL;

		if (back2 == 0xef && back1 == 0xbb)
		{
		    char_u *dest = p - 2;

		    /* Usually a BOM is at the beginning of a file, and so at
		     * the beginning of a line; then we can just step over it.
		     */
		    if (start == dest)
			start = p + 1;
		    else
		    {
			/* have to shuffle buf to close gap */
			int adjust_prevlen = 0;

			if (dest < buf)
			{
			    adjust_prevlen = (int)(buf - dest); /* must be 1 or 2 */
			    dest = buf;
			}
			if (readlen > p - buf + 1)
			    mch_memmove(dest, p + 1, readlen - (p - buf) - 1);
			readlen -= 3 - adjust_prevlen;
			prevlen -= adjust_prevlen;
			p = dest - 1;
		    }
		}
	    }
#endif
	} /* for */

	if (failed || (cnt >= maxline && maxline >= 0) || readlen <= 0)
	    break;
	if (start < p)
	{
	    /* There's part of a line in buf, store it in "prev". */
	    if (p - start + prevlen >= prevsize)
	    {
		/* need bigger "prev" buffer */
		char_u *newprev;

		/* A common use case is ordinary text files and "prev" gets a
		 * fragment of a line, so the first allocation is made
		 * small, to avoid repeatedly 'allocing' large and
		 * 'reallocing' small. */
		if (prevsize == 0)
		    prevsize = (long)(p - start);
		else
		{
		    long grow50pc = (prevsize * 3) / 2;
		    long growmin  = (long)((p - start) * 2 + prevlen);
		    prevsize = grow50pc > growmin ? grow50pc : growmin;
		}
		newprev = prev == NULL ? alloc(prevsize)
						: vim_realloc(prev, prevsize);
		if (newprev == NULL)
		{
		    do_outofmem_msg((long_u)prevsize);
		    failed = TRUE;
		    break;
		}
		prev = newprev;
	    }
	    /* Add the line part to end of "prev". */
	    mch_memmove(prev + prevlen, start, p - start);
	    prevlen += (long)(p - start);
	}
    } /* while */

    /*
     * For a negative line count use only the lines at the end of the file,
     * free the rest.
     */
    if (!failed && maxline < 0)
	while (cnt > -maxline)
	{
	    listitem_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first);
	    --cnt;
	}

    if (failed)
    {
	list_free(rettv->vval.v_list);
	/* readfile doc says an empty list is returned on error */
	rettv->vval.v_list = list_alloc();
    }

    vim_free(prev);
    fclose(fd);
}

    static void
return_register(int regname, typval_T *rettv)
{
    char_u buf[2] = {0, 0};

    buf[0] = (char_u)regname;
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * "reg_executing()" function
 */
    static void
f_reg_executing(typval_T *argvars UNUSED, typval_T *rettv)
{
    return_register(reg_executing, rettv);
}

/*
 * "reg_recording()" function
 */
    static void
f_reg_recording(typval_T *argvars UNUSED, typval_T *rettv)
{
    return_register(reg_recording, rettv);
}

#if defined(FEAT_RELTIME)
/*
 * Convert a List to proftime_T.
 * Return FAIL when there is something wrong.
 */
    static int
list2proftime(typval_T *arg, proftime_T *tm)
{
    long	n1, n2;
    int	error = FALSE;

    if (arg->v_type != VAR_LIST || arg->vval.v_list == NULL
					     || arg->vval.v_list->lv_len != 2)
	return FAIL;
    n1 = list_find_nr(arg->vval.v_list, 0L, &error);
    n2 = list_find_nr(arg->vval.v_list, 1L, &error);
# ifdef WIN3264
    tm->HighPart = n1;
    tm->LowPart = n2;
# else
    tm->tv_sec = n1;
    tm->tv_usec = n2;
# endif
    return error ? FAIL : OK;
}
#endif /* FEAT_RELTIME */

/*
 * "reltime()" function
 */
    static void
f_reltime(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_RELTIME
    proftime_T	res;
    proftime_T	start;

    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	/* No arguments: get current time. */
	profile_start(&res);
    }
    else if (argvars[1].v_type == VAR_UNKNOWN)
    {
	if (list2proftime(&argvars[0], &res) == FAIL)
	    return;
	profile_end(&res);
    }
    else
    {
	/* Two arguments: compute the difference. */
	if (list2proftime(&argvars[0], &start) == FAIL
		|| list2proftime(&argvars[1], &res) == FAIL)
	    return;
	profile_sub(&res, &start);
    }

    if (rettv_list_alloc(rettv) == OK)
    {
	long	n1, n2;

# ifdef WIN3264
	n1 = res.HighPart;
	n2 = res.LowPart;
# else
	n1 = res.tv_sec;
	n2 = res.tv_usec;
# endif
	list_append_number(rettv->vval.v_list, (varnumber_T)n1);
	list_append_number(rettv->vval.v_list, (varnumber_T)n2);
    }
#endif
}

#ifdef FEAT_FLOAT
/*
 * "reltimefloat()" function
 */
    static void
f_reltimefloat(typval_T *argvars UNUSED, typval_T *rettv)
{
# ifdef FEAT_RELTIME
    proftime_T	tm;
# endif

    rettv->v_type = VAR_FLOAT;
    rettv->vval.v_float = 0;
# ifdef FEAT_RELTIME
    if (list2proftime(&argvars[0], &tm) == OK)
	rettv->vval.v_float = profile_float(&tm);
# endif
}
#endif

/*
 * "reltimestr()" function
 */
    static void
f_reltimestr(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_RELTIME
    proftime_T	tm;
#endif

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_RELTIME
    if (list2proftime(&argvars[0], &tm) == OK)
	rettv->vval.v_string = vim_strsave((char_u *)profile_msg(&tm));
#endif
}

#if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11)
    static void
make_connection(void)
{
    if (X_DISPLAY == NULL
# ifdef FEAT_GUI
	    && !gui.in_use
# endif
	    )
    {
	x_force_connect = TRUE;
	setup_term_clip();
	x_force_connect = FALSE;
    }
}

    static int
check_connection(void)
{
    make_connection();
    if (X_DISPLAY == NULL)
    {
	EMSG(_("E240: No connection to the X server"));
	return FAIL;
    }
    return OK;
}
#endif

#ifdef FEAT_CLIENTSERVER
    static void
remote_common(typval_T *argvars, typval_T *rettv, int expr)
{
    char_u	*server_name;
    char_u	*keys;
    char_u	*r = NULL;
    char_u	buf[NUMBUFLEN];
    int		timeout = 0;
# ifdef WIN32
    HWND	w;
# else
    Window	w;
# endif

    if (check_restricted() || check_secure())
	return;

# ifdef FEAT_X11
    if (check_connection() == FAIL)
	return;
# endif
    if (argvars[2].v_type != VAR_UNKNOWN
	    && argvars[3].v_type != VAR_UNKNOWN)
	timeout = tv_get_number(&argvars[3]);

    server_name = tv_get_string_chk(&argvars[0]);
    if (server_name == NULL)
	return;		/* type error; errmsg already given */
    keys = tv_get_string_buf(&argvars[1], buf);
# ifdef WIN32
    if (serverSendToVim(server_name, keys, &r, &w, expr, timeout, TRUE) < 0)
# else
    if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout,
								  0, TRUE) < 0)
# endif
    {
	if (r != NULL)
	{
	    EMSG(r);		/* sending worked but evaluation failed */
	    vim_free(r);
	}
	else
	    EMSG2(_("E241: Unable to send to %s"), server_name);
	return;
    }

    rettv->vval.v_string = r;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	dictitem_T	v;
	char_u		str[30];
	char_u		*idvar;

	idvar = tv_get_string_chk(&argvars[2]);
	if (idvar != NULL && *idvar != NUL)
	{
	    sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
	    v.di_tv.v_type = VAR_STRING;
	    v.di_tv.vval.v_string = vim_strsave(str);
	    set_var(idvar, &v.di_tv, FALSE);
	    vim_free(v.di_tv.vval.v_string);
	}
    }
}
#endif

/*
 * "remote_expr()" function
 */
    static void
f_remote_expr(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_CLIENTSERVER
    remote_common(argvars, rettv, TRUE);
#endif
}

/*
 * "remote_foreground()" function
 */
    static void
f_remote_foreground(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_CLIENTSERVER
# ifdef WIN32
    /* On Win32 it's done in this application. */
    {
	char_u	*server_name = tv_get_string_chk(&argvars[0]);

	if (server_name != NULL)
	    serverForeground(server_name);
    }
# else
    /* Send a foreground() expression to the server. */
    argvars[1].v_type = VAR_STRING;
    argvars[1].vval.v_string = vim_strsave((char_u *)"foreground()");
    argvars[2].v_type = VAR_UNKNOWN;
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
    remote_common(argvars, rettv, TRUE);
    vim_free(argvars[1].vval.v_string);
# endif
#endif
}

    static void
f_remote_peek(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_CLIENTSERVER
    dictitem_T	v;
    char_u	*s = NULL;
# ifdef WIN32
    long_u	n = 0;
# endif
    char_u	*serverid;

    if (check_restricted() || check_secure())
    {
	rettv->vval.v_number = -1;
	return;
    }
    serverid = tv_get_string_chk(&argvars[0]);
    if (serverid == NULL)
    {
	rettv->vval.v_number = -1;
	return;		/* type error; errmsg already given */
    }
# ifdef WIN32
    sscanf((const char *)serverid, SCANF_HEX_LONG_U, &n);
    if (n == 0)
	rettv->vval.v_number = -1;
    else
    {
	s = serverGetReply((HWND)n, FALSE, FALSE, FALSE, 0);
	rettv->vval.v_number = (s != NULL);
    }
# else
    if (check_connection() == FAIL)
	return;

    rettv->vval.v_number = serverPeekReply(X_DISPLAY,
						serverStrToWin(serverid), &s);
# endif

    if (argvars[1].v_type != VAR_UNKNOWN && rettv->vval.v_number > 0)
    {
	char_u		*retvar;

	v.di_tv.v_type = VAR_STRING;
	v.di_tv.vval.v_string = vim_strsave(s);
	retvar = tv_get_string_chk(&argvars[1]);
	if (retvar != NULL)
	    set_var(retvar, &v.di_tv, FALSE);
	vim_free(v.di_tv.vval.v_string);
    }
#else
    rettv->vval.v_number = -1;
#endif
}

    static void
f_remote_read(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*r = NULL;

#ifdef FEAT_CLIENTSERVER
    char_u	*serverid = tv_get_string_chk(&argvars[0]);

    if (serverid != NULL && !check_restricted() && !check_secure())
    {
	int timeout = 0;
# ifdef WIN32
	/* The server's HWND is encoded in the 'id' parameter */
	long_u		n = 0;
# endif

	if (argvars[1].v_type != VAR_UNKNOWN)
	    timeout = tv_get_number(&argvars[1]);

# ifdef WIN32
	sscanf((char *)serverid, SCANF_HEX_LONG_U, &n);
	if (n != 0)
	    r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout);
	if (r == NULL)
# else
	if (check_connection() == FAIL
		|| serverReadReply(X_DISPLAY, serverStrToWin(serverid),
						       &r, FALSE, timeout) < 0)
# endif
	    EMSG(_("E277: Unable to read a server reply"));
    }
#endif
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = r;
}

/*
 * "remote_send()" function
 */
    static void
f_remote_send(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
#ifdef FEAT_CLIENTSERVER
    remote_common(argvars, rettv, FALSE);
#endif
}

/*
 * "remote_startserver()" function
 */
    static void
f_remote_startserver(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_CLIENTSERVER
    char_u	*server = tv_get_string_chk(&argvars[0]);

    if (server == NULL)
	return;		/* type error; errmsg already given */
    if (serverName != NULL)
	EMSG(_("E941: already started a server"));
    else
    {
# ifdef FEAT_X11
	if (check_connection() == OK)
	    serverRegisterName(X_DISPLAY, server);
# else
	serverSetName(server);
# endif
    }
#else
    EMSG(_("E942: +clientserver feature not available"));
#endif
}

/*
 * "remove()" function
 */
    static void
f_remove(typval_T *argvars, typval_T *rettv)
{
    list_T	*l;
    listitem_T	*item, *item2;
    listitem_T	*li;
    long	idx;
    long	end;
    char_u	*key;
    dict_T	*d;
    dictitem_T	*di;
    char_u	*arg_errmsg = (char_u *)N_("remove() argument");

    if (argvars[0].v_type == VAR_DICT)
    {
	if (argvars[2].v_type != VAR_UNKNOWN)
	    EMSG2(_(e_toomanyarg), "remove()");
	else if ((d = argvars[0].vval.v_dict) != NULL
		&& !tv_check_lock(d->dv_lock, arg_errmsg, TRUE))
	{
	    key = tv_get_string_chk(&argvars[1]);
	    if (key != NULL)
	    {
		di = dict_find(d, key, -1);
		if (di == NULL)
		    EMSG2(_(e_dictkey), key);
		else if (!var_check_fixed(di->di_flags, arg_errmsg, TRUE)
			    && !var_check_ro(di->di_flags, arg_errmsg, TRUE))
		{
		    *rettv = di->di_tv;
		    init_tv(&di->di_tv);
		    dictitem_remove(d, di);
		}
	    }
	}
    }
    else if (argvars[0].v_type != VAR_LIST)
	EMSG2(_(e_listdictarg), "remove()");
    else if ((l = argvars[0].vval.v_list) != NULL
	    && !tv_check_lock(l->lv_lock, arg_errmsg, TRUE))
    {
	int	    error = FALSE;

	idx = (long)tv_get_number_chk(&argvars[1], &error);
	if (error)
	    ;		/* type error: do nothing, errmsg already given */
	else if ((item = list_find(l, idx)) == NULL)
	    EMSGN(_(e_listidx), idx);
	else
	{
	    if (argvars[2].v_type == VAR_UNKNOWN)
	    {
		/* Remove one item, return its value. */
		vimlist_remove(l, item, item);
		*rettv = item->li_tv;
		vim_free(item);
	    }
	    else
	    {
		/* Remove range of items, return list with values. */
		end = (long)tv_get_number_chk(&argvars[2], &error);
		if (error)
		    ;		/* type error: do nothing */
		else if ((item2 = list_find(l, end)) == NULL)
		    EMSGN(_(e_listidx), end);
		else
		{
		    int	    cnt = 0;

		    for (li = item; li != NULL; li = li->li_next)
		    {
			++cnt;
			if (li == item2)
			    break;
		    }
		    if (li == NULL)  /* didn't find "item2" after "item" */
			EMSG(_(e_invrange));
		    else
		    {
			vimlist_remove(l, item, item2);
			if (rettv_list_alloc(rettv) == OK)
			{
			    l = rettv->vval.v_list;
			    l->lv_first = item;
			    l->lv_last = item2;
			    item->li_prev = NULL;
			    item2->li_next = NULL;
			    l->lv_len = cnt;
			}
		    }
		}
	    }
	}
    }
}

/*
 * "rename({from}, {to})" function
 */
    static void
f_rename(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];

    if (check_restricted() || check_secure())
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]),
				      tv_get_string_buf(&argvars[1], buf));
}

/*
 * "repeat()" function
 */
    static void
f_repeat(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;
    int		n;
    int		slen;
    int		len;
    char_u	*r;
    int		i;

    n = (int)tv_get_number(&argvars[1]);
    if (argvars[0].v_type == VAR_LIST)
    {
	if (rettv_list_alloc(rettv) == OK && argvars[0].vval.v_list != NULL)
	    while (n-- > 0)
		if (list_extend(rettv->vval.v_list,
					argvars[0].vval.v_list, NULL) == FAIL)
		    break;
    }
    else
    {
	p = tv_get_string(&argvars[0]);
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = NULL;

	slen = (int)STRLEN(p);
	len = slen * n;
	if (len <= 0)
	    return;

	r = alloc(len + 1);
	if (r != NULL)
	{
	    for (i = 0; i < n; i++)
		mch_memmove(r + i * slen, p, (size_t)slen);
	    r[len] = NUL;
	}

	rettv->vval.v_string = r;
    }
}

/*
 * "resolve()" function
 */
    static void
f_resolve(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;
#ifdef HAVE_READLINK
    char_u	*buf = NULL;
#endif

    p = tv_get_string(&argvars[0]);
#ifdef FEAT_SHORTCUT
    {
	char_u	*v = NULL;

	v = mch_resolve_shortcut(p);
	if (v != NULL)
	    rettv->vval.v_string = v;
	else
	    rettv->vval.v_string = vim_strsave(p);
    }
#else
# ifdef HAVE_READLINK
    {
	char_u	*cpy;
	int	len;
	char_u	*remain = NULL;
	char_u	*q;
	int	is_relative_to_current = FALSE;
	int	has_trailing_pathsep = FALSE;
	int	limit = 100;

	p = vim_strsave(p);

	if (p[0] == '.' && (vim_ispathsep(p[1])
				   || (p[1] == '.' && (vim_ispathsep(p[2])))))
	    is_relative_to_current = TRUE;

	len = STRLEN(p);
	if (len > 0 && after_pathsep(p, p + len))
	{
	    has_trailing_pathsep = TRUE;
	    p[len - 1] = NUL; /* the trailing slash breaks readlink() */
	}

	q = getnextcomp(p);
	if (*q != NUL)
	{
	    /* Separate the first path component in "p", and keep the
	     * remainder (beginning with the path separator). */
	    remain = vim_strsave(q - 1);
	    q[-1] = NUL;
	}

	buf = alloc(MAXPATHL + 1);
	if (buf == NULL)
	    goto fail;

	for (;;)
	{
	    for (;;)
	    {
		len = readlink((char *)p, (char *)buf, MAXPATHL);
		if (len <= 0)
		    break;
		buf[len] = NUL;

		if (limit-- == 0)
		{
		    vim_free(p);
		    vim_free(remain);
		    EMSG(_("E655: Too many symbolic links (cycle?)"));
		    rettv->vval.v_string = NULL;
		    goto fail;
		}

		/* Ensure that the result will have a trailing path separator
		 * if the argument has one. */
		if (remain == NULL && has_trailing_pathsep)
		    add_pathsep(buf);

		/* Separate the first path component in the link value and
		 * concatenate the remainders. */
		q = getnextcomp(vim_ispathsep(*buf) ? buf + 1 : buf);
		if (*q != NUL)
		{
		    if (remain == NULL)
			remain = vim_strsave(q - 1);
		    else
		    {
			cpy = concat_str(q - 1, remain);
			if (cpy != NULL)
			{
			    vim_free(remain);
			    remain = cpy;
			}
		    }
		    q[-1] = NUL;
		}

		q = gettail(p);
		if (q > p && *q == NUL)
		{
		    /* Ignore trailing path separator. */
		    q[-1] = NUL;
		    q = gettail(p);
		}
		if (q > p && !mch_isFullName(buf))
		{
		    /* symlink is relative to directory of argument */
		    cpy = alloc((unsigned)(STRLEN(p) + STRLEN(buf) + 1));
		    if (cpy != NULL)
		    {
			STRCPY(cpy, p);
			STRCPY(gettail(cpy), buf);
			vim_free(p);
			p = cpy;
		    }
		}
		else
		{
		    vim_free(p);
		    p = vim_strsave(buf);
		}
	    }

	    if (remain == NULL)
		break;

	    /* Append the first path component of "remain" to "p". */
	    q = getnextcomp(remain + 1);
	    len = q - remain - (*q != NUL);
	    cpy = vim_strnsave(p, STRLEN(p) + len);
	    if (cpy != NULL)
	    {
		STRNCAT(cpy, remain, len);
		vim_free(p);
		p = cpy;
	    }
	    /* Shorten "remain". */
	    if (*q != NUL)
		STRMOVE(remain, q - 1);
	    else
		VIM_CLEAR(remain);
	}

	/* If the result is a relative path name, make it explicitly relative to
	 * the current directory if and only if the argument had this form. */
	if (!vim_ispathsep(*p))
	{
	    if (is_relative_to_current
		    && *p != NUL
		    && !(p[0] == '.'
			&& (p[1] == NUL
			    || vim_ispathsep(p[1])
			    || (p[1] == '.'
				&& (p[2] == NUL
				    || vim_ispathsep(p[2]))))))
	    {
		/* Prepend "./". */
		cpy = concat_str((char_u *)"./", p);
		if (cpy != NULL)
		{
		    vim_free(p);
		    p = cpy;
		}
	    }
	    else if (!is_relative_to_current)
	    {
		/* Strip leading "./". */
		q = p;
		while (q[0] == '.' && vim_ispathsep(q[1]))
		    q += 2;
		if (q > p)
		    STRMOVE(p, p + 2);
	    }
	}

	/* Ensure that the result will have no trailing path separator
	 * if the argument had none.  But keep "/" or "//". */
	if (!has_trailing_pathsep)
	{
	    q = p + STRLEN(p);
	    if (after_pathsep(p, q))
		*gettail_sep(p) = NUL;
	}

	rettv->vval.v_string = p;
    }
# else
    rettv->vval.v_string = vim_strsave(p);
# endif
#endif

    simplify_filename(rettv->vval.v_string);

#ifdef HAVE_READLINK
fail:
    vim_free(buf);
#endif
    rettv->v_type = VAR_STRING;
}

/*
 * "reverse({list})" function
 */
    static void
f_reverse(typval_T *argvars, typval_T *rettv)
{
    list_T	*l;
    listitem_T	*li, *ni;

    if (argvars[0].v_type != VAR_LIST)
	EMSG2(_(e_listarg), "reverse()");
    else if ((l = argvars[0].vval.v_list) != NULL
	    && !tv_check_lock(l->lv_lock,
				    (char_u *)N_("reverse() argument"), TRUE))
    {
	li = l->lv_last;
	l->lv_first = l->lv_last = NULL;
	l->lv_len = 0;
	while (li != NULL)
	{
	    ni = li->li_prev;
	    list_append(l, li);
	    li = ni;
	}
	rettv_list_set(rettv, l);
	l->lv_idx = l->lv_len - l->lv_idx - 1;
    }
}

#define SP_NOMOVE	0x01	    /* don't move cursor */
#define SP_REPEAT	0x02	    /* repeat to find outer pair */
#define SP_RETCOUNT	0x04	    /* return matchcount */
#define SP_SETPCMARK	0x08	    /* set previous context mark */
#define SP_START	0x10	    /* accept match at start position */
#define SP_SUBPAT	0x20	    /* return nr of matching sub-pattern */
#define SP_END		0x40	    /* leave cursor at end of match */
#define SP_COLUMN	0x80	    /* start at cursor column */

/*
 * Get flags for a search function.
 * Possibly sets "p_ws".
 * Returns BACKWARD, FORWARD or zero (for an error).
 */
    static int
get_search_arg(typval_T *varp, int *flagsp)
{
    int		dir = FORWARD;
    char_u	*flags;
    char_u	nbuf[NUMBUFLEN];
    int		mask;

    if (varp->v_type != VAR_UNKNOWN)
    {
	flags = tv_get_string_buf_chk(varp, nbuf);
	if (flags == NULL)
	    return 0;		/* type error; errmsg already given */
	while (*flags != NUL)
	{
	    switch (*flags)
	    {
		case 'b': dir = BACKWARD; break;
		case 'w': p_ws = TRUE; break;
		case 'W': p_ws = FALSE; break;
		default:  mask = 0;
			  if (flagsp != NULL)
			     switch (*flags)
			     {
				 case 'c': mask = SP_START; break;
				 case 'e': mask = SP_END; break;
				 case 'm': mask = SP_RETCOUNT; break;
				 case 'n': mask = SP_NOMOVE; break;
				 case 'p': mask = SP_SUBPAT; break;
				 case 'r': mask = SP_REPEAT; break;
				 case 's': mask = SP_SETPCMARK; break;
				 case 'z': mask = SP_COLUMN; break;
			     }
			  if (mask == 0)
			  {
			      EMSG2(_(e_invarg2), flags);
			      dir = 0;
			  }
			  else
			      *flagsp |= mask;
	    }
	    if (dir == 0)
		break;
	    ++flags;
	}
    }
    return dir;
}

/*
 * Shared by search() and searchpos() functions.
 */
    static int
search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
{
    int		flags;
    char_u	*pat;
    pos_T	pos;
    pos_T	save_cursor;
    int		save_p_ws = p_ws;
    int		dir;
    int		retval = 0;	/* default: FAIL */
    long	lnum_stop = 0;
    proftime_T	tm;
#ifdef FEAT_RELTIME
    long	time_limit = 0;
#endif
    int		options = SEARCH_KEEP;
    int		subpatnum;

    pat = tv_get_string(&argvars[0]);
    dir = get_search_arg(&argvars[1], flagsp);	/* may set p_ws */
    if (dir == 0)
	goto theend;
    flags = *flagsp;
    if (flags & SP_START)
	options |= SEARCH_START;
    if (flags & SP_END)
	options |= SEARCH_END;
    if (flags & SP_COLUMN)
	options |= SEARCH_COL;

    /* Optional arguments: line number to stop searching and timeout. */
    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN)
    {
	lnum_stop = (long)tv_get_number_chk(&argvars[2], NULL);
	if (lnum_stop < 0)
	    goto theend;
#ifdef FEAT_RELTIME
	if (argvars[3].v_type != VAR_UNKNOWN)
	{
	    time_limit = (long)tv_get_number_chk(&argvars[3], NULL);
	    if (time_limit < 0)
		goto theend;
	}
#endif
    }

#ifdef FEAT_RELTIME
    /* Set the time limit, if there is one. */
    profile_setlimit(time_limit, &tm);
#endif

    /*
     * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
     * Check to make sure only those flags are set.
     * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
     * flags cannot be set. Check for that condition also.
     */
    if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
	    || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK)))
    {
	EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
	goto theend;
    }

    pos = save_cursor = curwin->w_cursor;
    subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
			   options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL);
    if (subpatnum != FAIL)
    {
	if (flags & SP_SUBPAT)
	    retval = subpatnum;
	else
	    retval = pos.lnum;
	if (flags & SP_SETPCMARK)
	    setpcmark();
	curwin->w_cursor = pos;
	if (match_pos != NULL)
	{
	    /* Store the match cursor position */
	    match_pos->lnum = pos.lnum;
	    match_pos->col = pos.col + 1;
	}
	/* "/$" will put the cursor after the end of the line, may need to
	 * correct that here */
	check_cursor();
    }

    /* If 'n' flag is used: restore cursor position. */
    if (flags & SP_NOMOVE)
	curwin->w_cursor = save_cursor;
    else
	curwin->w_set_curswant = TRUE;
theend:
    p_ws = save_p_ws;

    return retval;
}

#ifdef FEAT_FLOAT

/*
 * round() is not in C90, use ceil() or floor() instead.
 */
    float_T
vim_round(float_T f)
{
    return f > 0 ? floor(f + 0.5) : ceil(f - 0.5);
}

/*
 * "round({float})" function
 */
    static void
f_round(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = vim_round(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "screenattr()" function
 */
    static void
f_screenattr(typval_T *argvars, typval_T *rettv)
{
    int		row;
    int		col;
    int		c;

    row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
    col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
    if (row < 0 || row >= screen_Rows
	    || col < 0 || col >= screen_Columns)
	c = -1;
    else
	c = ScreenAttrs[LineOffset[row] + col];
    rettv->vval.v_number = c;
}

/*
 * "screenchar()" function
 */
    static void
f_screenchar(typval_T *argvars, typval_T *rettv)
{
    int		row;
    int		col;
    int		off;
    int		c;

    row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
    col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
    if (row < 0 || row >= screen_Rows
	    || col < 0 || col >= screen_Columns)
	c = -1;
    else
    {
	off = LineOffset[row] + col;
#ifdef FEAT_MBYTE
	if (enc_utf8 && ScreenLinesUC[off] != 0)
	    c = ScreenLinesUC[off];
	else
#endif
	    c = ScreenLines[off];
    }
    rettv->vval.v_number = c;
}

/*
 * "screencol()" function
 *
 * First column is 1 to be consistent with virtcol().
 */
    static void
f_screencol(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = screen_screencol() + 1;
}

/*
 * "screenrow()" function
 */
    static void
f_screenrow(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = screen_screenrow() + 1;
}

/*
 * "search()" function
 */
    static void
f_search(typval_T *argvars, typval_T *rettv)
{
    int		flags = 0;

    rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
}

/*
 * "searchdecl()" function
 */
    static void
f_searchdecl(typval_T *argvars, typval_T *rettv)
{
    int		locally = 1;
    int		thisblock = 0;
    int		error = FALSE;
    char_u	*name;

    rettv->vval.v_number = 1;	/* default: FAIL */

    name = tv_get_string_chk(&argvars[0]);
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	locally = (int)tv_get_number_chk(&argvars[1], &error) == 0;
	if (!error && argvars[2].v_type != VAR_UNKNOWN)
	    thisblock = (int)tv_get_number_chk(&argvars[2], &error) != 0;
    }
    if (!error && name != NULL)
	rettv->vval.v_number = find_decl(name, (int)STRLEN(name),
				     locally, thisblock, SEARCH_KEEP) == FAIL;
}

/*
 * Used by searchpair() and searchpairpos()
 */
    static int
searchpair_cmn(typval_T *argvars, pos_T *match_pos)
{
    char_u	*spat, *mpat, *epat;
    typval_T	*skip;
    int		save_p_ws = p_ws;
    int		dir;
    int		flags = 0;
    char_u	nbuf1[NUMBUFLEN];
    char_u	nbuf2[NUMBUFLEN];
    int		retval = 0;		/* default: FAIL */
    long	lnum_stop = 0;
    long	time_limit = 0;

    /* Get the three pattern arguments: start, middle, end. Will result in an
     * error if not a valid argument. */
    spat = tv_get_string_chk(&argvars[0]);
    mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
    epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
    if (spat == NULL || mpat == NULL || epat == NULL)
	goto theend;	    /* type error */

    /* Handle the optional fourth argument: flags */
    dir = get_search_arg(&argvars[3], &flags); /* may set p_ws */
    if (dir == 0)
	goto theend;

    /* Don't accept SP_END or SP_SUBPAT.
     * Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
     */
    if ((flags & (SP_END | SP_SUBPAT)) != 0
	    || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK)))
    {
	EMSG2(_(e_invarg2), tv_get_string(&argvars[3]));
	goto theend;
    }

    /* Using 'r' implies 'W', otherwise it doesn't work. */
    if (flags & SP_REPEAT)
	p_ws = FALSE;

    /* Optional fifth argument: skip expression */
    if (argvars[3].v_type == VAR_UNKNOWN
	    || argvars[4].v_type == VAR_UNKNOWN)
	skip = NULL;
    else
    {
	skip = &argvars[4];
	if (skip->v_type != VAR_FUNC && skip->v_type != VAR_PARTIAL
	    && skip->v_type != VAR_STRING)
	{
	    /* Type error */
	    EMSG2(_(e_invarg2), tv_get_string(&argvars[4]));
	    goto theend;
	}
	if (argvars[5].v_type != VAR_UNKNOWN)
	{
	    lnum_stop = (long)tv_get_number_chk(&argvars[5], NULL);
	    if (lnum_stop < 0)
	    {
		EMSG2(_(e_invarg2), tv_get_string(&argvars[5]));
		goto theend;
	    }
#ifdef FEAT_RELTIME
	    if (argvars[6].v_type != VAR_UNKNOWN)
	    {
		time_limit = (long)tv_get_number_chk(&argvars[6], NULL);
		if (time_limit < 0)
		{
		    EMSG2(_(e_invarg2), tv_get_string(&argvars[6]));
		    goto theend;
		}
	    }
#endif
	}
    }

    retval = do_searchpair(spat, mpat, epat, dir, skip, flags,
					    match_pos, lnum_stop, time_limit);

theend:
    p_ws = save_p_ws;

    return retval;
}

/*
 * "searchpair()" function
 */
    static void
f_searchpair(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = searchpair_cmn(argvars, NULL);
}

/*
 * "searchpairpos()" function
 */
    static void
f_searchpairpos(typval_T *argvars, typval_T *rettv)
{
    pos_T	match_pos;
    int		lnum = 0;
    int		col = 0;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (searchpair_cmn(argvars, &match_pos) > 0)
    {
	lnum = match_pos.lnum;
	col = match_pos.col;
    }

    list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
    list_append_number(rettv->vval.v_list, (varnumber_T)col);
}

/*
 * Search for a start/middle/end thing.
 * Used by searchpair(), see its documentation for the details.
 * Returns 0 or -1 for no match,
 */
    long
do_searchpair(
    char_u	*spat,	    /* start pattern */
    char_u	*mpat,	    /* middle pattern */
    char_u	*epat,	    /* end pattern */
    int		dir,	    /* BACKWARD or FORWARD */
    typval_T	*skip,	    /* skip expression */
    int		flags,	    /* SP_SETPCMARK and other SP_ values */
    pos_T	*match_pos,
    linenr_T	lnum_stop,  /* stop at this line if not zero */
    long	time_limit UNUSED) /* stop after this many msec */
{
    char_u	*save_cpo;
    char_u	*pat, *pat2 = NULL, *pat3 = NULL;
    long	retval = 0;
    pos_T	pos;
    pos_T	firstpos;
    pos_T	foundpos;
    pos_T	save_cursor;
    pos_T	save_pos;
    int		n;
    int		r;
    int		nest = 1;
    int		use_skip = FALSE;
    int		err;
    int		options = SEARCH_KEEP;
    proftime_T	tm;

    /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
    save_cpo = p_cpo;
    p_cpo = empty_option;

#ifdef FEAT_RELTIME
    /* Set the time limit, if there is one. */
    profile_setlimit(time_limit, &tm);
#endif

    /* Make two search patterns: start/end (pat2, for in nested pairs) and
     * start/middle/end (pat3, for the top pair). */
    pat2 = alloc((unsigned)(STRLEN(spat) + STRLEN(epat) + 17));
    pat3 = alloc((unsigned)(STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25));
    if (pat2 == NULL || pat3 == NULL)
	goto theend;
    sprintf((char *)pat2, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
    if (*mpat == NUL)
	STRCPY(pat3, pat2);
    else
	sprintf((char *)pat3, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)",
							    spat, epat, mpat);
    if (flags & SP_START)
	options |= SEARCH_START;

    if (skip != NULL)
    {
	/* Empty string means to not use the skip expression. */
	if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC)
	    use_skip = skip->vval.v_string != NULL
						&& *skip->vval.v_string != NUL;
    }

    save_cursor = curwin->w_cursor;
    pos = curwin->w_cursor;
    CLEAR_POS(&firstpos);
    CLEAR_POS(&foundpos);
    pat = pat3;
    for (;;)
    {
	n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
				     options, RE_SEARCH, lnum_stop, &tm, NULL);
	if (n == FAIL || (firstpos.lnum != 0 && EQUAL_POS(pos, firstpos)))
	    /* didn't find it or found the first match again: FAIL */
	    break;

	if (firstpos.lnum == 0)
	    firstpos = pos;
	if (EQUAL_POS(pos, foundpos))
	{
	    /* Found the same position again.  Can happen with a pattern that
	     * has "\zs" at the end and searching backwards.  Advance one
	     * character and try again. */
	    if (dir == BACKWARD)
		decl(&pos);
	    else
		incl(&pos);
	}
	foundpos = pos;

	/* clear the start flag to avoid getting stuck here */
	options &= ~SEARCH_START;

	/* If the skip pattern matches, ignore this match. */
	if (use_skip)
	{
	    save_pos = curwin->w_cursor;
	    curwin->w_cursor = pos;
	    err = FALSE;
	    r = eval_expr_to_bool(skip, &err);
	    curwin->w_cursor = save_pos;
	    if (err)
	    {
		/* Evaluating {skip} caused an error, break here. */
		curwin->w_cursor = save_cursor;
		retval = -1;
		break;
	    }
	    if (r)
		continue;
	}

	if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2))
	{
	    /* Found end when searching backwards or start when searching
	     * forward: nested pair. */
	    ++nest;
	    pat = pat2;		/* nested, don't search for middle */
	}
	else
	{
	    /* Found end when searching forward or start when searching
	     * backward: end of (nested) pair; or found middle in outer pair. */
	    if (--nest == 1)
		pat = pat3;	/* outer level, search for middle */
	}

	if (nest == 0)
	{
	    /* Found the match: return matchcount or line number. */
	    if (flags & SP_RETCOUNT)
		++retval;
	    else
		retval = pos.lnum;
	    if (flags & SP_SETPCMARK)
		setpcmark();
	    curwin->w_cursor = pos;
	    if (!(flags & SP_REPEAT))
		break;
	    nest = 1;	    /* search for next unmatched */
	}
    }

    if (match_pos != NULL)
    {
	/* Store the match cursor position */
	match_pos->lnum = curwin->w_cursor.lnum;
	match_pos->col = curwin->w_cursor.col + 1;
    }

    /* If 'n' flag is used or search failed: restore cursor position. */
    if ((flags & SP_NOMOVE) || retval == 0)
	curwin->w_cursor = save_cursor;

theend:
    vim_free(pat2);
    vim_free(pat3);
    if (p_cpo == empty_option)
	p_cpo = save_cpo;
    else
	/* Darn, evaluating the {skip} expression changed the value. */
	free_string_option(save_cpo);

    return retval;
}

/*
 * "searchpos()" function
 */
    static void
f_searchpos(typval_T *argvars, typval_T *rettv)
{
    pos_T	match_pos;
    int		lnum = 0;
    int		col = 0;
    int		n;
    int		flags = 0;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    n = search_cmn(argvars, &match_pos, &flags);
    if (n > 0)
    {
	lnum = match_pos.lnum;
	col = match_pos.col;
    }

    list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
    list_append_number(rettv->vval.v_list, (varnumber_T)col);
    if (flags & SP_SUBPAT)
	list_append_number(rettv->vval.v_list, (varnumber_T)n);
}

    static void
f_server2client(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_CLIENTSERVER
    char_u	buf[NUMBUFLEN];
    char_u	*server = tv_get_string_chk(&argvars[0]);
    char_u	*reply = tv_get_string_buf_chk(&argvars[1], buf);

    rettv->vval.v_number = -1;
    if (server == NULL || reply == NULL)
	return;
    if (check_restricted() || check_secure())
	return;
# ifdef FEAT_X11
    if (check_connection() == FAIL)
	return;
# endif

    if (serverSendReply(server, reply) < 0)
    {
	EMSG(_("E258: Unable to send to client"));
	return;
    }
    rettv->vval.v_number = 0;
#else
    rettv->vval.v_number = -1;
#endif
}

    static void
f_serverlist(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*r = NULL;

#ifdef FEAT_CLIENTSERVER
# ifdef WIN32
    r = serverGetVimNames();
# else
    make_connection();
    if (X_DISPLAY != NULL)
	r = serverGetVimNames(X_DISPLAY);
# endif
#endif
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = r;
}

/*
 * "setbufline()" function
 */
    static void
f_setbufline(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;
    buf_T	*buf;

    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL)
	rettv->vval.v_number = 1; /* FAIL */
    else
    {
	lnum = tv_get_lnum_buf(&argvars[1], buf);
	set_buffer_lines(buf, lnum, FALSE, &argvars[2], rettv);
    }
}

/*
 * "setbufvar()" function
 */
    static void
f_setbufvar(typval_T *argvars, typval_T *rettv UNUSED)
{
    buf_T	*buf;
    char_u	*varname, *bufvarname;
    typval_T	*varp;
    char_u	nbuf[NUMBUFLEN];

    if (check_restricted() || check_secure())
	return;
    (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
    varname = tv_get_string_chk(&argvars[1]);
    buf = get_buf_tv(&argvars[0], FALSE);
    varp = &argvars[2];

    if (buf != NULL && varname != NULL && varp != NULL)
    {
	if (*varname == '&')
	{
	    long	numval;
	    char_u	*strval;
	    int		error = FALSE;
	    aco_save_T	aco;

	    /* set curbuf to be our buf, temporarily */
	    aucmd_prepbuf(&aco, buf);

	    ++varname;
	    numval = (long)tv_get_number_chk(varp, &error);
	    strval = tv_get_string_buf_chk(varp, nbuf);
	    if (!error && strval != NULL)
		set_option_value(varname, numval, strval, OPT_LOCAL);

	    /* reset notion of buffer */
	    aucmd_restbuf(&aco);
	}
	else
	{
	    buf_T *save_curbuf = curbuf;

	    bufvarname = alloc((unsigned)STRLEN(varname) + 3);
	    if (bufvarname != NULL)
	    {
		curbuf = buf;
		STRCPY(bufvarname, "b:");
		STRCPY(bufvarname + 2, varname);
		set_var(bufvarname, varp, TRUE);
		vim_free(bufvarname);
		curbuf = save_curbuf;
	    }
	}
    }
}

    static void
f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED)
{
    dict_T	*d;
    dictitem_T	*di;
    char_u	*csearch;

    if (argvars[0].v_type != VAR_DICT)
    {
	EMSG(_(e_dictreq));
	return;
    }

    if ((d = argvars[0].vval.v_dict) != NULL)
    {
	csearch = dict_get_string(d, (char_u *)"char", FALSE);
	if (csearch != NULL)
	{
#ifdef FEAT_MBYTE
	    if (enc_utf8)
	    {
		int pcc[MAX_MCO];
		int c = utfc_ptr2char(csearch, pcc);

		set_last_csearch(c, csearch, utfc_ptr2len(csearch));
	    }
	    else
#endif
		set_last_csearch(PTR2CHAR(csearch),
						csearch, MB_PTR2LEN(csearch));
	}

	di = dict_find(d, (char_u *)"forward", -1);
	if (di != NULL)
	    set_csearch_direction((int)tv_get_number(&di->di_tv)
							? FORWARD : BACKWARD);

	di = dict_find(d, (char_u *)"until", -1);
	if (di != NULL)
	    set_csearch_until(!!tv_get_number(&di->di_tv));
    }
}

/*
 * "setcmdpos()" function
 */
    static void
f_setcmdpos(typval_T *argvars, typval_T *rettv)
{
    int		pos = (int)tv_get_number(&argvars[0]) - 1;

    if (pos >= 0)
	rettv->vval.v_number = set_cmdline_pos(pos);
}

/*
 * "setfperm({fname}, {mode})" function
 */
    static void
f_setfperm(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    char_u	modebuf[NUMBUFLEN];
    char_u	*mode_str;
    int		i;
    int		mask;
    int		mode = 0;

    rettv->vval.v_number = 0;
    fname = tv_get_string_chk(&argvars[0]);
    if (fname == NULL)
	return;
    mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
    if (mode_str == NULL)
	return;
    if (STRLEN(mode_str) != 9)
    {
	EMSG2(_(e_invarg2), mode_str);
	return;
    }

    mask = 1;
    for (i = 8; i >= 0; --i)
    {
	if (mode_str[i] != '-')
	    mode |= mask;
	mask = mask << 1;
    }
    rettv->vval.v_number = mch_setperm(fname, mode) == OK;
}

/*
 * "setline()" function
 */
    static void
f_setline(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum = tv_get_lnum(&argvars[0]);

    set_buffer_lines(curbuf, lnum, FALSE, &argvars[1], rettv);
}

/*
 * Used by "setqflist()" and "setloclist()" functions
 */
    static void
set_qf_ll_list(
    win_T	*wp UNUSED,
    typval_T	*list_arg UNUSED,
    typval_T	*action_arg UNUSED,
    typval_T	*what_arg UNUSED,
    typval_T	*rettv)
{
#ifdef FEAT_QUICKFIX
    static char *e_invact = N_("E927: Invalid action: '%s'");
    char_u	*act;
    int		action = 0;
    static int	recursive = 0;
#endif

    rettv->vval.v_number = -1;

#ifdef FEAT_QUICKFIX
    if (list_arg->v_type != VAR_LIST)
	EMSG(_(e_listreq));
    else if (recursive != 0)
	EMSG(_(e_au_recursive));
    else
    {
	list_T  *l = list_arg->vval.v_list;
	dict_T	*d = NULL;
	int	valid_dict = TRUE;

	if (action_arg->v_type == VAR_STRING)
	{
	    act = tv_get_string_chk(action_arg);
	    if (act == NULL)
		return;		/* type error; errmsg already given */
	    if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') &&
		    act[1] == NUL)
		action = *act;
	    else
		EMSG2(_(e_invact), act);
	}
	else if (action_arg->v_type == VAR_UNKNOWN)
	    action = ' ';
	else
	    EMSG(_(e_stringreq));

	if (action_arg->v_type != VAR_UNKNOWN
		&& what_arg->v_type != VAR_UNKNOWN)
	{
	    if (what_arg->v_type == VAR_DICT)
		d = what_arg->vval.v_dict;
	    else
	    {
		EMSG(_(e_dictreq));
		valid_dict = FALSE;
	    }
	}

	++recursive;
	if (l != NULL && action && valid_dict && set_errorlist(wp, l, action,
		     (char_u *)(wp == NULL ? ":setqflist()" : ":setloclist()"),
		     d) == OK)
	    rettv->vval.v_number = 0;
	--recursive;
    }
#endif
}

/*
 * "setloclist()" function
 */
    static void
f_setloclist(typval_T *argvars, typval_T *rettv)
{
    win_T	*win;

    rettv->vval.v_number = -1;

    win = find_win_by_nr_or_id(&argvars[0]);
    if (win != NULL)
	set_qf_ll_list(win, &argvars[1], &argvars[2], &argvars[3], rettv);
}

/*
 * "setmatches()" function
 */
    static void
f_setmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    list_T	*l;
    listitem_T	*li;
    dict_T	*d;
    list_T	*s = NULL;

    rettv->vval.v_number = -1;
    if (argvars[0].v_type != VAR_LIST)
    {
	EMSG(_(e_listreq));
	return;
    }
    if ((l = argvars[0].vval.v_list) != NULL)
    {

	/* To some extent make sure that we are dealing with a list from
	 * "getmatches()". */
	li = l->lv_first;
	while (li != NULL)
	{
	    if (li->li_tv.v_type != VAR_DICT
		    || (d = li->li_tv.vval.v_dict) == NULL)
	    {
		EMSG(_(e_invarg));
		return;
	    }
	    if (!(dict_find(d, (char_u *)"group", -1) != NULL
			&& (dict_find(d, (char_u *)"pattern", -1) != NULL
			    || dict_find(d, (char_u *)"pos1", -1) != NULL)
			&& dict_find(d, (char_u *)"priority", -1) != NULL
			&& dict_find(d, (char_u *)"id", -1) != NULL))
	    {
		EMSG(_(e_invarg));
		return;
	    }
	    li = li->li_next;
	}

	clear_matches(curwin);
	li = l->lv_first;
	while (li != NULL)
	{
	    int		i = 0;
	    char_u	buf[5];
	    dictitem_T  *di;
	    char_u	*group;
	    int		priority;
	    int		id;
	    char_u	*conceal;

	    d = li->li_tv.vval.v_dict;
	    if (dict_find(d, (char_u *)"pattern", -1) == NULL)
	    {
		if (s == NULL)
		{
		    s = list_alloc();
		    if (s == NULL)
			return;
		}

		/* match from matchaddpos() */
		for (i = 1; i < 9; i++)
		{
		    sprintf((char *)buf, (char *)"pos%d", i);
		    if ((di = dict_find(d, (char_u *)buf, -1)) != NULL)
		    {
			if (di->di_tv.v_type != VAR_LIST)
			    return;

			list_append_tv(s, &di->di_tv);
			s->lv_refcount++;
		    }
		    else
			break;
		}
	    }

	    group = dict_get_string(d, (char_u *)"group", TRUE);
	    priority = (int)dict_get_number(d, (char_u *)"priority");
	    id = (int)dict_get_number(d, (char_u *)"id");
	    conceal = dict_find(d, (char_u *)"conceal", -1) != NULL
			      ? dict_get_string(d, (char_u *)"conceal", TRUE)
			      : NULL;
	    if (i == 0)
	    {
		match_add(curwin, group,
		    dict_get_string(d, (char_u *)"pattern", FALSE),
		    priority, id, NULL, conceal);
	    }
	    else
	    {
		match_add(curwin, group, NULL, priority, id, s, conceal);
		list_unref(s);
		s = NULL;
	    }
	    vim_free(group);
	    vim_free(conceal);

	    li = li->li_next;
	}
	rettv->vval.v_number = 0;
    }
#endif
}

/*
 * "setpos()" function
 */
    static void
f_setpos(typval_T *argvars, typval_T *rettv)
{
    pos_T	pos;
    int		fnum;
    char_u	*name;
    colnr_T	curswant = -1;

    rettv->vval.v_number = -1;
    name = tv_get_string_chk(argvars);
    if (name != NULL)
    {
	if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK)
	{
	    if (--pos.col < 0)
		pos.col = 0;
	    if (name[0] == '.' && name[1] == NUL)
	    {
		/* set cursor; "fnum" is ignored */
		curwin->w_cursor = pos;
		if (curswant >= 0)
		{
		    curwin->w_curswant = curswant - 1;
		    curwin->w_set_curswant = FALSE;
		}
		check_cursor();
		rettv->vval.v_number = 0;
	    }
	    else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL)
	    {
		/* set mark */
		if (setmark_pos(name[1], &pos, fnum) == OK)
		    rettv->vval.v_number = 0;
	    }
	    else
		EMSG(_(e_invarg));
	}
    }
}

/*
 * "setqflist()" function
 */
    static void
f_setqflist(typval_T *argvars, typval_T *rettv)
{
    set_qf_ll_list(NULL, &argvars[0], &argvars[1], &argvars[2], rettv);
}

/*
 * "setreg()" function
 */
    static void
f_setreg(typval_T *argvars, typval_T *rettv)
{
    int		regname;
    char_u	*strregname;
    char_u	*stropt;
    char_u	*strval;
    int		append;
    char_u	yank_type;
    long	block_len;

    block_len = -1;
    yank_type = MAUTO;
    append = FALSE;

    strregname = tv_get_string_chk(argvars);
    rettv->vval.v_number = 1;		/* FAIL is default */

    if (strregname == NULL)
	return;		/* type error; errmsg already given */
    regname = *strregname;
    if (regname == 0 || regname == '@')
	regname = '"';

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	stropt = tv_get_string_chk(&argvars[2]);
	if (stropt == NULL)
	    return;		/* type error */
	for (; *stropt != NUL; ++stropt)
	    switch (*stropt)
	    {
		case 'a': case 'A':	/* append */
		    append = TRUE;
		    break;
		case 'v': case 'c':	/* character-wise selection */
		    yank_type = MCHAR;
		    break;
		case 'V': case 'l':	/* line-wise selection */
		    yank_type = MLINE;
		    break;
		case 'b': case Ctrl_V:	/* block-wise selection */
		    yank_type = MBLOCK;
		    if (VIM_ISDIGIT(stropt[1]))
		    {
			++stropt;
			block_len = getdigits(&stropt) - 1;
			--stropt;
		    }
		    break;
	    }
    }

    if (argvars[1].v_type == VAR_LIST)
    {
	char_u		**lstval;
	char_u		**allocval;
	char_u		buf[NUMBUFLEN];
	char_u		**curval;
	char_u		**curallocval;
	list_T		*ll = argvars[1].vval.v_list;
	listitem_T	*li;
	int		len;

	/* If the list is NULL handle like an empty list. */
	len = ll == NULL ? 0 : ll->lv_len;

	/* First half: use for pointers to result lines; second half: use for
	 * pointers to allocated copies. */
	lstval = (char_u **)alloc(sizeof(char_u *) * ((len + 1) * 2));
	if (lstval == NULL)
	    return;
	curval = lstval;
	allocval = lstval + len + 2;
	curallocval = allocval;

	for (li = ll == NULL ? NULL : ll->lv_first; li != NULL;
							     li = li->li_next)
	{
	    strval = tv_get_string_buf_chk(&li->li_tv, buf);
	    if (strval == NULL)
		goto free_lstval;
	    if (strval == buf)
	    {
		/* Need to make a copy, next tv_get_string_buf_chk() will
		 * overwrite the string. */
		strval = vim_strsave(buf);
		if (strval == NULL)
		    goto free_lstval;
		*curallocval++ = strval;
	    }
	    *curval++ = strval;
	}
	*curval++ = NULL;

	write_reg_contents_lst(regname, lstval, -1,
						append, yank_type, block_len);
free_lstval:
	while (curallocval > allocval)
	    vim_free(*--curallocval);
	vim_free(lstval);
    }
    else
    {
	strval = tv_get_string_chk(&argvars[1]);
	if (strval == NULL)
	    return;
	write_reg_contents_ex(regname, strval, -1,
						append, yank_type, block_len);
    }
    rettv->vval.v_number = 0;
}

/*
 * "settabvar()" function
 */
    static void
f_settabvar(typval_T *argvars, typval_T *rettv)
{
    tabpage_T	*save_curtab;
    tabpage_T	*tp;
    char_u	*varname, *tabvarname;
    typval_T	*varp;

    rettv->vval.v_number = 0;

    if (check_restricted() || check_secure())
	return;

    tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
    varname = tv_get_string_chk(&argvars[1]);
    varp = &argvars[2];

    if (varname != NULL && varp != NULL && tp != NULL)
    {
	save_curtab = curtab;
	goto_tabpage_tp(tp, FALSE, FALSE);

	tabvarname = alloc((unsigned)STRLEN(varname) + 3);
	if (tabvarname != NULL)
	{
	    STRCPY(tabvarname, "t:");
	    STRCPY(tabvarname + 2, varname);
	    set_var(tabvarname, varp, TRUE);
	    vim_free(tabvarname);
	}

	/* Restore current tabpage */
	if (valid_tabpage(save_curtab))
	    goto_tabpage_tp(save_curtab, FALSE, FALSE);
    }
}

/*
 * "settabwinvar()" function
 */
    static void
f_settabwinvar(typval_T *argvars, typval_T *rettv)
{
    setwinvar(argvars, rettv, 1);
}

/*
 * "settagstack()" function
 */
    static void
f_settagstack(typval_T *argvars, typval_T *rettv)
{
    static char *e_invact2 = N_("E962: Invalid action: '%s'");
    win_T	*wp;
    dict_T	*d;
    int		action = 'r';

    rettv->vval.v_number = -1;

    // first argument: window number or id
    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL)
	return;

    // second argument: dict with items to set in the tag stack
    if (argvars[1].v_type != VAR_DICT)
    {
	EMSG(_(e_dictreq));
	return;
    }
    d = argvars[1].vval.v_dict;
    if (d == NULL)
	return;

    // third argument: action - 'a' for append and 'r' for replace.
    // default is to replace the stack.
    if (argvars[2].v_type == VAR_UNKNOWN)
	action = 'r';
    else if (argvars[2].v_type == VAR_STRING)
    {
	char_u	*actstr;
	actstr = tv_get_string_chk(&argvars[2]);
	if (actstr == NULL)
	    return;
	if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL)
	    action = *actstr;
	else
	{
	    EMSG2(_(e_invact2), actstr);
	    return;
	}
    }
    else
    {
	EMSG(_(e_stringreq));
	return;
    }

    if (set_tagstack(wp, d, action) == OK)
	rettv->vval.v_number = 0;
}

/*
 * "setwinvar()" function
 */
    static void
f_setwinvar(typval_T *argvars, typval_T *rettv)
{
    setwinvar(argvars, rettv, 0);
}

#ifdef FEAT_CRYPT
/*
 * "sha256({string})" function
 */
    static void
f_sha256(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;

    p = tv_get_string(&argvars[0]);
    rettv->vval.v_string = vim_strsave(
				    sha256_bytes(p, (int)STRLEN(p), NULL, 0));
    rettv->v_type = VAR_STRING;
}
#endif /* FEAT_CRYPT */

/*
 * "shellescape({string})" function
 */
    static void
f_shellescape(typval_T *argvars, typval_T *rettv)
{
    int do_special = non_zero_arg(&argvars[1]);

    rettv->vval.v_string = vim_strsave_shellescape(
			   tv_get_string(&argvars[0]), do_special, do_special);
    rettv->v_type = VAR_STRING;
}

/*
 * shiftwidth() function
 */
    static void
f_shiftwidth(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = 0;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	long	col;

	col = (long)tv_get_number_chk(argvars, NULL);
	if (col < 0)
	    return;	// type error; errmsg already given
#ifdef FEAT_VARTABS
	rettv->vval.v_number = get_sw_value_col(curbuf, col);
	return;
#endif
    }

    rettv->vval.v_number = get_sw_value(curbuf);
}

#ifdef FEAT_SIGNS
/*
 * "sign_define()" function
 */
    static void
f_sign_define(typval_T *argvars, typval_T *rettv)
{
    char_u	*name;
    dict_T	*dict;
    char_u	*icon = NULL;
    char_u	*linehl = NULL;
    char_u	*text = NULL;
    char_u	*texthl = NULL;

    rettv->vval.v_number = -1;

    name = tv_get_string_chk(&argvars[0]);
    if (name == NULL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	if (argvars[1].v_type != VAR_DICT)
	{
	    EMSG(_(e_dictreq));
	    return;
	}

	// sign attributes
	dict = argvars[1].vval.v_dict;
	if (dict_find(dict, (char_u *)"icon", -1) != NULL)
	    icon = dict_get_string(dict, (char_u *)"icon", TRUE);
	if (dict_find(dict, (char_u *)"linehl", -1) != NULL)
	    linehl = dict_get_string(dict, (char_u *)"linehl", TRUE);
	if (dict_find(dict, (char_u *)"text", -1) != NULL)
	    text = dict_get_string(dict, (char_u *)"text", TRUE);
	if (dict_find(dict, (char_u *)"texthl", -1) != NULL)
	    texthl = dict_get_string(dict, (char_u *)"texthl", TRUE);
    }

    if (sign_define_by_name(name, icon, linehl, text, texthl) == OK)
	rettv->vval.v_number = 0;

    vim_free(icon);
    vim_free(linehl);
    vim_free(text);
    vim_free(texthl);
}

/*
 * "sign_getdefined()" function
 */
    static void
f_sign_getdefined(typval_T *argvars, typval_T *rettv)
{
    char_u	*name = NULL;

    if (rettv_list_alloc_id(rettv, aid_sign_getdefined) != OK)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
	name = tv_get_string(&argvars[0]);

    sign_getlist(name, rettv->vval.v_list);
}

/*
 * "sign_getplaced()" function
 */
    static void
f_sign_getplaced(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf = NULL;
    dict_T	*dict;
    dictitem_T	*di;
    linenr_T	lnum = 0;
    int		sign_id = 0;
    char_u	*group = NULL;
    int		notanum = FALSE;

    if (rettv_list_alloc_id(rettv, aid_sign_getplaced) != OK)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	// get signs placed in this buffer
	buf = find_buffer(&argvars[0]);
	if (buf == NULL)
	{
	    EMSG2(_("E158: Invalid buffer name: %s"),
						tv_get_string(&argvars[0]));
	    return;
	}

	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    if (argvars[1].v_type != VAR_DICT ||
				((dict = argvars[1].vval.v_dict) == NULL))
	    {
		EMSG(_(e_dictreq));
		return;
	    }
	    if ((di = dict_find(dict, (char_u *)"lnum", -1)) != NULL)
	    {
		// get signs placed at this line
		(void)tv_get_number_chk(&di->di_tv, &notanum);
		if (notanum)
		    return;
		lnum = tv_get_lnum(&di->di_tv);
	    }
	    if ((di = dict_find(dict, (char_u *)"id", -1)) != NULL)
	    {
		// get sign placed with this identifier
		sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
		if (notanum)
		    return;
	    }
	    if ((di = dict_find(dict, (char_u *)"group", -1)) != NULL)
	    {
		group = tv_get_string_chk(&di->di_tv);
		if (group == NULL)
		    return;
		if (*group == '\0')	// empty string means global group
		    group = NULL;
	    }
	}
    }

    sign_get_placed(buf, lnum, sign_id, group, rettv->vval.v_list);
}

/*
 * "sign_place()" function
 */
    static void
f_sign_place(typval_T *argvars, typval_T *rettv)
{
    int		sign_id;
    char_u	*group = NULL;
    char_u	*sign_name;
    buf_T	*buf;
    dict_T	*dict;
    dictitem_T	*di;
    linenr_T	lnum = 0;
    int		prio = SIGN_DEF_PRIO;
    int		notanum = FALSE;

    rettv->vval.v_number = -1;

    // Sign identifer
    sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
    if (notanum)
	return;
    if (sign_id < 0)
    {
	EMSG(_(e_invarg));
	return;
    }

    // Sign group
    group = tv_get_string_chk(&argvars[1]);
    if (group == NULL)
	return;
    if (group[0] == '\0')
	group = NULL;			// global sign group
    else
    {
	group = vim_strsave(group);
	if (group == NULL)
	    return;
    }

    // Sign name
    sign_name = tv_get_string_chk(&argvars[2]);
    if (sign_name == NULL)
	goto cleanup;

    // Buffer to place the sign
    buf = find_buffer(&argvars[3]);
    if (buf == NULL)
    {
	EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(&argvars[2]));
	goto cleanup;
    }

    if (argvars[4].v_type != VAR_UNKNOWN)
    {
	if (argvars[4].v_type != VAR_DICT ||
				((dict = argvars[4].vval.v_dict) == NULL))
	{
	    EMSG(_(e_dictreq));
	    goto cleanup;
	}

	// Line number where the sign is to be placed
	if ((di = dict_find(dict, (char_u *)"lnum", -1)) != NULL)
	{
	    (void)tv_get_number_chk(&di->di_tv, &notanum);
	    if (notanum)
		goto cleanup;
	    lnum = tv_get_lnum(&di->di_tv);
	}
	if ((di = dict_find(dict, (char_u *)"priority", -1)) != NULL)
	{
	    // Sign priority
	    prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
	    if (notanum)
		goto cleanup;
	}
    }

    if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK)
	rettv->vval.v_number = sign_id;

cleanup:
    vim_free(group);
}

/*
 * "sign_undefine()" function
 */
    static void
f_sign_undefine(typval_T *argvars, typval_T *rettv)
{
    char_u *name;

    rettv->vval.v_number = -1;

    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	// Free all the signs
	free_signs();
	rettv->vval.v_number = 0;
    }
    else
    {
	// Free only the specified sign
	name = tv_get_string_chk(&argvars[0]);
	if (name == NULL)
	    return;

	if (sign_undefine_by_name(name) == OK)
	    rettv->vval.v_number = 0;
    }
}

/*
 * "sign_unplace()" function
 */
    static void
f_sign_unplace(typval_T *argvars, typval_T *rettv)
{
    dict_T	*dict;
    dictitem_T	*di;
    int		sign_id = 0;
    buf_T	*buf = NULL;
    char_u	*group = NULL;

    rettv->vval.v_number = -1;

    if (argvars[0].v_type != VAR_STRING)
    {
	EMSG(_(e_invarg));
	return;
    }

    group = tv_get_string(&argvars[0]);
    if (group[0] == '\0')
	group = NULL;			// global sign group
    else
    {
	group = vim_strsave(group);
	if (group == NULL)
	    return;
    }

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	if (argvars[1].v_type != VAR_DICT)
	{
	    EMSG(_(e_dictreq));
	    return;
	}
	dict = argvars[1].vval.v_dict;

	if ((di = dict_find(dict, (char_u *)"buffer", -1)) != NULL)
	{
	    buf = find_buffer(&di->di_tv);
	    if (buf == NULL)
	    {
		EMSG2(_("E158: Invalid buffer name: %s"),
						tv_get_string(&di->di_tv));
		return;
	    }
	}
	if (dict_find(dict, (char_u *)"id", -1) != NULL)
	    sign_id = dict_get_number(dict, (char_u *)"id");
    }

    if (buf == NULL)
    {
	// Delete the sign in all the buffers
	FOR_ALL_BUFFERS(buf)
	    if (sign_unplace(sign_id, group, buf, 0) == OK)
		rettv->vval.v_number = 0;
    }
    else
    {
	if (sign_unplace(sign_id, group, buf, 0) == OK)
	    rettv->vval.v_number = 0;
    }
    vim_free(group);
}
#endif

/*
 * "simplify()" function
 */
    static void
f_simplify(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;

    p = tv_get_string(&argvars[0]);
    rettv->vval.v_string = vim_strsave(p);
    simplify_filename(rettv->vval.v_string);	/* simplify in place */
    rettv->v_type = VAR_STRING;
}

#ifdef FEAT_FLOAT
/*
 * "sin()" function
 */
    static void
f_sin(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = sin(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "sinh()" function
 */
    static void
f_sinh(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = sinh(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

static int
#ifdef __BORLANDC__
    _RTLENTRYF
#endif
	item_compare(const void *s1, const void *s2);
static int
#ifdef __BORLANDC__
    _RTLENTRYF
#endif
	item_compare2(const void *s1, const void *s2);

/* struct used in the array that's given to qsort() */
typedef struct
{
    listitem_T	*item;
    int		idx;
} sortItem_T;

/* struct storing information about current sort */
typedef struct
{
    int		item_compare_ic;
    int		item_compare_numeric;
    int		item_compare_numbers;
#ifdef FEAT_FLOAT
    int		item_compare_float;
#endif
    char_u	*item_compare_func;
    partial_T	*item_compare_partial;
    dict_T	*item_compare_selfdict;
    int		item_compare_func_err;
    int		item_compare_keep_zero;
} sortinfo_T;
static sortinfo_T	*sortinfo = NULL;
#define ITEM_COMPARE_FAIL 999

/*
 * Compare functions for f_sort() and f_uniq() below.
 */
    static int
#ifdef __BORLANDC__
_RTLENTRYF
#endif
item_compare(const void *s1, const void *s2)
{
    sortItem_T  *si1, *si2;
    typval_T	*tv1, *tv2;
    char_u	*p1, *p2;
    char_u	*tofree1 = NULL, *tofree2 = NULL;
    int		res;
    char_u	numbuf1[NUMBUFLEN];
    char_u	numbuf2[NUMBUFLEN];

    si1 = (sortItem_T *)s1;
    si2 = (sortItem_T *)s2;
    tv1 = &si1->item->li_tv;
    tv2 = &si2->item->li_tv;

    if (sortinfo->item_compare_numbers)
    {
	varnumber_T	v1 = tv_get_number(tv1);
	varnumber_T	v2 = tv_get_number(tv2);

	return v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
    }

#ifdef FEAT_FLOAT
    if (sortinfo->item_compare_float)
    {
	float_T	v1 = tv_get_float(tv1);
	float_T	v2 = tv_get_float(tv2);

	return v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
    }
#endif

    /* tv2string() puts quotes around a string and allocates memory.  Don't do
     * that for string variables. Use a single quote when comparing with a
     * non-string to do what the docs promise. */
    if (tv1->v_type == VAR_STRING)
    {
	if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric)
	    p1 = (char_u *)"'";
	else
	    p1 = tv1->vval.v_string;
    }
    else
	p1 = tv2string(tv1, &tofree1, numbuf1, 0);
    if (tv2->v_type == VAR_STRING)
    {
	if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric)
	    p2 = (char_u *)"'";
	else
	    p2 = tv2->vval.v_string;
    }
    else
	p2 = tv2string(tv2, &tofree2, numbuf2, 0);
    if (p1 == NULL)
	p1 = (char_u *)"";
    if (p2 == NULL)
	p2 = (char_u *)"";
    if (!sortinfo->item_compare_numeric)
    {
	if (sortinfo->item_compare_ic)
	    res = STRICMP(p1, p2);
	else
	    res = STRCMP(p1, p2);
    }
    else
    {
	double n1, n2;
	n1 = strtod((char *)p1, (char **)&p1);
	n2 = strtod((char *)p2, (char **)&p2);
	res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
    }

    /* When the result would be zero, compare the item indexes.  Makes the
     * sort stable. */
    if (res == 0 && !sortinfo->item_compare_keep_zero)
	res = si1->idx > si2->idx ? 1 : -1;

    vim_free(tofree1);
    vim_free(tofree2);
    return res;
}

    static int
#ifdef __BORLANDC__
_RTLENTRYF
#endif
item_compare2(const void *s1, const void *s2)
{
    sortItem_T  *si1, *si2;
    int		res;
    typval_T	rettv;
    typval_T	argv[3];
    int		dummy;
    char_u	*func_name;
    partial_T	*partial = sortinfo->item_compare_partial;

    /* shortcut after failure in previous call; compare all items equal */
    if (sortinfo->item_compare_func_err)
	return 0;

    si1 = (sortItem_T *)s1;
    si2 = (sortItem_T *)s2;

    if (partial == NULL)
	func_name = sortinfo->item_compare_func;
    else
	func_name = partial_name(partial);

    /* Copy the values.  This is needed to be able to set v_lock to VAR_FIXED
     * in the copy without changing the original list items. */
    copy_tv(&si1->item->li_tv, &argv[0]);
    copy_tv(&si2->item->li_tv, &argv[1]);

    rettv.v_type = VAR_UNKNOWN;		/* clear_tv() uses this */
    res = call_func(func_name, (int)STRLEN(func_name),
				 &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE,
				 partial, sortinfo->item_compare_selfdict);
    clear_tv(&argv[0]);
    clear_tv(&argv[1]);

    if (res == FAIL)
	res = ITEM_COMPARE_FAIL;
    else
	res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err);
    if (sortinfo->item_compare_func_err)
	res = ITEM_COMPARE_FAIL;  /* return value has wrong type */
    clear_tv(&rettv);

    /* When the result would be zero, compare the pointers themselves.  Makes
     * the sort stable. */
    if (res == 0 && !sortinfo->item_compare_keep_zero)
	res = si1->idx > si2->idx ? 1 : -1;

    return res;
}

/*
 * "sort({list})" function
 */
    static void
do_sort_uniq(typval_T *argvars, typval_T *rettv, int sort)
{
    list_T	*l;
    listitem_T	*li;
    sortItem_T	*ptrs;
    sortinfo_T	*old_sortinfo;
    sortinfo_T	info;
    long	len;
    long	i;

    /* Pointer to current info struct used in compare function. Save and
     * restore the current one for nested calls. */
    old_sortinfo = sortinfo;
    sortinfo = &info;

    if (argvars[0].v_type != VAR_LIST)
	EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
    else
    {
	l = argvars[0].vval.v_list;
	if (l == NULL || tv_check_lock(l->lv_lock,
	     (char_u *)(sort ? N_("sort() argument") : N_("uniq() argument")),
									TRUE))
	    goto theend;
	rettv_list_set(rettv, l);

	len = list_len(l);
	if (len <= 1)
	    goto theend;	/* short list sorts pretty quickly */

	info.item_compare_ic = FALSE;
	info.item_compare_numeric = FALSE;
	info.item_compare_numbers = FALSE;
#ifdef FEAT_FLOAT
	info.item_compare_float = FALSE;
#endif
	info.item_compare_func = NULL;
	info.item_compare_partial = NULL;
	info.item_compare_selfdict = NULL;
	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    /* optional second argument: {func} */
	    if (argvars[1].v_type == VAR_FUNC)
		info.item_compare_func = argvars[1].vval.v_string;
	    else if (argvars[1].v_type == VAR_PARTIAL)
		info.item_compare_partial = argvars[1].vval.v_partial;
	    else
	    {
		int	    error = FALSE;

		i = (long)tv_get_number_chk(&argvars[1], &error);
		if (error)
		    goto theend;	/* type error; errmsg already given */
		if (i == 1)
		    info.item_compare_ic = TRUE;
		else if (argvars[1].v_type != VAR_NUMBER)
		    info.item_compare_func = tv_get_string(&argvars[1]);
		else if (i != 0)
		{
		    EMSG(_(e_invarg));
		    goto theend;
		}
		if (info.item_compare_func != NULL)
		{
		    if (*info.item_compare_func == NUL)
		    {
			/* empty string means default sort */
			info.item_compare_func = NULL;
		    }
		    else if (STRCMP(info.item_compare_func, "n") == 0)
		    {
			info.item_compare_func = NULL;
			info.item_compare_numeric = TRUE;
		    }
		    else if (STRCMP(info.item_compare_func, "N") == 0)
		    {
			info.item_compare_func = NULL;
			info.item_compare_numbers = TRUE;
		    }
#ifdef FEAT_FLOAT
		    else if (STRCMP(info.item_compare_func, "f") == 0)
		    {
			info.item_compare_func = NULL;
			info.item_compare_float = TRUE;
		    }
#endif
		    else if (STRCMP(info.item_compare_func, "i") == 0)
		    {
			info.item_compare_func = NULL;
			info.item_compare_ic = TRUE;
		    }
		}
	    }

	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		/* optional third argument: {dict} */
		if (argvars[2].v_type != VAR_DICT)
		{
		    EMSG(_(e_dictreq));
		    goto theend;
		}
		info.item_compare_selfdict = argvars[2].vval.v_dict;
	    }
	}

	/* Make an array with each entry pointing to an item in the List. */
	ptrs = (sortItem_T *)alloc((int)(len * sizeof(sortItem_T)));
	if (ptrs == NULL)
	    goto theend;

	i = 0;
	if (sort)
	{
	    /* sort(): ptrs will be the list to sort */
	    for (li = l->lv_first; li != NULL; li = li->li_next)
	    {
		ptrs[i].item = li;
		ptrs[i].idx = i;
		++i;
	    }

	    info.item_compare_func_err = FALSE;
	    info.item_compare_keep_zero = FALSE;
	    /* test the compare function */
	    if ((info.item_compare_func != NULL
					 || info.item_compare_partial != NULL)
		    && item_compare2((void *)&ptrs[0], (void *)&ptrs[1])
							 == ITEM_COMPARE_FAIL)
		EMSG(_("E702: Sort compare function failed"));
	    else
	    {
		/* Sort the array with item pointers. */
		qsort((void *)ptrs, (size_t)len, sizeof(sortItem_T),
		    info.item_compare_func == NULL
					  && info.item_compare_partial == NULL
					       ? item_compare : item_compare2);

		if (!info.item_compare_func_err)
		{
		    /* Clear the List and append the items in sorted order. */
		    l->lv_first = l->lv_last = l->lv_idx_item = NULL;
		    l->lv_len = 0;
		    for (i = 0; i < len; ++i)
			list_append(l, ptrs[i].item);
		}
	    }
	}
	else
	{
	    int	(*item_compare_func_ptr)(const void *, const void *);

	    /* f_uniq(): ptrs will be a stack of items to remove */
	    info.item_compare_func_err = FALSE;
	    info.item_compare_keep_zero = TRUE;
	    item_compare_func_ptr = info.item_compare_func != NULL
					  || info.item_compare_partial != NULL
					       ? item_compare2 : item_compare;

	    for (li = l->lv_first; li != NULL && li->li_next != NULL;
							     li = li->li_next)
	    {
		if (item_compare_func_ptr((void *)&li, (void *)&li->li_next)
									 == 0)
		    ptrs[i++].item = li;
		if (info.item_compare_func_err)
		{
		    EMSG(_("E882: Uniq compare function failed"));
		    break;
		}
	    }

	    if (!info.item_compare_func_err)
	    {
		while (--i >= 0)
		{
		    li = ptrs[i].item->li_next;
		    ptrs[i].item->li_next = li->li_next;
		    if (li->li_next != NULL)
			li->li_next->li_prev = ptrs[i].item;
		    else
			l->lv_last = ptrs[i].item;
		    list_fix_watch(l, li);
		    listitem_free(li);
		    l->lv_len--;
		}
	    }
	}

	vim_free(ptrs);
    }
theend:
    sortinfo = old_sortinfo;
}

/*
 * "sort({list})" function
 */
    static void
f_sort(typval_T *argvars, typval_T *rettv)
{
    do_sort_uniq(argvars, rettv, TRUE);
}

/*
 * "uniq({list})" function
 */
    static void
f_uniq(typval_T *argvars, typval_T *rettv)
{
    do_sort_uniq(argvars, rettv, FALSE);
}

/*
 * "soundfold({word})" function
 */
    static void
f_soundfold(typval_T *argvars, typval_T *rettv)
{
    char_u	*s;

    rettv->v_type = VAR_STRING;
    s = tv_get_string(&argvars[0]);
#ifdef FEAT_SPELL
    rettv->vval.v_string = eval_soundfold(s);
#else
    rettv->vval.v_string = vim_strsave(s);
#endif
}

/*
 * "spellbadword()" function
 */
    static void
f_spellbadword(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*word = (char_u *)"";
    hlf_T	attr = HLF_COUNT;
    int		len = 0;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

#ifdef FEAT_SPELL
    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	/* Find the start and length of the badly spelled word. */
	len = spell_move_to(curwin, FORWARD, TRUE, TRUE, &attr);
	if (len != 0)
	{
	    word = ml_get_cursor();
	    curwin->w_set_curswant = TRUE;
	}
    }
    else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL)
    {
	char_u	*str = tv_get_string_chk(&argvars[0]);
	int	capcol = -1;

	if (str != NULL)
	{
	    /* Check the argument for spelling. */
	    while (*str != NUL)
	    {
		len = spell_check(curwin, str, &attr, &capcol, FALSE);
		if (attr != HLF_COUNT)
		{
		    word = str;
		    break;
		}
		str += len;
		capcol -= len;
	    }
	}
    }
#endif

    list_append_string(rettv->vval.v_list, word, len);
    list_append_string(rettv->vval.v_list, (char_u *)(
			attr == HLF_SPB ? "bad" :
			attr == HLF_SPR ? "rare" :
			attr == HLF_SPL ? "local" :
			attr == HLF_SPC ? "caps" :
			""), -1);
}

/*
 * "spellsuggest()" function
 */
    static void
f_spellsuggest(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_SPELL
    char_u	*str;
    int		typeerr = FALSE;
    int		maxcount;
    garray_T	ga;
    int		i;
    listitem_T	*li;
    int		need_capital = FALSE;
#endif

    if (rettv_list_alloc(rettv) == FAIL)
	return;

#ifdef FEAT_SPELL
    if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL)
    {
	str = tv_get_string(&argvars[0]);
	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr);
	    if (maxcount <= 0)
		return;
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		need_capital = (int)tv_get_number_chk(&argvars[2], &typeerr);
		if (typeerr)
		    return;
	    }
	}
	else
	    maxcount = 25;

	spell_suggest_list(&ga, str, maxcount, need_capital, FALSE);

	for (i = 0; i < ga.ga_len; ++i)
	{
	    str = ((char_u **)ga.ga_data)[i];

	    li = listitem_alloc();
	    if (li == NULL)
		vim_free(str);
	    else
	    {
		li->li_tv.v_type = VAR_STRING;
		li->li_tv.v_lock = 0;
		li->li_tv.vval.v_string = str;
		list_append(rettv->vval.v_list, li);
	    }
	}
	ga_clear(&ga);
    }
#endif
}

    static void
f_split(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	*end;
    char_u	*pat = NULL;
    regmatch_T	regmatch;
    char_u	patbuf[NUMBUFLEN];
    char_u	*save_cpo;
    int		match;
    colnr_T	col = 0;
    int		keepempty = FALSE;
    int		typeerr = FALSE;

    /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
    save_cpo = p_cpo;
    p_cpo = (char_u *)"";

    str = tv_get_string(&argvars[0]);
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	pat = tv_get_string_buf_chk(&argvars[1], patbuf);
	if (pat == NULL)
	    typeerr = TRUE;
	if (argvars[2].v_type != VAR_UNKNOWN)
	    keepempty = (int)tv_get_number_chk(&argvars[2], &typeerr);
    }
    if (pat == NULL || *pat == NUL)
	pat = (char_u *)"[\\x01- ]\\+";

    if (rettv_list_alloc(rettv) == FAIL)
	return;
    if (typeerr)
	return;

    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
    if (regmatch.regprog != NULL)
    {
	regmatch.rm_ic = FALSE;
	while (*str != NUL || keepempty)
	{
	    if (*str == NUL)
		match = FALSE;	/* empty item at the end */
	    else
		match = vim_regexec_nl(&regmatch, str, col);
	    if (match)
		end = regmatch.startp[0];
	    else
		end = str + STRLEN(str);
	    if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0
			   && *str != NUL && match && end < regmatch.endp[0]))
	    {
		if (list_append_string(rettv->vval.v_list, str,
						    (int)(end - str)) == FAIL)
		    break;
	    }
	    if (!match)
		break;
	    /* Advance to just after the match. */
	    if (regmatch.endp[0] > str)
		col = 0;
	    else
	    {
		/* Don't get stuck at the same match. */
#ifdef FEAT_MBYTE
		col = (*mb_ptr2len)(regmatch.endp[0]);
#else
		col = 1;
#endif
	    }
	    str = regmatch.endp[0];
	}

	vim_regfree(regmatch.regprog);
    }

    p_cpo = save_cpo;
}

#ifdef FEAT_FLOAT
/*
 * "sqrt()" function
 */
    static void
f_sqrt(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = sqrt(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "str2float()" function
 */
    static void
f_str2float(typval_T *argvars, typval_T *rettv)
{
    char_u *p = skipwhite(tv_get_string(&argvars[0]));
    int     isneg = (*p == '-');

    if (*p == '+' || *p == '-')
	p = skipwhite(p + 1);
    (void)string2float(p, &rettv->vval.v_float);
    if (isneg)
	rettv->vval.v_float *= -1;
    rettv->v_type = VAR_FLOAT;
}
#endif

/*
 * "str2nr()" function
 */
    static void
f_str2nr(typval_T *argvars, typval_T *rettv)
{
    int		base = 10;
    char_u	*p;
    varnumber_T	n;
    int		what;
    int		isneg;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	base = (int)tv_get_number(&argvars[1]);
	if (base != 2 && base != 8 && base != 10 && base != 16)
	{
	    EMSG(_(e_invarg));
	    return;
	}
    }

    p = skipwhite(tv_get_string(&argvars[0]));
    isneg = (*p == '-');
    if (*p == '+' || *p == '-')
	p = skipwhite(p + 1);
    switch (base)
    {
	case 2: what = STR2NR_BIN + STR2NR_FORCE; break;
	case 8: what = STR2NR_OCT + STR2NR_FORCE; break;
	case 16: what = STR2NR_HEX + STR2NR_FORCE; break;
	default: what = 0;
    }
    vim_str2nr(p, NULL, NULL, what, &n, NULL, 0);
    if (isneg)
	rettv->vval.v_number = -n;
    else
	rettv->vval.v_number = n;

}

#ifdef HAVE_STRFTIME
/*
 * "strftime({format}[, {time}])" function
 */
    static void
f_strftime(typval_T *argvars, typval_T *rettv)
{
    char_u	result_buf[256];
    struct tm	*curtime;
    time_t	seconds;
    char_u	*p;

    rettv->v_type = VAR_STRING;

    p = tv_get_string(&argvars[0]);
    if (argvars[1].v_type == VAR_UNKNOWN)
	seconds = time(NULL);
    else
	seconds = (time_t)tv_get_number(&argvars[1]);
    curtime = localtime(&seconds);
    /* MSVC returns NULL for an invalid value of seconds. */
    if (curtime == NULL)
	rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)"));
    else
    {
# ifdef FEAT_MBYTE
	vimconv_T   conv;
	char_u	    *enc;

	conv.vc_type = CONV_NONE;
	enc = enc_locale();
	convert_setup(&conv, p_enc, enc);
	if (conv.vc_type != CONV_NONE)
	    p = string_convert(&conv, p, NULL);
# endif
	if (p != NULL)
	    (void)strftime((char *)result_buf, sizeof(result_buf),
							  (char *)p, curtime);
	else
	    result_buf[0] = NUL;

# ifdef FEAT_MBYTE
	if (conv.vc_type != CONV_NONE)
	    vim_free(p);
	convert_setup(&conv, enc, p_enc);
	if (conv.vc_type != CONV_NONE)
	    rettv->vval.v_string = string_convert(&conv, result_buf, NULL);
	else
# endif
	    rettv->vval.v_string = vim_strsave(result_buf);

# ifdef FEAT_MBYTE
	/* Release conversion descriptors */
	convert_setup(&conv, NULL, NULL);
	vim_free(enc);
# endif
    }
}
#endif

/*
 * "strgetchar()" function
 */
    static void
f_strgetchar(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    int		len;
    int		error = FALSE;
    int		charidx;

    rettv->vval.v_number = -1;
    str = tv_get_string_chk(&argvars[0]);
    if (str == NULL)
	return;
    len = (int)STRLEN(str);
    charidx = (int)tv_get_number_chk(&argvars[1], &error);
    if (error)
	return;
#ifdef FEAT_MBYTE
    {
	int	byteidx = 0;

	while (charidx >= 0 && byteidx < len)
	{
	    if (charidx == 0)
	    {
		rettv->vval.v_number = mb_ptr2char(str + byteidx);
		break;
	    }
	    --charidx;
	    byteidx += MB_CPTR2LEN(str + byteidx);
	}
    }
#else
    if (charidx < len)
	rettv->vval.v_number = str[charidx];
#endif
}

/*
 * "stridx()" function
 */
    static void
f_stridx(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];
    char_u	*needle;
    char_u	*haystack;
    char_u	*save_haystack;
    char_u	*pos;
    int		start_idx;

    needle = tv_get_string_chk(&argvars[1]);
    save_haystack = haystack = tv_get_string_buf_chk(&argvars[0], buf);
    rettv->vval.v_number = -1;
    if (needle == NULL || haystack == NULL)
	return;		/* type error; errmsg already given */

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	int	    error = FALSE;

	start_idx = (int)tv_get_number_chk(&argvars[2], &error);
	if (error || start_idx >= (int)STRLEN(haystack))
	    return;
	if (start_idx >= 0)
	    haystack += start_idx;
    }

    pos	= (char_u *)strstr((char *)haystack, (char *)needle);
    if (pos != NULL)
	rettv->vval.v_number = (varnumber_T)(pos - save_haystack);
}

/*
 * "string()" function
 */
    void
f_string(typval_T *argvars, typval_T *rettv)
{
    char_u	*tofree;
    char_u	numbuf[NUMBUFLEN];

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = tv2string(&argvars[0], &tofree, numbuf,
								get_copyID());
    /* Make a copy if we have a value but it's not in allocated memory. */
    if (rettv->vval.v_string != NULL && tofree == NULL)
	rettv->vval.v_string = vim_strsave(rettv->vval.v_string);
}

/*
 * "strlen()" function
 */
    static void
f_strlen(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = (varnumber_T)(STRLEN(
					      tv_get_string(&argvars[0])));
}

/*
 * "strchars()" function
 */
    static void
f_strchars(typval_T *argvars, typval_T *rettv)
{
    char_u		*s = tv_get_string(&argvars[0]);
    int			skipcc = 0;
#ifdef FEAT_MBYTE
    varnumber_T		len = 0;
    int			(*func_mb_ptr2char_adv)(char_u **pp);
#endif

    if (argvars[1].v_type != VAR_UNKNOWN)
	skipcc = (int)tv_get_number_chk(&argvars[1], NULL);
    if (skipcc < 0 || skipcc > 1)
	EMSG(_(e_invarg));
    else
    {
#ifdef FEAT_MBYTE
	func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
	while (*s != NUL)
	{
	    func_mb_ptr2char_adv(&s);
	    ++len;
	}
	rettv->vval.v_number = len;
#else
	rettv->vval.v_number = (varnumber_T)(STRLEN(s));
#endif
    }
}

/*
 * "strdisplaywidth()" function
 */
    static void
f_strdisplaywidth(typval_T *argvars, typval_T *rettv)
{
    char_u	*s = tv_get_string(&argvars[0]);
    int		col = 0;

    if (argvars[1].v_type != VAR_UNKNOWN)
	col = (int)tv_get_number(&argvars[1]);

    rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, s) - col);
}

/*
 * "strwidth()" function
 */
    static void
f_strwidth(typval_T *argvars, typval_T *rettv)
{
    char_u	*s = tv_get_string(&argvars[0]);

    rettv->vval.v_number = (varnumber_T)(
#ifdef FEAT_MBYTE
	    mb_string2cells(s, -1)
#else
	    STRLEN(s)
#endif
	    );
}

/*
 * "strcharpart()" function
 */
    static void
f_strcharpart(typval_T *argvars, typval_T *rettv)
{
#ifdef FEAT_MBYTE
    char_u	*p;
    int		nchar;
    int		nbyte = 0;
    int		charlen;
    int		len = 0;
    int		slen;
    int		error = FALSE;

    p = tv_get_string(&argvars[0]);
    slen = (int)STRLEN(p);

    nchar = (int)tv_get_number_chk(&argvars[1], &error);
    if (!error)
    {
	if (nchar > 0)
	    while (nchar > 0 && nbyte < slen)
	    {
		nbyte += MB_CPTR2LEN(p + nbyte);
		--nchar;
	    }
	else
	    nbyte = nchar;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    charlen = (int)tv_get_number(&argvars[2]);
	    while (charlen > 0 && nbyte + len < slen)
	    {
		int off = nbyte + len;

		if (off < 0)
		    len += 1;
		else
		    len += MB_CPTR2LEN(p + off);
		--charlen;
	    }
	}
	else
	    len = slen - nbyte;    /* default: all bytes that are available. */
    }

    /*
     * Only return the overlap between the specified part and the actual
     * string.
     */
    if (nbyte < 0)
    {
	len += nbyte;
	nbyte = 0;
    }
    else if (nbyte > slen)
	nbyte = slen;
    if (len < 0)
	len = 0;
    else if (nbyte + len > slen)
	len = slen - nbyte;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strnsave(p + nbyte, len);
#else
    f_strpart(argvars, rettv);
#endif
}

/*
 * "strpart()" function
 */
    static void
f_strpart(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;
    int		n;
    int		len;
    int		slen;
    int		error = FALSE;

    p = tv_get_string(&argvars[0]);
    slen = (int)STRLEN(p);

    n = (int)tv_get_number_chk(&argvars[1], &error);
    if (error)
	len = 0;
    else if (argvars[2].v_type != VAR_UNKNOWN)
	len = (int)tv_get_number(&argvars[2]);
    else
	len = slen - n;	    /* default len: all bytes that are available. */

    /*
     * Only return the overlap between the specified part and the actual
     * string.
     */
    if (n < 0)
    {
	len += n;
	n = 0;
    }
    else if (n > slen)
	n = slen;
    if (len < 0)
	len = 0;
    else if (n + len > slen)
	len = slen - n;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strnsave(p + n, len);
}

/*
 * "strridx()" function
 */
    static void
f_strridx(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];
    char_u	*needle;
    char_u	*haystack;
    char_u	*rest;
    char_u	*lastmatch = NULL;
    int		haystack_len, end_idx;

    needle = tv_get_string_chk(&argvars[1]);
    haystack = tv_get_string_buf_chk(&argvars[0], buf);

    rettv->vval.v_number = -1;
    if (needle == NULL || haystack == NULL)
	return;		/* type error; errmsg already given */

    haystack_len = (int)STRLEN(haystack);
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	/* Third argument: upper limit for index */
	end_idx = (int)tv_get_number_chk(&argvars[2], NULL);
	if (end_idx < 0)
	    return;	/* can never find a match */
    }
    else
	end_idx = haystack_len;

    if (*needle == NUL)
    {
	/* Empty string matches past the end. */
	lastmatch = haystack + end_idx;
    }
    else
    {
	for (rest = haystack; *rest != '\0'; ++rest)
	{
	    rest = (char_u *)strstr((char *)rest, (char *)needle);
	    if (rest == NULL || rest > haystack + end_idx)
		break;
	    lastmatch = rest;
	}
    }

    if (lastmatch == NULL)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
}

/*
 * "strtrans()" function
 */
    static void
f_strtrans(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = transstr(tv_get_string(&argvars[0]));
}

/*
 * "submatch()" function
 */
    static void
f_submatch(typval_T *argvars, typval_T *rettv)
{
    int		error = FALSE;
    int		no;
    int		retList = 0;

    no = (int)tv_get_number_chk(&argvars[0], &error);
    if (error)
	return;
    if (no < 0 || no >= NSUBEXP)
    {
	EMSGN(_("E935: invalid submatch number: %d"), no);
	return;
    }
    if (argvars[1].v_type != VAR_UNKNOWN)
	retList = (int)tv_get_number_chk(&argvars[1], &error);
    if (error)
	return;

    if (retList == 0)
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = reg_submatch(no);
    }
    else
    {
	rettv->v_type = VAR_LIST;
	rettv->vval.v_list = reg_submatch_list(no);
    }
}

/*
 * "substitute()" function
 */
    static void
f_substitute(typval_T *argvars, typval_T *rettv)
{
    char_u	patbuf[NUMBUFLEN];
    char_u	subbuf[NUMBUFLEN];
    char_u	flagsbuf[NUMBUFLEN];

    char_u	*str = tv_get_string_chk(&argvars[0]);
    char_u	*pat = tv_get_string_buf_chk(&argvars[1], patbuf);
    char_u	*sub = NULL;
    typval_T	*expr = NULL;
    char_u	*flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);

    if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL)
	expr = &argvars[2];
    else
	sub = tv_get_string_buf_chk(&argvars[2], subbuf);

    rettv->v_type = VAR_STRING;
    if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
								|| flg == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = do_string_sub(str, pat, sub, expr, flg);
}

/*
 * "swapinfo(swap_filename)" function
 */
    static void
f_swapinfo(typval_T *argvars, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) == OK)
	get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
}

/*
 * "swapname(expr)" function
 */
    static void
f_swapname(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;

    rettv->v_type = VAR_STRING;
    buf = get_buf_tv(&argvars[0], FALSE);
    if (buf == NULL || buf->b_ml.ml_mfp == NULL
					|| buf->b_ml.ml_mfp->mf_fname == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
}

/*
 * "synID(lnum, col, trans)" function
 */
    static void
f_synID(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		id = 0;
#ifdef FEAT_SYN_HL
    linenr_T	lnum;
    colnr_T	col;
    int		trans;
    int		transerr = FALSE;

    lnum = tv_get_lnum(argvars);		/* -1 on type error */
    col = (linenr_T)tv_get_number(&argvars[1]) - 1;	/* -1 on type error */
    trans = (int)tv_get_number_chk(&argvars[2], &transerr);

    if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
	    && col >= 0 && col < (long)STRLEN(ml_get(lnum)))
	id = syn_get_id(curwin, lnum, (colnr_T)col, trans, NULL, FALSE);
#endif

    rettv->vval.v_number = id;
}

/*
 * "synIDattr(id, what [, mode])" function
 */
    static void
f_synIDattr(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*p = NULL;
#ifdef FEAT_SYN_HL
    int		id;
    char_u	*what;
    char_u	*mode;
    char_u	modebuf[NUMBUFLEN];
    int		modec;

    id = (int)tv_get_number(&argvars[0]);
    what = tv_get_string(&argvars[1]);
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	mode = tv_get_string_buf(&argvars[2], modebuf);
	modec = TOLOWER_ASC(mode[0]);
	if (modec != 't' && modec != 'c' && modec != 'g')
	    modec = 0;	/* replace invalid with current */
    }
    else
    {
#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
	if (USE_24BIT)
	    modec = 'g';
	else
#endif
	    if (t_colors > 1)
	    modec = 'c';
	else
	    modec = 't';
    }

    switch (TOLOWER_ASC(what[0]))
    {
	case 'b':
		if (TOLOWER_ASC(what[1]) == 'g')	/* bg[#] */
		    p = highlight_color(id, what, modec);
		else					/* bold */
		    p = highlight_has_attr(id, HL_BOLD, modec);
		break;

	case 'f':					/* fg[#] or font */
		p = highlight_color(id, what, modec);
		break;

	case 'i':
		if (TOLOWER_ASC(what[1]) == 'n')	/* inverse */
		    p = highlight_has_attr(id, HL_INVERSE, modec);
		else					/* italic */
		    p = highlight_has_attr(id, HL_ITALIC, modec);
		break;

	case 'n':					/* name */
		p = get_highlight_name_ext(NULL, id - 1, FALSE);
		break;

	case 'r':					/* reverse */
		p = highlight_has_attr(id, HL_INVERSE, modec);
		break;

	case 's':
		if (TOLOWER_ASC(what[1]) == 'p')	/* sp[#] */
		    p = highlight_color(id, what, modec);
							/* strikeout */
		else if (TOLOWER_ASC(what[1]) == 't' &&
			TOLOWER_ASC(what[2]) == 'r')
		    p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
		else					/* standout */
		    p = highlight_has_attr(id, HL_STANDOUT, modec);
		break;

	case 'u':
		if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c')
							/* underline */
		    p = highlight_has_attr(id, HL_UNDERLINE, modec);
		else
							/* undercurl */
		    p = highlight_has_attr(id, HL_UNDERCURL, modec);
		break;
    }

    if (p != NULL)
	p = vim_strsave(p);
#endif
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = p;
}

/*
 * "synIDtrans(id)" function
 */
    static void
f_synIDtrans(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		id;

#ifdef FEAT_SYN_HL
    id = (int)tv_get_number(&argvars[0]);

    if (id > 0)
	id = syn_get_final_id(id);
    else
#endif
	id = 0;

    rettv->vval.v_number = id;
}

/*
 * "synconcealed(lnum, col)" function
 */
    static void
f_synconcealed(typval_T *argvars UNUSED, typval_T *rettv)
{
#if defined(FEAT_SYN_HL) && defined(FEAT_CONCEAL)
    linenr_T	lnum;
    colnr_T	col;
    int		syntax_flags = 0;
    int		cchar;
    int		matchid = 0;
    char_u	str[NUMBUFLEN];
#endif

    rettv_list_set(rettv, NULL);

#if defined(FEAT_SYN_HL) && defined(FEAT_CONCEAL)
    lnum = tv_get_lnum(argvars);		/* -1 on type error */
    col = (colnr_T)tv_get_number(&argvars[1]) - 1;	/* -1 on type error */

    vim_memset(str, NUL, sizeof(str));

    if (rettv_list_alloc(rettv) != FAIL)
    {
	if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
	    && col >= 0 && col <= (long)STRLEN(ml_get(lnum))
	    && curwin->w_p_cole > 0)
	{
	    (void)syn_get_id(curwin, lnum, col, FALSE, NULL, FALSE);
	    syntax_flags = get_syntax_info(&matchid);

	    /* get the conceal character */
	    if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3)
	    {
		cchar = syn_get_sub_char();
		if (cchar == NUL && curwin->w_p_cole == 1)
		    cchar = (lcs_conceal == NUL) ? ' ' : lcs_conceal;
		if (cchar != NUL)
		{
# ifdef FEAT_MBYTE
		    if (has_mbyte)
			(*mb_char2bytes)(cchar, str);
		    else
# endif
			str[0] = cchar;
		}
	    }
	}

	list_append_number(rettv->vval.v_list,
					    (syntax_flags & HL_CONCEAL) != 0);
	/* -1 to auto-determine strlen */
	list_append_string(rettv->vval.v_list, str, -1);
	list_append_number(rettv->vval.v_list, matchid);
    }
#endif
}

/*
 * "synstack(lnum, col)" function
 */
    static void
f_synstack(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_SYN_HL
    linenr_T	lnum;
    colnr_T	col;
    int		i;
    int		id;
#endif

    rettv_list_set(rettv, NULL);

#ifdef FEAT_SYN_HL
    lnum = tv_get_lnum(argvars);		/* -1 on type error */
    col = (colnr_T)tv_get_number(&argvars[1]) - 1;	/* -1 on type error */

    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
	    && col >= 0 && col <= (long)STRLEN(ml_get(lnum))
	    && rettv_list_alloc(rettv) != FAIL)
    {
	(void)syn_get_id(curwin, lnum, (colnr_T)col, FALSE, NULL, TRUE);
	for (i = 0; ; ++i)
	{
	    id = syn_get_stack_item(i);
	    if (id < 0)
		break;
	    if (list_append_number(rettv->vval.v_list, id) == FAIL)
		break;
	}
    }
#endif
}

    static void
get_cmd_output_as_rettv(
    typval_T	*argvars,
    typval_T	*rettv,
    int		retlist)
{
    char_u	*res = NULL;
    char_u	*p;
    char_u	*infile = NULL;
    int		err = FALSE;
    FILE	*fd;
    list_T	*list = NULL;
    int		flags = SHELL_SILENT;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
    if (check_restricted() || check_secure())
	goto errret;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	/*
	 * Write the text to a temp file, to be used for input of the shell
	 * command.
	 */
	if ((infile = vim_tempname('i', TRUE)) == NULL)
	{
	    EMSG(_(e_notmp));
	    goto errret;
	}

	fd = mch_fopen((char *)infile, WRITEBIN);
	if (fd == NULL)
	{
	    EMSG2(_(e_notopen), infile);
	    goto errret;
	}
	if (argvars[1].v_type == VAR_NUMBER)
	{
	    linenr_T	lnum;
	    buf_T	*buf;

	    buf = buflist_findnr(argvars[1].vval.v_number);
	    if (buf == NULL)
	    {
		EMSGN(_(e_nobufnr), argvars[1].vval.v_number);
		fclose(fd);
		goto errret;
	    }

	    for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++)
	    {
		for (p = ml_get_buf(buf, lnum, FALSE); *p != NUL; ++p)
		    if (putc(*p == '\n' ? NUL : *p, fd) == EOF)
		    {
			err = TRUE;
			break;
		    }
		if (putc(NL, fd) == EOF)
		{
		    err = TRUE;
		    break;
		}
	    }
	}
	else if (argvars[1].v_type == VAR_LIST)
	{
	    if (write_list(fd, argvars[1].vval.v_list, TRUE) == FAIL)
		err = TRUE;
	}
	else
	{
	    size_t	len;
	    char_u	buf[NUMBUFLEN];

	    p = tv_get_string_buf_chk(&argvars[1], buf);
	    if (p == NULL)
	    {
		fclose(fd);
		goto errret;		/* type error; errmsg already given */
	    }
	    len = STRLEN(p);
	    if (len > 0 && fwrite(p, len, 1, fd) != 1)
		err = TRUE;
	}
	if (fclose(fd) != 0)
	    err = TRUE;
	if (err)
	{
	    EMSG(_("E677: Error writing temp file"));
	    goto errret;
	}
    }

    /* Omit SHELL_COOKED when invoked with ":silent".  Avoids that the shell
     * echoes typeahead, that messes up the display. */
    if (!msg_silent)
	flags += SHELL_COOKED;

    if (retlist)
    {
	int		len;
	listitem_T	*li;
	char_u		*s = NULL;
	char_u		*start;
	char_u		*end;
	int		i;

	res = get_cmd_output(tv_get_string(&argvars[0]), infile, flags, &len);
	if (res == NULL)
	    goto errret;

	list = list_alloc();
	if (list == NULL)
	    goto errret;

	for (i = 0; i < len; ++i)
	{
	    start = res + i;
	    while (i < len && res[i] != NL)
		++i;
	    end = res + i;

	    s = alloc((unsigned)(end - start + 1));
	    if (s == NULL)
		goto errret;

	    for (p = s; start < end; ++p, ++start)
		*p = *start == NUL ? NL : *start;
	    *p = NUL;

	    li = listitem_alloc();
	    if (li == NULL)
	    {
		vim_free(s);
		goto errret;
	    }
	    li->li_tv.v_type = VAR_STRING;
	    li->li_tv.v_lock = 0;
	    li->li_tv.vval.v_string = s;
	    list_append(list, li);
	}

	rettv_list_set(rettv, list);
	list = NULL;
    }
    else
    {
	res = get_cmd_output(tv_get_string(&argvars[0]), infile, flags, NULL);
#ifdef USE_CR
	/* translate <CR> into <NL> */
	if (res != NULL)
	{
	    char_u	*s;

	    for (s = res; *s; ++s)
	    {
		if (*s == CAR)
		    *s = NL;
	    }
	}
#else
# ifdef USE_CRNL
	/* translate <CR><NL> into <NL> */
	if (res != NULL)
	{
	    char_u	*s, *d;

	    d = res;
	    for (s = res; *s; ++s)
	    {
		if (s[0] == CAR && s[1] == NL)
		    ++s;
		*d++ = *s;
	    }
	    *d = NUL;
	}
# endif
#endif
	rettv->vval.v_string = res;
	res = NULL;
    }

errret:
    if (infile != NULL)
    {
	mch_remove(infile);
	vim_free(infile);
    }
    if (res != NULL)
	vim_free(res);
    if (list != NULL)
	list_free(list);
}

/*
 * "system()" function
 */
    static void
f_system(typval_T *argvars, typval_T *rettv)
{
    get_cmd_output_as_rettv(argvars, rettv, FALSE);
}

/*
 * "systemlist()" function
 */
    static void
f_systemlist(typval_T *argvars, typval_T *rettv)
{
    get_cmd_output_as_rettv(argvars, rettv, TRUE);
}

/*
 * "tabpagebuflist()" function
 */
    static void
f_tabpagebuflist(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    tabpage_T	*tp;
    win_T	*wp = NULL;

    if (argvars[0].v_type == VAR_UNKNOWN)
	wp = firstwin;
    else
    {
	tp = find_tabpage((int)tv_get_number(&argvars[0]));
	if (tp != NULL)
	    wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
    }
    if (wp != NULL && rettv_list_alloc(rettv) != FAIL)
    {
	for (; wp != NULL; wp = wp->w_next)
	    if (list_append_number(rettv->vval.v_list,
						wp->w_buffer->b_fnum) == FAIL)
		break;
    }
}

/*
 * "tabpagenr()" function
 */
    static void
f_tabpagenr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		nr = 1;
    char_u	*arg;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	arg = tv_get_string_chk(&argvars[0]);
	nr = 0;
	if (arg != NULL)
	{
	    if (STRCMP(arg, "$") == 0)
		nr = tabpage_index(NULL) - 1;
	    else
		EMSG2(_(e_invexpr2), arg);
	}
    }
    else
	nr = tabpage_index(curtab);
    rettv->vval.v_number = nr;
}


/*
 * Common code for tabpagewinnr() and winnr().
 */
    static int
get_winnr(tabpage_T *tp, typval_T *argvar)
{
    win_T	*twin;
    int		nr = 1;
    win_T	*wp;
    char_u	*arg;

    twin = (tp == curtab) ? curwin : tp->tp_curwin;
    if (argvar->v_type != VAR_UNKNOWN)
    {
	arg = tv_get_string_chk(argvar);
	if (arg == NULL)
	    nr = 0;		/* type error; errmsg already given */
	else if (STRCMP(arg, "$") == 0)
	    twin = (tp == curtab) ? lastwin : tp->tp_lastwin;
	else if (STRCMP(arg, "#") == 0)
	{
	    twin = (tp == curtab) ? prevwin : tp->tp_prevwin;
	    if (twin == NULL)
		nr = 0;
	}
	else
	{
	    EMSG2(_(e_invexpr2), arg);
	    nr = 0;
	}
    }

    if (nr > 0)
	for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
					      wp != twin; wp = wp->w_next)
	{
	    if (wp == NULL)
	    {
		/* didn't find it in this tabpage */
		nr = 0;
		break;
	    }
	    ++nr;
	}
    return nr;
}

/*
 * "tabpagewinnr()" function
 */
    static void
f_tabpagewinnr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		nr = 1;
    tabpage_T	*tp;

    tp = find_tabpage((int)tv_get_number(&argvars[0]));
    if (tp == NULL)
	nr = 0;
    else
	nr = get_winnr(tp, &argvars[1]);
    rettv->vval.v_number = nr;
}

/*
 * "tagfiles()" function
 */
    static void
f_tagfiles(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*fname;
    tagname_T	tn;
    int		first;

    if (rettv_list_alloc(rettv) == FAIL)
	return;
    fname = alloc(MAXPATHL);
    if (fname == NULL)
	return;

    for (first = TRUE; ; first = FALSE)
	if (get_tagfname(&tn, first, fname) == FAIL
		|| list_append_string(rettv->vval.v_list, fname, -1) == FAIL)
	    break;
    tagname_free(&tn);
    vim_free(fname);
}

/*
 * "taglist()" function
 */
    static void
f_taglist(typval_T *argvars, typval_T *rettv)
{
    char_u  *fname = NULL;
    char_u  *tag_pattern;

    tag_pattern = tv_get_string(&argvars[0]);

    rettv->vval.v_number = FALSE;
    if (*tag_pattern == NUL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
	fname = tv_get_string(&argvars[1]);
    if (rettv_list_alloc(rettv) == OK)
	(void)get_tags(rettv->vval.v_list, tag_pattern, fname);
}

/*
 * "tempname()" function
 */
    static void
f_tempname(typval_T *argvars UNUSED, typval_T *rettv)
{
    static int	x = 'A';

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_tempname(x, FALSE);

    /* Advance 'x' to use A-Z and 0-9, so that there are at least 34 different
     * names.  Skip 'I' and 'O', they are used for shell redirection. */
    do
    {
	if (x == 'Z')
	    x = '0';
	else if (x == '9')
	    x = 'A';
	else
	{
#ifdef EBCDIC
	    if (x == 'I')
		x = 'J';
	    else if (x == 'R')
		x = 'S';
	    else
#endif
		++x;
	}
    } while (x == 'I' || x == 'O');
}

#ifdef FEAT_FLOAT
/*
 * "tan()" function
 */
    static void
f_tan(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = tan(f);
    else
	rettv->vval.v_float = 0.0;
}

/*
 * "tanh()" function
 */
    static void
f_tanh(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = tanh(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "test_alloc_fail(id, countdown, repeat)" function
 */
    static void
f_test_alloc_fail(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (argvars[0].v_type != VAR_NUMBER
	    || argvars[0].vval.v_number <= 0
	    || argvars[1].v_type != VAR_NUMBER
	    || argvars[1].vval.v_number < 0
	    || argvars[2].v_type != VAR_NUMBER)
	EMSG(_(e_invarg));
    else
    {
	alloc_fail_id = argvars[0].vval.v_number;
	if (alloc_fail_id >= aid_last)
	    EMSG(_(e_invarg));
	alloc_fail_countdown = argvars[1].vval.v_number;
	alloc_fail_repeat = argvars[2].vval.v_number;
	did_outofmem_msg = FALSE;
    }
}

/*
 * "test_autochdir()"
 */
    static void
f_test_autochdir(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#if defined(FEAT_AUTOCHDIR)
    test_autochdir = TRUE;
#endif
}

/*
 * "test_feedinput()"
 */
    static void
f_test_feedinput(typval_T *argvars, typval_T *rettv UNUSED)
{
#ifdef USE_INPUT_BUF
    char_u	*val = tv_get_string_chk(&argvars[0]);

    if (val != NULL)
    {
	trash_input_buf();
	add_to_input_buf_csi(val, (int)STRLEN(val));
    }
#endif
}

/*
 * "test_option_not_set({name})" function
 */
    static void
f_test_option_not_set(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *name = (char_u *)"";

    if (argvars[0].v_type != VAR_STRING)
	EMSG(_(e_invarg));
    else
    {
	name = tv_get_string(&argvars[0]);
	if (reset_option_was_set(name) == FAIL)
	    EMSG2(_(e_invarg2), name);
    }
}

/*
 * "test_override({name}, {val})" function
 */
    static void
f_test_override(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *name = (char_u *)"";
    int     val;
    static int save_starting = -1;

    if (argvars[0].v_type != VAR_STRING
	    || (argvars[1].v_type) != VAR_NUMBER)
	EMSG(_(e_invarg));
    else
    {
	name = tv_get_string(&argvars[0]);
	val = (int)tv_get_number(&argvars[1]);

	if (STRCMP(name, (char_u *)"redraw") == 0)
	    disable_redraw_for_testing = val;
	else if (STRCMP(name, (char_u *)"redraw_flag") == 0)
	    ignore_redraw_flag_for_testing = val;
	else if (STRCMP(name, (char_u *)"char_avail") == 0)
	    disable_char_avail_for_testing = val;
	else if (STRCMP(name, (char_u *)"starting") == 0)
	{
	    if (val)
	    {
		if (save_starting < 0)
		    save_starting = starting;
		starting = 0;
	    }
	    else
	    {
		starting = save_starting;
		save_starting = -1;
	    }
	}
	else if (STRCMP(name, (char_u *)"nfa_fail") == 0)
	    nfa_fail_for_testing = val;
	else if (STRCMP(name, (char_u *)"ALL") == 0)
	{
	    disable_char_avail_for_testing = FALSE;
	    disable_redraw_for_testing = FALSE;
	    ignore_redraw_flag_for_testing = FALSE;
	    nfa_fail_for_testing = FALSE;
	    if (save_starting >= 0)
	    {
		starting = save_starting;
		save_starting = -1;
	    }
	}
	else
	    EMSG2(_(e_invarg2), name);
    }
}

/*
 * "test_garbagecollect_now()" function
 */
    static void
f_test_garbagecollect_now(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    /* This is dangerous, any Lists and Dicts used internally may be freed
     * while still in use. */
    garbage_collect(TRUE);
}

/*
 * "test_ignore_error()" function
 */
    static void
f_test_ignore_error(typval_T *argvars, typval_T *rettv UNUSED)
{
     ignore_error_for_testing(tv_get_string(&argvars[0]));
}

#ifdef FEAT_JOB_CHANNEL
    static void
f_test_null_channel(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_CHANNEL;
    rettv->vval.v_channel = NULL;
}
#endif

    static void
f_test_null_dict(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv_dict_set(rettv, NULL);
}

#ifdef FEAT_JOB_CHANNEL
    static void
f_test_null_job(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_JOB;
    rettv->vval.v_job = NULL;
}
#endif

    static void
f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv_list_set(rettv, NULL);
}

    static void
f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_PARTIAL;
    rettv->vval.v_partial = NULL;
}

    static void
f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
}

#ifdef FEAT_GUI
    static void
f_test_scrollbar(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u	*which;
    long	value;
    int		dragging;
    scrollbar_T *sb = NULL;

    if (argvars[0].v_type != VAR_STRING
	    || (argvars[1].v_type) != VAR_NUMBER
	    || (argvars[2].v_type) != VAR_NUMBER)
    {
	EMSG(_(e_invarg));
	return;
    }
    which = tv_get_string(&argvars[0]);
    value = tv_get_number(&argvars[1]);
    dragging = tv_get_number(&argvars[2]);

    if (STRCMP(which, "left") == 0)
	sb = &curwin->w_scrollbars[SBAR_LEFT];
    else if (STRCMP(which, "right") == 0)
	sb = &curwin->w_scrollbars[SBAR_RIGHT];
    else if (STRCMP(which, "hor") == 0)
	sb = &gui.bottom_sbar;
    if (sb == NULL)
    {
	EMSG2(_(e_invarg2), which);
	return;
    }
    gui_drag_scrollbar(sb, value, dragging);
# ifndef USE_ON_FLY_SCROLL
    // need to loop through normal_cmd() to handle the scroll events
    exec_normal(FALSE, TRUE, FALSE);
# endif
}
#endif

    static void
f_test_settime(typval_T *argvars, typval_T *rettv UNUSED)
{
    time_for_testing = (time_t)tv_get_number(&argvars[0]);
}

#if defined(FEAT_JOB_CHANNEL) || defined(FEAT_TIMERS) || defined(PROTO)
/*
 * Get a callback from "arg".  It can be a Funcref or a function name.
 * When "arg" is zero return an empty string.
 * Return NULL for an invalid argument.
 */
    char_u *
get_callback(typval_T *arg, partial_T **pp)
{
    if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL)
    {
	*pp = arg->vval.v_partial;
	++(*pp)->pt_refcount;
	return partial_name(*pp);
    }
    *pp = NULL;
    if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING)
    {
	func_ref(arg->vval.v_string);
	return arg->vval.v_string;
    }
    if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)
	return (char_u *)"";
    EMSG(_("E921: Invalid callback argument"));
    return NULL;
}

/*
 * Unref/free "callback" and "partial" returned by get_callback().
 */
    void
free_callback(char_u *callback, partial_T *partial)
{
    if (partial != NULL)
	partial_unref(partial);
    else if (callback != NULL)
    {
	func_unref(callback);
	vim_free(callback);
    }
}
#endif

#ifdef FEAT_TIMERS
/*
 * "timer_info([timer])" function
 */
    static void
f_timer_info(typval_T *argvars, typval_T *rettv)
{
    timer_T *timer = NULL;

    if (rettv_list_alloc(rettv) != OK)
	return;
    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	if (argvars[0].v_type != VAR_NUMBER)
	    EMSG(_(e_number_exp));
	else
	{
	    timer = find_timer((int)tv_get_number(&argvars[0]));
	    if (timer != NULL)
		add_timer_info(rettv, timer);
	}
    }
    else
	add_timer_info_all(rettv);
}

/*
 * "timer_pause(timer, paused)" function
 */
    static void
f_timer_pause(typval_T *argvars, typval_T *rettv UNUSED)
{
    timer_T	*timer = NULL;
    int		paused = (int)tv_get_number(&argvars[1]);

    if (argvars[0].v_type != VAR_NUMBER)
	EMSG(_(e_number_exp));
    else
    {
	timer = find_timer((int)tv_get_number(&argvars[0]));
	if (timer != NULL)
	    timer->tr_paused = paused;
    }
}

/*
 * "timer_start(time, callback [, options])" function
 */
    static void
f_timer_start(typval_T *argvars, typval_T *rettv)
{
    long	msec = (long)tv_get_number(&argvars[0]);
    timer_T	*timer;
    int		repeat = 0;
    char_u	*callback;
    dict_T	*dict;
    partial_T	*partial;

    rettv->vval.v_number = -1;
    if (check_secure())
	return;
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	if (argvars[2].v_type != VAR_DICT
				   || (dict = argvars[2].vval.v_dict) == NULL)
	{
	    EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
	    return;
	}
	if (dict_find(dict, (char_u *)"repeat", -1) != NULL)
	    repeat = dict_get_number(dict, (char_u *)"repeat");
    }

    callback = get_callback(&argvars[1], &partial);
    if (callback == NULL)
	return;

    timer = create_timer(msec, repeat);
    if (timer == NULL)
	free_callback(callback, partial);
    else
    {
	if (partial == NULL)
	    timer->tr_callback = vim_strsave(callback);
	else
	    /* pointer into the partial */
	    timer->tr_callback = callback;
	timer->tr_partial = partial;
	rettv->vval.v_number = (varnumber_T)timer->tr_id;
    }
}

/*
 * "timer_stop(timer)" function
 */
    static void
f_timer_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    timer_T *timer;

    if (argvars[0].v_type != VAR_NUMBER)
    {
	EMSG(_(e_number_exp));
	return;
    }
    timer = find_timer((int)tv_get_number(&argvars[0]));
    if (timer != NULL)
	stop_timer(timer);
}

/*
 * "timer_stopall()" function
 */
    static void
f_timer_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    stop_all_timers();
}
#endif

/*
 * "tolower(string)" function
 */
    static void
f_tolower(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = strlow_save(tv_get_string(&argvars[0]));
}

/*
 * "toupper(string)" function
 */
    static void
f_toupper(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = strup_save(tv_get_string(&argvars[0]));
}

/*
 * "tr(string, fromstr, tostr)" function
 */
    static void
f_tr(typval_T *argvars, typval_T *rettv)
{
    char_u	*in_str;
    char_u	*fromstr;
    char_u	*tostr;
    char_u	*p;
#ifdef FEAT_MBYTE
    int		inlen;
    int		fromlen;
    int		tolen;
    int		idx;
    char_u	*cpstr;
    int		cplen;
    int		first = TRUE;
#endif
    char_u	buf[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    garray_T	ga;

    in_str = tv_get_string(&argvars[0]);
    fromstr = tv_get_string_buf_chk(&argvars[1], buf);
    tostr = tv_get_string_buf_chk(&argvars[2], buf2);

    /* Default return value: empty string. */
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
    if (fromstr == NULL || tostr == NULL)
	    return;		/* type error; errmsg already given */
    ga_init2(&ga, (int)sizeof(char), 80);

#ifdef FEAT_MBYTE
    if (!has_mbyte)
#endif
	/* not multi-byte: fromstr and tostr must be the same length */
	if (STRLEN(fromstr) != STRLEN(tostr))
	{
#ifdef FEAT_MBYTE
error:
#endif
	    EMSG2(_(e_invarg2), fromstr);
	    ga_clear(&ga);
	    return;
	}

    /* fromstr and tostr have to contain the same number of chars */
    while (*in_str != NUL)
    {
#ifdef FEAT_MBYTE
	if (has_mbyte)
	{
	    inlen = (*mb_ptr2len)(in_str);
	    cpstr = in_str;
	    cplen = inlen;
	    idx = 0;
	    for (p = fromstr; *p != NUL; p += fromlen)
	    {
		fromlen = (*mb_ptr2len)(p);
		if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0)
		{
		    for (p = tostr; *p != NUL; p += tolen)
		    {
			tolen = (*mb_ptr2len)(p);
			if (idx-- == 0)
			{
			    cplen = tolen;
			    cpstr = p;
			    break;
			}
		    }
		    if (*p == NUL)	/* tostr is shorter than fromstr */
			goto error;
		    break;
		}
		++idx;
	    }

	    if (first && cpstr == in_str)
	    {
		/* Check that fromstr and tostr have the same number of
		 * (multi-byte) characters.  Done only once when a character
		 * of in_str doesn't appear in fromstr. */
		first = FALSE;
		for (p = tostr; *p != NUL; p += tolen)
		{
		    tolen = (*mb_ptr2len)(p);
		    --idx;
		}
		if (idx != 0)
		    goto error;
	    }

	    (void)ga_grow(&ga, cplen);
	    mch_memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
	    ga.ga_len += cplen;

	    in_str += inlen;
	}
	else
#endif
	{
	    /* When not using multi-byte chars we can do it faster. */
	    p = vim_strchr(fromstr, *in_str);
	    if (p != NULL)
		ga_append(&ga, tostr[p - fromstr]);
	    else
		ga_append(&ga, *in_str);
	    ++in_str;
	}
    }

    /* add a terminating NUL */
    (void)ga_grow(&ga, 1);
    ga_append(&ga, NUL);

    rettv->vval.v_string = ga.ga_data;
}

/*
 * "trim({expr})" function
 */
    static void
f_trim(typval_T *argvars, typval_T *rettv)
{
    char_u	buf1[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    char_u	*head = tv_get_string_buf_chk(&argvars[0], buf1);
    char_u	*mask = NULL;
    char_u	*tail;
    char_u	*prev;
    char_u	*p;
    int		c1;

    rettv->v_type = VAR_STRING;
    if (head == NULL)
    {
	rettv->vval.v_string = NULL;
	return;
    }

    if (argvars[1].v_type == VAR_STRING)
	mask = tv_get_string_buf_chk(&argvars[1], buf2);

    while (*head != NUL)
    {
	c1 = PTR2CHAR(head);
	if (mask == NULL)
	{
	    if (c1 > ' ' && c1 != 0xa0)
		break;
	}
	else
	{
	    for (p = mask; *p != NUL; MB_PTR_ADV(p))
		if (c1 == PTR2CHAR(p))
		    break;
	    if (*p == NUL)
		break;
	}
	MB_PTR_ADV(head);
    }

    for (tail = head + STRLEN(head); tail > head; tail = prev)
    {
	prev = tail;
	MB_PTR_BACK(head, prev);
	c1 = PTR2CHAR(prev);
	if (mask == NULL)
	{
	    if (c1 > ' ' && c1 != 0xa0)
		break;
	}
	else
	{
	    for (p = mask; *p != NUL; MB_PTR_ADV(p))
		if (c1 == PTR2CHAR(p))
		    break;
	    if (*p == NUL)
		break;
	}
    }
    rettv->vval.v_string = vim_strnsave(head, (int)(tail - head));
}

#ifdef FEAT_FLOAT
/*
 * "trunc({float})" function
 */
    static void
f_trunc(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	/* trunc() is not in C90, use floor() or ceil() instead. */
	rettv->vval.v_float = f > 0 ? floor(f) : ceil(f);
    else
	rettv->vval.v_float = 0.0;
}
#endif

/*
 * "type(expr)" function
 */
    static void
f_type(typval_T *argvars, typval_T *rettv)
{
    int n = -1;

    switch (argvars[0].v_type)
    {
	case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
	case VAR_STRING: n = VAR_TYPE_STRING; break;
	case VAR_PARTIAL:
	case VAR_FUNC:   n = VAR_TYPE_FUNC; break;
	case VAR_LIST:   n = VAR_TYPE_LIST; break;
	case VAR_DICT:   n = VAR_TYPE_DICT; break;
	case VAR_FLOAT:  n = VAR_TYPE_FLOAT; break;
	case VAR_SPECIAL:
	     if (argvars[0].vval.v_number == VVAL_FALSE
		     || argvars[0].vval.v_number == VVAL_TRUE)
		 n = VAR_TYPE_BOOL;
	     else
		 n = VAR_TYPE_NONE;
	     break;
	case VAR_JOB:     n = VAR_TYPE_JOB; break;
	case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break;
	case VAR_UNKNOWN:
	     internal_error("f_type(UNKNOWN)");
	     n = -1;
	     break;
    }
    rettv->vval.v_number = n;
}

/*
 * "undofile(name)" function
 */
    static void
f_undofile(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
#ifdef FEAT_PERSISTENT_UNDO
    {
	char_u *fname = tv_get_string(&argvars[0]);

	if (*fname == NUL)
	{
	    /* If there is no file name there will be no undo file. */
	    rettv->vval.v_string = NULL;
	}
	else
	{
	    char_u *ffname = FullName_save(fname, FALSE);

	    if (ffname != NULL)
		rettv->vval.v_string = u_get_undo_file_name(ffname, FALSE);
	    vim_free(ffname);
	}
    }
#else
    rettv->vval.v_string = NULL;
#endif
}

/*
 * "undotree()" function
 */
    static void
f_undotree(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) == OK)
    {
	dict_T *dict = rettv->vval.v_dict;
	list_T *list;

	dict_add_number(dict, "synced", (long)curbuf->b_u_synced);
	dict_add_number(dict, "seq_last", curbuf->b_u_seq_last);
	dict_add_number(dict, "save_last", (long)curbuf->b_u_save_nr_last);
	dict_add_number(dict, "seq_cur", curbuf->b_u_seq_cur);
	dict_add_number(dict, "time_cur", (long)curbuf->b_u_time_cur);
	dict_add_number(dict, "save_cur", (long)curbuf->b_u_save_nr_cur);

	list = list_alloc();
	if (list != NULL)
	{
	    u_eval_tree(curbuf->b_u_oldhead, list);
	    dict_add_list(dict, "entries", list);
	}
    }
}

/*
 * "values(dict)" function
 */
    static void
f_values(typval_T *argvars, typval_T *rettv)
{
    dict_list(argvars, rettv, 1);
}

/*
 * "virtcol(string)" function
 */
    static void
f_virtcol(typval_T *argvars, typval_T *rettv)
{
    colnr_T	vcol = 0;
    pos_T	*fp;
    int		fnum = curbuf->b_fnum;

    fp = var2fpos(&argvars[0], FALSE, &fnum);
    if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
						    && fnum == curbuf->b_fnum)
    {
	getvvcol(curwin, fp, NULL, NULL, &vcol);
	++vcol;
    }

    rettv->vval.v_number = vcol;
}

/*
 * "visualmode()" function
 */
    static void
f_visualmode(typval_T *argvars, typval_T *rettv)
{
    char_u	str[2];

    rettv->v_type = VAR_STRING;
    str[0] = curbuf->b_visual_mode_eval;
    str[1] = NUL;
    rettv->vval.v_string = vim_strsave(str);

    /* A non-zero number or non-empty string argument: reset mode. */
    if (non_zero_arg(&argvars[0]))
	curbuf->b_visual_mode_eval = NUL;
}

/*
 * "wildmenumode()" function
 */
    static void
f_wildmenumode(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_WILDMENU
    if (wild_menu_showing)
	rettv->vval.v_number = 1;
#endif
}

/*
 * "winbufnr(nr)" function
 */
    static void
f_winbufnr(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = wp->w_buffer->b_fnum;
}

/*
 * "wincol()" function
 */
    static void
f_wincol(typval_T *argvars UNUSED, typval_T *rettv)
{
    validate_cursor();
    rettv->vval.v_number = curwin->w_wcol + 1;
}

/*
 * "winheight(nr)" function
 */
    static void
f_winheight(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = wp->w_height;
}

/*
 * "winlayout()" function
 */
    static void
f_winlayout(typval_T *argvars, typval_T *rettv)
{
    tabpage_T	*tp;

    if (rettv_list_alloc(rettv) != OK)
	return;

    if (argvars[0].v_type == VAR_UNKNOWN)
	tp = curtab;
    else
    {
	tp = find_tabpage((int)tv_get_number(&argvars[0]));
	if (tp == NULL)
	    return;
    }

    get_framelayout(tp->tp_topframe, rettv->vval.v_list, TRUE);
}

/*
 * "winline()" function
 */
    static void
f_winline(typval_T *argvars UNUSED, typval_T *rettv)
{
    validate_cursor();
    rettv->vval.v_number = curwin->w_wrow + 1;
}

/*
 * "winnr()" function
 */
    static void
f_winnr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		nr = 1;

    nr = get_winnr(curtab, &argvars[0]);
    rettv->vval.v_number = nr;
}

/*
 * "winrestcmd()" function
 */
    static void
f_winrestcmd(typval_T *argvars UNUSED, typval_T *rettv)
{
    win_T	*wp;
    int		winnr = 1;
    garray_T	ga;
    char_u	buf[50];

    ga_init2(&ga, (int)sizeof(char), 70);
    FOR_ALL_WINDOWS(wp)
    {
	sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
	ga_concat(&ga, buf);
	sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
	ga_concat(&ga, buf);
	++winnr;
    }
    ga_append(&ga, NUL);

    rettv->vval.v_string = ga.ga_data;
    rettv->v_type = VAR_STRING;
}

/*
 * "winrestview()" function
 */
    static void
f_winrestview(typval_T *argvars, typval_T *rettv UNUSED)
{
    dict_T	*dict;

    if (argvars[0].v_type != VAR_DICT
	    || (dict = argvars[0].vval.v_dict) == NULL)
	EMSG(_(e_invarg));
    else
    {
	if (dict_find(dict, (char_u *)"lnum", -1) != NULL)
	    curwin->w_cursor.lnum = (linenr_T)dict_get_number(dict, (char_u *)"lnum");
	if (dict_find(dict, (char_u *)"col", -1) != NULL)
	    curwin->w_cursor.col = (colnr_T)dict_get_number(dict, (char_u *)"col");
#ifdef FEAT_VIRTUALEDIT
	if (dict_find(dict, (char_u *)"coladd", -1) != NULL)
	    curwin->w_cursor.coladd = (colnr_T)dict_get_number(dict, (char_u *)"coladd");
#endif
	if (dict_find(dict, (char_u *)"curswant", -1) != NULL)
	{
	    curwin->w_curswant = (colnr_T)dict_get_number(dict, (char_u *)"curswant");
	    curwin->w_set_curswant = FALSE;
	}

	if (dict_find(dict, (char_u *)"topline", -1) != NULL)
	    set_topline(curwin, (linenr_T)dict_get_number(dict, (char_u *)"topline"));
#ifdef FEAT_DIFF
	if (dict_find(dict, (char_u *)"topfill", -1) != NULL)
	    curwin->w_topfill = (int)dict_get_number(dict, (char_u *)"topfill");
#endif
	if (dict_find(dict, (char_u *)"leftcol", -1) != NULL)
	    curwin->w_leftcol = (colnr_T)dict_get_number(dict, (char_u *)"leftcol");
	if (dict_find(dict, (char_u *)"skipcol", -1) != NULL)
	    curwin->w_skipcol = (colnr_T)dict_get_number(dict, (char_u *)"skipcol");

	check_cursor();
	win_new_height(curwin, curwin->w_height);
	win_new_width(curwin, curwin->w_width);
	changed_window_setting();

	if (curwin->w_topline <= 0)
	    curwin->w_topline = 1;
	if (curwin->w_topline > curbuf->b_ml.ml_line_count)
	    curwin->w_topline = curbuf->b_ml.ml_line_count;
#ifdef FEAT_DIFF
	check_topfill(curwin, TRUE);
#endif
    }
}

/*
 * "winsaveview()" function
 */
    static void
f_winsaveview(typval_T *argvars UNUSED, typval_T *rettv)
{
    dict_T	*dict;

    if (rettv_dict_alloc(rettv) == FAIL)
	return;
    dict = rettv->vval.v_dict;

    dict_add_number(dict, "lnum", (long)curwin->w_cursor.lnum);
    dict_add_number(dict, "col", (long)curwin->w_cursor.col);
#ifdef FEAT_VIRTUALEDIT
    dict_add_number(dict, "coladd", (long)curwin->w_cursor.coladd);
#endif
    update_curswant();
    dict_add_number(dict, "curswant", (long)curwin->w_curswant);

    dict_add_number(dict, "topline", (long)curwin->w_topline);
#ifdef FEAT_DIFF
    dict_add_number(dict, "topfill", (long)curwin->w_topfill);
#endif
    dict_add_number(dict, "leftcol", (long)curwin->w_leftcol);
    dict_add_number(dict, "skipcol", (long)curwin->w_skipcol);
}

/*
 * "winwidth(nr)" function
 */
    static void
f_winwidth(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = wp->w_width;
}

/*
 * "wordcount()" function
 */
    static void
f_wordcount(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) == FAIL)
	return;
    cursor_pos_info(rettv->vval.v_dict);
}

/*
 * "writefile()" function
 */
    static void
f_writefile(typval_T *argvars, typval_T *rettv)
{
    int		binary = FALSE;
    int		append = FALSE;
#ifdef HAVE_FSYNC
    int		do_fsync = p_fs;
#endif
    char_u	*fname;
    FILE	*fd;
    int		ret = 0;
    listitem_T	*li;
    list_T	*list;

    rettv->vval.v_number = -1;
    if (check_restricted() || check_secure())
	return;

    if (argvars[0].v_type != VAR_LIST)
    {
	EMSG2(_(e_listarg), "writefile()");
	return;
    }
    list = argvars[0].vval.v_list;
    if (list == NULL)
	return;
    for (li = list->lv_first; li != NULL; li = li->li_next)
	if (tv_get_string_chk(&li->li_tv) == NULL)
	    return;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	char_u *arg2 = tv_get_string_chk(&argvars[2]);

	if (arg2 == NULL)
	    return;
	if (vim_strchr(arg2, 'b') != NULL)
	    binary = TRUE;
	if (vim_strchr(arg2, 'a') != NULL)
	    append = TRUE;
#ifdef HAVE_FSYNC
	if (vim_strchr(arg2, 's') != NULL)
	    do_fsync = TRUE;
	else if (vim_strchr(arg2, 'S') != NULL)
	    do_fsync = FALSE;
#endif
    }

    fname = tv_get_string_chk(&argvars[1]);
    if (fname == NULL)
	return;

    /* Always open the file in binary mode, library functions have a mind of
     * their own about CR-LF conversion. */
    if (*fname == NUL || (fd = mch_fopen((char *)fname,
				      append ? APPENDBIN : WRITEBIN)) == NULL)
    {
	EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
	ret = -1;
    }
    else
    {
	if (write_list(fd, list, binary) == FAIL)
	    ret = -1;
#ifdef HAVE_FSYNC
	else if (do_fsync)
	    /* Ignore the error, the user wouldn't know what to do about it.
	     * May happen for a device. */
	    vim_ignored = fsync(fileno(fd));
#endif
	fclose(fd);
    }

    rettv->vval.v_number = ret;
}

/*
 * "xor(expr, expr)" function
 */
    static void
f_xor(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
					^ tv_get_number_chk(&argvars[1], NULL);
}

#endif /* FEAT_EVAL */