view src/evalwindow.c @ 33399:95db67c7b754 v9.0.1958

patch 9.0.1958: cannot complete option values Commit: https://github.com/vim/vim/commit/900894b09a95398dfc75599e9f0aa2ea25723384 Author: Yee Cheng Chin <ychin.git@gmail.com> Date: Fri Sep 29 20:42:32 2023 +0200 patch 9.0.1958: cannot complete option values Problem: cannot complete option values Solution: Add completion functions for several options Add cmdline tab-completion for setting string options Add tab-completion for setting string options on the cmdline using `:set=` (along with `:set+=` and `:set-=`). The existing tab completion for setting options currently only works when nothing is typed yet, and it only fills in with the existing value, e.g. when the user does `:set diffopt=<Tab>` it will be completed to `set diffopt=internal,filler,closeoff` and nothing else. This isn't too useful as a user usually wants auto-complete to suggest all the possible values, such as 'iblank', or 'algorithm:patience'. For set= and set+=, this adds a new optional callback function for each option that can be invoked when doing completion. This allows for each option to have control over how completion works. For example, in 'diffopt', it will suggest the default enumeration, but if `algorithm:` is selected, it will further suggest different algorithm types like 'meyers' and 'patience'. When using set=, the existing option value will be filled in as the first choice to preserve the existing behavior. When using set+= this won't happen as it doesn't make sense. For flag list options (e.g. 'mouse' and 'guioptions'), completion will take into account existing typed values (and in the case of set+=, the existing option value) to make sure it doesn't suggest duplicates. For set-=, there is a new `ExpandSettingSubtract` function which will handle flag list and comma-separated options smartly, by only suggesting values that currently exist in the option. Note that Vim has some existing code that adds special handling for 'filetype', 'syntax', and misc dir options like 'backupdir'. This change preserves them as they already work, instead of converting to the new callback API for each option. closes: #13182 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
author Christian Brabandt <cb@256bit.org>
date Fri, 29 Sep 2023 20:45:04 +0200
parents b89cfd86e18e
children a522c6c0127b
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.
 */

/*
 * evalwindow.c: Window related builtin functions
 */

#include "vim.h"

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

    static int
win_getid(typval_T *argvars)
{
    int	    winnr;
    win_T   *wp;

    if (argvars[0].v_type == VAR_UNKNOWN)
	return curwin->w_id;
    winnr = tv_get_number(&argvars[0]);
    if (winnr <= 0)
	return 0;

    if (argvars[1].v_type == VAR_UNKNOWN)
	wp = firstwin;
    else
    {
	tabpage_T	*tp;
	int		tabnr = tv_get_number(&argvars[1]);

	FOR_ALL_TABPAGES(tp)
	    if (--tabnr == 0)
		break;
	if (tp == NULL)
	    return -1;
	if (tp == curtab)
	    wp = firstwin;
	else
	    wp = tp->tp_firstwin;
    }
    for ( ; wp != NULL; wp = wp->w_next)
	if (--winnr == 0)
	    return wp->w_id;
    return 0;
}

    static void
win_id2tabwin(typval_T *argvars, list_T *list)
{
    win_T	*wp;
    tabpage_T   *tp;
    int		winnr = 1;
    int		tabnr = 1;
    int		id = tv_get_number(&argvars[0]);

    FOR_ALL_TABPAGES(tp)
    {
	FOR_ALL_WINDOWS_IN_TAB(tp, wp)
	{
	    if (wp->w_id == id)
	    {
		list_append_number(list, tabnr);
		list_append_number(list, winnr);
		return;
	    }
	    ++winnr;
	}
	++tabnr;
	winnr = 1;
    }
    list_append_number(list, 0);
    list_append_number(list, 0);
}

/*
 * Return the window pointer of window "id".
 */
    win_T *
win_id2wp(int id)
{
    return win_id2wp_tp(id, NULL);
}

/*
 * Return the window and tab pointer of window "id".
 * Returns NULL when not found.
 */
    win_T *
win_id2wp_tp(int id, tabpage_T **tpp)
{
    win_T	*wp;
    tabpage_T   *tp;

    FOR_ALL_TAB_WINDOWS(tp, wp)
	if (wp->w_id == id)
	{
	    if (tpp != NULL)
		*tpp = tp;
	    return wp;
	}
#ifdef FEAT_PROP_POPUP
    // popup windows are in separate lists
     FOR_ALL_TABPAGES(tp)
	 FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
	     if (wp->w_id == id)
	     {
		 if (tpp != NULL)
		     *tpp = tp;
		 return wp;
	     }
    FOR_ALL_POPUPWINS(wp)
	if (wp->w_id == id)
	{
	    if (tpp != NULL)
		*tpp = curtab;  // any tabpage would do
	    return wp;
	}
#endif

    return NULL;
}

    static int
win_id2win(typval_T *argvars)
{
    win_T   *wp;
    int	    nr = 1;
    int	    id = tv_get_number(&argvars[0]);

    FOR_ALL_WINDOWS(wp)
    {
	if (wp->w_id == id)
	    return nr;
	++nr;
    }
    return 0;
}

    void
win_findbuf(typval_T *argvars, list_T *list)
{
    win_T	*wp;
    tabpage_T   *tp;
    int		bufnr = tv_get_number(&argvars[0]);

    FOR_ALL_TAB_WINDOWS(tp, wp)
	    if (wp->w_buffer->b_fnum == bufnr)
		list_append_number(list, wp->w_id);
}

/*
 * Find window specified by "vp" in tabpage "tp".
 * Returns current window if "vp" is number zero.
 * Returns NULL if not found.
 */
    win_T *
find_win_by_nr(
    typval_T	*vp,
    tabpage_T	*tp)	// NULL for current tab page
{
    win_T	*wp;
    int		nr = (int)tv_get_number_chk(vp, NULL);

    if (nr < 0)
	return NULL;
    if (nr == 0)
	return curwin;

    FOR_ALL_WINDOWS_IN_TAB(tp, wp)
    {
	if (nr >= LOWEST_WIN_ID)
	{
	    if (wp->w_id == nr)
		return wp;
	}
	else if (--nr <= 0)
	    break;
    }
    if (nr >= LOWEST_WIN_ID)
    {
#ifdef FEAT_PROP_POPUP
	// check tab-local popup windows
	for (wp = (tp == NULL ? curtab : tp)->tp_first_popupwin;
						   wp != NULL; wp = wp->w_next)
	    if (wp->w_id == nr)
		return wp;
	// check global popup windows
	FOR_ALL_POPUPWINS(wp)
	    if (wp->w_id == nr)
		return wp;
#endif
	return NULL;
    }
    return wp;
}

/*
 * Find a window: When using a Window ID in any tab page, when using a number
 * in the current tab page.
 * Returns NULL when not found.
 */
    win_T *
find_win_by_nr_or_id(typval_T *vp)
{
    int	nr = (int)tv_get_number_chk(vp, NULL);

    if (nr >= LOWEST_WIN_ID)
	return win_id2wp(tv_get_number(vp));
    return find_win_by_nr(vp, NULL);
}

/*
 * Find window specified by "wvp" in tabpage "tvp".
 * Returns the tab page in 'ptp'
 */
    win_T *
find_tabwin(
    typval_T	*wvp,	// VAR_UNKNOWN for current window
    typval_T	*tvp,	// VAR_UNKNOWN for current tab page
    tabpage_T	**ptp)
{
    win_T	*wp = NULL;
    tabpage_T	*tp = NULL;
    long	n;

    if (wvp->v_type != VAR_UNKNOWN)
    {
	if (tvp->v_type != VAR_UNKNOWN)
	{
	    n = (long)tv_get_number(tvp);
	    if (n >= 0)
		tp = find_tabpage(n);
	}
	else
	    tp = curtab;

	if (tp != NULL)
	{
	    wp = find_win_by_nr(wvp, tp);
	    if (wp == NULL && wvp->v_type == VAR_NUMBER
						&& wvp->vval.v_number != -1)
		// A window with the specified number is not found
		tp = NULL;
	}
    }
    else
    {
	wp = curwin;
	tp = curtab;
    }

    if (ptp != NULL)
	*ptp = tp;

    return wp;
}

/*
 * Get the layout of the given tab page for winlayout() and add it to "l".
 */
    static void
get_framelayout(frame_T *fr, list_T *l, int outer)
{
    frame_T	*child;
    list_T	*fr_list;
    list_T	*win_list;

    if (fr == NULL)
	return;

    if (outer)
	// outermost call from f_winlayout()
	fr_list = l;
    else
    {
	fr_list = list_alloc();
	if (fr_list == NULL)
	    return;
	if (list_append_list(l, fr_list) == FAIL)
	{
	    vim_free(fr_list);
	    return;
	}
    }

    if (fr->fr_layout == FR_LEAF)
    {
	if (fr->fr_win != NULL)
	{
	    list_append_string(fr_list, (char_u *)"leaf", -1);
	    list_append_number(fr_list, fr->fr_win->w_id);
	}
    }
    else
    {
	list_append_string(fr_list,
	     fr->fr_layout == FR_ROW ?  (char_u *)"row" : (char_u *)"col", -1);

	win_list = list_alloc();
	if (win_list == NULL)
	    return;
	if (list_append_list(fr_list, win_list) == FAIL)
	{
	    vim_free(win_list);
	    return;
	}

	child = fr->fr_child;
	while (child != NULL)
	{
	    get_framelayout(child, win_list, FALSE);
	    child = child->fr_next;
	}
    }
}

/*
 * 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)
    {
	int	invalid_arg = FALSE;

	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;
	}
	else
	{
	    long	count;
	    char_u	*endp;

	    // Extract the window count (if specified). e.g. winnr('3j')
	    count = strtol((char *)arg, (char **)&endp, 10);
	    if (count <= 0)
		count = 1;	// if count is not specified, default to 1
	    if (endp != NULL && *endp != '\0')
	    {
		if (STRCMP(endp, "j") == 0)
		    twin = win_vert_neighbor(tp, twin, FALSE, count);
		else if (STRCMP(endp, "k") == 0)
		    twin = win_vert_neighbor(tp, twin, TRUE, count);
		else if (STRCMP(endp, "h") == 0)
		    twin = win_horz_neighbor(tp, twin, TRUE, count);
		else if (STRCMP(endp, "l") == 0)
		    twin = win_horz_neighbor(tp, twin, FALSE, count);
		else
		    invalid_arg = TRUE;
	    }
	    else
		invalid_arg = TRUE;
	}
	if (twin == NULL)
	    nr = 0;

	if (invalid_arg)
	{
	    semsg(_(e_invalid_expression_str), arg);
	    nr = 0;
	}
    }

    if (nr <= 0)
	return 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;
}

/*
 * 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;

    // make sure w_botline is valid
    validate_botline_win(wp);

    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);
    dict_add_number(dict, "topline", wp->w_topline);
    dict_add_number(dict, "botline", wp->w_botline - 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, "textoff", win_col_off(wp));
    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;
}

/*
 * 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_ALL_WINDOWS_IN_TAB(tp, wp)
	    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
 */
    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) == FAIL)
	return;

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	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;
    }
}

/*
 * "getwininfo()" function
 */
    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) == FAIL)
	return;

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	wparg = win_id2wp(tv_get_number(&argvars[0]));
	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;
	}
    }
#ifdef FEAT_PROP_POPUP
    if (wparg != NULL)
    {
	tabnr = 0;
	FOR_ALL_TABPAGES(tp)
	{
	    tabnr++;
	    FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
	    if (wp == wparg)
		break;
	}
	d = get_win_info(wparg, tp == NULL ? 0 : tabnr, 0);
	if (d != NULL)
	    list_append_dict(rettv->vval.v_list, d);
    }
#endif
}

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

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

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

#if defined(FEAT_GUI) \
	|| (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
	|| defined(MSWIN)
    {
	varnumber_T timeout = 100;

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

	(void)ui_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
 */
    void
f_getwinposx(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = -1;
#if defined(FEAT_GUI) \
	|| (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
	|| defined(MSWIN)

    {
	int	    x, y;

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

/*
 * "getwinposy()" function
 */
    void
f_getwinposy(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = -1;
#if defined(FEAT_GUI) \
	|| (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
	|| defined(MSWIN)
    {
	int	    x, y;

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

/*
 * "tabpagenr()" function
 */
    void
f_tabpagenr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		nr = 1;
    char_u	*arg;

    if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
	return;

    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 if (STRCMP(arg, "#") == 0)
		nr = valid_tabpage(lastused_tabpage) ?
					tabpage_index(lastused_tabpage) : 0;
	    else
		semsg(_(e_invalid_expression_str), arg);
	}
    }
    else
	nr = tabpage_index(curtab);
    rettv->vval.v_number = nr;
}

/*
 * "tabpagewinnr()" function
 */
    void
f_tabpagewinnr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		nr = 1;
    tabpage_T	*tp;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL))
	return;

    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;
}

/*
 * "win_execute()" function
 */
    void
f_win_execute(typval_T *argvars, typval_T *rettv)
{
    int		id;
    tabpage_T	*tp;
    win_T	*wp;
    switchwin_T	switchwin;

    // Return an empty string if something fails.
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_string_or_list_arg(argvars, 1) == FAIL
		|| check_for_opt_string_arg(argvars, 2) == FAIL))
	return;

    id = (int)tv_get_number(argvars);
    wp = win_id2wp_tp(id, &tp);
    if (wp == NULL || tp == NULL)
	return;

    pos_T	curpos = wp->w_cursor;
    char_u	cwd[MAXPATHL];
    int	cwd_status = FAIL;
#ifdef FEAT_AUTOCHDIR
    char_u	autocwd[MAXPATHL];
    int	apply_acd = FALSE;
#endif

    // Getting and setting directory can be slow on some systems, only do
    // this when the current or target window/tab have a local directory or
    // 'acd' is set.
    if (curwin != wp
	    && (curwin->w_localdir != NULL
		|| wp->w_localdir != NULL
		|| (curtab != tp
		    && (curtab->tp_localdir != NULL
			|| tp->tp_localdir != NULL))
#ifdef FEAT_AUTOCHDIR
		|| p_acd
#endif
	       ))
	cwd_status = mch_dirname(cwd, MAXPATHL);

#ifdef FEAT_AUTOCHDIR
    // If 'acd' is set, check we are using that directory.  If yes, then
    // apply 'acd' afterwards, otherwise restore the current directory.
    if (cwd_status == OK && p_acd)
    {
	do_autochdir();
	apply_acd = mch_dirname(autocwd, MAXPATHL) == OK
	    && STRCMP(cwd, autocwd) == 0;
    }
#endif

    if (switch_win_noblock(&switchwin, wp, tp, TRUE) == OK)
    {
	check_cursor();
	execute_common(argvars, rettv, 1);
    }
    restore_win_noblock(&switchwin, TRUE);
#ifdef FEAT_AUTOCHDIR
    if (apply_acd)
	do_autochdir();
    else
#endif
	if (cwd_status == OK)
	    mch_chdir((char *)cwd);

    // Update the status line if the cursor moved.
    if (win_valid(wp) && !EQUAL_POS(curpos, wp->w_cursor))
	wp->w_redr_status = TRUE;

    // In case the command moved the cursor or changed the Visual area,
    // check it is valid.
    check_cursor();
    if (VIsual_active)
	check_pos(curbuf, &VIsual);
}

/*
 * "win_findbuf()" function
 */
    void
f_win_findbuf(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    if (rettv_list_alloc(rettv) == OK)
	win_findbuf(argvars, rettv->vval.v_list);
}

/*
 * "win_getid()" function
 */
    void
f_win_getid(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_opt_number_arg(argvars, 0) == FAIL
		|| (argvars[0].v_type != VAR_UNKNOWN
		    && check_for_opt_number_arg(argvars, 1) == FAIL)))
	return;

    rettv->vval.v_number = win_getid(argvars);
}

/*
 * "win_gotoid()" function
 */
    void
f_win_gotoid(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;
    tabpage_T   *tp;
    int		id;

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    id = tv_get_number(&argvars[0]);
    if (cmdwin_type != 0)
    {
	emsg(_(e_invalid_in_cmdline_window));
	return;
    }
#if defined(FEAT_PROP_POPUP) && defined(FEAT_TERMINAL)
    if (popup_is_popup(curwin) && curbuf->b_term != NULL)
    {
	emsg(_(e_not_allowed_for_terminal_in_popup_window));
	return;
    }
#endif
    FOR_ALL_TAB_WINDOWS(tp, wp)
	if (wp->w_id == id)
	{
	    // When jumping to another buffer stop Visual mode.
	    if (VIsual_active && wp->w_buffer != curbuf)
		end_visual_mode();
	    goto_tabpage_win(tp, wp);
	    rettv->vval.v_number = 1;
	    return;
	}
}

/*
 * "win_id2tabwin()" function
 */
    void
f_win_id2tabwin(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    if (rettv_list_alloc(rettv) == OK)
	win_id2tabwin(argvars, rettv->vval.v_list);
}

/*
 * "win_id2win()" function
 */
    void
f_win_id2win(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = win_id2win(argvars);
}

/*
 * "win_move_separator()" function
 */
    void
f_win_move_separator(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;
    int		offset;

    rettv->vval.v_number = FALSE;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL || win_valid_popup(wp))
	return;
    if (!win_valid(wp))
    {
	emsg(_(e_cannot_resize_window_in_another_tab_page));
	return;
    }

    offset = (int)tv_get_number(&argvars[1]);
    win_drag_vsep_line(wp, offset);
    rettv->vval.v_number = TRUE;
}

/*
 * "win_move_statusline()" function
 */
    void
f_win_move_statusline(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;
    int		offset;

    rettv->vval.v_number = FALSE;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL || win_valid_popup(wp))
	return;
    if (!win_valid(wp))
    {
	emsg(_(e_cannot_resize_window_in_another_tab_page));
	return;
    }

    offset = (int)tv_get_number(&argvars[1]);
    win_drag_status_line(wp, offset);
    rettv->vval.v_number = TRUE;
}

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

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

    if (in_vim9script() && check_for_number_arg(argvars, 0) == 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);
}

/*
 * Move the window wp into a new split of targetwin in a given direction
 */
    static void
win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags)
{
    int	    dir;
    int	    height = wp->w_height;
    win_T   *oldwin = curwin;

    if (wp == targetwin)
	return;

    // Jump to the target window
    if (curwin != targetwin)
	win_goto(targetwin);

    // Remove the old window and frame from the tree of frames
    (void)winframe_remove(wp, &dir, NULL);
    win_remove(wp, NULL);
    last_status(FALSE);	    // may need to remove last status line
    (void)win_comp_pos();   // recompute window positions

    // Split a window on the desired side and put the old window there
    (void)win_split_ins(size, flags, wp, dir);

    // If splitting horizontally, try to preserve height
    if (size == 0 && !(flags & WSP_VERT))
    {
	win_setheight_win(height, wp);
	if (p_ea)
	    win_equal(wp, TRUE, 'v');
    }

#if defined(FEAT_GUI)
    // When 'guioptions' includes 'L' or 'R' may have to remove or add
    // scrollbars.  Have to update them anyway.
    gui_may_update_scrollbars();
#endif

    if (oldwin != curwin)
	win_goto(oldwin);
}

/*
 * "win_splitmove()" function
 */
    void
f_win_splitmove(typval_T *argvars, typval_T *rettv)
{
    win_T   *wp;
    win_T   *targetwin;
    int     flags = 0, size = 0;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL
		|| check_for_opt_dict_arg(argvars, 2) == FAIL))
	return;

    wp = find_win_by_nr_or_id(&argvars[0]);
    targetwin = find_win_by_nr_or_id(&argvars[1]);

    if (wp == NULL || targetwin == NULL || wp == targetwin
	    || !win_valid(wp) || !win_valid(targetwin)
	    || win_valid_popup(wp) || win_valid_popup(targetwin))
    {
	emsg(_(e_invalid_window_number));
	rettv->vval.v_number = -1;
	return;
    }

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	dict_T      *d;
	dictitem_T  *di;

	if (check_for_nonnull_dict_arg(argvars, 2) == FAIL)
	    return;

	d = argvars[2].vval.v_dict;
	if (dict_get_bool(d, "vertical", FALSE))
	    flags |= WSP_VERT;
	if ((di = dict_find(d, (char_u *)"rightbelow", -1)) != NULL)
	    flags |= tv_get_bool(&di->di_tv) ? WSP_BELOW : WSP_ABOVE;
	size = (int)dict_get_number(d, "size");
    }

    win_move_into_split(wp, targetwin, size, flags);
}

/*
 * "win_gettype(nr)" function
 */
    void
f_win_gettype(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp = curwin;

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

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	wp = find_win_by_nr_or_id(&argvars[0]);
	if (wp == NULL)
	{
	    rettv->vval.v_string = vim_strsave((char_u *)"unknown");
	    return;
	}
    }
    if (is_aucmd_win(wp))
	rettv->vval.v_string = vim_strsave((char_u *)"autocmd");
#if defined(FEAT_QUICKFIX)
    else if (wp->w_p_pvw)
	rettv->vval.v_string = vim_strsave((char_u *)"preview");
#endif
#ifdef FEAT_PROP_POPUP
    else if (WIN_IS_POPUP(wp))
	rettv->vval.v_string = vim_strsave((char_u *)"popup");
#endif
    else if (wp == curwin && cmdwin_type != 0)
	rettv->vval.v_string = vim_strsave((char_u *)"command");
#ifdef FEAT_QUICKFIX
    else if (bt_quickfix(wp->w_buffer))
	rettv->vval.v_string = vim_strsave((char_u *)
		(wp->w_llist_ref != NULL ? "loclist" : "quickfix"));
#endif

}

/*
 * "getcmdwintype()" function
 */
    void
f_getcmdwintype(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
    rettv->vval.v_string = alloc(2);
    if (rettv->vval.v_string == NULL)
	return;

    rettv->vval.v_string[0] = cmdwin_type;
    rettv->vval.v_string[1] = NUL;
}

/*
 * "winbufnr(nr)" function
 */
    void
f_winbufnr(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    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
 */
    void
f_wincol(typval_T *argvars UNUSED, typval_T *rettv)
{
    validate_cursor();
    rettv->vval.v_number = curwin->w_wcol + 1;
}

/*
 * "winheight(nr)" function
 */
    void
f_winheight(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    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
 */
    void
f_winlayout(typval_T *argvars, typval_T *rettv)
{
    tabpage_T	*tp;

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

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	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
 */
    void
f_winline(typval_T *argvars UNUSED, typval_T *rettv)
{
    validate_cursor();
    rettv->vval.v_number = curwin->w_wrow + 1;
}

/*
 * "winnr()" function
 */
    void
f_winnr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		nr = 1;

    if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
	return;

    nr = get_winnr(curtab, &argvars[0]);
    rettv->vval.v_number = nr;
}

/*
 * "winrestcmd()" function
 */
    void
f_winrestcmd(typval_T *argvars UNUSED, typval_T *rettv)
{
    win_T	*wp;
    int		i;
    int		winnr;
    garray_T	ga;
    char_u	buf[50];

    ga_init2(&ga, sizeof(char), 70);

    // Do this twice to handle some window layouts properly.
    for (i = 0; i < 2; ++i)
    {
	winnr = 1;
	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
 */
    void
f_winrestview(typval_T *argvars, typval_T *rettv UNUSED)
{
    dict_T	*dict;

    if (check_for_nonnull_dict_arg(argvars, 0) == FAIL)
	return;

    dict = argvars[0].vval.v_dict;
    if (dict_has_key(dict, "lnum"))
	curwin->w_cursor.lnum = (linenr_T)dict_get_number(dict, "lnum");
    if (dict_has_key(dict, "col"))
	curwin->w_cursor.col = (colnr_T)dict_get_number(dict, "col");
    if (dict_has_key(dict, "coladd"))
	curwin->w_cursor.coladd = (colnr_T)dict_get_number(dict, "coladd");
    if (dict_has_key(dict, "curswant"))
    {
	curwin->w_curswant = (colnr_T)dict_get_number(dict, "curswant");
	curwin->w_set_curswant = FALSE;
    }

    if (dict_has_key(dict, "topline"))
	set_topline(curwin, (linenr_T)dict_get_number(dict, "topline"));
#ifdef FEAT_DIFF
    if (dict_has_key(dict, "topfill"))
	curwin->w_topfill = (int)dict_get_number(dict, "topfill");
#endif
    if (dict_has_key(dict, "leftcol"))
	curwin->w_leftcol = (colnr_T)dict_get_number(dict, "leftcol");
    if (dict_has_key(dict, "skipcol"))
	curwin->w_skipcol = (colnr_T)dict_get_number(dict, "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
 */
    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);
    dict_add_number(dict, "coladd", (long)curwin->w_cursor.coladd);
    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
 */
    void
f_winwidth(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    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;
}
#endif // FEAT_EVAL

#if defined(FEAT_EVAL) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \
	|| defined(PROTO)
/*
 * Set "win" to be the curwin and "tp" to be the current tab page.
 * restore_win() MUST be called to undo, also when FAIL is returned.
 * No autocommands will be executed until restore_win() is called.
 * When "no_display" is TRUE the display won't be affected, no redraw is
 * triggered, another tabpage access is limited.
 * Returns FAIL if switching to "win" failed.
 */
    int
switch_win(
	switchwin_T *switchwin,
	win_T	    *win,
	tabpage_T   *tp,
	int	    no_display)
{
    block_autocmds();
    return switch_win_noblock(switchwin, win, tp, no_display);
}

/*
 * As switch_win() but without blocking autocommands.
 */
    int
switch_win_noblock(
	switchwin_T *switchwin,
	win_T	    *win,
	tabpage_T   *tp,
	int	    no_display)
{
    CLEAR_POINTER(switchwin);
    switchwin->sw_curwin = curwin;
    if (win == curwin)
	switchwin->sw_same_win = TRUE;
    else
    {
	// Disable Visual selection, because redrawing may fail.
	switchwin->sw_visual_active = VIsual_active;
	VIsual_active = FALSE;
    }

    if (tp != NULL)
    {
	switchwin->sw_curtab = curtab;
	if (no_display)
	{
	    curtab->tp_firstwin = firstwin;
	    curtab->tp_lastwin = lastwin;
	    curtab->tp_topframe = topframe;
	    curtab = tp;
	    firstwin = curtab->tp_firstwin;
	    lastwin = curtab->tp_lastwin;
	    topframe = curtab->tp_topframe;
	}
	else
	    goto_tabpage_tp(tp, FALSE, FALSE);
    }
    if (!win_valid(win))
	return FAIL;
    curwin = win;
    curbuf = curwin->w_buffer;
    return OK;
}

/*
 * Restore current tabpage and window saved by switch_win(), if still valid.
 * When "no_display" is TRUE the display won't be affected, no redraw is
 * triggered.
 */
    void
restore_win(
	switchwin_T *switchwin,
	int	    no_display)
{
    restore_win_noblock(switchwin, no_display);
    unblock_autocmds();
}

/*
 * As restore_win() but without unblocking autocommands.
 */
    void
restore_win_noblock(
	switchwin_T *switchwin,
	int	    no_display)
{
    if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab))
    {
	if (no_display)
	{
	    curtab->tp_firstwin = firstwin;
	    curtab->tp_lastwin = lastwin;
	    curtab->tp_topframe = topframe;
	    curtab = switchwin->sw_curtab;
	    firstwin = curtab->tp_firstwin;
	    lastwin = curtab->tp_lastwin;
	    topframe = curtab->tp_topframe;
	}
	else
	    goto_tabpage_tp(switchwin->sw_curtab, FALSE, FALSE);
    }

    if (!switchwin->sw_same_win)
	VIsual_active = switchwin->sw_visual_active;

    if (win_valid(switchwin->sw_curwin))
    {
	curwin = switchwin->sw_curwin;
	curbuf = curwin->w_buffer;
    }
# ifdef FEAT_PROP_POPUP
    else if (WIN_IS_POPUP(curwin))
	// original window was closed and now we're in a popup window: Go
	// to the first valid window.
	win_goto(firstwin);
# endif
}
#endif