#include<stdio.h> 
 
/* 




                 VV                   VV    EEEEEEEEEEEEEE   
                  VV                 VV     EE  
                   VV               VV      EE
                    VV             VV       EE
                     VV           VV        EE
                      VV         VV         EEEEEEEEEEEEEE
                       VV       VV          EE
                        VV     VV           EE
                         VV   VV            EE
                          VV VV             EE
                           VVV              EE
                            V               EEEEEEEEEEEEEE
                                                       
 
 
 
 
                       MODULE  : findcmd.c
 
                       VERSION : v3.5
 
                       REVISED : 12 Jul 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains the FIND and SUBSTITUTE commands
 
               for the VE editor.                                
 
 










*/

/* .page */

/*****************
 * INCLUDE FILES *
 *****************/

#define FIND       'f'
#define SUBSTITUTE 's'

#include <string.h>
#include <curses.h>

#include "../def/common.def"
#include "../def/kbdif.def"
#include "../def/cmdix.def"

#include "../str/vestruct.str"

#include "../inc/vedbase.inc"
#include "../inc/termif.inc"
#include "../inc/kbdif.inc"

#include "../ext/lnklst.ext"
#include "../ext/veutils.ext"
#include "../ext/termif.ext"

#include "../ext/findcmd.ext"


/********************
 * STATIC VARIABLES *
 ********************/

static char this_cmd;           /* Either Substitute or Find        */
static int  this_page;          /* String was on displayed page ?   */
static int  verify;             /* Boolean for verify on replace    */

static int  old_cx;             /* Pre-command CX                   */
static int  first_cx;           /* CX for initial command           */
static int  last_cx;            /* CX last found                    */

static int  replacements_made;  /* Number of replacements done      */
static struct node *first_line; /* Curr line for first iteration    */
static struct node *last_line;  /* Line found on last iteration     */

static char *verify_str = " v";

/* .page */

BOOL substring_of ( test_str, host_str, tlen, hlen, x_val )

/******************************************************************************
 *                                                                            *
 *   Determine whether the test string is a subset of the host string.  If    *
 *   it is, return TRUE, and the x value of the START of test_str within      *
 *   host_str.  If it is not, return FALSE.                                   *
 *                                                                            *
 ******************************************************************************/

 char test_str [];      /* String to look for in host_str                   */
 char host_str [];      /* String to be searched for occurance of test_str  */
 int  tlen;             /* Length of test_str                               */
 int  hlen;             /* Length of host_str                               */
 int  *x_val;           /* Starting index on entry, end index on exit       */

 begin
    register int x;           /* High speed loop counter                    */
    int ix = 0;               /* Result of cmpb operation                   */
    int end_ix;               /* Last position of host_str to search        */
    int is_substring = FALSE; /* Return result                              */

    if ( test_str[0] == options.sol_match_char ) begin

       /* Special case of matching only at the start of the line. These     */
       /* cases are indicated by having the start-of-line-match-char, which */
       /* defaults to '^' (like regexp) as the start of test_str.           */

       if ( *x_val != 0 ) then     /* If not at col 0 you can't match       */
          return ( FALSE);

       else if ( tlen == 1 ) then  /* At col 0, asked to match sol, done!   */
          return ( TRUE );

       else begin
          ix = cmpb ( &host_str[0], &test_str[1], tlen-1 );
          if ( ix == -1 ) then
             return ( TRUE );
          else
             return ( FALSE );
       end

    end /* only match start of line */

    if ( test_str[tlen-1] == options.eol_match_char ) begin

       /* Special case of matching only at the end of the line. These cases */
       /* are indicated by having the end-of-line-match-char, which         */
       /* defaults to '$' (like regexp) as the start of test_str.           */

       if (*x_val > (hlen-tlen+1)) then /* test_str can't fit, can't match! */
          return ( FALSE );

       if ( tlen == 1 ) begin           /* Asked to match eol-just go there! */
          *x_val = hlen;
          return ( TRUE );
       end

       else begin
          ix = cmpb ( &host_str[hlen-tlen+1], &test_str[0], tlen-1 );
          if ( ix == -1 ) begin
             *x_val = hlen - tlen + 1;
             return ( TRUE );
          end
          else
             return ( FALSE );
       end

    end /* only match end of line */

    end_ix = hlen - tlen + 1;

    /* All the special cases taken care of. Now lets get down to business.  */
    /* Slide test_str along host_str, a character at a time, and at each    */
    /* character position, check for a match. cmpb returns -1 if it matches */

    for (x = *x_val; x < end_ix; x++) begin

       if (host_str[x] == test_str[0]) then
           ix = cmpb (&host_str[x], &test_str[0], tlen );

       if (ix == -1 ) begin

           /* We have found an instance of the test_str in the         */
           /* host_str. Ladies and Gentlemen, we have a winner!        */

           is_substring = TRUE;
           break;

       end /* we have an instance... */

    end /* for */

    *x_val = x;

    return ( is_substring );

end /* substring_of */

/* .page */

void find_cmd ()

/******************************************************************************
 *                                                                            *
 *   This is a stub for the find command.  It sets a local variable to say    *
 *   how the find/substitute set was entered, and then calls the combined     *
 *   find and substitute command                                              *
 *                                                                            *
 ******************************************************************************/

begin

    this_cmd = FIND;

    find_and_substitute_cmds ();

end /* find_cmd */




void subs_cmd ()

/******************************************************************************
 *                                                                            *
 *   This is a stub for the substitute command.  It sets a local variable to  *
 *   say how the find/substitute set was entered, and then calls the combined *
 *   find and substitute command                                              *
 *                                                                            *
 ******************************************************************************/

begin

    this_cmd = SUBSTITUTE;

    find_and_substitute_cmds ();

end

/* .page */

void find_and_substitute_cmds ()

/******************************************************************************
 *                                                                            *
 *   This is a common front end for the find and substitute commands.  It     *
 *   prompts for a string to find, and then, if this is a substitute command, *
 *   prompts for a string to replace the found string with.                   *
 *                                                                            *
 ******************************************************************************/

begin
    register int x, prompt_ix, max_len;

    update_curr_line ();     /* Update this line before leaving it          */
    old_cx = curr_screen.cx; /* Retain pre-call cx                          */

    if (curr_rep == 1 && curr_cmd != commands[again_ix] ) begin  

        /* This is the first time through - prompt for text strings */

        verify = FALSE;

        prompt_ix = (this_cmd == FIND ? P_FIND : P_SUBSTITUTE);
        push_status_line (prompt_msg[prompt_ix]);

        /* Read in the string to be found.  If the user enters ENTER, then  */
        /* use the previous contents of the find buffer.                    */

        x = strlen(prompt_msg[prompt_ix]);

        /* Compute the maximum length of the replacement string. Since  */
        /* find_buf is an ASCII buffer, of length 82, we arbitrarily    */
        /* restrict the maximum find string to 78. If we don't have     */
        /* enough display space to handle 78 chars, we restrict it      */
        /* to the available display space. The available display space  */
        /* is physical_cols - 25 (25 is strlen of Replace prompt + 1)   */

        max_len = (physical_cols - 25 < 78) ? physical_cols - 25 : 78;

        temp.length = prompt_read_str ( x,  0,  find_buf.text, -2, -2, temp.text, max_len );
        if ( temp.length != 0 ) then
             movb ( &temp, &find_buf, sizeof(find_buf) );

        /* Similarly, if this is the substitute command, prompt for the     */
        /* string to replace with.  If the user enters ENTER, use the old   */
        /* one.                                                             */

        if (this_cmd == SUBSTITUTE) 
        begin
            /* Compute the maximum length of the replacement string. Since  */
            /* subs_buf is an ASCII buffer, of length 82, we arbitrarily    */
            /* restrict the maximum replacement string to 78. If we don't   */
            /* have enough display space to handle 78 chars, we restrict it */
            /* to the available display space. The available display space  */
            /* is physical_cols - 7 (7 is strlen of With prompt + 1)        */

            max_len = (physical_cols - 7 < 78) ? physical_cols - 7 : 78;

            write_divider_msg ( "With: ", 6, 2 ); /* 9 = strlen of with prompt */
            temp.length = prompt_read_str ( 6, 2, subs_buf.text, -2, -2, temp.text, max_len );
            clear_divider_msg ();

            if ( temp.length != 0 ) begin

                 /* Check to see if the user entered a space and the letter */
                 /* 'v'. If this is the case, they want VE to prompt them   */
                 /* for verification before each replacement is done. Note  */
                 /* this fact.                                              */

                 x =  cmpb ( verify_str, &temp.text[temp.length - 2], 2);

                 if ( x == -1 ) begin
                      verify       = TRUE;
                      temp.length -= 2;
                      temp.text[temp.length] = 0;
                 end

                 movb ( &temp, &subs_buf, sizeof(subs_buf) );

            end /* temp.length != 0 */

        end /* this_cmd == SUBSTITUTE */

    end /* curr_rep = 1 */

    else
        /* If this is more than the first rep, then at least one find cmd    */
        /* has been executed.  Because the find command leaves the cursor at */
        /* the head of the string, not the tail, repeated finds will just    */
        /* refind the same string.  Hence, move the cursor forward one, so   */
        /* that the last found string will not be refound.                   */

        if (curr_screen.cx <= MAX_LINE_SIZE-2) then
            curr_screen.cx++;

    /* Call the correct procedure to execute the desired command             */

    if (this_cmd == FIND) then
        find_proc ();
    else
        substitute_proc ();

    /* Update the MODE display of status line when all done.                 */

    if (curr_rep == requested_reps) then
        pop_status_line ();

end /* find_and_substitute_cmds */

/* .page */

int find_proc ()

/******************************************************************************
 *                                                                            *
 *   This procedure is the heart of the FIND command.  Based on the contents  *
 *   of the find_buf, it will search an entire file for those contents.  If   *
 *   they are found, it will move the current line to the line containing the *
 *   string, and update the screen accordingly                                *
 *                                                                            *
 ******************************************************************************/

begin
    int found, wrapped;            /* Booleans                               */
    int x, y;                      /* Search indices                         */
    struct node *final_line;       /* Line to end search at                  */

    wrapped = found = FALSE;       /* Have not wrapped around end of file    */

    update_curr_line ();
    final_line = curr_line->fwd_link;

    while (!found) begin

        x = curr_screen.cx;

        found = substring_of ( &find_buf.text,   &curr_line->text,
                               find_buf.length,  curr_line->length,   &x );
        curr_screen.cx = 0;

        /* If the string has not been found, move onto the next line,       */
        /* accounting for wrap around in the move forward                   */

        if (!found) begin

            /* Advance to the next line.  If the entire file has been       */
            /* searched without success, get out with failure.              */

            curr_line = curr_line->fwd_link;

            if ((curr_line == final_line)  && wrapped) then
                break;

            /* If we have reached the end of the file, wrap around          */

            if (curr_line == EOL) begin
                curr_line = curr_file.head;
                wrapped = TRUE;
            end

        end /* if not found */

        else
            curr_screen.cx = x;


     end /* while !found */

     /* If the string was not found, inform the user, and reset curr_line.  */
     /* Set the repetition count so that no further repititions will occur  */

     if (!found) begin

         x = curr_screen.cx = old_cx;  /* Get cursor back where it belongs  */

         if (curr_rep == 1 && curr_cmd != commands[again_ix] ) begin

             /* This is the first time through for a Find or Substitute     */
             /* command, and the target was not found. Tell the user so.    */

             writechar ( BEL );

             strcpy ( temp.text, "Not Found: " );
             strncat ( temp.text, find_buf.text, 28 );
             write_ve_msg ( temp.text );
         end

         if ( curr_cmd == commands[again_ix]) begin

            /* This is an Again of a Find or Substitute and the target was  */
            /* not found. Tell the user so.                                 */

            writechar ( BEL );
            sprintf ( temp.text, "%s %s", "No more occurances:", &find_buf.text );
            write_ve_msg ( temp.text );
         end

         if (final_line != EOL) then
             curr_line = final_line->bwd_link;
         else
             curr_line = curr_file.head;

         curr_rep = requested_reps;
     end

     if (found || curr_rep > 1) begin  /* String found at some time          */

         /* If this is the first iteration, record the first line that was   */
         /* changed, for use by future iterations.                           */

         if ( curr_rep == 1 && curr_cmd != commands[again_ix] ) begin
              first_cx   = curr_screen.cx;
              first_line = curr_line;
         end

         /* If this is the second or greater iteration, and we have     */
         /* refound the same occurance as was found the first time, go  */
         /* back to the last known postion, and tell the user.          */

         else if (    (curr_line       == first_line )
                   && ( curr_screen.cx == first_cx   ) ) begin

             curr_line      = last_line;
             curr_screen.cx = last_cx;

             curr_rep = requested_reps;
             found    = FALSE;

             if ( this_cmd != SUBSTITUTE ) begin
                 writechar ( BEL );
                 strcpy ( temp.text, "No more occurances: " );
                 strncat ( temp.text, find_buf.text, 28 );
                 write_ve_msg ( temp.text );
             end
         end

         last_cx   = curr_screen.cx;   /* Record last found set              */
         last_line = curr_line;

         /* Is the string on the currently displayed page ?                  */

         this_page = displayed (curr_line, &y);

         if (this_page) then            /* string was on this page           */
             ti_move_cursor (curr_screen.cx, (curr_screen.cy = y));

         else begin                     /* string not on this page           */

             if (curr_rep == requested_reps) begin

                 curr_screen.cy = update_curr_screen (11);
                 draw_curr_screen ();   /* display the new screen            */

                 this_page = TRUE;
             end
         end

     end /* string found */

     return (found);

end /* find_proc */

/* .page */

void substitute_proc ()

/*****************************************************************************
 *                                                                           *
 *    Substitute the string at current CX for the string in the subs buffer  *
 *                                                                           *
 *****************************************************************************/

begin
    int x, i;
    int found;
    int find_len;
    struct asc_buf undo_orig;

    if ( curr_rep == 1 && curr_cmd != commands[again_ix] ) then
       replacements_made = 0;

    found = find_proc ();   /* Attempt to find the string to be replaced    */

    if (found) begin        /* If found, proceed                            */

        if ( curr_line->length - find_buf.length + subs_buf.length > MAX_LINE_SIZE - 2 )
        begin
           write_ve_msg ( "Can't replace - makes line too long" );
           return;
        end

        if ( verify ) begin

            /* If the verify option has been specified, move the cursor to  */
            /* the string to be replaced (this may involve redrawing the    */
            /* screen if the string is not on the displayed page), and      */
            /* prompt for permission to do the replace.                     */

            if ( !this_page ) begin
                 curr_screen.cy = update_curr_screen ( 11 );
                 draw_curr_screen ();

                 this_page = TRUE;
            end

            write_status_line ( "OK to replace ? " );
            restore_cursor ();

            temp.text[0] = toupper ( read_char() );

            if ( temp.text[0] != 'Y' ) then
                return;

        end /* verify */

        /* Do the actual RAM substitution */

        replacements_made++;

        /* Check for replacements where the find buffer contains the        */
        /* special start-of-line (usually '^') or end-of-line (usually '$') */
        /* chars. If so, they are not part of the actual text found, and    */
        /* the effective length of the buffer must be reduced by one in the */
        /* actual substitution.                                             */

        find_len = find_buf.length;
        if ( find_buf.text[0]                 == options.sol_match_char   ||
             find_buf.text[find_buf.length-1] == options.eol_match_char ) then 
             find_len -= 1;

        /* Push an undo action onto the undo queue. Note that because of    */
        /* the possibility of '^' and '$' characters in the find buffer,    */
        /* we cannot directly pass the find buffer to the undo system.      */
        /* Instead we create a temporary undo text buffer, containing only  */
        /* the actual file text, and then pass THAT to the undo system.     */

        undo_orig.length = find_len;
        movb ( &curr_line->text[curr_screen.cx], undo_orig.text, find_len );
        push_undo_substitute ( &undo_orig, &subs_buf, get_curr_line_num(), 
                              curr_screen.cx, curr_screen.cy );

        /* Perform the actual substitution by copying up to the start of    */
        /* found string, then appending the substitution text, and then     */
        /* appending the remainder of the original line that follows the    */
        /* found text.                                                      */

        movb (&curr_line->text, &line_buf.text, curr_screen.cx);
        movb (&subs_buf.text, &line_buf.text[curr_screen.cx], subs_buf.length);
        movb (&curr_line->text[curr_screen.cx + find_len],
              &line_buf.text  [curr_screen.cx + subs_buf.length],
              curr_line->length - (curr_screen.cx + find_len));

        line_buf.length = curr_line->length - find_len + subs_buf.length;

        /* Update the substituted line, and, since we are replacing the     */
        /* physical buffer in which the line resides, update the first or   */
        /* last line indicators.                                            */

        update_curr_line ();

        if ( curr_rep == 1 && curr_cmd != commands[again_ix] ) then
             first_line = curr_line;
        else
             last_line = curr_line;

        /* Update the screen display now. If the find and subs buffers were  */
        /* the same length, the size of displayed line has not changed. Just */
        /* write over the effected area                                      */

        if (find_len == subs_buf.length && this_page) begin

            if (curr_screen.cx <= columns - 1) begin  /* if in display area  */

                x = curr_screen.cx;
                i = 0;

                while (x <= columns - 1 && i < find_len) 
                begin
                    writechar (curr_line->text[x]);
                    x++; i++;
                end
            end
        end

        /* Line length has changed: redraw the changed portion of the line  */

        else if (this_page) begin
            ti_move_cursor ( curr_screen.cx, curr_screen.cy );
            ti_clear_to_eol ( curr_screen.cy );
            writeln (curr_line->text, curr_line->length, curr_screen.cx);
        end

    end /* found */

    /* If this is the end of the requested repetitions, issue a message */
    /* saying how many replacements were done.                          */

    if ( ( found || curr_cmd != commands[again_ix] )         && 
       curr_rep == requested_reps && replacements_made > 0 ) begin
       sprintf ( undo_orig.text, "%d %s", replacements_made, "replacement(s) made" );
       write_ve_msg ( undo_orig.text );   
    end

    i = 0;

    /* To stop the possibility of infinite repitition in the case where */
    /* the subs_buf contains an instance of the find_buf, adjust the    */
    /* termination conditions, and the cx value.                        */

    if ( substring_of (&find_buf.text,     &subs_buf.text,
                        find_buf.length,    subs_buf.length,   &i ) ) begin

         if ( curr_rep == 1 && curr_cmd != commands[again_ix] ) then
              first_cx += i;

         curr_screen.cx += subs_buf.length;
    end

end /* substitute_proc */

