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

    if (argvars[0].v_type == VAR_DICT)
    {
	if (argvars[2].v_type != VAR_UNKNOWN)
	    semsg(_(e_toomanyarg), "remove()");
	else if ((d = argvars[0].vval.v_dict) != NULL
		&& !tv_check_lock(d->dv_lock, arg_errmsg, TRUE))
	{
	    key = tv_get_string_chk(&argvars[1]);
	    if (key != NULL)
	    {
		di = dict_find(d, key, -1);
		if (di == NULL)
		    semsg(_(e_dictkey), key);
		else if (!var_check_fixed(di->di_flags, arg_errmsg, TRUE)
			    && !var_check_ro(di->di_flags, arg_errmsg, TRUE))
		{
		    *rettv = di->di_tv;
		    init_tv(&di->di_tv);
		    dictitem_remove(d, di);
		}
	    }
	}
    }
    else if (argvars[0].v_type == VAR_BLOB)
    {
	idx = (long)tv_get_number_chk(&argvars[1], &error);
	if (!error)
	{
	    blob_T  *b = argvars[0].vval.v_blob;
	    int	    len = blob_len(b);
	    char_u  *p;

	    if (idx < 0)
		// count from the end
		idx = len + idx;
	    if (idx < 0 || idx >= len)
	    {
		semsg(_(e_blobidx), idx);
		return;
	    }
	    if (argvars[2].v_type == VAR_UNKNOWN)
	    {
		// Remove one item, return its value.
		p = (char_u *)b->bv_ga.ga_data;
		rettv->vval.v_number = (varnumber_T) *(p + idx);
		mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
		--b->bv_ga.ga_len;
	    }
	    else
	    {
		blob_T  *blob;

		// Remove range of items, return list with values.
		end = (long)tv_get_number_chk(&argvars[2], &error);
		if (error)
		    return;
		if (end < 0)
		    // count from the end
		    end = len + end;
		if (end >= len || idx > end)
		{
		    semsg(_(e_blobidx), end);
		    return;
		}
		blob = blob_alloc();
		if (blob == NULL)
		    return;
		blob->bv_ga.ga_len = end - idx + 1;
		if (ga_grow(&blob->bv_ga, end - idx + 1) == FAIL)
		{
		    vim_free(blob);
		    return;
		}
		p = (char_u *)b->bv_ga.ga_data;
		mch_memmove((char_u *)blob->bv_ga.ga_data, p + idx,
						      (size_t)(end - idx + 1));
		++blob->bv_refcount;
		rettv->v_type = VAR_BLOB;
		rettv->vval.v_blob = blob;

		mch_memmove(p + idx, p + end + 1, (size_t)(len - end));
		b->bv_ga.ga_len -= end - idx + 1;
	    }
	}
    }
    else if (argvars[0].v_type != VAR_LIST)
	semsg(_(e_listdictblobarg), "remove()");
    else if ((l = argvars[0].vval.v_list) != NULL
			       && !tv_check_lock(l->lv_lock, arg_errmsg, TRUE))
    {
	idx = (long)tv_get_number_chk(&argvars[1], &error);
	if (error)
	    ;		// type error: do nothing, errmsg already given
	else if ((item = list_find(l, idx)) == NULL)
	    semsg(_(e_listidx), idx);
	else
	{
	    if (argvars[2].v_type == VAR_UNKNOWN)
	    {
		/* Remove one item, return its value. */
		vimlist_remove(l, item, item);
		*rettv = item->li_tv;
		vim_free(item);
	    }
	    else
	    {
		// Remove range of items, return list with values.
		end = (long)tv_get_number_chk(&argvars[2], &error);
		if (error)
		    ;		// type error: do nothing
		else if ((item2 = list_find(l, end)) == NULL)
		    semsg(_(e_listidx), end);
		else
		{
		    int	    cnt = 0;

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