do_searchpair(
    char_u	*spat,	    /* start pattern */
    char_u	*mpat,	    /* middle pattern */
    char_u	*epat,	    /* end pattern */
    int		dir,	    /* BACKWARD or FORWARD */
    typval_T	*skip,	    /* skip expression */
    int		flags,	    /* SP_SETPCMARK and other SP_ values */
    pos_T	*match_pos,
    linenr_T	lnum_stop,  /* stop at this line if not zero */
    long	time_limit UNUSED) /* stop after this many msec */
{
    char_u	*save_cpo;
    char_u	*pat, *pat2 = NULL, *pat3 = NULL;
    long	retval = 0;
    pos_T	pos;
    pos_T	firstpos;
    pos_T	foundpos;
    pos_T	save_cursor;
    pos_T	save_pos;
    int		n;
    int		r;
    int		nest = 1;
    int		use_skip = FALSE;
    int		err;
    int		options = SEARCH_KEEP;
    proftime_T	tm;

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

#ifdef FEAT_RELTIME
    /* Set the time limit, if there is one. */
    profile_setlimit(time_limit, &tm);
#endif

    /* Make two search patterns: start/end (pat2, for in nested pairs) and
     * start/middle/end (pat3, for the top pair). */
    pat2 = alloc((unsigned)(STRLEN(spat) + STRLEN(epat) + 17));
    pat3 = alloc((unsigned)(STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25));
    if (pat2 == NULL || pat3 == NULL)
	goto theend;
    sprintf((char *)pat2, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
    if (*mpat == NUL)
	STRCPY(pat3, pat2);
    else
	sprintf((char *)pat3, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)",
							    spat, epat, mpat);
    if (flags & SP_START)
	options |= SEARCH_START;

    if (skip != NULL)
    {
	/* Empty string means to not use the skip expression. */
	if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC)
	    use_skip = skip->vval.v_string != NULL
						&& *skip->vval.v_string != NUL;
    }

    save_cursor = curwin->w_cursor;
    pos = curwin->w_cursor;
    CLEAR_POS(&firstpos);
    CLEAR_POS(&foundpos);
    pat = pat3;
    for (;;)
    {
	n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
				     options, RE_SEARCH, lnum_stop, &tm, NULL);
	if (n == FAIL || (firstpos.lnum != 0 && EQUAL_POS(pos, firstpos)))
	    /* didn't find it or found the first match again: FAIL */
	    break;

	if (firstpos.lnum == 0)
	    firstpos = pos;
	if (EQUAL_POS(pos, foundpos))
	{
	    /* Found the same position again.  Can happen with a pattern that
	     * has "\zs" at the end and searching backwards.  Advance one
	     * character and try again. */
	    if (dir == BACKWARD)
		decl(&pos);
	    else
		incl(&pos);
	}
	foundpos = pos;

	/* clear the start flag to avoid getting stuck here */
	options &= ~SEARCH_START;

	/* If the skip pattern matches, ignore this match. */
	if (use_skip)
	{
	    save_pos = curwin->w_cursor;
	    curwin->w_cursor = pos;
	    err = FALSE;
	    r = eval_expr_to_bool(skip, &err);
	    curwin->w_cursor = save_pos;
	    if (err)
	    {
		/* Evaluating {skip} caused an error, break here. */
		curwin->w_cursor = save_cursor;
		retval = -1;
		break;
	    }
	    if (r)
		continue;
	}

	if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2))
	{
	    /* Found end when searching backwards or start when searching
	     * forward: nested pair. */
	    ++nest;
	    pat = pat2;		/* nested, don't search for middle */
	}
	else
	{
	    /* Found end when searching forward or start when searching
	     * backward: end of (nested) pair; or found middle in outer pair. */
	    if (--nest == 1)
		pat = pat3;	/* outer level, search for middle */
	}

	if (nest == 0)
	{
	    /* Found the match: return matchcount or line number. */
	    if (flags & SP_RETCOUNT)
		++retval;
	    else
		retval = pos.lnum;
	    if (flags & SP_SETPCMARK)
		setpcmark();
	    curwin->w_cursor = pos;
	    if (!(flags & SP_REPEAT))
		break;
	    nest = 1;	    /* search for next unmatched */
	}
    }

    if (match_pos != NULL)
    {
	/* Store the match cursor position */
	match_pos->lnum = curwin->w_cursor.lnum;
	match_pos->col = curwin->w_cursor.col + 1;
    }

    /* If 'n' flag is used or search failed: restore cursor position. */
    if ((flags & SP_NOMOVE) || retval == 0)
	curwin->w_cursor = save_cursor;

theend:
    vim_free(pat2);
    vim_free(pat3);
    if (p_cpo == empty_option)
	p_cpo = save_cpo;
    else
	/* Darn, evaluating the {skip} expression changed the value. */
	free_string_option(save_cpo);

    return retval;
}