/* srch_act.c - part of asedit program */

/*
 * Copyright 1991 - 1994,  Andrzej Stochniol, London, UK
 *
 * ASEDIT text editor, both binary and source (hereafter, Software) is
 * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains
 * with AS.
 *
 * AS grants you (hereafter, Licensee) a license to use the Software
 * for academic, research and internal business purposes only, without a
 * fee.  Licensee may distribute the binary and source code (if released)
 * to third parties provided that the copyright notice and this statement
 * appears on all copies and that no charge is associated with such copies.
 *
 * Licensee may make derivative works.  However, if Licensee distributes
 * any derivative work based on or derived from the Software, then
 * Licensee will:
 * (1) notify AS regarding its distribution of the derivative work, and
 * (2) clearly notify users that such derivative work is a modified version
 *      and not the original ASEDIT distributed by AS.
 *
 * Any Licensee wishing to make commercial use of the Software should
 * contact AS to negotiate an appropriate license for such commercial use.
 * Commercial use includes:
 * (1) integration of all or part of the source code into a product for sale
 *     or license by or on behalf of Licensee to third parties, or
 * (2) distribution of the binary code or source code to third parties that
 *     need it to utilize a commercial product sold or licensed by or on 
 *     behalf of Licensee.
 *
 * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS
 * SOFTWARE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
 * IMPLIED WARRANTY.  IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * By using or copying this Software, Licensee agrees to abide by the
 * copyright law and all other applicable laws, and the terms of this
 * license.
 * AS shall have the right to terminate this license immediately by
 * written notice upon Licensee's breach of, or non-compliance with, any
 * of its terms.  Licensee may be held legally responsible for any
 * copyright infringement that is caused or encouraged by Licensee's
 * failure to abide by the terms of this license.
 *
 *
 * 	Andrzej Stochniol	(A.Stochniol@ic.ac.uk)
 * 	30 Hatch Road
 * 	London SW16 4PN
 * 	UK
 */

#include <stdio.h>
#include <string.h>

#include <Xm/ToggleB.h>
#include <Xm/ToggleBG.h>
#include <Xm/Text.h>

#include "asedit.h"


#ifdef _NO_PROTO        /* prototypes for non-ANSI systems follow ... */
static void asedit_find();
static void asedit_change();
static void asedit_mark_set();
static void asedit_mark_goto();
static void asedit_undo();
static void asedit_redo();
static void asedit_forward_match();
static void asedit_backward_match();

#else  /* ! _NO_PROTO, ANSI prototypes follow */

static void asedit_find(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_change(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_mark_set(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_mark_goto(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_undo(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_redo(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_forward_match(Widget w, XEvent *event, char **params, Cardinal *num_params);
static void asedit_backward_match(Widget w, XEvent *event, char **params, Cardinal *num_params);

#endif  /* end of conditional _NO_PROTO prototypes  compilation */




/* extra actions for edit text widgets */
XtActionsRec AseditExtraActionsTable[] = {
  {"asedit-find",               (XtActionProc)asedit_find},
  {"asedit-change",             (XtActionProc)asedit_change},
  {"asedit-mark-set",           (XtActionProc)asedit_mark_set},
  {"asedit-mark-goto",          (XtActionProc)asedit_mark_goto},
  {"asedit-undo",               (XtActionProc)asedit_undo},
  {"asedit-redo",               (XtActionProc)asedit_redo},
  {"asedit-forward-match",      (XtActionProc)asedit_forward_match},
  {"asedit-backward-match",     (XtActionProc)asedit_backward_match},
};

Cardinal lenAseditExtraActionsTable = XtNumber(AseditExtraActionsTable);


/*****************************  find_string  ********************************
*
*	finds the string search_string in the text widget "win->edit_text"
*/
#ifdef _NO_PROTO
void find_string( win, start)
    aseditWindowStruct *win;
    Boolean start;

#else  /* ! _NO_PROTO */

void find_string(aseditWindowStruct *win, Boolean start)
#endif
{
    /* "start" must be True when this procedure is called to start the find process
       and it must be False when it is called second time when the bottom or top
       of the file has been reached in the previous call */

    /* first_pos, last_pos - are set to show the position of the
       found text */

    long len, position, start_position, end_position;
    long buf_size;

    String text_buffer;
    Arg    al[2];
    register int ac;
    Boolean search_forward=   True;	/* default search direction */
    Boolean case_sensitive=   True;
    Boolean whole_words_only= False;

    char   work[256];
    XmString  xmstr;		/* work XmString */



    len = strlen(win->search_string);

    if(len == 0L)
    {
	show_error_message(win, (char *) lstr.sm_have_to_enter);
	win->found_string_start_pos = -1L;      /* to avoid continuing the
						   replace action */
	return;
    }
    /* get the state of case_sensitive and whole_words_only toggles (if created) */
    if(win->case_sensitive_toggle)
	case_sensitive   = XmToggleButtonGetState (win->case_sensitive_toggle);
    if(win->whole_words_only_toggle)
	whole_words_only = XmToggleButtonGetState (win->whole_words_only_toggle);

    /* get the copy of the internally stored text
       and the cursor position (i.e. starting position) */
    XtSetArg(al[0], XmNvalue, &text_buffer);
    XtSetArg(al[1], XmNcursorPosition, &start_position);
    XtGetValues(win->edit_text, al, 2);

    buf_size = (long) strlen(text_buffer);

    if(start)
    {
	/* get the state of search_forward toggle gadget (if available)*/
	if (win->forward_toggle)
	    search_forward   = XmToggleButtonGadgetGetState (win->forward_toggle);

	/* save values for next call */
	win->cntSearch.forward = search_forward;
	win->cntSearch.startPosition = start_position;
	end_position = -1L;		/* search up to the edge of the file (i.e.
					up to the end for forward search, up to the
					beginning for the backward search */
    }
    else
    {
	start_position = -1;	/* start at the beginning or at the end of the
				   file (dependent on the search direction) */
	end_position   = win->cntSearch.startPosition;	/* do not go beyond
							   the overall start position;
							   finish off the search */
	search_forward = win->cntSearch.forward;	/* use the saved value */
    }

    /* find a search string position in the text widget buffer;
       if the position is < 0 the string has not been found,
       therefore pop up appriopriate dialog;
       if position >= 0 OK go there ... */
    position =  find_string_pos_in_buffer(win->search_string, text_buffer,
					start_position, end_position, search_forward,
					case_sensitive, whole_words_only);
    if(position >= 0L)
    {

	/* set the first and last position of the text found */
	win->found_string_start_pos = position;
	win->found_string_end_pos  = position + len;

	/* for the forward search set the cursor to be posittioned after
	   the string found, for the backward search - before the string found */
	if(search_forward)  position += len;

	/* set the selection for the text found */
	/* XmTextSetSelection also sets the position of the insert cursor to
	   last_pos and then invokes the widget's XmNmotionVerifyCallback */
	XmTextSetSelection(win->edit_text, win->found_string_start_pos, win->found_string_end_pos, win->timestamp_search_request);


	/* set the cursor position; next search will start from that place */
	XmTextSetInsertionPosition(win->edit_text, position);


    }
    else
    {
	win->found_string_start_pos = win->found_string_end_pos = -1;	/* the search was unsuccessful */
	if(!start  ||  (win->cntSearch.forward && win->cntSearch.startPosition == 0L) ||
		   (!win->cntSearch.forward && win->cntSearch.startPosition == buf_size))
	{

	    show_error_message(win, (char *)lstr.sm_not_found);

	}
	else
	{
	    /* show that a search might  be continued from the edges of the file,
	       i.e. ask a question about continuing the find process
	     */
	    win->cntSearch.on = True;	/* to recognise the reason for the question */

	    if(win->cntSearch.forward) strcpy(work, (char *)lstr.sq_doc_end);
	    else        		      strcpy(work, (char *)lstr.sq_doc_beg);
	    xmstr = XmStringCreateLtoR(work, charset);
	    if(win->continue_search_question == NULL)	/* create it first */
			create_continue_search_question(win);
	    ac = 0;
	    XtSetArg(al[ac], XmNmessageString, xmstr );  ac++;
	    XtSetValues(win->continue_search_question, al, ac);

	    asManageDialog(win->continue_search_question);
	    XmStringFree(xmstr);	/* free memory allocated for XmString */
	}
    }
    XtFree(text_buffer);
    return;

} /* find_string */


/*****************************  change_all_found_strings  *******************
*
*	find all strings in the text widget and change them to a new string
*	without	asking for confirmation
*/
#ifdef _NO_PROTO
void change_all_found_strings(win, start)
    aseditWindowStruct *win;
    Boolean start;
#else  /* ! _NO_PROTO */

void change_all_found_strings(aseditWindowStruct *win, Boolean start)
#endif
{
    /* "start" must be True when this procedure is called to start the process
       of changing of all text occurrences;
       and it must be False when it is called second time when the bottom or top
       of the file has been reached in the previous call */

    long len, position, start_position, end_position;
    long first_pos, last_pos;		/* positions of the text found */
    long buf_size, new_string_len;
    String text_buffer;
    Arg    al[2];
    register int ac;
    char   work[256];
    XmString  xmstr;		/* work XmString */

    len = strlen(win->search_string);
    new_string_len = strlen(win->new_string);

    if(len == 0L)
    {
	show_error_message(win, (char *) lstr.sm_have_to_enter);
	win->found_string_start_pos = -1L;	/* to avoid continuing the
						   replace action */
	return;
    }
    if(start)
    {
	/* get the state of search_forward toggle gadget - only when the whole
	   search is started (we don't allow the user to change it during the whole
	   process; otherwise it would be difficult to find out when
	   the whole process is finished) */
	win->chgAll.searchForward   = XmToggleButtonGadgetGetState (win->forward_toggle);
	/* get the state of case_sensitive and whole_words_only toggles */
	win->chgAll.caseSensitive   = XmToggleButtonGetState (win->case_sensitive_toggle);
	win->chgAll.wholeWordsOnly  = XmToggleButtonGetState (win->whole_words_only_toggle);

	/* get the address of the internally stored text
	   and the cursor position (i.e. starting position) */
	XtSetArg(al[0], XmNvalue, &text_buffer);
	XtSetArg(al[1], XmNcursorPosition, &start_position);
	XtGetValues(win->edit_text, al, 2);
	win->chgAll.itemsChanged = 0;
	win->chgAll.startPosition = start_position;
	end_position	= -1;		/* search up to the edge of the file (i.e.
					up to the end for forward search, up to the
					beginning for the backward search */
    }
    else
    {
	/* get the address of the internally stored text */
	XtSetArg(al[0], XmNvalue, &text_buffer);
	XtGetValues(win->edit_text, al, 1);
	start_position = -1;	/* start at the beginning or at the end of the
				   file (dependent on the search direction) */
	end_position   = win->chgAll.startPosition;	/* do not go beyond
							   the overall start position;
							   finish off the search */
    }

    /* find a search string position in the text widget buffer;
       if the position is < 0 the string has not been found,
       therefore pop up appriopriate dialog;
       if position >= 0 OK go there ... */
    position =  find_string_pos_in_buffer(win->search_string, text_buffer,
					start_position, end_position, win->chgAll.searchForward,
					win->chgAll.caseSensitive, win->chgAll.wholeWordsOnly);
    while (position >= 0L)
    {
	/* the position returned always shows the first character of the string
	   found, no matter if we search forward or backward so we can set
	   the first and last position of the text found as follows: */

	/* set the first and last position of the found text */
	first_pos = position;
	last_pos  = position+len;

	/* to speed the process of changing we do not mark the found text
	   nor we change the cursor position during the substitution process */

	XmTextReplace(win->edit_text, first_pos, last_pos, win->new_string);
	win->chgAll.itemsChanged++;	/* increase the counter */

	/* move the starting position of the search to start a new search
	   just after the text changed (or before it for a backward search) */
	if(win->chgAll.searchForward) start_position = last_pos + (new_string_len - len);
	else		   start_position = first_pos;

	/* update the win->chgAll.startPosition in the case when the text was
	   changed before it (take into account a difference in the length)*/
	if(position < win->chgAll.startPosition)
		win->chgAll.startPosition += (new_string_len - len);
	if(win->chgAll.startPosition < 0) win->chgAll.startPosition = 0;

	XtFree(text_buffer);
	/* get the address of the internally stored text (it may have been changed)
	   and search again */
	XtSetArg(al[0], XmNvalue, &text_buffer);
	XtGetValues(win->edit_text, al, 1);

	/* set the end_position of the search appropriately
	   to the mode of the search (up to the edge of the file or finish off
	   the search beyond the edge of the file) */
	if(!start) end_position = win->chgAll.startPosition;  /* else = -1 */

	position =  find_string_pos_in_buffer(win->search_string, text_buffer,
					start_position, end_position, win->chgAll.searchForward,
					win->chgAll.caseSensitive, win->chgAll.wholeWordsOnly);

    }
    /* convert the the last found position (that is = the start  of the
    unsuccessful search) into the line & column number and display them;
    set the cursor position to it */
    XtSetArg(al[0], XmNcursorPosition, start_position);
    XtSetValues(win->edit_text, al, 1);

    buf_size = (long) strlen(text_buffer);

    if(win->status_line_on)	/* calculate line & column only when they are shown */
    {
	textPosToLineColumn(text_buffer, buf_size, start_position, win->tabsize,
			&(win->line), &(win->column), &(win->column_ntab) );
	write_ll(win->line_number,   charset, "%-5ld", win->line);
	write_ll(win->column_number, charset, "%-4ld", win->column);

    }

    XtFree(text_buffer);

    if(!start  ||  (win->chgAll.searchForward && win->chgAll.startPosition == 0L) ||
		   (!win->chgAll.searchForward && win->chgAll.startPosition == buf_size))
    {
	/* 3 situations may generate the end of the search message:
	   - it was a second call to this procedure to finish off the previous
	     search starting from the beginning (or the end) up to the overall start
	   - it was a forward search  started right from the text beginning,
	   - it was a backward search started right from the text end
	*/

	sprintf(work, (char *)lstr.sm_change_all_completed,
			win->chgAll.itemsChanged);
	xmstr = XmStringCreateLtoR(work, charset);
	if(win->search_end_message == NULL)	/* create it first */
		create_search_end_message(win);
	ac = 0;
	XtSetArg(al[ac], XmNmessageString, xmstr );  ac++;
	XtSetValues(win->search_end_message, al, ac);
	XmStringFree(xmstr);	/* free memory allocated for XmString */
	asManageDialog(win->search_end_message);

    }
    else
    {	/* a question about continuing the change process is asked */
	if(win->chgAll.searchForward) strcpy(work, (char *)lstr.sq_doc_end);
	else        		      strcpy(work, (char *)lstr.sq_doc_beg);
	xmstr = XmStringCreateLtoR(work, charset);
	if(win->continue_search_question == NULL)	/* create it first */
		create_continue_search_question(win);
	ac = 0;
	XtSetArg(al[ac], XmNmessageString, xmstr );  ac++;
	XtSetValues(win->continue_search_question, al, ac);

	/* set a logical value in cntSearch to show that the change all
	   calls continue search question
	*/
	win->cntSearch.on = False;

	asManageDialog(win->continue_search_question);
	XmStringFree(xmstr);	/* free memory allocated for XmString */
    }
    return;

} /* change_all_found_strings */


/* some simple utility functions ..... (self explanatory)  */

#ifdef _NO_PROTO
void set_change_buttons_sensitivity(win, state)
     aseditWindowStruct *win;
     Boolean state;
#else  /* ! _NO_PROTO */

void set_change_buttons_sensitivity(aseditWindowStruct *win, Boolean state)
#endif
{
     /* when state = False - turn off the sensitivity of the "action" buttons
			     of the change dialog and set the default button
			     the cancel ("Close") button;
	when state = True  - restore the default sensitive state of the "action"
			     buttons and restore the default button to the Ok
     */
     Arg al[2];
     register int ac;

     /* set the appropriate default button  */
     /* DO that before setting sensitivities !!!! */

     ac = 0;
     if(state == True)
     {
	/* in Motif 1.2.2 on SGI (IRIX5) setting default Button does not work; moreover when
	   we set afterwards the change_verify_button to insensitive the program dumps!!!
	   so as from 11.01. we distinguish between the old and the new method of
	   setting the default button (the new being setting the traversal )
	*/
	if(xmUseVersion < 1002)	/* the old */
	    { XtSetArg(al[ac], XmNdefaultButton, win->change_verify_button); ac++; }
	else
	    {
		XtSetSensitive(win->change_verify_button, state);	/* first make it sensitive
					otherwise the traversal would not take place !!! */
		XFlush(display);
		XmProcessTraversal(win->change_verify_button, XmTRAVERSE_CURRENT);
	    }
     }
     else
     {
	 /* set the deafult button to the Cancel button;
	    otherwise when we press Enter the "change_verify_button" is activated
	    no matter that it is actually insensitive (in Motif 1.0, 1.1) */

	if(xmUseVersion < 1002)
	    { XtSetArg(al[ac], XmNdefaultButton, win->cancel_replace_dialog_button); ac++; }
	else
	    XmProcessTraversal(win->cancel_replace_dialog_button, XmTRAVERSE_CURRENT);
     }
     if(xmUseVersion < 1002)  XtSetValues(win->replace_dialog, al, ac);

     /* set the sensitivity of the change buttons sensitivity */
     XtSetSensitive(win->change_verify_button, state);
     XtSetSensitive(win->change_all_button, state);

     /* as of 1.25 we set the editable status of the text widgets as well !!!
	so the user will never attempt to change them during the change
	process
      */
     ac = 0;
     XtSetArg(al[ac],XmNeditable, state); 	ac++;
     XtSetValues(win->text_to_find, al, ac);
     XtSetValues(win->new_text, al, ac);


}    /* set_change_buttons_sensitivity */

/*****************************  go_to_line  ********************************
*
*       go to a specified line in "win->edit_text"
*/
#ifdef _NO_PROTO
void go_to_line( win, line_no, setTopCharacter)
    aseditWindowStruct *win;
    long line_no;
    Boolean setTopCharacter;

#else  /* ! _NO_PROTO */

void go_to_line(aseditWindowStruct *win, long line_no, Boolean setTopCharacter)
#endif
{
    long position, found_lines;
    String text_buffer;
    char   work[326];      
    Arg    al[3];
    register int ac;

    /* get a copy of the internally stored text */
    XtSetArg(al[0], XmNvalue, &text_buffer);
    XtGetValues(win->edit_text, al, 1);
    
    /* find a position of the line in the text widget buffer;
      if the position is < 0 the line has not been found,
      therefore pop up appriopriate dialog;
      if position >= 0 OK go there ... */
    position = find_line_pos(line_no, text_buffer, &found_lines);
    
    XtFree(text_buffer);

    if(position >= 0L)
    {
	/* setting the topCharacter and insertionPosition via 
	   convenience functions; the XtSetValues method apparently doesn't work
	   on AIX 1.2.3 Motif version;
	   Note also, that we use XmTextShowPosition to defend from
	   autoShowCursorPosition set fo False.
	*/
	XmTextSetInsertionPosition(win->edit_text, position);
	if(setTopCharacter) XmTextSetTopCharacter(win->edit_text, position);
	XmTextShowPosition(win->edit_text, position);
	

        /* display the line & column numbers (column = 1) */
        win->line = line_no;        /* global value of line & column */
        win->column = 1;
        win->column_ntab = 1;
        write_ll(win->line_number,   charset, "%-5ld", win->line);
        write_ls(win->column_number, charset, "%s",    "1   ");

    }
    else
    {
        sprintf(work, (char *)lstr.sm_line_range, found_lines);
        show_error_message(win, work);
    }

}   /* go_to_line */

typedef struct 
{
    char c;
    char mate;
    int  impliedDirection;
} delimiterPair;

enum pairImpliedDirection { IMPLIED_BACKWARD = -1, IMPLIED_NONE, IMPLIED_FORWARD};

/* Note that we might have directional and nondirectional matching.
   Braces, brackets and parentheses are directional; suppose we tell
   the editor to find the match for an opening brace ({) or bracket ([).
   The matching delimiter can't be located before the one you've selected
   so even if you've requested search backward we have to force the search
   forward (etc.) (so both commands Ctrl+Q, Ctrl+[ and Ctrl+Q, Ctrl+], or alternative
   ones,  would do for directional pair maching).

   However, if you tell the editor to find the match for a double quote (")
   or a single quote ('), it doesn't know automatically which way to go.
   You must specify the search direction by giving the correct match pair
   command. If you give the command Ctrl+Q, Ctrl+[, the editor searches
   forward for the match; if you give the command Ctrl+Q, Ctrl+], it
   searches backward for the match.

   Note also that all pairs that are nondirectional (direction is not
   implied) are not nestable as well (because the left and right
   characters are the same).

*/


static delimiterPair pairs[] =
{
    {'{',  '}',  IMPLIED_FORWARD},
    {'(',  ')',  IMPLIED_FORWARD},
    {'[',  ']',  IMPLIED_FORWARD},
    {'<',  '>',  IMPLIED_FORWARD},

    {'}',  '{',  IMPLIED_BACKWARD},
    {')',  '(',  IMPLIED_BACKWARD},
    {']',  '[',  IMPLIED_BACKWARD},
    {'>',  '<',  IMPLIED_BACKWARD},

    {'"',  '"',  IMPLIED_NONE},
    {'`',  '`',  IMPLIED_NONE},
    {'/',  '/',  IMPLIED_NONE},
    {'\'', '\'', IMPLIED_NONE},
    {'\\', '\\', IMPLIED_NONE},
};


/* find_mate_pos_in_buffer returns position of the found string in the character buffer;
   if the text has not been found (or in case of error) returns negative number;
   The search is started from startpos and is carried forward when "forward"
   is True (when "forward" is False the search direction is backward).
   The search is case sensitive for case_sensitive=True and only whole
   words are found when whole_words_only=True.
   The startpos and endpos should contain the range where the string should
   be searched for. When startpos or endpos is < 0 the default value will be
   assigned for it inside the procedure (i.e. beginnning or end of the file );
 */

#ifdef _NO_PROTO
long find_mate_pos_in_buffer(buf, c , mate,
			startpos, endpos, forward)
char *buf;
char c;
char mate;
long startpos;
long endpos;
Boolean forward;
#else  /* _NO_PROTO */
long find_mate_pos_in_buffer(char *buf, char c, char mate,
			long startpos, long endpos, Boolean forward)
#endif
{
    long pos = -1L;
    long buf_size = (long) strlen(buf);
    register long i, n;
    int  nestLevel;

    if(buf_size <= 0L)      return(-2L);

    /* setting the default value for negative startpos and/or endpos */
    if(startpos < 0L)
    {
	if(forward) startpos = 0L;
	else	    startpos = buf_size;
    }
    if(endpos < 0L)
    {
	if(forward) endpos   = buf_size;
	else	    endpos   = 0L;
    }

    /* test (and correct) the search range */
    if(endpos > buf_size)     endpos=buf_size;
    if(startpos > buf_size) startpos=buf_size;


    nestLevel = 1;	/* keeps track of how many delimiter levels the search
			   enters and exits
			*/
    if(forward)
    {
	/* search forward starting from startpos position */

	for( i = startpos; i < endpos; i++)
	{
	  if(buf[i] == mate) nestLevel--;
	  else if(buf[i] == c) nestLevel++;

	  if(nestLevel == 0)
	  {
		pos = i;
		break; /* mate found: now "i" shows position of the mate
				in the buffer relatively to the buffer beginning */
	  }
	}
    }
    else
    {
	/* search backward starting from startpos-1 position */
	for( i = startpos-1; i >= endpos; i--)
	{
	  if(buf[i] == mate) nestLevel--;
	  else if(buf[i] == c) nestLevel++;

	  if(nestLevel == 0)
	  {
	      pos = i;
	      break; 	/* mateg found: now "i" shows position of the mate
				in the buffer relatively to the buffer beginning */
	  }
	}

    }

    return(pos);

}   /* find_mate_pos_in_buffer */


#ifdef _NO_PROTO
void findDelimiterMate(win, forward)
    aseditWindowStruct *win;
    Boolean forward;
#else  /* _NO_PROTO */

void findDelimiterMate(aseditWindowStruct *win, Boolean forward)
#endif
{

    XmTextPosition pos, startpos, matepos;
    Arg al[1];                 /* arg list                 */
    int i, n_pairs, id = -1, status;
    char *txt=NULL, sub_txt[10];
    char c='\0';

    /* get the value of the cursor position */
    XtSetArg(al[0], XmNcursorPosition, &pos);
    XtGetValues(win->edit_text, al, 1);
    if(pos >= 0)
    {
	/* the cursor is positioned just before the delimiter in question */
	n_pairs = XtNumber(pairs);

	/* get the single character to speed the
	   process when a user calls this procedure for a wrong character
	*/
        txt = getTextSubstring(win->edit_text, pos, 1);
	if(!txt)	return;		/* should not happen */

	c = txt[0];			/* character for the pair */
        XtFree(txt);

	/* find the pair */

	for(i=0; i<n_pairs; i++)
	{
	    if(c == pairs[i].c)
	    {
		id = i;
		break;
	    }
	}
	if(id >= 0)
	{
	    /* the normal call for an existing pair; process it */

	    /* get the whole edit buffer now */
	    txt = (char *)XmTextGetString(win->edit_text);
	    if(!txt)	return;		/* should not happen */

	    if(pairs[id].impliedDirection == IMPLIED_FORWARD)
				forward = True;		/* reset the flag */
	    else if(pairs[id].impliedDirection == IMPLIED_BACKWARD)
				forward = False;
	    else ;		/* do no change forward flag */

	    if(forward) startpos = pos+1;	/* starting position for search */
	    else        startpos = pos;

	    matepos = find_mate_pos_in_buffer(txt, pairs[id].c , pairs[id].mate,
			startpos, -1L, forward);
	    if(matepos >= 0)
	    {
		if(lstr.selectDelimitedText)	/* select delimited text */
		{
		    if(matepos < pos)
			XmTextSetSelection(win->edit_text, matepos, pos+1, win->timestamp_search_request);
		    else 
			XmTextSetSelection(win->edit_text, pos, matepos+1, win->timestamp_search_request);
		}

		XmTextSetInsertionPosition(win->edit_text, matepos);
	    }
	}
	if(txt) XtFree(txt);		/* free the memory */
    }


}   /* findDelimiterMate */


/**************************     find_win_for_edit_text  ************************
 finds asedit win structure for the given edit_text windget (a win element)
*/
#ifdef _NO_PROTO
static aseditWindowStruct * find_win_for_edit_text(edit_text)
    Widget edit_text;
#else  /* ! _NO_PROTO */

static aseditWindowStruct * find_win_for_edit_text(Widget edit_text)
#endif
{
    aseditWindowStruct *wc=NULL;    /* current window */

    /* DO that for all asedit windows .... */
    wc = asedit_last_window;            /* start from the last */

    if(wc == NULL)  return(wc);             /* sanity check */

    do          /* loop until there is no previous window */
    {
        if( wc->edit_text == edit_text) break;  /* FOUND */

        wc   = wc->prev;

    } while(wc);

    /* if not found, returns NULL */

    return(wc);

}   /* find_win_for_edit_text */


#ifdef _NO_PROTO
static void asedit_undo(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_undo(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{

    aseditWindowStruct *win =  find_win_for_edit_text(w);

    if(win->can_undo)
    {
      TurnWatchCursor(True);
      if(win)     /* sanity check */
      {
        UndoRedo(win, MENU_UNDO);
      }
      TurnWatchCursor(False);
    }

}   /* asedit_undo */



#ifdef _NO_PROTO
static void asedit_redo(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_redo(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{
    aseditWindowStruct *win =  find_win_for_edit_text(w);

    if(win->can_redo)
    {
      TurnWatchCursor(True);
      if(win)     /* sanity check */
      {
        UndoRedo(win, MENU_REDO);
      }
      TurnWatchCursor(False);
    }

}   /* asedit_redo */



#ifdef _NO_PROTO
static void asedit_find(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_find(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{

    aseditWindowStruct *win =  find_win_for_edit_text(w);

    TurnWatchCursor(True);
    if(win)     /* sanity check */
    {
        /* set the reason for managing replace_dialog */
        win->search_reason = MENU_FIND;
        show_replace_dialog(win);
    }
    TurnWatchCursor(False);

}   /* asedit_find */

#ifdef _NO_PROTO
static void asedit_change(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_change(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{
    aseditWindowStruct *win =  find_win_for_edit_text(w);

    /* the change action should be performed only for editable files !! */
    if(!win->file_read_only)
    {
      TurnWatchCursor(True);
      if(win)     /* sanity check */
      {
        /* set the reason for managing replace_dialog */
        win->search_reason = MENU_CHANGE;
        show_replace_dialog(win);
      }
      TurnWatchCursor(False);
    }

}   /* asedit_change */


#ifdef _NO_PROTO
static void asedit_mark_set(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_mark_set(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{

    aseditWindowStruct *win =  find_win_for_edit_text(w);
    int         mark=0;

    if(win && num_params)     /* sanity check */
    {

        mark = (int) atol(params[0]);
        if(mark >= 0 && mark < TOTAL_MARKS)     /* sanity check */
        {
	    XmTextPosition pos;
	    Arg al[1];                 /* arg list                 */

            /* get the value of the cursor position */
            XtSetArg(al[0], XmNcursorPosition, &pos);
            XtGetValues(win->edit_text, al, 1);
            win->mark_pos[mark] = pos;
            win->mark_set[mark] = True;
            win->marks_used     = True;
            XtSetSensitive( win->menu_mark_go_button[mark], True);
        }
    }

}   /* asedit_mark_set */


#ifdef _NO_PROTO
static void asedit_mark_goto(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */
static void asedit_mark_goto(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{

    aseditWindowStruct *win =  find_win_for_edit_text(w);
    int         mark=0;

    if(win && win->marks_used && num_params)     /* sanity check */
    {

        mark = (int) atol(params[0]);
        if(mark >= 0 && mark < TOTAL_MARKS)     /* sanity check */
        {
            if(win->mark_set[mark]) XmTextSetCursorPosition(win->edit_text, win->mark_pos[mark]);
        }
    }

}   /* asedit_mark_goto */



#ifdef _NO_PROTO
static void asedit_forward_match(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_forward_match(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{

    aseditWindowStruct *win =  find_win_for_edit_text(w);

    if(win)     /* sanity check */
    {
	/* set the timestamp needed to set selection of the delimited text */
	win->timestamp_search_request = event->xkey.time;

	findDelimiterMate(win, True);
    }

}   /* asedit_forward_match */


#ifdef _NO_PROTO
static void asedit_backward_match(w, event, params, num_params)
    Widget w;
    XEvent *event;
    char **params;
    Cardinal *num_params;
#else  /* _NO_PROTO */

static void asedit_backward_match(Widget w, XEvent *event, char **params, Cardinal *num_params)
#endif
{

    aseditWindowStruct *win =  find_win_for_edit_text(w);
    int         mark=0;

    if(win)     /* sanity check */
    {
	/* set the timestamp needed to set selection of the delimited text */
	win->timestamp_search_request = event->xkey.time;

	findDelimiterMate(win, False);
    }

}   /* asedit_backward_match */







