#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  : modeproc.c 
 
                       VERSION : v3.4
 
                       REVISED : 16 Jun 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains the basic character processors
 
               for VE's three modes of operation: INSERT, OVERWRITE,
 
               and COMMAND.  These processors place text into the file
 
               in the first two modes, and execute commands in the last

               mode.








*/

/* .page */

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

#include <curses.h>

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

#define MAX_REP_DIGITS 8        /* Max digits in a repitition count */

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

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

#include "../ext/strprim.ext"
#include "../ext/termif.ext"
#include "../ext/veutils.ext"
#include "../ext/lnklst.ext"
#include "../ext/termif.ext"
#include "../ext/modeproc.ext"
#include "../ext/undo.ext"
 
/******************
 * MODULE GLOBALS *
 ******************/

static int auto_indent_level = 0;  /* Initial auto indent level              */

/* Last command information for use with the Again command.                  */
/* This is separate from the global last_cmd information, which is used for  */
/* other purposes throughout VE. The ag_last_cmd is the last command that    */
/* was part of the again command set, vs. the last command run. The two will */
/* diverge frequently, since only commands that change the file are part of  */
/* the again command set.                                                    */

static char ag_last_cmd        = 'I';
static int  ag_last_cmd_ix     = insert_ix;
static int  ag_last_reps       = 1;

/* .page */

/********************
 * LOCAL PROCEDURES *
 ********************/

void tab_handler ()

/*****************************************************************************
 *                                                                           *
 *   Handle the input of a TAB character by moving to the next TAB stop.     *
 *                                                                           *
 *****************************************************************************/

begin
    register int old_cx, new_cx;

    /* Compute the new TAB stop, and then pass the correct number of spaces  */
    /* to the visual handler.                                                */

    new_cx = ((curr_screen.cx / options.tab_stop) + 1) * options.tab_stop;

    for ( old_cx = curr_screen.cx; old_cx < new_cx; old_cx++ )
          visual_processor ( ' ' );

end /* tab_handler */

/* .page */

void enter_handler ()

/****************************************************************************
 *                                                                          *
 *   Handle ENTER key in both M_INSERT and M_OVERWRITE mode                 *
 *                                                                          *
 ****************************************************************************/

begin
    register int i;           /* Simple loop counter                        */
    int leading_white_space;  /* White space at head of current line        */

    /* If the cursor is at the start of a new line, insert a line above this */
    /* line, and move this line down one.  The cursor will remain on this    */
    /* after it has moved down                                               */

    if (curr_screen.cx == 0) begin

        update_curr_line ();

        /* Push an undo action before starting. Since an ENTER at the start  */
        /* of a line simply creates a new line, and the undo action is to    */
        /* delete that line, this code shamelessly re-uses the undo action   */
        /* for block paste, for which the undo behaviour is the same - del   */
        /* lines that have been inserted.                                    */

        if ( undo_state != US_undoing )
           push_undo_block_paste ( insert_ix, 1, get_curr_line_num(),
                                   curr_screen.cx, curr_screen.cy     );
        new_line = (struct node *) ve_alloc (DES_SIZE);

        new_line->length = 0;
        add (&curr_file, curr_line->bwd_link, new_line, &status);
        curr_line = new_line;

        ti_insert_line ( curr_screen.cy );
        cursor_down_cmd ();
    end

    /* Else, the cursor is at middle, end of a line.  This line must be     */
    /* broken into two pieces, with one line being made out of each piece.  */

    else begin

        /* If the auto indent function is on, compute the number of bytes of  */
        /* space at the start of this line, so that the next line can be auto */
        /* indented.                                                          */

        leading_white_space = white_space(&line_buf);

        if ( !options.auto_indent )
           leading_white_space = 0;
        else 
           /* If the line has content on it (not just all white space) then   */
           /* reset the auto indent level to match - else use the old one.    */

           if ( line_buf.length != 0 && line_buf.length != leading_white_space )
              auto_indent_level = leading_white_space;
           else
              leading_white_space = auto_indent_level;

        /* If the cursor is beyond the end of the line, then the new line     */
        /* being created is of zero length.  Else, it has a positive length   */
        /* Truncate the current line, and set up its length accordingly.      */

        if (curr_screen.cx >= line_buf.length) then
            temp.length = 0;

        else begin
            temp.length = line_buf.length - curr_screen.cx + leading_white_space;

            setb (' ', &temp.text[0], leading_white_space + 5 );

            movb (&line_buf.text[curr_screen.cx],
                  &temp.text[leading_white_space],
                  temp.length - leading_white_space);

            line_buf.length = curr_screen.cx; /* truncate the current line  */
        end

        /*  Copy the possibly truncated current line buffer to the current    */
        /*  line and push an undo action. In this case, the way to undo an    */
        /*  ENTER is to do a DEL at the end of line, which causes a line_join */
        /*  So, push the undo of a char insertion at the end of the line      */

        update_curr_line();

        if ( undo_state != US_undoing )
           push_undo_ins_char ( ' ', insert_ix, get_curr_line_num(),
                                curr_line->length, curr_screen.cy    );

        /* Create a new line and add it to the file                         */
    
        new_line = (struct node *) ve_alloc (temp.length + DES_SIZE);
    
        setb (' ', new_line, temp.length + DES_SIZE);
        movb (&temp.length, &new_line->length, temp.length + sizeof(int));
    
        add (&curr_file, curr_line, new_line, &status);

        /* Do the correct thing to the screen display.  Clear to the end    */
        /* of the line, insert a new line, and write it onto the screen     */
    
        if (curr_screen.cx <= columns - 1) then
            ti_clear_to_eol ( curr_screen.cy );

        cursor_down_cmd ();

        if ( curr_screen.cy == lines-1 )
        begin
           ti_move_cursor  ( ti_get_left_cx(), lines-1 );
           ti_clear_to_eol ( lines-1 );
        end
        else
           ti_insert_line ( curr_screen.cy );

        curr_line = new_line;

        ti_move_cursor ( (curr_screen.cx = 0), curr_screen.cy );
        writeln (curr_line->text, curr_line->length, 0);

        curr_screen.cx = leading_white_space;
        restore_cursor ();
    end

    curr_screen.cy = update_curr_screen (curr_screen.cy);

end /* enter handler */

/* .page */

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

void cmd_processor ( key )

/****************************************************************************
 *                                                                          *
 *   Accept one command key and execute that command.  If the key is        *
 *   numeric, save in a number conversion buffer, for eventual use as a     *
 *   repition count.  In this case, the command received will be executed   *
 *   a number of times.                                                     *
 *                                                                          *
 ****************************************************************************/

char key;           /* An entered command key from the user                 */

begin
    char cmd;       /* Command to execute, derived from the input key       */
    int  cmd_ix;    /* Index of that command in total command set           */
    int  clear;     /* Clear status line or not                             */
    int  temp_reps; /* Hold requested_reps during command execution         */
    int  temp_rs;   /* Hold read_state during command execution             */

    int  undo_queue_size;     /* Size of undo queue before cmd executed     */
    int  new_undo_queue_size; /* Size of undo queue after cmd executed      */

    /* If the input character is a number, then it is part of a repetition  */
    /* count.  Queue the number up until it is terminated by a non numeric  */
    /* key.  At that time, the number will be converted.                    */

    requested_reps = 0;    /* Number of reps not known at this point        */
    curr_rep       = 0;

    if ((key == '*') || (key >= '0' && key <= '9' )) begin

        /* If this is the first character of the repetition count, clear    */
        /* the status line area where it will be echoed                     */

        if ( rep_ix == 0 )
            set_status_line (TRUE, E_OK, (ve_mode = M_COMMAND) );

        /* The key is either a "*", or a number. The user is entering a     */
        /* repetition count for a command. No matter what has been entered  */
        /* until now, entering a '*' resets the count to just the '*'       */

        if (key == '*') begin
            rep_ix = 0;
            set_status_line (TRUE, E_OK, (ve_mode = M_COMMAND) );
        end
        else begin
           /* A number has been entered. Check for the pathological case of */
           /* the user entering '*' and then a number. If this occurs,      */
           /* expunge the '*' and start again with the number               */

           if ( rep_buf.text[0] == '*' ) begin
              rep_ix = 0;
              set_status_line (TRUE, E_OK, (ve_mode = M_COMMAND) );
           end
        end

        /* save and echo the key */

        if ( rep_ix < MAX_REP_DIGITS )
        begin
           rep_buf.text[rep_ix++] = key;

           ti_move_cursor (rep_ix, 0);
           writechar (key);
        end
        else
           writechar (BEL);

        return;

    end /* User is entering a repetition count */

    /* If this key is non numeric, and the repetition buffer is not empty   */
    /* then check for Backspace/Rubout. If encountered do the right thing.  */
    /* Else, convert the buffer to a number                                 */

    if (rep_ix != 0) begin

        /* Backspace/Rubout? If so, do the rubout and exit. Note that this   */
        /* means that you can never enter a repetition count for the Rubout  */
        /* command. Rubout is also used to edit the repetition count.        */

        if ( key == commands[rubout_ix] ) begin
           ti_rubout ();
           rep_ix--;
           return;
        end

        if (rep_buf.text[0] == '*') then   /* If * entered for count, use   */
            requested_reps = 32000;        /* very large num ~ infinity     */

        else begin                         /* Else, convert entered num.    */
            rep_buf.text[rep_ix++] = ' ';
            sscanf (rep_buf.text, "%d", &requested_reps);
        end

        if (requested_reps == 0) then    /* Another pathological error case */
            requested_reps = 1;

        /* Repetition count entry is over. We are going to execute the      */
        /* currently entered key as a command. Clean up.                    */

        rep_ix = 0;
        restore_cursor ();
    end

    if ( key == ENTER ) begin
       /* User has entered ESC ENTER. They want to exit from                */
       /* command mode to their previous mode. Help them out.               */

       ve_mode = last_ve_mode;
       set_status_line (TRUE, E_OK, ve_mode);
       restore_cursor ();
       return;
    end

    /* OK, all the preliminaries are done. Lets get down to running this     */
    /* puppy! Assign the current command and look up its command index       */

    cmd    = curr_cmd = key;
    cmd_ix = findb (commands, cmd, NUM_CMDS);

    /* Now check for the case where the user has just completed a repetition */
    /* count. In this case, they have entered something like "ESC n key",    */
    /* where "n" is the repetition count. In this case, we may need to unmap */
    /* an extended keyboard command. See module termif.c, function           */
    /* unmap_extended_keyboard_key, for all the documentation you can stand  */
    /* on this particular topic.                                             */

    if ( requested_reps == 0 ) then   /* if no count entered request 1 rep   */
       requested_reps = 1;
    else                              /* count entered, unmap if needed      */
       cmd = unmap_extended_keyboard_key ( &cmd_ix );
    
    /* If the AGAIN command is received, repeat the last command, including  */
    /* its repetition count. Note that ag_last_cmd and ag_last_reps are only */
    /* set by commands that are part of the "again command set", which is    */
    /* generally commands that change the file, plus the Find command. The   */
    /* Again command set is defined in vedbase.c. Note the we purposely do   */
    /* not set curr_cmd to the command from Again, so that some commands can */
    /* can check whether they are being run via an Again command, and thus   */
    /* skip interactive prompting, just using values from the last run.      */

    if (cmd == commands[again_ix]) begin
        cmd            = ag_last_cmd;
        cmd_ix         = ag_last_cmd_ix;
        requested_reps = ag_last_reps;
    end

    /* If the command is not recognized, tell the user                       */

    if (cmd_ix == -1) then
        set_status_line (TRUE, E_UNKNOWN_CMD, last_ve_mode);

    /* Else, call the command processor from the command processor table     */
    /* as many times as is indicated by the repetition count                 */

    else begin
        temp_reps = requested_reps; /* Save reps - cmds may change it        */
        temp_rs   = read_state;     /* Save read_state - cmds may change it  */

        /* Run the command */

        while (curr_rep++ < requested_reps) begin

            (*cmd_proc[cmd_ix])();       /* Call the command processor       */

        end

        /* Now check to see if undo is supported for the command that was    */
        /* just run. If not, flush the undo queue to maintain file integrity */

        if ( !undo_supported[cmd_ix] )
             flush_undo_queue ();

        /* If this was a command that can be repeated by the Again command,  */
        /* record that fact for later use. Commands executed as part of a    */
        /* macro are omitted from this, so that the ESC-x (eXecute Macro)    */
        /* command can be re-executed with the Again command. Practically,   */
        /* this means that you can't embed an Again command in a macro. A    */
        /* small price to pay!                                               */

        if ( in_again_cmd_set[cmd_ix] && temp_rs == RS_TRANSPARENT ) begin
           ag_last_cmd    = cmd;
           ag_last_cmd_ix = cmd_ix;
           ag_last_reps   = temp_reps;
        end

        /* Record global last_cmd information for use by other commands      */

        last_cmd    = cmd;
        last_cmd_ix = cmd_ix;
        last_reps   = temp_reps;

        /* Knock VE back out of command and back into either Insert or      */
        /* Overwrite. This involves updating the displayed mode, if it is   */
        /* Command, and changing the actual mode itself back.               */

        if ( displayed_mode == M_COMMAND )
           set_status_line ( TRUE, E_OK, last_ve_mode );

        ve_mode = last_ve_mode;

    end

end /* cmd_processor */

/* .page */

void visual_processor (key)

/******************************************************************************
 *                                                                            *
 *   A key has been entered while in visual mode.  Add it or overwrite it to  *
 *   the text currently displayed on screen.                                  *
 *                                                                            *
 ******************************************************************************/

char key;

begin
    register int i;            /* Loop counter                              */
    register char old_key;     /* Current char at overwrite position        */
    int temp_cx;               /* Temporary holder for current cx           */

    /* If not already done, move the current line into the work buffer      */

    load_line_buf ();

    /* Handle the entry of a ENTER key                                      */

    if (key == ENTER) then
        enter_handler ();

    /* Handle the entry of a TAB key                                        */

    else if (key == TAB && options.input_tab_translation == TRUE) then
        tab_handler ();

    /* Key is not ENTER or TAB (or tabs are not enabled) : handle by mode   */

    else begin
        /* A key has been received while in overwrite mode.  Use the entered */
        /* key to overwrite the current key at the cursor position.          */

        if (ve_mode == M_OVERWRITE) begin

            if ( curr_screen.cx >= MAX_LINE_SIZE - 2 )
            begin
               write_ve_msg ( "Maximum line size reached" );
               return;
            end

            if (curr_screen.cx < columns - 1) then
                writechar (key);

            old_key = line_buf.text[curr_screen.cx];
            line_buf.text[curr_screen.cx++] = key;

            if ( undo_state != US_undoing )
               push_undo_ins_char ( old_key, overwrite_ix, get_curr_line_num(), 
                                    curr_screen.cx, curr_screen.cy );

            if (curr_screen.cx > line_buf.length) then
                line_buf.length = curr_screen.cx;
        end

        /* A key has been received while in the insert mode.  Insert that   */
        /* key at the current cursor position.                              */

        if (ve_mode == M_INSERT) begin

            if ( line_buf.length >= MAX_LINE_SIZE - 2 || 
                 curr_screen.cx  >= MAX_LINE_SIZE - 2 )
            begin
               write_ve_msg ( "Maximum line size reached" );
               return;
            end

            if (curr_screen.cx > line_buf.length) then

                /* If the user cursors out beyond the end of the line and   */
                /* attempts to enter a char, let them do it if we are not   */
                /* wrapping text on the fly. If we are, this makes no       */
                /* sense. Slap their wrist with a BEL, and force them to be */
                /* sensible (i.e. force them back to line end)              */

                if ( !options.wrap_text ) then
                   line_buf.length = curr_screen.cx;

                else begin
                   writechar ( BEL );
                   curr_screen.cx = line_buf.length;
                   restore_cursor ();
                end

            if (curr_screen.cx < columns - 1)   /* Update the display       */
                ti_insert_char ( (key == '\t') ? wtab_char : key );

            if ( undo_state != US_undoing )
               push_undo_ins_char ( ' ', insert_ix, get_curr_line_num(), 
                                    curr_screen.cx, curr_screen.cy );

            /* Insert the character into line_buf by creating a space in the */
            /* buffer and then putting the key into it                       */

            for ( i = line_buf.length++ ; i >= curr_screen.cx; i-- )
               line_buf.text[i+1] = line_buf.text[i];

            line_buf.text[curr_screen.cx++] = key;

            /* Finally, check for on-the-fly text wrapping being on. If it  */
            /* is, check the current line for wrapping and wrap if needed   */

            if ( options.wrap_text ) begin

               /* The undo system doesn't understand text wrapping. If we   */
               /* start to wrap text, pull the plug on the undo queue!      */

               flush_undo_queue ();

               wrap_line ( FALSE );

            end /* options.wrap_text */

        end /* mode = M_INSERT */

    end /* key is not ENTER or TAB */

end /* visual_processor */

