zcopydevice2(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gx_device *new_dev;
    int code;

    check_read_type(op[-1], t_device);
    check_type(*op, t_boolean);
    if (op[-1].value.pdevice == NULL)
        /* This can happen if we invalidated devices on the stack by calling nulldevice after they were pushed */
        return_error(gs_error_undefined);

    code = gs_copydevice2(&new_dev, op[-1].value.pdevice, op->value.boolval,
                          imemory);
    if (code < 0)
        return code;
    new_dev->memory = imemory;
    make_tav(op - 1, t_device, icurrent_space | a_all, pdevice, new_dev);
    pop(1);
    return 0;
}

Bug 700176: check the *output* device for LockSafetyParams

When calling .setdevice we were checking if LockSafetyParams was set, and if so
throwing an invalidaccess error.

The problem is, if another device, for example the pdf14 compositor is the 'top'
device, that does not (and cannot) honour LockSafetyParams.

To solve this, we'll now use the (relatively new) gxdso_current_output_device
spec_op to retrieve the *actual* output device, and check the LockSafetyParams
flag in that.


zcurrentdevice(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gx_device *dev = gs_currentdevice(igs);
    gs_ref_memory_t *mem = (gs_ref_memory_t *) dev->memory;

    push(1);
    make_tav(op, t_device,
             (mem == 0 ? avm_foreign : imemory_space(mem)) | a_all,
             pdevice, dev);
    return 0;
}


zspec_op(i_ctx_t *i_ctx_p)
{
    os_ptr  op = osp;
    gx_device *dev = gs_currentdevice(igs);
    int i, nprocs = sizeof(spec_op_defs) / sizeof(spec_op_t), code, proc = -1;
    ref opname, nref, namestr;
    char *data;

    /* At the very minimum we need a name object telling us which sepc_op to perform */
    check_op(1);
    if (!r_has_type(op, t_name))
        return_error(gs_error_typecheck);

    ref_assign(&opname, op);

    /* Find the relevant spec_op name */
    for (i=0;i<nprocs;i++) {
        code = names_ref(imemory->gs_lib_ctx->gs_name_table, (const byte *)spec_op_defs[i].name, strlen(spec_op_defs[i].name), &nref, 0);
        if (code < 0)
            return code;
        if (name_eq(&opname, &nref)) {
            proc = i;
            break;
        }
    }

    if (proc < 0)
        return_error(gs_error_undefined);

    pop(1);     /* We don't need the name of the spec_op any more */
    op = osp;

    switch(proc) {
        case 0:
            {
                stack_param_list list;
                dev_param_req_t request;
                ref rkeys;
                /* Get a single device parameter, we should be supplied with
                 * the name of the paramter, as a name object.
                 */
                check_op(1);
                if (!r_has_type(op, t_name))
                    return_error(gs_error_typecheck);

                ref_assign(&opname, op);
                name_string_ref(imemory, &opname, &namestr);

                data = (char *)gs_alloc_bytes(imemory, r_size(&namestr) + 1, "temporary special_op string");
                if (data == 0)
                    return_error(gs_error_VMerror);
                memset(data, 0x00, r_size(&namestr) + 1);
                memcpy(data, namestr.value.bytes, r_size(&namestr));

                /* Discard the parameter name now, we're done with it */
                pop (1);
                /* Make a null object so that the stack param list won't check for requests */
                make_null(&rkeys);
                stack_param_list_write(&list, &o_stack, &rkeys, iimemory);
                /* Stuff the data into a structure for passing to the spec_op */
                request.Param = data;
                request.list = &list;

                code = dev_proc(dev, dev_spec_op)(dev, gxdso_get_dev_param, &request, sizeof(dev_param_req_t));

                gs_free_object(imemory, data, "temporary special_op string");

                if (code < 0) {
                    if (code == gs_error_undefined) {
                        op = osp;
                        push(1);
                        make_bool(op, 0);
                    } else
                        return_error(code);
                } else {
                    op = osp;
                    push(1);
                    make_bool(op, 1);
                }
            }
            break;
        default:
            /* Belt and braces; it shold not be possible to get here, as the table
             * containing the names should mirror the entries in this switch. If we
             * found a name there should be a matching case here.
             */
            return_error(gs_error_undefined);
            break;
    }
    return 0;
}


znulldevice(i_ctx_t *i_ctx_p)
{
    int code = gs_nulldevice(igs);
    invalidate_stack_devices(i_ctx_p);
    clear_pagedevice(istate);
    return code;
}


zgetdeviceparams(i_ctx_t *i_ctx_p)
{
    return zget_device_params(i_ctx_p, false);
}


zdevicename(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    const char *dname;

    check_read_type(*op, t_device);
    if (op->value.pdevice == NULL)
        /* This can happen if we invalidated devices on the stack by calling nulldevice after they were pushed */
        return_error(gs_error_undefined);

    dname = op->value.pdevice->dname;
    make_const_string(op, avm_foreign | a_readonly, strlen(dname),
                      (const byte *)dname);
    return 0;
}


zgetbitsrect(i_ctx_t *i_ctx_p)
{	/*
         * alpha? is 0 for no alpha, -1 for alpha first, 1 for alpha last.
         * std_depth is null for native pixels, depth/component for
         * standard color space.
         */
    os_ptr op = osp;
    gx_device *dev;
    gs_int_rect rect;
    gs_get_bits_params_t params;
    int w, h;
    gs_get_bits_options_t options =
        GB_ALIGN_ANY | GB_RETURN_COPY | GB_OFFSET_0 | GB_RASTER_STANDARD |
        GB_PACKING_CHUNKY;
    int depth;
    uint raster;
    int num_rows;
    int code;

    check_read_type(op[-7], t_device);
    dev = op[-7].value.pdevice;
    if (dev == NULL)
        /* This can happen if we invalidated devices on the stack by calling nulldevice after they were pushed */
        return_error(gs_error_undefined);

    check_int_leu(op[-6], dev->width);
    rect.p.x = op[-6].value.intval;
    check_int_leu(op[-5], dev->height);
    rect.p.y = op[-5].value.intval;
    check_int_leu(op[-4], dev->width);
    w = op[-4].value.intval;
    check_int_leu(op[-3], dev->height);
    h = op[-3].value.intval;
    check_type(op[-2], t_integer);
    /*
     * We use if/else rather than switch because the value is long,
     * which is not supported as a switch value in pre-ANSI C.
     */
    if (op[-2].value.intval == -1)
        options |= GB_ALPHA_FIRST;
    else if (op[-2].value.intval == 0)
        options |= GB_ALPHA_NONE;
    else if (op[-2].value.intval == 1)
        options |= GB_ALPHA_LAST;
    else
        return_error(gs_error_rangecheck);
    if (r_has_type(op - 1, t_null)) {
        options |= GB_COLORS_NATIVE;
        depth = dev->color_info.depth;
    } else {
        static const gs_get_bits_options_t depths[17] = {
            0, GB_DEPTH_1, GB_DEPTH_2, 0, GB_DEPTH_4, 0, 0, 0, GB_DEPTH_8,
            0, 0, 0, GB_DEPTH_12, 0, 0, 0, GB_DEPTH_16
        };
        gs_get_bits_options_t depth_option;
        int std_depth;

        check_int_leu(op[-1], 16);
        std_depth = (int)op[-1].value.intval;
        depth_option = depths[std_depth];
        if (depth_option == 0)
            return_error(gs_error_rangecheck);
        options |= depth_option | GB_COLORS_NATIVE;
        depth = (dev->color_info.num_components +
                 (options & GB_ALPHA_NONE ? 0 : 1)) * std_depth;
    }
    if (w == 0)
        return_error(gs_error_rangecheck);
    raster = (w * depth + 7) >> 3;
    check_write_type(*op, t_string);
    num_rows = r_size(op) / raster;
    h = min(h, num_rows);
    if (h == 0)
        return_error(gs_error_rangecheck);
    rect.q.x = rect.p.x + w;
    rect.q.y = rect.p.y + h;
    params.options = options;
    params.data[0] = op->value.bytes;
    code = (*dev_proc(dev, get_bits_rectangle))(dev, &rect, &params, NULL);
    if (code < 0)
        return code;
    make_int(op - 7, h);
    op[-6] = *op;
    r_set_size(op - 6, h * raster);
    pop(6);
    return 0;
}


zget_device_params(i_ctx_t *i_ctx_p, bool is_hardware)
{
    os_ptr op = osp;
    ref rkeys;
    gx_device *dev;
    stack_param_list list;
    int code;
    ref *pmark;

    check_read_type(op[-1], t_device);

    if(!r_has_type(op, t_null)) {
        check_type(*op, t_dictionary);
    }
    rkeys = *op;
    dev = op[-1].value.pdevice;
    if (op[-1].value.pdevice == NULL)
        /* This can happen if we invalidated devices on the stack by calling nulldevice after they were pushed */
        return_error(gs_error_undefined);
    pop(1);
    stack_param_list_write(&list, &o_stack, &rkeys, iimemory);
    code = gs_get_device_or_hardware_params(dev, (gs_param_list *) & list,
                                            is_hardware);
    if (code < 0) {
        /* We have to put back the top argument. */
        if (list.count > 0)
            ref_stack_pop(&o_stack, list.count * 2 - 1);
        else
            ref_stack_push(&o_stack, 1);
        *osp = rkeys;
        return code;
    }
    pmark = ref_stack_index(&o_stack, list.count * 2);
    make_mark(pmark);
    return 0;
}


zcurrentoutputdevice(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gx_device *odev = NULL, *dev = gs_currentdevice(igs);
    gs_ref_memory_t *mem = (gs_ref_memory_t *) dev->memory;
    int code = dev_proc(dev, dev_spec_op)(dev,
                        gxdso_current_output_device, (void *)&odev, 0);
    if (code < 0)
        return code;

    push(1);
    make_tav(op, t_device,
             (mem == 0 ? avm_foreign : imemory_space(mem)) | a_all,
             pdevice, odev);
    return 0;
}


zflushpage(i_ctx_t *i_ctx_p)
{
    return gs_flushpage(igs);
}


zdoneshowpage(i_ctx_t *i_ctx_p)
{
    gx_device *dev = gs_currentdevice(igs);
    gx_device *tdev = (*dev_proc(dev, get_page_device)) (dev);

    if (tdev != 0)
        tdev->ShowpageCount++;
    return 0;
}


zgetdevice(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    const gx_device *dev;

    check_type(*op, t_integer);
    if (op->value.intval != (int)(op->value.intval))
        return_error(gs_error_rangecheck);	/* won't fit in an int */
    dev = gs_getdevice((int)(op->value.intval));
    if (dev == 0)		/* index out of range */
        return_error(gs_error_rangecheck);
    /* Device prototypes are read-only; */
    /* the cast is logically unnecessary. */
    make_tav(op, t_device, avm_foreign | a_readonly, pdevice,
             (gx_device *) dev);
    return 0;
}


zgethardwareparams(i_ctx_t *i_ctx_p)
{
    return zget_device_params(i_ctx_p, true);
}


zgetdefaultdevice(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    const gx_device *dev;

    dev = gs_getdefaultlibdevice(imemory);
    if (dev == 0) /* couldn't find a default device */
        return_error(gs_error_unknownerror);
    push(1);
    make_tav(op, t_device, avm_foreign | a_readonly, pdevice,
                (gx_device *) dev);
    return 0;
}


zoutputpage(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    int code;

    check_type(op[-1], t_integer);
    check_type(*op, t_boolean);
    if (gs_debug[':']) {
        gs_main_instance *minst = get_minst_from_memory((gs_memory_t *)i_ctx_p->memory.current->non_gc_memory);

        print_resource_usage(minst, &(i_ctx_p->memory), "Outputpage start");
    }
    code = gs_output_page(igs, (int)op[-1].value.intval,
                          op->value.boolval);
    if (code < 0)
        return code;
    pop(2);
    if (gs_debug[':']) {
        gs_main_instance *minst = get_minst_from_memory((gs_memory_t *)i_ctx_p->memory.current->non_gc_memory);

        print_resource_usage(minst, &(i_ctx_p->memory), "Outputpage end");
    }
    return 0;
}


zmakewordimagedevice(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    os_ptr op1 = op - 1;
    gs_matrix imat;
    gx_device *new_dev;
    const byte *colors;
    int colors_size;
    int code;

    check_int_leu(op[-3], max_uint >> 1);	/* width */
    check_int_leu(op[-2], max_uint >> 1);	/* height */
    check_type(*op, t_boolean);
    if (r_has_type(op1, t_null)) {	/* true color */
        colors = 0;
        colors_size = -24;	/* 24-bit true color */
    } else if (r_has_type(op1, t_integer)) {
        /*
         * We use if/else rather than switch because the value is long,
         * which is not supported as a switch value in pre-ANSI C.
         */
        if (op1->value.intval != 16 && op1->value.intval != 24 &&
            op1->value.intval != 32
            )
            return_error(gs_error_rangecheck);
        colors = 0;
        colors_size = -op1->value.intval;
    } else {
        check_type(*op1, t_string);	/* palette */
        if (r_size(op1) > 3 * 256)
            return_error(gs_error_rangecheck);
        colors = op1->value.bytes;
        colors_size = r_size(op1);
    }
    if ((code = read_matrix(imemory, op - 4, &imat)) < 0)
        return code;
    /* Everything OK, create device */
    code = gs_makewordimagedevice(&new_dev, &imat,
                                  (int)op[-3].value.intval,
                                  (int)op[-2].value.intval,
                                  colors, colors_size,
                                  op->value.boolval, true, imemory);
    if (code == 0) {
        new_dev->memory = imemory;
        make_tav(op - 4, t_device, imemory_space(iimemory) | a_all,
                 pdevice, new_dev);
        pop(4);
    }
    return code;
}


zputdeviceparams(i_ctx_t *i_ctx_p)
{
    uint count = ref_stack_counttomark(&o_stack);
    ref *prequire_all;
    ref *ppolicy;
    ref *pdev;
    gx_device *dev;
    stack_param_list list;
    int code;
    int old_width, old_height;
    int i, dest;

    if (count == 0)
        return_error(gs_error_unmatchedmark);
    prequire_all = ref_stack_index(&o_stack, count);
    ppolicy = ref_stack_index(&o_stack, count + 1);
    pdev = ref_stack_index(&o_stack, count + 2);
    if (pdev == 0)
        return_error(gs_error_stackunderflow);
    check_type_only(*prequire_all, t_boolean);
    check_write_type_only(*pdev, t_device);
    dev = pdev->value.pdevice;
    if (dev == NULL)
        /* This can happen if we invalidated devices on the stack by calling nulldevice after they were pushed */
        return_error(gs_error_undefined);
    code = stack_param_list_read(&list, &o_stack, 0, ppolicy,
                                 prequire_all->value.boolval, iimemory);
    if (code < 0)
        return code;
    old_width = dev->width;
    old_height = dev->height;
    code = gs_putdeviceparams(dev, (gs_param_list *) & list);
    /* Check for names that were undefined or caused errors. */
    for (dest = count - 2, i = 0; i < count >> 1; i++)
        if (list.results[i] < 0) {
            *ref_stack_index(&o_stack, dest) =
                *ref_stack_index(&o_stack, count - (i << 1) - 2);
            gs_errorname(i_ctx_p, list.results[i],
                         ref_stack_index(&o_stack, dest - 1));
            dest -= 2;
        }
    iparam_list_release(&list);
    if (code < 0) {		/* There were errors reported. */
        ref_stack_pop(&o_stack, dest + 1);
        return (code == gs_error_Fatal) ? code : 0;	/* cannot continue from Fatal */
    }
    if (code > 0 || (code == 0 && (dev->width != old_width || dev->height != old_height))) {
        /*
         * The device was open and is now closed, or its dimensions have
         * changed.  If it was the current device, call setdevice to
         * reinstall it and erase the page.
         */
        /****** DOESN'T FIND ALL THE GSTATES THAT REFERENCE THE DEVICE. ******/
        if (gs_currentdevice(igs) == dev) {
            bool was_open = dev->is_open;

            code = gs_setdevice_no_erase(igs, dev);
            /* If the device wasn't closed, setdevice won't erase the page. */
            if (was_open && code >= 0)
                code = 1;
        }
    }
    if (code < 0)
        return code;
    ref_stack_pop(&o_stack, count + 1);
    make_bool(osp, code);
    clear_pagedevice(istate);
    return 0;
}


static void invalidate_stack_devices(i_ctx_t *i_ctx_p)
{
    os_ptr op = osbot;
    while (op != ostop) {
        if (r_has_type(op, t_device))
            op->value.pdevice = 0;
        op++;
    }
}


getcmdkeycmd(
	int		promptc UNUSED,
	void		*cookie UNUSED,
	int		indent UNUSED,
	getline_opt_T	do_concat UNUSED)
{
    garray_T	line_ga;
    int		c1 = -1;
    int		c2;
    int		cmod = 0;
    int		aborted = FALSE;

    ga_init2(&line_ga, 1, 32);

    // no mapping for these characters
    no_mapping++;

    got_int = FALSE;
    while (c1 != NUL && !aborted)
    {
	if (ga_grow(&line_ga, 32) != OK)
	{
	    aborted = TRUE;
	    break;
	}

	if (vgetorpeek(FALSE) == NUL)
	{
	    // incomplete <Cmd> is an error, because there is not much the user
	    // could do in this state.
	    emsg(_(e_cmd_mapping_must_end_with_cr));
	    aborted = TRUE;
	    break;
	}

	// Get one character at a time.
	c1 = vgetorpeek(TRUE);

	// Get two extra bytes for special keys
	if (c1 == K_SPECIAL)
	{
	    c1 = vgetorpeek(TRUE);
	    c2 = vgetorpeek(TRUE);
	    if (c1 == KS_MODIFIER)
	    {
		cmod = c2;
		continue;
	    }
	    c1 = TO_SPECIAL(c1, c2);
	}
	if (c1 == Ctrl_V)
	{
	    // CTRL-V is followed by octal, hex or other characters, reverses
	    // what AppendToRedobuffLit() does.
	    no_reduce_keys = TRUE;  //  don't merge modifyOtherKeys
	    c1 = get_literal(TRUE);
	    no_reduce_keys = FALSE;
	}

	if (got_int)
	    aborted = TRUE;
	else if (c1 == '\r' || c1 == '\n')
	    c1 = NUL;  // end the line
	else if (c1 == ESC)
	    aborted = TRUE;
	else if (c1 == K_COMMAND || c1 == K_SCRIPT_COMMAND)
	{
	    // give a nicer error message for this special case
	    emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
	    aborted = TRUE;
	}
	else if (IS_SPECIAL(c1))
	{
	    if (c1 == K_SNR)
		ga_concat(&line_ga, (char_u *)"<SNR>");
	    else
	    {
		semsg(e_cmd_maping_must_not_include_str_key,
					       get_special_key_name(c1, cmod));
		aborted = TRUE;
	    }
	}
	else
	    ga_append(&line_ga, c1);

	cmod = 0;
    }

    no_mapping--;

    if (aborted)
	ga_clear(&line_ga);

    return (char_u *)line_ga.ga_data;
}

patch 8.2.4233: crash when recording and using Select mode

Problem:    Crash when recording and using Select mode.
Solution:   When deleting the last recorded character check there is something
            to delete.

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


check_simplify_modifier(int max_offset)
{
    int		offset;
    char_u	*tp;

    for (offset = 0; offset < max_offset; ++offset)
    {
	if (offset + 3 >= typebuf.tb_len)
	    break;
	tp = typebuf.tb_buf + typebuf.tb_off + offset;
	if (tp[0] == K_SPECIAL && tp[1] == KS_MODIFIER)
	{
	    // A modifier was not used for a mapping, apply it to ASCII keys.
	    // Shift would already have been applied.
	    int modifier = tp[2];
	    int	c = tp[3];
	    int new_c = merge_modifyOtherKeys(c, &modifier);

	    if (new_c != c)
	    {
		char_u	new_string[MB_MAXBYTES];
		int	len;

		if (offset == 0)
		{
		    // At the start: remember the character and mod_mask before
		    // merging, in some cases, e.g. at the hit-return prompt,
		    // they are put back in the typeahead buffer.
		    vgetc_char = c;
		    vgetc_mod_mask = tp[2];
		}
		len = mb_char2bytes(new_c, new_string);
		if (modifier == 0)
		{
		    if (put_string_in_typebuf(offset, 4, new_string, len,
							   NULL, 0, 0) == FAIL)
		    return -1;
		}
		else
		{
		    tp[2] = modifier;
		    if (put_string_in_typebuf(offset + 3, 1, new_string, len,
							   NULL, 0, 0) == FAIL)
		    return -1;
		}
		return len;
	    }
	}
    }
    return 0;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


read_redo(int init, int old_redo)
{
    static buffblock_T	*bp;
    static char_u	*p;
    int			c;
    int			n;
    char_u		buf[MB_MAXBYTES + 1];
    int			i;

    if (init)
    {
	if (old_redo)
	    bp = old_redobuff.bh_first.b_next;
	else
	    bp = redobuff.bh_first.b_next;
	if (bp == NULL)
	    return FAIL;
	p = bp->b_str;
	return OK;
    }
    if ((c = *p) != NUL)
    {
	// Reverse the conversion done by add_char_buff()
	// For a multi-byte character get all the bytes and return the
	// converted character.
	if (has_mbyte && (c != K_SPECIAL || p[1] == KS_SPECIAL))
	    n = MB_BYTE2LEN_CHECK(c);
	else
	    n = 1;
	for (i = 0; ; ++i)
	{
	    if (c == K_SPECIAL) // special key or escaped K_SPECIAL
	    {
		c = TO_SPECIAL(p[1], p[2]);
		p += 2;
	    }
#ifdef FEAT_GUI
	    if (c == CSI)	// escaped CSI
		p += 2;
#endif
	    if (*++p == NUL && bp->b_next != NULL)
	    {
		bp = bp->b_next;
		p = bp->b_str;
	    }
	    buf[i] = c;
	    if (i == n - 1)	// last byte of a character
	    {
		if (n != 1)
		    c = (*mb_ptr2char)(buf);
		break;
	    }
	    c = *p;
	    if (c == NUL)	// cannot happen?
		break;
	}
    }

    return c;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static typebuf_T saved_typebuf[NSCRIPT];

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


merge_modifyOtherKeys(int c_arg, int *modifiers)
{
    int c = c_arg;

    if (*modifiers & MOD_MASK_CTRL)
    {
	if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_'))
	    c &= 0x1f;
	else if (c == '6')
	    // CTRL-6 is equivalent to CTRL-^
	    c = 0x1e;
#ifdef FEAT_GUI_GTK
	// These mappings look arbitrary at the first glance, but in fact
	// resemble quite exactly the behaviour of the GTK+ 1.2 GUI on my
	// machine.  The only difference is BS vs. DEL for CTRL-8 (makes
	// more sense and is consistent with usual terminal behaviour).
	else if (c == '2')
	    c = NUL;
	else if (c >= '3' && c <= '7')
	    c = c ^ 0x28;
	else if (c == '8')
	    c = BS;
	else if (c == '?')
	    c = DEL;
#endif
	if (c != c_arg)
	    *modifiers &= ~MOD_MASK_CTRL;
    }
    if ((*modifiers & (MOD_MASK_META | MOD_MASK_ALT))
	    && c >= 0 && c <= 127)
    {
	c += 0x80;
	*modifiers &= ~(MOD_MASK_META|MOD_MASK_ALT);
    }
    return c;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_SCRIPT	2	// tb_noremap: remap local script mappings

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


noremap_keys(void)
{
    return KeyNoremap & (RM_NONE|RM_SCRIPT);
}

static int	KeyNoremap = 0;

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_SCRIPT	2	// tb_noremap: remap local script mappings

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);


ins_char_typebuf(int c, int modifier)
{
    char_u	buf[MB_MAXBYTES * 3 + 4];
    int		len = 0;

    if (modifier != 0)
    {
	buf[0] = K_SPECIAL;
	buf[1] = KS_MODIFIER;
	buf[2] = modifier;
	buf[3] = NUL;
	len = 3;
    }
    if (IS_SPECIAL(c))
    {
	buf[len] = K_SPECIAL;
	buf[len + 1] = K_SECOND(c);
	buf[len + 2] = K_THIRD(c);
	buf[len + 3] = NUL;
	len += 3;
    }
    else
    {
	char_u	*p = buf + len;
	int	char_len = (*mb_char2bytes)(c, p);
#ifdef FEAT_GUI
	int	save_gui_in_use = gui.in_use;

	gui.in_use = FALSE;
#endif
	// if the character contains CSI or K_SPECIAL bytes they need escaping
	len += fix_input_buffer(p, char_len);
#ifdef FEAT_GUI
	gui.in_use = save_gui_in_use;
#endif
    }
    (void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
    return len;
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


f_getchar(typval_T *argvars, typval_T *rettv)
{
    getchar_common(argvars, rettv);
}

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

static void	init_typebuf(void);

static void	free_typebuf(void);

static void	closescript(void);

# define MAX_REPEAT_PARSE 8

static int	block_redo = FALSE;

static void	may_sync_undo(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int old_char = -1;

static int typeahead_char = 0;

static int	KeyNoremap = 0;

static int	last_recorded_len = 0;

static char_u	typebuf_init[TYPELEN_INIT];

mapblock_T	*last_used_map = NULL;

static typebuf_T saved_typebuf[NSCRIPT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	read_readbuf(buffheader_T *buf, int advance);

#define MINIMAL_SIZE 20			// minimal size for b_str

#define RM_YES		0	// tb_noremap: remap

#define TYPELEN_INIT	(5 * (MAXMAPLEN + 3))

static int	inchar(char_u *buf, int maxlen, long wait_time);

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_SCRIPT	2	// tb_noremap: remap local script mappings

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


saveRedobuff(save_redo_T *save_redo)
{
    char_u	*s;

    save_redo->sr_redobuff = redobuff;
    redobuff.bh_first.b_next = NULL;
    save_redo->sr_old_redobuff = old_redobuff;
    old_redobuff.bh_first.b_next = NULL;

    // Make a copy, so that ":normal ." in a function works.
    s = get_buffcont(&save_redo->sr_redobuff, FALSE);
    if (s != NULL)
    {
	add_buff(&redobuff, s, -1L);
	vim_free(s);
    }
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int	block_redo = FALSE;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

mapblock_T	*last_used_map = NULL;

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};


plain_vgetc(void)
{
    int c;

    do
	c = safe_vgetc();
    while (c == K_IGNORE
	    || c == K_VER_SCROLLBAR || c == K_HOR_SCROLLBAR
	    || c == K_MOUSEMOVE);

    if (c == K_PS)
	// Only handle the first pasted character.  Drop the rest, since we
	// don't know what to do with it.
	c = bracketed_paste(PASTE_ONE_CHAR, FALSE, NULL);

    return c;
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


start_redo_ins(void)
{
    int	    c;

    if (read_redo(TRUE, FALSE) == FAIL)
	return FAIL;
    start_stuff();

    // skip the count and the command character
    while ((c = read_redo(FALSE, FALSE)) != NUL)
    {
	if (vim_strchr((char_u *)"AaIiRrOo", c) != NULL)
	{
	    if (c == 'O' || c == 'o')
		add_buff(&readbuf2, NL_STR, -1L);
	    break;
	}
    }

    // copy the typed text from the redo buffer into the stuff buffer
    copy_redo(FALSE);
    block_redo = TRUE;
    return OK;
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


AppendToRedobuffLit(
    char_u	*str,
    int		len)	    // length of "str" or -1 for up to the NUL
{
    char_u	*s = str;
    int		c;
    char_u	*start;

    if (block_redo)
	return;

    while (len < 0 ? *s != NUL : s - str < len)
    {
	// Put a string of normal characters in the redo buffer (that's
	// faster).
	start = s;
	while (*s >= ' '
#ifndef EBCDIC
		&& *s < DEL	// EBCDIC: all chars above space are normal
#endif
		&& (len < 0 || s - str < len))
	    ++s;

	// Don't put '0' or '^' as last character, just in case a CTRL-D is
	// typed next.
	if (*s == NUL && (s[-1] == '0' || s[-1] == '^'))
	    --s;
	if (s > start)
	    add_buff(&redobuff, start, (long)(s - start));

	if (*s == NUL || (len >= 0 && s - str >= len))
	    break;

	// Handle a special or multibyte character.
	if (has_mbyte)
	    // Handle composing chars separately.
	    c = mb_cptr2char_adv(&s);
	else
	    c = *s++;
	if (c < ' ' || c == DEL || (*s == NUL && (c == '0' || c == '^')))
	    add_char_buff(&redobuff, Ctrl_V);

	// CTRL-V '0' must be inserted as CTRL-V 048 (EBCDIC: xf0)
	if (*s == NUL && c == '0')
#ifdef EBCDIC
	    add_buff(&redobuff, (char_u *)"xf0", 3L);
#else
	    add_buff(&redobuff, (char_u *)"048", 3L);
#endif
	else
	    add_char_buff(&redobuff, c);
    }
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


start_redo(long count, int old_redo)
{
    int	    c;

    // init the pointers; return if nothing to redo
    if (read_redo(TRUE, old_redo) == FAIL)
	return FAIL;

    c = read_redo(FALSE, old_redo);

    // copy the buffer name, if present
    if (c == '"')
    {
	add_buff(&readbuf2, (char_u *)"\"", 1L);
	c = read_redo(FALSE, old_redo);

	// if a numbered buffer is used, increment the number
	if (c >= '1' && c < '9')
	    ++c;
	add_char_buff(&readbuf2, c);

	// the expression register should be re-evaluated
	if (c == '=')
	{
	    add_char_buff(&readbuf2, CAR);
	    cmd_silent = TRUE;
	}

	c = read_redo(FALSE, old_redo);
    }

    if (c == 'v')   // redo Visual
    {
	VIsual = curwin->w_cursor;
	VIsual_active = TRUE;
	VIsual_select = FALSE;
	VIsual_reselect = TRUE;
	redo_VIsual_busy = TRUE;
	c = read_redo(FALSE, old_redo);
    }

    // try to enter the count (in place of a previous count)
    if (count)
    {
	while (VIM_ISDIGIT(c))	// skip "old" count
	    c = read_redo(FALSE, old_redo);
	add_num_buff(&readbuf2, count);
    }

    // copy from the redo buffer into the stuff buffer
    add_char_buff(&readbuf2, c);
    copy_redo(old_redo);
    return OK;
}

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


parse_queued_messages(void)
{
    int	    old_curwin_id;
    int	    old_curbuf_fnum;
    int	    i;
    int	    save_may_garbage_collect = may_garbage_collect;
    static int entered = 0;
    int	    was_safe = get_was_safe_state();

    // Do not handle messages while redrawing, because it may cause buffers to
    // change or be wiped while they are being redrawn.
    // Also bail out when parsing messages was explicitly disabled.
    if (updating_screen || dont_parse_messages)
	return;

    // If memory allocation fails during startup we'll exit but curbuf or
    // curwin could be NULL.
    if (curbuf == NULL || curwin == NULL)
       return;

    old_curbuf_fnum = curbuf->b_fnum;
    old_curwin_id = curwin->w_id;

    ++entered;

    // may_garbage_collect is set in main_loop() to do garbage collection when
    // blocking to wait on a character.  We don't want that while parsing
    // messages, a callback may invoke vgetc() while lists and dicts are in use
    // in the call stack.
    may_garbage_collect = FALSE;

    // Loop when a job ended, but don't keep looping forever.
    for (i = 0; i < MAX_REPEAT_PARSE; ++i)
    {
	// For Win32 mch_breakcheck() does not check for input, do it here.
# if (defined(MSWIN) || defined(__HAIKU__)) && defined(FEAT_JOB_CHANNEL)
	channel_handle_events(FALSE);
# endif

# ifdef FEAT_NETBEANS_INTG
	// Process the queued netbeans messages.
	netbeans_parse_messages();
# endif
# ifdef FEAT_JOB_CHANNEL
	// Write any buffer lines still to be written.
	channel_write_any_lines();

	// Process the messages queued on channels.
	channel_parse_messages();
# endif
# if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11)
	// Process the queued clientserver messages.
	server_parse_messages();
# endif
# ifdef FEAT_JOB_CHANNEL
	// Check if any jobs have ended.  If so, repeat the above to handle
	// changes, e.g. stdin may have been closed.
	if (job_check_ended())
	    continue;
# endif
# ifdef FEAT_TERMINAL
	free_unused_terminals();
# endif
# ifdef FEAT_SOUND_CANBERRA
	if (has_sound_callback_in_queue())
	    invoke_sound_callback();
# endif
#ifdef SIGUSR1
	if (got_sigusr1)
	{
	    apply_autocmds(EVENT_SIGUSR1, NULL, NULL, FALSE, curbuf);
	    got_sigusr1 = FALSE;
	}
#endif
	break;
    }

    // When not nested we'll go back to waiting for a typed character.  If it
    // was safe before then this triggers a SafeStateAgain autocommand event.
    if (entered == 1 && was_safe)
	may_trigger_safestateagain();

    may_garbage_collect = save_may_garbage_collect;

    // If the current window or buffer changed we need to bail out of the
    // waiting loop.  E.g. when a job exit callback closes the terminal window.
    if (curwin->w_id != old_curwin_id || curbuf->b_fnum != old_curbuf_fnum)
	ins_char_typebuf(K_IGNORE, 0);

    --entered;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static typebuf_T saved_typebuf[NSCRIPT];

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

# define MAX_REPEAT_PARSE 8

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


vgetc(void)
{
    int		c, c2;
    int		n;
    char_u	buf[MB_MAXBYTES + 1];
    int		i;

#ifdef FEAT_EVAL
    // Do garbage collection when garbagecollect() was called previously and
    // we are now at the toplevel.
    if (may_garbage_collect && want_garbage_collect)
	garbage_collect(FALSE);
#endif

    /*
     * If a character was put back with vungetc, it was already processed.
     * Return it directly.
     */
    if (old_char != -1)
    {
	c = old_char;
	old_char = -1;
	mod_mask = old_mod_mask;
	mouse_row = old_mouse_row;
	mouse_col = old_mouse_col;
    }
    else
    {
	mod_mask = 0;
	vgetc_mod_mask = 0;
	vgetc_char = 0;
	last_recorded_len = 0;

	for (;;)		// this is done twice if there are modifiers
	{
	    int did_inc = FALSE;

	    if (mod_mask
#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK)
		    || im_is_preediting()
#endif
#if defined(FEAT_PROP_POPUP)
		    || popup_no_mapping()
#endif
		    )
	    {
		// no mapping after modifier has been read
		++no_mapping;
		++allow_keys;
		did_inc = TRUE;	// mod_mask may change value
	    }
	    c = vgetorpeek(TRUE);
	    if (did_inc)
	    {
		--no_mapping;
		--allow_keys;
	    }

	    // Get two extra bytes for special keys
	    if (c == K_SPECIAL
#ifdef FEAT_GUI
		    || (c == CSI)
#endif
	       )
	    {
		int	    save_allow_keys = allow_keys;

		++no_mapping;
		allow_keys = 0;		// make sure BS is not found
		c2 = vgetorpeek(TRUE);	// no mapping for these chars
		c = vgetorpeek(TRUE);
		--no_mapping;
		allow_keys = save_allow_keys;
		if (c2 == KS_MODIFIER)
		{
		    mod_mask = c;
		    continue;
		}
		c = TO_SPECIAL(c2, c);

#if defined(FEAT_GUI_MSWIN) && defined(FEAT_MENU) && defined(FEAT_TEAROFF)
		// Handle K_TEAROFF here, the caller of vgetc() doesn't need to
		// know that a menu was torn off
		if (
# ifdef VIMDLL
		    gui.in_use &&
# endif
		    c == K_TEAROFF)
		{
		    char_u	name[200];
		    int		i;

		    // get menu path, it ends with a <CR>
		    for (i = 0; (c = vgetorpeek(TRUE)) != '\r'; )
		    {
			name[i] = c;
			if (i < 199)
			    ++i;
		    }
		    name[i] = NUL;
		    gui_make_tearoff(name);
		    continue;
		}
#endif
#if defined(FEAT_GUI) && defined(FEAT_GUI_GTK) && defined(FEAT_MENU)
		// GTK: <F10> normally selects the menu, but it's passed until
		// here to allow mapping it.  Intercept and invoke the GTK
		// behavior if it's not mapped.
		if (c == K_F10 && gui.menubar != NULL)
		{
		    gtk_menu_shell_select_first(
					   GTK_MENU_SHELL(gui.menubar), FALSE);
		    continue;
		}
#endif
#ifdef FEAT_GUI
		// Handle focus event here, so that the caller doesn't need to
		// know about it.  Return K_IGNORE so that we loop once (needed
		// if 'lazyredraw' is set).
		if (c == K_FOCUSGAINED || c == K_FOCUSLOST)
		{
		    ui_focus_change(c == K_FOCUSGAINED);
		    c = K_IGNORE;
		}

		// Translate K_CSI to CSI.  The special key is only used to
		// avoid it being recognized as the start of a special key.
		if (c == K_CSI)
		    c = CSI;
#endif
	    }
	    // a keypad or special function key was not mapped, use it like
	    // its ASCII equivalent
	    switch (c)
	    {
		case K_KPLUS:	c = '+'; break;
		case K_KMINUS:	c = '-'; break;
		case K_KDIVIDE:	c = '/'; break;
		case K_KMULTIPLY: c = '*'; break;
		case K_KENTER:	c = CAR; break;
		case K_KPOINT:
#ifdef MSWIN
				// Can be either '.' or a ',',
				// depending on the type of keypad.
				c = MapVirtualKey(VK_DECIMAL, 2); break;
#else
				c = '.'; break;
#endif
		case K_K0:	c = '0'; break;
		case K_K1:	c = '1'; break;
		case K_K2:	c = '2'; break;
		case K_K3:	c = '3'; break;
		case K_K4:	c = '4'; break;
		case K_K5:	c = '5'; break;
		case K_K6:	c = '6'; break;
		case K_K7:	c = '7'; break;
		case K_K8:	c = '8'; break;
		case K_K9:	c = '9'; break;

		case K_XHOME:
		case K_ZHOME:	if (mod_mask == MOD_MASK_SHIFT)
				{
				    c = K_S_HOME;
				    mod_mask = 0;
				}
				else if (mod_mask == MOD_MASK_CTRL)
				{
				    c = K_C_HOME;
				    mod_mask = 0;
				}
				else
				    c = K_HOME;
				break;
		case K_XEND:
		case K_ZEND:	if (mod_mask == MOD_MASK_SHIFT)
				{
				    c = K_S_END;
				    mod_mask = 0;
				}
				else if (mod_mask == MOD_MASK_CTRL)
				{
				    c = K_C_END;
				    mod_mask = 0;
				}
				else
				    c = K_END;
				break;

		case K_XUP:	c = K_UP; break;
		case K_XDOWN:	c = K_DOWN; break;
		case K_XLEFT:	c = K_LEFT; break;
		case K_XRIGHT:	c = K_RIGHT; break;
	    }

	    // For a multi-byte character get all the bytes and return the
	    // converted character.
	    // Note: This will loop until enough bytes are received!
	    if (has_mbyte && (n = MB_BYTE2LEN_CHECK(c)) > 1)
	    {
		++no_mapping;
		buf[0] = c;
		for (i = 1; i < n; ++i)
		{
		    buf[i] = vgetorpeek(TRUE);
		    if (buf[i] == K_SPECIAL
#ifdef FEAT_GUI
			    || (buf[i] == CSI)
#endif
			    )
		    {
			// Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER
			// sequence, which represents a K_SPECIAL (0x80),
			// or a CSI - KS_EXTRA - KE_CSI sequence, which
			// represents a CSI (0x9B),
			// or a K_SPECIAL - KS_EXTRA - KE_CSI, which is CSI
			// too.
			c = vgetorpeek(TRUE);
			if (vgetorpeek(TRUE) == KE_CSI && c == KS_EXTRA)
			    buf[i] = CSI;
		    }
		}
		--no_mapping;
		c = (*mb_ptr2char)(buf);
	    }

	    if (vgetc_char == 0)
	    {
		vgetc_mod_mask = mod_mask;
		vgetc_char = c;
	    }

	    break;
	}
    }

#ifdef FEAT_EVAL
    /*
     * In the main loop "may_garbage_collect" can be set to do garbage
     * collection in the first next vgetc().  It's disabled after that to
     * avoid internally used Lists and Dicts to be freed.
     */
    may_garbage_collect = FALSE;
#endif

#ifdef FEAT_BEVAL_TERM
    if (c != K_MOUSEMOVE && c != K_IGNORE && c != K_CURSORHOLD)
    {
	// Don't trigger 'balloonexpr' unless only the mouse was moved.
	bevalexpr_due_set = FALSE;
	ui_remove_balloon();
    }
#endif
#ifdef FEAT_PROP_POPUP
    // Only filter keys that do not come from ":normal".  Keys from feedkeys()
    // are filtered.
    if ((!ex_normal_busy || in_feedkeys) && popup_do_filter(c))
    {
	if (c == Ctrl_C)
	    got_int = FALSE;  // avoid looping
	c = K_IGNORE;
    }
#endif

    // Need to process the character before we know it's safe to do something
    // else.
    if (c != K_IGNORE)
	state_no_longer_safe("key typed");

    return c;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


char_avail(void)
{
    int	    retval;

#ifdef FEAT_EVAL
    // When test_override("char_avail", 1) was called pretend there is no
    // typeahead.
    if (disable_char_avail_for_testing)
	return FALSE;
#endif
    ++no_mapping;
    retval = vpeekc();
    --no_mapping;
    return (retval != NUL);
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


typebuf_changed(
    int		tb_change_cnt)	// old value of typebuf.tb_change_cnt
{
    return (tb_change_cnt != 0 && (typebuf.tb_change_cnt != tb_change_cnt
#if defined(FEAT_CLIENTSERVER) || defined(FEAT_EVAL)
	    || typebuf_was_filled
#endif
	   ));
}

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


stuffescaped(char_u *arg, int literally)
{
    int		c;
    char_u	*start;

    while (*arg != NUL)
    {
	// Stuff a sequence of normal ASCII characters, that's fast.  Also
	// stuff K_SPECIAL to get the effect of a special key when "literally"
	// is TRUE.
	start = arg;
	while ((*arg >= ' '
#ifndef EBCDIC
		    && *arg < DEL // EBCDIC: chars above space are normal
#endif
		    )
		|| (*arg == K_SPECIAL && !literally))
	    ++arg;
	if (arg > start)
	    stuffReadbuffLen(start, (long)(arg - start));

	// stuff a single special character
	if (*arg != NUL)
	{
	    if (has_mbyte)
		c = mb_cptr2char_adv(&arg);
	    else
		c = *arg++;
	    if (literally && ((c < ' ' && c != TAB) || c == DEL))
		stuffcharReadbuff(Ctrl_V);
	    stuffcharReadbuff(c);
	}
    }
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


del_typebuf(int len, int offset)
{
    int	    i;

    if (len == 0)
	return;		// nothing to do

    typebuf.tb_len -= len;

    /*
     * Easy case: Just increase typebuf.tb_off.
     */
    if (offset == 0 && typebuf.tb_buflen - (typebuf.tb_off + len)
							 >= 3 * MAXMAPLEN + 3)
	typebuf.tb_off += len;
    /*
     * Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[]
     */
    else
    {
	i = typebuf.tb_off + offset;
	/*
	 * Leave some extra room at the end to avoid reallocation.
	 */
	if (typebuf.tb_off > MAXMAPLEN)
	{
	    mch_memmove(typebuf.tb_buf + MAXMAPLEN,
			     typebuf.tb_buf + typebuf.tb_off, (size_t)offset);
	    mch_memmove(typebuf.tb_noremap + MAXMAPLEN,
			 typebuf.tb_noremap + typebuf.tb_off, (size_t)offset);
	    typebuf.tb_off = MAXMAPLEN;
	}
	// adjust typebuf.tb_buf (include the NUL at the end)
	mch_memmove(typebuf.tb_buf + typebuf.tb_off + offset,
						     typebuf.tb_buf + i + len,
				       (size_t)(typebuf.tb_len - offset + 1));
	// adjust typebuf.tb_noremap[]
	mch_memmove(typebuf.tb_noremap + typebuf.tb_off + offset,
						 typebuf.tb_noremap + i + len,
					   (size_t)(typebuf.tb_len - offset));
    }

    if (typebuf.tb_maplen > offset)		// adjust tb_maplen
    {
	if (typebuf.tb_maplen < offset + len)
	    typebuf.tb_maplen = offset;
	else
	    typebuf.tb_maplen -= len;
    }
    if (typebuf.tb_silent > offset)		// adjust tb_silent
    {
	if (typebuf.tb_silent < offset + len)
	    typebuf.tb_silent = offset;
	else
	    typebuf.tb_silent -= len;
    }
    if (typebuf.tb_no_abbr_cnt > offset)	// adjust tb_no_abbr_cnt
    {
	if (typebuf.tb_no_abbr_cnt < offset + len)
	    typebuf.tb_no_abbr_cnt = offset;
	else
	    typebuf.tb_no_abbr_cnt -= len;
    }

#if defined(FEAT_CLIENTSERVER) || defined(FEAT_EVAL)
    // Reset the flag that text received from a client or from feedkeys()
    // was inserted in the typeahead buffer.
    typebuf_was_filled = FALSE;
#endif
    if (++typebuf.tb_change_cnt == 0)
	typebuf.tb_change_cnt = 1;
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_YES		0	// tb_noremap: remap

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_SCRIPT	2	// tb_noremap: remap local script mappings

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

#define TYPELEN_INIT	(5 * (MAXMAPLEN + 3))

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


restore_typeahead(tasave_T *tp, int overwrite UNUSED)
{
    if (tp->typebuf_valid)
    {
	free_typebuf();
	typebuf = tp->save_typebuf;
    }

    old_char = tp->old_char;
    old_mod_mask = tp->old_mod_mask;

    free_buff(&readbuf1);
    readbuf1 = tp->save_readbuf1;
    free_buff(&readbuf2);
    readbuf2 = tp->save_readbuf2;
# ifdef USE_INPUT_BUF
    set_input_buf(tp->save_inputbuf, overwrite);
# endif
}

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	free_typebuf(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


getchar_common(typval_T *argvars, typval_T *rettv)
{
    varnumber_T		n;
    int			error = FALSE;

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

#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_bool_chk(&argvars[0], &error))
	    // 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() != NUL: get a character.
	    // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE.
	    n = safe_vgetc();

	if (n == K_IGNORE || n == K_MOUSEMOVE
		|| n == K_VER_SCROLLBAR || n == K_HOR_SCROLLBAR)
	    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);
	}
	else if (has_mbyte)
	    i += (*mb_char2bytes)(n, temp + i);
	else
	    temp[i++] = n;
	temp[i++] = NUL;
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = vim_strsave(temp);

	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, FIND_POPUP);
		if (win == NULL)
		    return;
		(void)mouse_comp_pos(win, &row, &col, &lnum, NULL);
#ifdef FEAT_PROP_POPUP
		if (WIN_IS_POPUP(win))
		    winnr = 0;
		else
#endif
		    for (wp = firstwin; wp != win && wp != NULL;
							       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);
	    }
	}
    }
}


add_char_buff(buffheader_T *buf, int c)
{
    char_u	bytes[MB_MAXBYTES + 1];
    int		len;
    int		i;
    char_u	temp[4];

    if (IS_SPECIAL(c))
	len = 1;
    else
	len = (*mb_char2bytes)(c, bytes);
    for (i = 0; i < len; ++i)
    {
	if (!IS_SPECIAL(c))
	    c = bytes[i];

	if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL)
	{
	    // translate special key code into three byte sequence
	    temp[0] = K_SPECIAL;
	    temp[1] = K_SECOND(c);
	    temp[2] = K_THIRD(c);
	    temp[3] = NUL;
	}
#ifdef FEAT_GUI
	else if (c == CSI)
	{
	    // Translate a CSI to a CSI - KS_EXTRA - KE_CSI sequence
	    temp[0] = CSI;
	    temp[1] = KS_EXTRA;
	    temp[2] = (int)KE_CSI;
	    temp[3] = NUL;
	}
#endif
	else
	{
	    temp[0] = c;
	    temp[1] = NUL;
	}
	add_buff(buf, temp, -1L);
    }
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


fix_input_buffer(char_u *buf, int len)
{
    int		i;
    char_u	*p = buf;

    /*
     * Two characters are special: NUL and K_SPECIAL.
     * When compiled With the GUI CSI is also special.
     * Replace	     NUL by K_SPECIAL KS_ZERO	 KE_FILLER
     * Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER
     * Replace       CSI by K_SPECIAL KS_EXTRA   KE_CSI
     */
    for (i = len; --i >= 0; ++p)
    {
#ifdef FEAT_GUI
	// When the GUI is used any character can come after a CSI, don't
	// escape it.
	if (gui.in_use && p[0] == CSI && i >= 2)
	{
	    p += 2;
	    i -= 2;
	}
	// When the GUI is not used CSI needs to be escaped.
	else if (!gui.in_use && p[0] == CSI)
	{
	    mch_memmove(p + 3, p + 1, (size_t)i);
	    *p++ = K_SPECIAL;
	    *p++ = KS_EXTRA;
	    *p = (int)KE_CSI;
	    len += 2;
	}
	else
#endif
	if (p[0] == NUL || (p[0] == K_SPECIAL
		    // timeout may generate K_CURSORHOLD
		    && (i < 2 || p[1] != KS_EXTRA || p[2] != (int)KE_CURSORHOLD)
#if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL))
		    // Win32 console passes modifiers
		    && (
# ifdef VIMDLL
			gui.in_use ||
# endif
			(i < 2 || p[1] != KS_MODIFIER))
#endif
		    ))
	{
	    mch_memmove(p + 3, p + 1, (size_t)i);
	    p[2] = K_THIRD(p[0]);
	    p[1] = K_SECOND(p[0]);
	    p[0] = K_SPECIAL;
	    p += 2;
	    len += 2;
	}
    }
    *p = NUL;		// add trailing NUL
    return len;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


add_buff(
    buffheader_T	*buf,
    char_u		*s,
    long		slen)	// length of "s" or -1
{
    buffblock_T *p;
    long_u	    len;

    if (slen < 0)
	slen = (long)STRLEN(s);
    if (slen == 0)				// don't add empty strings
	return;

    if (buf->bh_first.b_next == NULL)	// first add to list
    {
	buf->bh_space = 0;
	buf->bh_curr = &(buf->bh_first);
    }
    else if (buf->bh_curr == NULL)	// buffer has already been read
    {
	iemsg(_(e_add_to_internal_buffer_that_was_already_read_from));
	return;
    }
    else if (buf->bh_index != 0)
	mch_memmove(buf->bh_first.b_next->b_str,
		    buf->bh_first.b_next->b_str + buf->bh_index,
		    STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1);
    buf->bh_index = 0;

    if (buf->bh_space >= (int)slen)
    {
	len = (long_u)STRLEN(buf->bh_curr->b_str);
	vim_strncpy(buf->bh_curr->b_str + len, s, (size_t)slen);
	buf->bh_space -= slen;
    }
    else
    {
	if (slen < MINIMAL_SIZE)
	    len = MINIMAL_SIZE;
	else
	    len = slen;
	p = alloc(offsetof(buffblock_T, b_str) + len + 1);
	if (p == NULL)
	    return; // no space, just forget it
	buf->bh_space = (int)(len - slen);
	vim_strncpy(p->b_str, s, (size_t)slen);

	p->b_next = buf->bh_curr->b_next;
	buf->bh_curr->b_next = p;
	buf->bh_curr = p;
    }
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


read_readbuf(buffheader_T *buf, int advance)
{
    char_u	c;
    buffblock_T	*curr;

    if (buf->bh_first.b_next == NULL)  // buffer is empty
	return NUL;

    curr = buf->bh_first.b_next;
    c = curr->b_str[buf->bh_index];

    if (advance)
    {
	if (curr->b_str[++buf->bh_index] == NUL)
	{
	    buf->bh_first.b_next = curr->b_next;
	    vim_free(curr);
	    buf->bh_index = 0;
	}
    }
    return c;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


save_typebuf(void)
{
    init_typebuf();
    saved_typebuf[curscript] = typebuf;
    // If out of memory: restore typebuf and close file.
    if (alloc_typebuf() == FAIL)
    {
	closescript();
	return FAIL;
    }
    return OK;
}

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static typebuf_T saved_typebuf[NSCRIPT];


do_cmdkey_command(int key UNUSED, int flags)
{
    int	    res;
#ifdef FEAT_EVAL
    sctx_T  save_current_sctx = {-1, 0, 0, 0};

    if (key == K_SCRIPT_COMMAND && last_used_map != NULL)
    {
	save_current_sctx = current_sctx;
	current_sctx = last_used_map->m_script_ctx;
    }
#endif

    res = do_cmdline(NULL, getcmdkeycmd, NULL, flags);

#ifdef FEAT_EVAL
    if (save_current_sctx.sc_sid >= 0)
	current_sctx = save_current_sctx;
#endif

    return res;
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


ungetchars(int len)
{
    if (reg_recording != 0)
    {
	delete_buff_tail(&recordbuff, len);
	last_recorded_len -= len;
    }
}

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


handle_mapping(
	    int *keylenp,
	    int *timedout,
	    int *mapdepth)
{
    mapblock_T	*mp = NULL;
    mapblock_T	*mp2;
    mapblock_T	*mp_match;
    int		mp_match_len = 0;
    int		max_mlen = 0;
    int		tb_c1;
    int		mlen;
#ifdef FEAT_LANGMAP
    int		nolmaplen;
#endif
    int		keylen = *keylenp;
    int		i;
    int		local_State = get_real_state();

    /*
     * Check for a mappable key sequence.
     * Walk through one maphash[] list until we find an entry that matches.
     *
     * Don't look for mappings if:
     * - no_mapping set: mapping disabled (e.g. for CTRL-V)
     * - maphash_valid not set: no mappings present.
     * - typebuf.tb_buf[typebuf.tb_off] should not be remapped
     * - in insert or cmdline mode and 'paste' option set
     * - waiting for "hit return to continue" and CR or SPACE typed
     * - waiting for a char with --more--
     * - in Ctrl-X mode, and we get a valid char for that mode
     */
    tb_c1 = typebuf.tb_buf[typebuf.tb_off];
    if (no_mapping == 0 && is_maphash_valid()
	    && (no_zero_mapping == 0 || tb_c1 != '0')
	    && (typebuf.tb_maplen == 0
		|| (p_remap
		    && (typebuf.tb_noremap[typebuf.tb_off]
				    & (RM_NONE|RM_ABBR)) == 0))
	    && !(p_paste && (State & (INSERT + CMDLINE)))
	    && !(State == HITRETURN && (tb_c1 == CAR || tb_c1 == ' '))
	    && State != ASKMORE
	    && State != CONFIRM
	    && !((ctrl_x_mode_not_default() && at_ctrl_x_key())
		    || (compl_status_local()
			&& (tb_c1 == Ctrl_N || tb_c1 == Ctrl_P))))
    {
#ifdef FEAT_GUI
	if (gui.in_use && tb_c1 == CSI && typebuf.tb_len >= 2
		&& typebuf.tb_buf[typebuf.tb_off + 1] == KS_MODIFIER)
	{
	    // The GUI code sends CSI KS_MODIFIER {flags}, but mappings expect
	    // K_SPECIAL KS_MODIFIER {flags}.
	    tb_c1 = K_SPECIAL;
	}
#endif
#ifdef FEAT_LANGMAP
	if (tb_c1 == K_SPECIAL)
	    nolmaplen = 2;
	else
	{
	    LANGMAP_ADJUST(tb_c1, (State & (CMDLINE | INSERT)) == 0
					    && get_real_state() != SELECTMODE);
	    nolmaplen = 0;
	}
#endif
	// First try buffer-local mappings.
	mp = get_buf_maphash_list(local_State, tb_c1);
	mp2 = get_maphash_list(local_State, tb_c1);
	if (mp == NULL)
	{
	    // There are no buffer-local mappings.
	    mp = mp2;
	    mp2 = NULL;
	}

	/*
	 * Loop until a partly matching mapping is found or all (local)
	 * mappings have been checked.
	 * The longest full match is remembered in "mp_match".
	 * A full match is only accepted if there is no partly match, so "aa"
	 * and "aaa" can both be mapped.
	 */
	mp_match = NULL;
	mp_match_len = 0;
	for ( ; mp != NULL;
	       mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next))
	{
	    // Only consider an entry if the first character matches and it is
	    // for the current state.
	    // Skip ":lmap" mappings if keys were mapped.
	    if (mp->m_keys[0] == tb_c1
		    && (mp->m_mode & local_State)
		    && !(mp->m_simplified && seenModifyOtherKeys
						     && typebuf.tb_maplen == 0)
		    && ((mp->m_mode & LANGMAP) == 0 || typebuf.tb_maplen == 0))
	    {
#ifdef FEAT_LANGMAP
		int	nomap = nolmaplen;
		int	c2;
#endif
		// find the match length of this mapping
		for (mlen = 1; mlen < typebuf.tb_len; ++mlen)
		{
#ifdef FEAT_LANGMAP
		    c2 = typebuf.tb_buf[typebuf.tb_off + mlen];
		    if (nomap > 0)
			--nomap;
		    else if (c2 == K_SPECIAL)
			nomap = 2;
		    else
			LANGMAP_ADJUST(c2, TRUE);
		    if (mp->m_keys[mlen] != c2)
#else
		    if (mp->m_keys[mlen] !=
					 typebuf.tb_buf[typebuf.tb_off + mlen])
#endif
			break;
		}

		// Don't allow mapping the first byte(s) of a multi-byte char.
		// Happens when mapping <M-a> and then changing 'encoding'.
		// Beware that 0x80 is escaped.
		{
		    char_u *p1 = mp->m_keys;
		    char_u *p2 = mb_unescape(&p1);

		    if (has_mbyte && p2 != NULL
					&& MB_BYTE2LEN(tb_c1) > mb_ptr2len(p2))
			mlen = 0;
		}

		// Check an entry whether it matches.
		// - Full match: mlen == keylen
		// - Partly match: mlen == typebuf.tb_len
		keylen = mp->m_keylen;
		if (mlen == keylen || (mlen == typebuf.tb_len
						   && typebuf.tb_len < keylen))
		{
		    char_u  *s;
		    int	    n;

		    // If only script-local mappings are allowed, check if the
		    // mapping starts with K_SNR.
		    s = typebuf.tb_noremap + typebuf.tb_off;
		    if (*s == RM_SCRIPT
			    && (mp->m_keys[0] != K_SPECIAL
				|| mp->m_keys[1] != KS_EXTRA
				|| mp->m_keys[2] != KE_SNR))
			continue;

		    // If one of the typed keys cannot be remapped, skip the
		    // entry.
		    for (n = mlen; --n >= 0; )
			if (*s++ & (RM_NONE|RM_ABBR))
			    break;
		    if (n >= 0)
			continue;

		    if (keylen > typebuf.tb_len)
		    {
			if (!*timedout && !(mp_match != NULL
							&& mp_match->m_nowait))
			{
			    // break at a partly match
			    keylen = KEYLEN_PART_MAP;
			    break;
			}
		    }
		    else if (keylen > mp_match_len)
		    {
			// found a longer match
			mp_match = mp;
			mp_match_len = keylen;
		    }
		}
		else
		    // No match; may have to check for termcode at next
		    // character.
		    if (max_mlen < mlen)
			max_mlen = mlen;
	    }
	}

	// If no partly match found, use the longest full match.
	if (keylen != KEYLEN_PART_MAP)
	{
	    mp = mp_match;
	    keylen = mp_match_len;
	}
    }

    /*
     * Check for match with 'pastetoggle'
     */
    if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL)))
    {
	for (mlen = 0; mlen < typebuf.tb_len && p_pt[mlen]; ++mlen)
	    if (p_pt[mlen] != typebuf.tb_buf[typebuf.tb_off + mlen])
		    break;
	if (p_pt[mlen] == NUL)	// match
	{
	    // write chars to script file(s)
	    if (mlen > typebuf.tb_maplen)
		gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen,
						     mlen - typebuf.tb_maplen);

	    del_typebuf(mlen, 0); // remove the chars
	    set_option_value((char_u *)"paste", (long)!p_paste, NULL, 0);
	    if (!(State & INSERT))
	    {
		msg_col = 0;
		msg_row = Rows - 1;
		msg_clr_eos();		// clear ruler
	    }
	    status_redraw_all();
	    redraw_statuslines();
	    showmode();
	    setcursor();
	    *keylenp = keylen;
	    return map_result_retry;
	}
	// Need more chars for partly match.
	if (mlen == typebuf.tb_len)
	    keylen = KEYLEN_PART_KEY;
	else if (max_mlen < mlen)
	    // no match, may have to check for termcode at next character
	    max_mlen = mlen + 1;
    }

    if ((mp == NULL || max_mlen >= mp_match_len) && keylen != KEYLEN_PART_MAP)
    {
	int	save_keylen = keylen;

	/*
	 * When no matching mapping found or found a non-matching mapping that
	 * matches at least what the matching mapping matched:
	 * Check if we have a terminal code, when:
	 * - mapping is allowed,
	 * - keys have not been mapped,
	 * - and not an ESC sequence, not in insert mode or p_ek is on,
	 * - and when not timed out,
	 */
	if ((no_mapping == 0 || allow_keys != 0)
		&& (typebuf.tb_maplen == 0
		    || (p_remap && typebuf.tb_noremap[
						    typebuf.tb_off] == RM_YES))
		&& !*timedout)
	{
	    keylen = check_termcode(max_mlen + 1, NULL, 0, NULL);

	    // If no termcode matched but 'pastetoggle' matched partially
	    // it's like an incomplete key sequence.
	    if (keylen == 0 && save_keylen == KEYLEN_PART_KEY)
		keylen = KEYLEN_PART_KEY;

	    // If no termcode matched, try to include the modifier into the
	    // key.  This is for when modifyOtherKeys is working.
	    if (keylen == 0 && !no_reduce_keys)
		keylen = check_simplify_modifier(max_mlen + 1);

	    // When getting a partial match, but the last characters were not
	    // typed, don't wait for a typed character to complete the
	    // termcode.  This helps a lot when a ":normal" command ends in an
	    // ESC.
	    if (keylen < 0 && typebuf.tb_len == typebuf.tb_maplen)
		keylen = 0;
	}
	else
	    keylen = 0;
	if (keylen == 0)	// no matching terminal code
	{
#ifdef AMIGA
	    // check for window bounds report
	    if (typebuf.tb_maplen == 0 && (typebuf.tb_buf[
						typebuf.tb_off] & 0xff) == CSI)
	    {
		char_u *s;

		for (s = typebuf.tb_buf + typebuf.tb_off + 1;
			   s < typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len
		   && (VIM_ISDIGIT(*s) || *s == ';' || *s == ' ');
			++s)
		    ;
		if (*s == 'r' || *s == '|') // found one
		{
		    del_typebuf(
			  (int)(s + 1 - (typebuf.tb_buf + typebuf.tb_off)), 0);
		    // get size and redraw screen
		    shell_resized();
		    *keylenp = keylen;
		    return map_result_retry;
		}
		if (*s == NUL)	    // need more characters
		    keylen = KEYLEN_PART_KEY;
	    }
	    if (keylen >= 0)
#endif
		// When there was a matching mapping and no termcode could be
		// replaced after another one, use that mapping (loop around).
		// If there was no mapping at all use the character from the
		// typeahead buffer right here.
		if (mp == NULL)
		{
		    *keylenp = keylen;
		    return map_result_get;    // get character from typeahead
		}
	}

	if (keylen > 0)	    // full matching terminal code
	{
#if defined(FEAT_GUI) && defined(FEAT_MENU)
	    if (typebuf.tb_len >= 2
		    && typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL
			      && typebuf.tb_buf[typebuf.tb_off + 1] == KS_MENU)
	    {
		int	idx;

		// Using a menu may cause a break in undo!  It's like using
		// gotchars(), but without recording or writing to a script
		// file.
		may_sync_undo();
		del_typebuf(3, 0);
		idx = get_menu_index(current_menu, local_State);
		if (idx != MENU_INDEX_INVALID)
		{
		    // In Select mode and a Visual mode menu is used:  Switch
		    // to Visual mode temporarily.  Append K_SELECT to switch
		    // back to Select mode.
		    if (VIsual_active && VIsual_select
					     && (current_menu->modes & VISUAL))
		    {
			VIsual_select = FALSE;
			(void)ins_typebuf(K_SELECT_STRING,
						   REMAP_NONE, 0, TRUE, FALSE);
		    }
		    ins_typebuf(current_menu->strings[idx],
				current_menu->noremap[idx],
				0, TRUE, current_menu->silent[idx]);
		}
	    }
#endif // FEAT_GUI && FEAT_MENU
	    *keylenp = keylen;
	    return map_result_retry;	// try mapping again
	}

	// Partial match: get some more characters.  When a matching mapping
	// was found use that one.
	if (mp == NULL || keylen < 0)
	    keylen = KEYLEN_PART_KEY;
	else
	    keylen = mp_match_len;
    }

    /*
     * complete match
     */
    if (keylen >= 0 && keylen <= typebuf.tb_len)
    {
	char_u *map_str;

#ifdef FEAT_EVAL
	int	save_m_expr;
	int	save_m_noremap;
	int	save_m_silent;
	char_u	*save_m_keys;
#else
# define save_m_noremap mp->m_noremap
# define save_m_silent mp->m_silent
#endif

	// write chars to script file(s)
	if (keylen > typebuf.tb_maplen)
	    gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen,
						   keylen - typebuf.tb_maplen);

	cmd_silent = (typebuf.tb_silent > 0);
	del_typebuf(keylen, 0);	// remove the mapped keys

	/*
	 * Put the replacement string in front of mapstr.
	 * The depth check catches ":map x y" and ":map y x".
	 */
	if (++*mapdepth >= p_mmd)
	{
	    emsg(_(e_recursive_mapping));
	    if (State & CMDLINE)
		redrawcmdline();
	    else
		setcursor();
	    flush_buffers(FLUSH_MINIMAL);
	    *mapdepth = 0;	// for next one
	    *keylenp = keylen;
	    return map_result_fail;
	}

	/*
	 * In Select mode and a Visual mode mapping is used: Switch to Visual
	 * mode temporarily.  Append K_SELECT to switch back to Select mode.
	 */
	if (VIsual_active && VIsual_select && (mp->m_mode & VISUAL))
	{
	    VIsual_select = FALSE;
	    (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, TRUE, FALSE);
	}

#ifdef FEAT_EVAL
	// Copy the values from *mp that are used, because evaluating the
	// expression may invoke a function that redefines the mapping, thereby
	// making *mp invalid.
	save_m_expr = mp->m_expr;
	save_m_noremap = mp->m_noremap;
	save_m_silent = mp->m_silent;
	save_m_keys = NULL;  // only saved when needed

	/*
	 * Handle ":map <expr>": evaluate the {rhs} as an expression.  Also
	 * save and restore the command line for "normal :".
	 */
	if (mp->m_expr)
	{
	    int save_vgetc_busy = vgetc_busy;
	    int save_may_garbage_collect = may_garbage_collect;
	    int was_screen_col = screen_cur_col;
	    int was_screen_row = screen_cur_row;

	    vgetc_busy = 0;
	    may_garbage_collect = FALSE;

	    save_m_keys = vim_strsave(mp->m_keys);
	    map_str = eval_map_expr(mp, NUL);

	    // The mapping may do anything, but we expect it to take care of
	    // redrawing.  Do put the cursor back where it was.
	    windgoto(was_screen_row, was_screen_col);
	    out_flush();

	    vgetc_busy = save_vgetc_busy;
	    may_garbage_collect = save_may_garbage_collect;
	}
	else
#endif
	    map_str = mp->m_str;

	/*
	 * Insert the 'to' part in the typebuf.tb_buf.
	 * If 'from' field is the same as the start of the 'to' field, don't
	 * remap the first character (but do allow abbreviations).
	 * If m_noremap is set, don't remap the whole 'to' part.
	 */
	if (map_str == NULL)
	    i = FAIL;
	else
	{
	    int noremap;

	    if (save_m_noremap != REMAP_YES)
		noremap = save_m_noremap;
	    else if (
#ifdef FEAT_EVAL
		STRNCMP(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys,
								(size_t)keylen)
#else
		STRNCMP(map_str, mp->m_keys, (size_t)keylen)
#endif
		   != 0)
		noremap = REMAP_YES;
	    else
		noremap = REMAP_SKIP;
	    i = ins_typebuf(map_str, noremap,
					 0, TRUE, cmd_silent || save_m_silent);
#ifdef FEAT_EVAL
	    if (save_m_expr)
		vim_free(map_str);
	    last_used_map = mp;
#endif
	}
#ifdef FEAT_EVAL
	vim_free(save_m_keys);
#endif
	*keylenp = keylen;
	if (i == FAIL)
	    return map_result_fail;
	return map_result_retry;
    }

    *keylenp = keylen;
    return map_result_nomatch;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_YES		0	// tb_noremap: remap

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_SCRIPT	2	// tb_noremap: remap local script mappings

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

#define TYPELEN_INIT	(5 * (MAXMAPLEN + 3))

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

# define MAX_REPEAT_PARSE 8

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;


ins_typebuf(
    char_u	*str,
    int		noremap,
    int		offset,
    int		nottyped,
    int		silent)
{
    char_u	*s1, *s2;
    int		newlen;
    int		addlen;
    int		i;
    int		newoff;
    int		val;
    int		nrm;

    init_typebuf();
    if (++typebuf.tb_change_cnt == 0)
	typebuf.tb_change_cnt = 1;
    state_no_longer_safe("ins_typebuf()");

    addlen = (int)STRLEN(str);

    if (offset == 0 && addlen <= typebuf.tb_off)
    {
	/*
	 * Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off]
	 */
	typebuf.tb_off -= addlen;
	mch_memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen);
    }
    else if (typebuf.tb_len == 0 && typebuf.tb_buflen
					       >= addlen + 3 * (MAXMAPLEN + 4))
    {
	/*
	 * Buffer is empty and string fits in the existing buffer.
	 * Leave some space before and after, if possible.
	 */
	typebuf.tb_off = (typebuf.tb_buflen - addlen - 3 * (MAXMAPLEN + 4)) / 2;
	mch_memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen);
    }
    else
    {
	int extra;

	/*
	 * Need to allocate a new buffer.
	 * In typebuf.tb_buf there must always be room for 3 * (MAXMAPLEN + 4)
	 * characters.  We add some extra room to avoid having to allocate too
	 * often.
	 */
	newoff = MAXMAPLEN + 4;
	extra = addlen + newoff + 4 * (MAXMAPLEN + 4);
	if (typebuf.tb_len > 2147483647 - extra)
	{
	    // string is getting too long for a 32 bit int
	    emsg(_(e_command_too_complex));    // also calls flush_buffers
	    setcursor();
	    return FAIL;
	}
	newlen = typebuf.tb_len + extra;
	s1 = alloc(newlen);
	if (s1 == NULL)		    // out of memory
	    return FAIL;
	s2 = alloc(newlen);
	if (s2 == NULL)		    // out of memory
	{
	    vim_free(s1);
	    return FAIL;
	}
	typebuf.tb_buflen = newlen;

	// copy the old chars, before the insertion point
	mch_memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off,
							      (size_t)offset);
	// copy the new chars
	mch_memmove(s1 + newoff + offset, str, (size_t)addlen);
	// copy the old chars, after the insertion point, including the	NUL at
	// the end
	mch_memmove(s1 + newoff + offset + addlen,
				     typebuf.tb_buf + typebuf.tb_off + offset,
				       (size_t)(typebuf.tb_len - offset + 1));
	if (typebuf.tb_buf != typebuf_init)
	    vim_free(typebuf.tb_buf);
	typebuf.tb_buf = s1;

	mch_memmove(s2 + newoff, typebuf.tb_noremap + typebuf.tb_off,
							      (size_t)offset);
	mch_memmove(s2 + newoff + offset + addlen,
		   typebuf.tb_noremap + typebuf.tb_off + offset,
					   (size_t)(typebuf.tb_len - offset));
	if (typebuf.tb_noremap != noremapbuf_init)
	    vim_free(typebuf.tb_noremap);
	typebuf.tb_noremap = s2;

	typebuf.tb_off = newoff;
    }
    typebuf.tb_len += addlen;

    // If noremap == REMAP_SCRIPT: do remap script-local mappings.
    if (noremap == REMAP_SCRIPT)
	val = RM_SCRIPT;
    else if (noremap == REMAP_SKIP)
	val = RM_ABBR;
    else
	val = RM_NONE;

    /*
     * Adjust typebuf.tb_noremap[] for the new characters:
     * If noremap == REMAP_NONE or REMAP_SCRIPT: new characters are
     *			(sometimes) not remappable
     * If noremap == REMAP_YES: all the new characters are mappable
     * If noremap  > 0: "noremap" characters are not remappable, the rest
     *			mappable
     */
    if (noremap == REMAP_SKIP)
	nrm = 1;
    else if (noremap < 0)
	nrm = addlen;
    else
	nrm = noremap;
    for (i = 0; i < addlen; ++i)
	typebuf.tb_noremap[typebuf.tb_off + i + offset] =
						  (--nrm >= 0) ? val : RM_YES;

    // tb_maplen and tb_silent only remember the length of mapped and/or
    // silent mappings at the start of the buffer, assuming that a mapped
    // sequence doesn't result in typed characters.
    if (nottyped || typebuf.tb_maplen > offset)
	typebuf.tb_maplen += addlen;
    if (silent || typebuf.tb_silent > offset)
    {
	typebuf.tb_silent += addlen;
	cmd_silent = TRUE;
    }
    if (typebuf.tb_no_abbr_cnt && offset == 0)	// and not used for abbrev.s
	typebuf.tb_no_abbr_cnt += addlen;

    return OK;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

#define RM_YES		0	// tb_noremap: remap

#define RM_NONE		1	// tb_noremap: don't remap

#define RM_SCRIPT	2	// tb_noremap: remap local script mappings

#define RM_ABBR		4	// tb_noremap: don't remap, do abbrev.

#define TYPELEN_INIT	(5 * (MAXMAPLEN + 3))

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

mapblock_T	*last_used_map = NULL;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


f_getcharstr(typval_T *argvars, typval_T *rettv)
{
    getchar_common(argvars, rettv);

    if (rettv->v_type == VAR_NUMBER)
    {
	char_u		temp[7];   // mbyte-char: 6, NUL: 1
	varnumber_T	n = rettv->vval.v_number;
	int		i = 0;

	if (n != 0)
	{
	    if (has_mbyte)
		i += (*mb_char2bytes)(n, temp + i);
	    else
		temp[i++] = n;
	}
	temp[i++] = NUL;
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = vim_strsave(temp);
    }
}


stuffReadbuffSpec(char_u *s)
{
    int c;

    while (*s != NUL)
    {
	if (*s == K_SPECIAL && s[1] != NUL && s[2] != NUL)
	{
	    // Insert special key literally.
	    stuffReadbuffLen(s, 3L);
	    s += 3;
	}
	else
	{
	    c = mb_ptr2char_adv(&s);
	    if (c == CAR || c == NL || c == ESC)
		c = ' ';
	    stuffcharReadbuff(c);
	}
    }
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


reset_last_used_map(mapblock_T *mp)
{
    if (last_used_map == mp)
	last_used_map = NULL;
}

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

mapblock_T	*last_used_map = NULL;

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};


at_ctrl_x_key(void)
{
    char_u  *p = typebuf.tb_buf + typebuf.tb_off;
    int	    c = *p;

    if (typebuf.tb_len > 3
	    && c == K_SPECIAL
	    && p[1] == KS_MODIFIER
	    && (p[2] & MOD_MASK_CTRL))
	c = p[3] & 0x1f;
    return vim_is_ctrl_x_key(c);
}

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;


get_buffcont(
    buffheader_T	*buffer,
    int			dozero)	    // count == zero is not an error
{
    long_u	    count = 0;
    char_u	    *p = NULL;
    char_u	    *p2;
    char_u	    *str;
    buffblock_T *bp;

    // compute the total length of the string
    for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next)
	count += (long_u)STRLEN(bp->b_str);

    if ((count || dozero) && (p = alloc(count + 1)) != NULL)
    {
	p2 = p;
	for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next)
	    for (str = bp->b_str; *str; )
		*p2++ = *str++;
	*p2 = NUL;
    }
    return (p);
}


gotchars(char_u *chars, int len)
{
    char_u		*s = chars;
    int			i;
    static char_u	buf[4];
    static int		buflen = 0;
    int			todo = len;

    while (todo--)
    {
	buf[buflen++] = *s++;

	// When receiving a special key sequence, store it until we have all
	// the bytes and we can decide what to do with it.
	if (buflen == 1 && buf[0] == K_SPECIAL)
	    continue;
	if (buflen == 2)
	    continue;
	if (buflen == 3 && buf[1] == KS_EXTRA
		       && (buf[2] == KE_FOCUSGAINED || buf[2] == KE_FOCUSLOST))
	{
	    // Drop K_FOCUSGAINED and K_FOCUSLOST, they are not useful in a
	    // recording.
	    buflen = 0;
	    continue;
	}

	// Handle one byte at a time; no translation to be done.
	for (i = 0; i < buflen; ++i)
	    updatescript(buf[i]);

	if (reg_recording != 0)
	{
	    buf[buflen] = NUL;
	    add_buff(&recordbuff, buf, (long)buflen);
	    // remember how many chars were last recorded
	    last_recorded_len += buflen;
	}
	buflen = 0;
    }
    may_sync_undo();

#ifdef FEAT_EVAL
    // output "debug mode" message next time in debug mode
    debug_did_msg = FALSE;
#endif

    // Since characters have been typed, consider the following to be in
    // another mapping.  Search string will be kept in history.
    ++maptick;
}

#define MINIMAL_SIZE 20			// minimal size for b_str

static buffheader_T redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T old_redobuff = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

static int typeahead_char = 0;

static int	block_redo = FALSE;

static int	KeyNoremap = 0;

static char_u	typebuf_init[TYPELEN_INIT];

static char_u	noremapbuf_init[TYPELEN_INIT];

static int	last_recorded_len = 0;

static int	read_readbuf(buffheader_T *buf, int advance);

static void	init_typebuf(void);

static void	may_sync_undo(void);

static void	free_typebuf(void);

static void	closescript(void);

static void	updatescript(int c);

static int	vgetorpeek(int);

static int	inchar(char_u *buf, int maxlen, long wait_time);

static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};

static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};

static typebuf_T saved_typebuf[NSCRIPT];

static int old_char = -1;

static int old_mod_mask;

static int old_mouse_row;

static int old_mouse_col;

typedef enum {
    map_result_fail,    // failed, break loop
    map_result_get,     // get a character from typeahead
    map_result_retry,   // try to map again
    map_result_nomatch  // no matching mapping, get char
} map_result_T;