#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  : macrocmd.c
 
                       VERSION : v3.5
 
                       REVISED : 12 Jul 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains the Macro-Command routines for VE.
 
               The Macro command allows the users to define macro commands
 
               which are sequences of other VE commands.      
 











*/

/* .page */

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

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

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

#define CHECK_PATTERN1 0x55aa
#define CHECK_PATTERN2 0xaa55

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

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

#include "../ext/veutils.ext"
#include "../ext/strprim.ext"
#include "../ext/termif.ext"
#include "../ext/kbdif.ext"
#include "../ext/lnklst.ext"
#include "../ext/stacks.ext"


/***************
 *  CONSTANTS  *
 ***************/

char *macro_name_msg           = "Macro name: ";
char *no_such_macro_msg        = "No such macro       ";
char *macro_already_exists_msg = "Macro already exists";
char *not_creating_msg         = "Not creating a macro";
char *macro_name_too_long_msg  = "Macro name is too long";
char *cant_nest_macros_msg     = "Cannot nest macros";

/********************
 * Global Variables *
 ********************/

static char result_msg[82];  /* String for read/write result messages       */

static struct macro_str *last_macro = NULL; /* Last macro executed          */

/* .page */

/*********************
 * PUBLIC PROCEDURES *
 *********************/

struct macro_str *get_macro ( macro_name )

/*****************************************************************************
 *                                                                           *
 *  Search the current macro list for a macro whose name is the same as the  *
 *  macro name supplied.                                                     *
 *                                                                           *
 *****************************************************************************/

 char macro_name [];

 begin
    struct macro_str *des;
    int len, name_len;

    des = (struct macro_str *) macro_list.fwd_link;
    name_len = strlen ( macro_name );

    while ( des != EOL ) begin

        len = strlen ( des->name );

        if ( len == name_len ) begin

            if ( strcmp ( macro_name, des->name ) == 0 ) then
                 return ( des );
        end

        des = (struct macro_str *) des->fwd_link;

    end

    return ( NULL );

end /* get_macro */

/* .page */

void macro_create ()

/*****************************************************************************
 *                                                                           *
 *  Create a new macro.  This involves getting a name for it, allocating a   *
 *  macro descriptor, and setting the read_char state machine to the         *
 *  LEARNING state.                                                          *
 *                                                                           *
 *****************************************************************************/

 begin
    struct macro_str *des;
    struct macro_str *temp_des = NULL;

    /* Create macro descriptor node, clear it, and link it to list */

    des = (struct macro_str *) ve_alloc ( sizeof(struct macro_str));
    setb ( 0, des, sizeof(struct macro_str) );

    send_node ( &macro_list, (struct node *) des );

    /* Read in a name for this macro, and then ensure that a macro of this  */
    /* name does not already exist.  If it does, forget this create.        */

    prompt_read_str ( 0, 0, macro_name_msg, -1, -1, filename_buf.text, 18 );

    /* Note that if user enters a blank ENTER, get_macro will return the    */
    /* descriptor of the macro we are just now creating. This will cause it */
    /* be deleted per the below, and we are left in a clean state.          */

    temp_des = get_macro ( filename_buf.text );        /* Look up this name */

    if ( temp_des != NULL ) begin

        /* The macro already exists.  Delete this descriptor, and exit. */

        des = (struct macro_str *) delete ( &macro_list, des, &status );
        free ( des );

        write_ve_msg ( macro_already_exists_msg );
    end

    else begin

        strcpy ( des->name, filename_buf.text );
        curr_macro = des;

        strcpy ( temp.text, "Defining macro: " );
        strcat ( temp.text, filename_buf.text          );
        write_ve_msg ( temp.text );

        read_state = RS_LEARNING;

    end

end /* macro_create */

/* .page */

void macro_stop ()

/*****************************************************************************
 *                                                                           *
 *  Stop the definition of the current macro.                                *
 *                                                                           *
 *****************************************************************************/

 begin
    struct macro_str *des;
    int    status;

    if ( curr_macro == NULL )
         write_ve_msg ( not_creating_msg );

    else if ( curr_macro->length <= 3 ) begin

        /* Null macro definition:  Delete it */

        des = (struct macro_str *) delete ( &macro_list, curr_macro, &status );
        free ( des );

        read_state = RS_TRANSPARENT;
        curr_macro = NULL;

        write_ve_msg ( "Empty macro - deleted" );
    end

    else begin

        /* This was a good macro definition: Remove the "ESC M S" from it,  */
        /* Keep it                                                          */

        curr_macro->length -= 3;      /* Removes ESC M S */

        strcpy ( temp.text, "Macro defined: " );
        strcat ( temp.text, curr_macro->name  );
        write_ve_msg ( temp.text );

        read_state = RS_TRANSPARENT;
        curr_macro = NULL;
    end

end /* macro_stop */

/* .page */

void macro_delete ( pathname )

/*****************************************************************************
 *                                                                           *
 *  Delete a macro.  Prompt the user for the name, and then find a macro     *
 *  of that name.  After finding it, delete it.                              *
 *                                                                           *
 *****************************************************************************/

 char *pathname;

 begin
    struct macro_str *des;

    if ( pathname == NULL ) then
         prompt_read_str ( 0, 0, macro_name_msg, -1, -1, temp.text, 18 );
    else
         strcpy ( temp.text, pathname );

    des = get_macro ( &temp.text );

    if ( des == NULL ) then
         write_ve_msg ( no_such_macro_msg );

    else begin
        /* Delete the macro, and tell the user as much. Just in case that    */
        /* that macro might be used by an Again command, check if it was the */
        /* last one executed. Clear the last_macro var if it was.            */

        if ( last_macro == des ) then
           last_macro = NULL;

        des = (struct macro_str *) delete ( &macro_list, des, &status );
        free ( des );

        strcpy ( result_msg, temp.text );
        strcat ( result_msg, " deleted"  );
        write_ve_msg ( result_msg );

    end

end /* macro_delete */

/* .page */

void macro_write ()

/*****************************************************************************
 *                                                                           *
 *  Write a macro out to a disk file.                                        *
 *                                                                           *
 *****************************************************************************/

 begin
    struct   macro_str *des;       /* Descriptor for macro being written     */
    char     display_fn[82];       /* Displayable filename                   */
    int      status = E_OK;        /* Status of file operations              */
    FILE     *conn = NULL;         /* File pointer for file being written    */
    register int i;                /* Index                                  */

    update_curr_line ();

    /* Get the name of the macro we are being requested to write out */

    prompt_read_str ( 0, 0, macro_name_msg, -1, -1, temp.text, 18 );
    des = get_macro ( temp.text );

    /* If it doesn't exist, tell the dummy */

    if ( des == NULL ) then
         write_ve_msg ( no_such_macro_msg );

    else begin

        /* The macro exists: Get a filename for it */

        open_file_dialog ( select_file_write, temp.text, MAX_LINE_SIZE, TRUE );

        if ( strlen(temp.text) > 0 )
           conn = write_open ( temp.text, FALSE, &status );

        if ( conn == NULL ) then
             write_ve_msg ( err_msg[E_OPEN_ERROR] );

        else if ( status == E_OK ) begin

            /* The file exists: write the macro to it. The binary image of */
            /* the macro descriptor is written out to the disk. Hence,     */
            /* macro files are not generally human readable.               */

            i                  = strlen(ve_masthead);
            des->major_release = ve_masthead[i-4];
            des->minor_release = ve_masthead[i-2];
            des->subrelease    = ve_masthead[i-1];

            des->check1 = CHECK_PATTERN1;
            des->check2 = CHECK_PATTERN2;

            fwrite ( des, sizeof(struct macro_str), 1, conn );
            write_close ( conn, temp.text );

            truncate_str_left (temp.text, display_fn, ERR_FIELD_WIDTH-10);
            strcpy ( result_msg, display_fn );
            strcat ( result_msg, " written"  );
            write_ve_msg ( result_msg );

        end /* File exists */

    end /* Macro exists */

end /* macro_write */

/* .page */

void macro_read ( pathname )

/*****************************************************************************
 *                                                                           *
 *  Read a macro in from a file, and link it to the list of current macros.  *
 *                                                                           *
 *****************************************************************************/

 char *pathname;

 begin
    struct macro_str *des;
    char display_fn[82];
    int    status;

    FILE *conn;

    if ( pathname == NULL ) then
         open_file_dialog ( select_file_read, temp.text, MAX_LINE_SIZE, TRUE );
    else
         strcpy ( temp.text, pathname );

    /* Attempt to open the macro file */

    conn = fopen ( temp.text, "rb" );  /* open for read, binary file       */

    if ( conn == NULL ) then
         ve_exception = E_OPEN_ERROR;

    else begin

        /* The file exists:  Allocate a descriptor, and read it in. 8 Extra */
        /* bytes are allocated for safety                                   */

        des = (struct macro_str *) ve_alloc (sizeof(struct macro_str)+8 );

        /* The macro is ready: go for it !! */

        setb ( 0, des, sizeof(struct macro_str)+8 );

        fread ( des, sizeof(struct macro_str), 1, conn );
        fclose ( conn );

        if ( des->check1 == CHECK_PATTERN1 && des->check2 == CHECK_PATTERN2 )
        begin
           des->fwd_link = des->bwd_link = EOL;
           send_node ( &macro_list, (struct node *) des );

           truncate_str_left (temp.text, display_fn, ERR_FIELD_WIDTH-10);
           strcpy ( result_msg, display_fn );
           strcat ( result_msg, " read"  );
           write_ve_msg ( result_msg );
        end
        else
           write_ve_msg ( "Not a VE macro file" );

    end /* The file exists */

end /* macro_read */

/* .page */

void macro_execute ( macro_name )

/*****************************************************************************
 *                                                                           *
 *  Execute the named macro, by setting up the read_char state machine to    *
 *  read from the current macro instead of the operating system.             *
 *                                                                           *
 *****************************************************************************/

 char *macro_name;

 begin
    struct macro_str *des = NULL;
    int    status;

    /* Initial check for an attempt to nest macros - one macro executing    */
    /* another - right now this is not supported. Disallow this during the  */
    /* creation of a macro.                                                 */

    if ( read_state == RS_LEARNING )
    begin
       write_ve_msg ( cant_nest_macros_msg );
       return;
    end

    /* If macro_execute is being run by the Again command, and there is a   */
    /* valid previous macro, just use it.                                   */

    if ( curr_cmd == commands[again_ix] && last_macro != NULL ) then
       des = last_macro;

    else begin
       /* Else, prompt for a macro name and try to look up that macro.      */

       if ( macro_name == NULL ) then
            prompt_read_str ( 0, 0, macro_name_msg, -1, -1, temp.text, 18 );
       else
            strcpy ( temp.text, macro_name );

       des = get_macro ( &temp.text );
    end

    if ( des == NULL ) then
         write_ve_msg ( no_such_macro_msg );

    else begin
        /* The macro exists: set it up for execution of it */

        curr_macro = des;
        last_macro = curr_macro;
        rs_ix      = 0;

        /* Set the status line, because after we enter RS_MACRO state,      */
        /* it won't produce any visual effects.  Better do it now.          */

        if ( macro_name == NULL ) then
             set_status_line ( TRUE, E_OK, last_ve_mode );

        read_state = RS_MACRO;

        /* If more than one rep has been requested, set up the rep count    */
        /* for the state machine, and clear the global rep count            */

        if ( requested_reps >= 1 ) then
             rs_reps_left = requested_reps - 1;

        curr_rep = requested_reps;

        push_undo_macro ( macro_ix, get_curr_line_num(), curr_screen.cx,
                          curr_screen.cy );
    end /* The macro exists */

end /* macro_execute */

/* .page */

void macro_list_all ()

/*****************************************************************************
 *                                                                           *
 *  Give the user a list of all of the macros that are currently defined.    *
 *                                                                           *
 *****************************************************************************/

 begin
    struct macro_str *des;
    int i, x, cx, cy, len, selection;
    char msg_buf [82];

    des = (struct macro_str *) macro_list.fwd_link;
    i = 0;

    create_full_screen_info_page ( " - Available Macros" );

    /* Index field at 0, Macro Name Field at 8 and Size Field at 32 */

    mv_writestr ( 0, 3, FALSE, "Idx     Macro Name              Macro Size" );

    while ( des != EOL && i < lines - 6 ) begin

        sprintf ( temp.text, "%d       %s", i, &des->name );
        mv_writestr ( 0, 5+i, FALSE, temp.text );

        sprintf ( temp.text, "%d", des->length );
        mv_writestr ( 32, 5 + i++, FALSE, temp.text );

        des = (struct macro_str *) des->fwd_link;

    end /* while */

    remove_full_screen_info_page ();

end /* macro_list_all */

/* .page */

void macro_cmd ()

/*****************************************************************************
 *                                                                           *
 *  VE's Macro command.  This command allows the user to define and store    *
 *  VE macros, which are collections of other VE commands.  Use of macros    *
 *  allows the user to perform a group of repetitive editing operations in   *
 *  a simple fashion.                                                        *
 *                                                                           *
 *  The following subcommands are supported:                                 *
 *                                                                           *
 *  (a) Create      - Create a new macro, initiate its definition            *
 *  (b) Stop        - Stop the definition of a macro                         *
 *  (c) Delete      - Delete an existant macro                               *
 *  (e) Write       - Write a macro to a disk file                           *
 *  (f) Read        - Read in a macro from a disk file                       *
 *  (g) eXecute     - Execute an existant macro                              *
 *  (h) List        - List all currently defined macros                      *
 *                                                                           *
 *****************************************************************************/

begin
    register char key                = ' ';
    register int  valid_cmd_received = FALSE;

    char macro_menu[] = "Create  Stop  Delete  Write  Read  eXecute  List : ";

    update_curr_line ();

    push_status_line ( macro_menu );

    while ( !valid_cmd_received && key != ENTER )
    begin
        key = toupper(read_char());

        valid_cmd_received = TRUE;  /* innocent until proven guilty          */

        if ( ( read_state == RS_LEARNING ) && ( key != 'S' ) )
        begin
           /* To avoid recurrant macros, dissallow all attempts to use the M */
           /* command while defining a macro.  ("Stop" is the exception!)    */
           /* Remove the three keys "ESC-m letter" that got us here.         */

           write_ve_msg ( cant_nest_macros_msg );
           curr_macro->length -= 3;
        end

        else if ( key == 'C' ) then
             macro_create ();

        else if ( key == 'S' ) then
             macro_stop ();

        else if ( key == 'D' ) then
             macro_delete ( NIL_STRING );

        else if ( key == 'W' ) then
             macro_write ();

        else if ( key == 'R' ) then
             macro_read ( NIL_STRING );

        else if ( key == 'X' ) then
             macro_execute ( NIL_STRING );

        else if ( key == 'L' ) then
             macro_list_all ();

        else if ( key != ENTER )
        begin
            /* If the key wasn't enter, it is invalid - tell the user so    */

            valid_cmd_received = FALSE;
            writechar ( BEL );
        end

    end /* while */

    pop_status_line ();

end /* macro_cmd */

