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


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains the block line commands for the VE
 
               editor.  All subcommands of the Block command are 
 
               implemented here. This module also contains the single line
 
               copy, cut and paste commands. The equivalent operations

               for columns (rectangles) are provided in columncmd.c.

               Both modules (this one and columncmd.c) allow the 

               Line and Column (rectangle) buffers to be exchanged,

               allowing line operations to be performed on columns and 
               
               visa versa.
*/

/* .page */

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

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

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

#define  DEL_TOO   0x5555

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

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

#include "../ext/veutils.ext"
#include "../ext/termif.ext"
#include "../ext/kbdif.ext"
#include "../ext/lnklst.ext"
#include "../ext/strprim.ext"
#include "../ext/blockutil.ext"
#include "../ext/blockcmd.ext"
#include "../ext/wordcmd.ext"

/* .page */

/*******************
 * LOCAL VARIABLES *
 *******************/

static char        key;            /* block subcommand selection    */
static int         length;         /* strings read in from console  */
static FILE        *block_conn;    /* files used in block writes    */

static char        start_char;     /* start and end text chars      */
static char        end_char;

static struct node *first, *last;  /* first and last nodes of block */
static struct node *curr;          /* curr node of block            */

static struct t_file_area last_file_area; /* For use with Again     */
static char               last_key;

static struct file_ctl    kill_block;

static char block_menu [] = "Copy  Append  Kill  Delete  Paste  Wrap  Read  SaveAs  Xchng  Flush : ";

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


/* Commands, and the procedures to be activated by them */

char block_cmds [10] = { 'C', 'A', 'K', 'D', 'P', 'W', 'R', 'S', 'X', 'F' };

void (*block_cmd_procs[10]) () = {
    &block_buffer,
    &block_buffer,
    &block_delete,
    &block_delete,
    &block_get,
    &block_wrap,
    &block_read,
    &block_save_as,
    &line_column_exchange,
    &block_purge    };

/* .page */

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

void purge_line_del_buffer ()

/****************************************************************************
 *                                                                          *
 * This function purges the contents of the line_del_block.                 *
 *                                                                          *
 ****************************************************************************/

begin
    int checksum;

    checksum = line_del_block.checksum;  /* Save the "cut" signal field     */

    purge_list ( &line_del_block );

    line_del_block.checksum = checksum;  /* Restore the "cut" signal field   */

end /* purge_line_del_buffer */

/* .page */

void copy_line_del_buffer_cmd ()

/****************************************************************************
 *                                                                          *
 * This function copies lines into the line_del_block. If a repetition      *
 * count is given, it will do multiple lines at once and then clear the     *
 * repetition count.                                                        *
 *                                                                          *
 ****************************************************************************/

begin
    int num_lines;         /* The number of lines to copy                   */

    update_curr_line ();

    purge_line_del_buffer ();

    /* Starting at the current line, copy as many lines as indicated by the */
    /* repetition count into the line_del_block.                            */

    cnode = curr_line;

    for ( num_lines = 0; num_lines < requested_reps; num_lines++ ) begin

        nnode = cnode->fwd_link;

        /* If we are called from cut_line_del_buffer_cmd, it has set the    */
        /* checksum field to DEL_TOO. Grotty, but an effective "cut" signal */
        /* In this case, delete the line as well.                           */

        if (line_del_block.checksum == DEL_TOO) begin
            pnode = delete (&curr_file, cnode, &status);

            /* Check for case of deleting ONLY line of file. VE *always*    */
            /* needs a current line, so if we have deleted the only line,   */
            /* lets replace it.                                             */

            if ( curr_file.nodes == 0 ) begin
               curr_line = (struct node *) ve_alloc (82 + DES_SIZE);
               curr_line->length = 0;
               send_node (&curr_file, curr_line);
            end
        end

        else begin  /* Not called from cut_line_del_buffer_cmd () */

            pnode = (struct node *) ve_alloc (cnode->length + DES_SIZE);
            movb (&cnode->length, &pnode->length, cnode->length + sizeof(int));
        end

        send_node (&line_del_block, pnode);

        cnode = nnode;
        if ( cnode == EOL ) then
           break;

    end /* for */

    /* Post a status message to the user */

    sprintf ( temp.text, "%d %s", line_del_block.nodes, "line(s) copied " );
    if (line_del_block.checksum == DEL_TOO ) then
       strcat ( temp.text, "and deleted" );
    write_ve_msg ( temp.text );

    requested_reps = 1;          /* Stop any iteration on repetition counts */

end /* copy_line_del_buffer_cmd */

/* .page */

void cut_line_del_buffer_cmd ()

/****************************************************************************
 *                                                                          *
 * This function copies lines into the line_del_block and then deletes      *
 * them. If a repetition count is given, it will do multiple lines at once  *
 * and then clear the repetition count.                                     *
 *                                                                          *
 ****************************************************************************/

begin
   int y;                              /* Y cursor co-ordinate              */
   BOOL on_screen;                     /* Is post del curr_line displayed?  */
   BOOL curr_was_top;                  /* Is current line at top of screen? */
   struct node *prev_line;             /* Our new post delete curr_line     */

   prev_line = curr_line->bwd_link;
   on_screen = displayed (prev_line, &y);
   curr_was_top = (curr_line == curr_screen.top);

   line_del_block.checksum = DEL_TOO;  /* signal that a cut is needed too   */

   copy_line_del_buffer_cmd ();        /* Copy to buffer and delete         */

   after_delete_screen_update ( prev_line, on_screen, curr_was_top, y );

   line_del_block.checksum = 0;        /* Reset the "cut" signal - done!    */

   push_undo_del_block ( cut_line_del_ix, &line_del_block, get_curr_line_num(),
                         curr_screen.cx, curr_screen.cy );

end /* cut_line_del_buffer_cmd */

/* .page */

void paste_line_del_buffer_cmd ()

/****************************************************************************
 *                                                                          *
 * This function pastes the contents of the line_del_block into the curr    *
 * file ABOVE the current line. It uses the block_get command to do this,   *
 * being very sneaky about it - it exchanges the control blocks for the     *
 * block buffer and the line del buffer, calls block_get, and then puts     *
 * everything back.                                                         *
 *                                                                          *
 ****************************************************************************/

begin
   struct file_ctl temp_block;      /* Holding spot for the block buffer    */

   update_curr_line ();             /* block_get inserts above curr_line    */

   /* Save off the current block buffer and copy the line_del_block into it */

   movb ( &line_block,     &temp_block, sizeof(temp_block) );
   movb ( &line_del_block, &line_block, sizeof(line_block) );
  
   block_get (); /* Do the pasting */

   /* Copy the current block back to the line_del block (just in case at    */
   /* some future time the block_get command should alter the block), and   */
   /* then restore the current block buffer to its original contents.       */

   movb ( &line_block, &line_del_block, sizeof(line_del_block) );
   movb ( &temp_block, &line_block,     sizeof(line_block)     );

   sprintf ( temp.text, "%d %s", line_del_block.nodes, "line(s) pasted" );
   write_ve_msg ( temp.text );

end /* paste_line_del_buffer_cmd */

/* .page */

void line_column_exchange ( )

/*****************************************************************************
 *                                                                           *
 * This function exchanges the contents of the Column and Line block buffers *
 * allowing line operations to be carried out on column areas and visa versa *
 *                                                                           *
 *****************************************************************************/

begin
   exchange_line_column_blocks ();

   write_ve_msg ( "Line and Column buffers exchanged" );

end /* line_column_exchange */

/* .page */

void block_purge ()

/*****************************************************************************
 *                                                                           *
 * Delete anything in the current block line buffer, and clear its           *
 * descriptor.                                                               *
 *                                                                           *
 *****************************************************************************/

begin
    purge_list ( &line_block );

    /* If called from the Flush subcommand, output a success message        */

    if ( key == 'F' ) then
       write_ve_msg ( "Line Block buffer cleared" );

end /* block_purge */

/* .page */

void block_save_as ()

/*****************************************************************************
 *                                                                           *
 *   Save the contents of the current block to a user specified file.        *
 *                                                                           *
 *****************************************************************************/

begin
    int num_chars;         /* Number of chars written                        */
    int num_lines;         /* Number of lines written                        */
    int checksum;          /* Checksum of written block                      */
    struct node *cnode;    /* Current line                                   */
    struct node *pnode;    /* Last line                                      */
    char display_fn [40];  /* size limited version of file name              */

    if ( ( block[0] == block[1] ) && ( line_block.head != EOL ) )
    begin
       /* User wishes to write out the contents of the block buffer (from  */
       /* a previous copy/append operation)                                */

       cnode = line_block.head;
       pnode = line_block.tail;
    end

    else 
    begin
       /* User wishes to write out the currently defined block             */

       cnode = block[0];           /* Pick up start and end locations      */
       pnode = block[1];
    end

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

    if ( strlen(filename_buf.text) != 0 )
    begin
       ve_exception = write_file ( filename_buf.text, FALSE, 'u', cnode, pnode, 
                                   &num_chars, &num_lines, &checksum );

       truncate_str_left ( filename_buf.text, display_fn, 16 );
       if ( ve_exception == E_OK ) then
          strcat ( display_fn, " has been written" );
       else
          strcat ( display_fn, " failed to write" );
       write_ve_msg ( display_fn );

       ve_exception = E_OK;
    end
    else
       write_ve_msg ( no_file_select_write );

end /* block_save_as */

/* .page */

void block_get ()

/*****************************************************************************
 *                                                                           *
 *   Get the contents of the current line block, and insert them into the    *
 *   user's file ABOVE the current line.                                     *
 *                                                                           *
 *   This is equivalent to "Edit, Paste" in most GUI editors.                *
 *                                                                           *
 *****************************************************************************/

begin
    register struct node *last_line, *new_line;
    register int cy, lines_inserted, cmd_ix, lines_to_end_of_screen;

    if (line_block.head == EOL)       /* Nothing in block to get !           */
    begin
        write_ve_msg ( "Line buffer empty - Nothing to paste" );
        return;
    end

    /* If the curr line is the last line of the file, then we need to insert */
    /* the lines we are pasting (getting) AFTER the current line, not before */
    /* Make the necessary adjustments.                                       */

    if ( (curr_line == curr_file.tail) && (curr_line != curr_file.head) )
    begin
       last_line = curr_line;
       curr_screen.cy++;
    end
    else
       last_line = curr_line = curr_line->bwd_link;

    lines_inserted = 0;
    lines_to_end_of_screen = lines - curr_screen.cy;

    cnode = line_block.head;

    /* For the entire contents of the block buffer, create a copy of the     */
    /* line in the buffer, and add that copy to the current file.            */

    while (cnode != EOL) begin

        new_line = (struct node *) ve_alloc (cnode->length + DES_SIZE);

        movb (&cnode->length, &new_line->length, cnode->length + sizeof(int));

        add (&curr_file, last_line, new_line, &status);
        last_line = new_line;

        cy = curr_screen.cy + lines_inserted++;

        /* Update the screen display by inserting the new line onto the     */
        /* screen, if we have not filled up the available space between the */
        /* current line and the end of the screen.                          */

        if (lines_inserted <= lines_to_end_of_screen) begin
            ti_move_cursor ( ti_get_left_cx(), cy );
            ti_insert_line ( cy );
            writeln  ( new_line->text, new_line->length, 0);
        end

        pnode = cnode;
        cnode = cnode->fwd_link;
    end

    if ( curr_line->fwd_link != NULL )
       curr_line = curr_line->fwd_link;

    /* Update the curr_screen structure, and output a result message.       */
    /* The write_ve_msg routine will restore the cursor.                    */

    cy = update_curr_screen (curr_screen.cy);

    sprintf ( temp.text, "%d %s", lines_inserted, "line(s) pasted" );
    write_ve_msg ( temp.text );

    /* Push an undo action. Since block_get can be called from both the     */
    /* Block Paste command and from the LineDelPaste command, determine     */
    /* which it is before pushing the undo action. Note that various undo   */
    /* actions also use block_get, so make sure we haven't been called from */
    /* an undo action. If so, don't queue another one!                      */

    if ( undo_state != US_undoing )
    begin
       cmd_ix = (curr_cmd == commands[paste_line_del_ix]) ? paste_line_del_ix : get_ix;

       push_undo_block_paste ( cmd_ix, line_block.nodes, get_curr_line_num(), 
                               curr_screen.cx, curr_screen.cy-1         );
    end

end /* block_get */

/* .page */

void block_buffer ()

/*****************************************************************************
 *                                                                           *
 *  Copy the delimited block into the current line block.  If this was       *
 *  called from the Block_Append function, simply add the delimited block to *
 *  the end of the current one.  Else, purge the current block first.  If    *
 *  this is called from the Block_Delete function, simply link the delimited *
 *  block onto the current block.  Else, copy each line to a new line which  *
 *  will be linked onto the current block.                                   *
 *                                                                           *
 *  This function is called from all of the Copy, Append, Kill and Delete    *
 *  block commands ('C', 'A', 'K' and 'D' respectively).                     *
 *                                                                           *
 *  This is equivalent to "Edit, Copy" in most GUI editors.                  *
 *                                                                           *
 *****************************************************************************/

begin
    int num_lines = 0;
    ve_exception  = E_LIST_ERROR;

    if (key != 'A' && key != 'K' )  /* Not Append or Kill - Purge buffer     */
        block_purge ();

    /* Pick up the start and end of the currently delimited block.   */

    curr  = first = block[0];
    last  = block[1]->fwd_link;

    while (curr != last) begin

        nnode = curr->fwd_link;

        if (key == 'K' || key == 'D')
        begin
            /* Called from Kill or Delete commands (Block_Delete function)  */
            /* Extract the current line from the file, but don't delete     */
            /* it (yet!)                                                    */

            pnode = delete (&curr_file, curr, &status);

            /* Check for case of deleting ONLY line of file. VE *always*    */
            /* needs a current line, so if we have deleted the only line,   */
            /* lets replace it.                                             */

            if ( curr_file.nodes == 0 ) begin
               curr_line = (struct node *) ve_alloc (82 + DES_SIZE);
               curr_line->length = 0;
               send_node (&curr_file, curr_line);
            end
        end

        else 
        begin
            /* Not called from Kill or Delete commands (Block_Delete), so   */
            /* make a copy of it for adding to the line buffer              */

            pnode = (struct node *) ve_alloc (curr->length + DES_SIZE);
            movb (&curr->length, &pnode->length, curr->length + sizeof(int));
        end

        /* If called from Kill command, add the line to the kill buffer,    */
        /* else add it to the line buffer.                                  */

        if ( key == 'K' )
           send_node (&kill_block, pnode);
        else
           send_node (&line_block, pnode);

        num_lines   += 1;
        ve_exception = E_OK;

        curr = nnode;

    end /* while curr != last */

    /* Post a status message to the user */

    sprintf ( temp.text, "%d %s", num_lines, "line(s)" );
    if ( key == 'K' )
       strcat ( temp.text, " killed" );
    else if ( key == 'D' )
       strcat ( temp.text, " copied and deleted" );
    else if ( key == 'A' )
       strcat ( temp.text, " appended" );
    else
       strcat ( temp.text, " copied" );
    write_ve_msg ( temp.text );

end /* block_buffer */

/* .page */

void block_delete ()

/******************************************************************************
 *                                                                            *
 *   Delete the currently delimited block from the users file, and place it   *
 *   into the current line block                                              *
 *                                                                            *
 *   This is equivalent to "Edit, Cut" in most GUI editors                    *
 *                                                                            *
 ******************************************************************************/

begin
    BOOL on_screen, curr_was_top;
    int y;

    init_list ( &kill_block );

    /* Pick up the start of the screen area to begin updating at            */

    curr_line = block[0];
    curr_was_top = (curr_line == curr_screen.top);

    curr_line = curr_line->bwd_link;
    on_screen = displayed (curr_line, &y);

    /* Move the delimited block from curr_file to line_block                */

    block_buffer ();

    after_delete_screen_update ( curr_line, on_screen, curr_was_top, y );

    if ( key == 'K' )
       push_undo_del_block ( block_ix, &kill_block, get_curr_line_num(),
                             curr_screen.cx, curr_screen.cy );
    else
       push_undo_del_block ( block_ix, &line_block, get_curr_line_num(),
                             curr_screen.cx, curr_screen.cy );

    purge_list ( &kill_block );

end /* block delete */

/* .page */

void block_read ()

/****************************************************************************
 *                                                                          *
 *   Read a file into the current line block and insert it into the file.   *
 *                                                                          *
 ****************************************************************************/

begin
    int num_chars, num_lines, checksum;  /* Information on read file        */
    char ff;                             /* File format                     */

    /* Prompt for the filename to read, attempt to read it.                 */

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

    if ( strlen(temp.text) != 0 )
    begin
       block_purge ();               /* Clear current contents of the block */

       ve_exception = read_file ( temp.text, &line_block, &num_chars, &num_lines,
                                             &checksum,   &ff );
       if (ve_exception != E_OK )
       begin
           block_purge ();
           write_ve_msg ( err_msg[E_OPEN_ERROR] );
       end
       else
           block_get ();     /* Insert the new block into the current file  */

   end
   else
      write_ve_msg ( no_file_select_read );

end /* block_read */

/* .page */

void block_cmd ()

/*****************************************************************************
 *                                                                           *
 *  Implement the BLOCK command.  This includes eight subcommands            *
 *  (1) Block_Buffer    : Place area into block buffer                       *
 *  (2) Block_Delete    : Delete from file and place into block buffer       *
 *  (3) Block_Get       : Get from block buffer into file                    *
 *  (4) Block_Write     : Write delimited block to file                      *
 *  (5) Block_Append    : Append delimited area to block                     *
 *  (6) Block_Purge     : Purge the current block                            *
 *  (7) Block_Read      : Read a file into the current block                 *
 *  (8) Line_Col_Xchng  : Exchange the contents of the line and column bufs  *
 *                                                                           *
 *****************************************************************************/

begin
    register int    cmd_ix, i;       /* Index of block command, loop counter */
    BOOL     block_defined;          /* Did user define a block?             */
    struct   t_file_area file_area;  /* Block definition                     */

    update_curr_line ();   /* Update current line before leaving it          */

    if ( curr_cmd == commands[again_ix] )
    begin
       /* We are being run via the VE Again command. Restore the last last   */
       /* file area and the last command selection and re-execute            */

       if ( last_key == 'D' )
       begin
          /* Cant repeat a Delete command via Again, since the original      */
          /* range has been deleted. Tell the user and exit.                 */

          write_ve_msg ( "Must define new block to repeat Delete" );
          return;
       end

       else
       begin
          movb ( &last_file_area, &file_area, sizeof(file_area) );
          file_area.cmd = last_key;

          block_defined = TRUE;
       end
    end

    else
    begin
       push_status_line ( block_menu );

       /* Interact with the user to define a block. If they don't define a      */
       /* block just exit.                                                      */

       block_defined = define_line_range ( &file_area, ti_get_left_cx(), 
                                           block_cmds, sizeof(block_cmds) );
    end

    if ( block_defined )
    begin

       block[0] = block_start = file_area.first_line;
       block[1] = block_end   = file_area.last_line;

       key    = file_area.cmd;
       cmd_ix = findb ( block_cmds, key, sizeof(block_cmds) );

       /* Execute the requested block command, repeating as many times as   */
       /* has been requeted by the user                                     */

       for ( i = 0; i < requested_reps; i++ )
          (*block_cmd_procs[cmd_ix])();

       requested_reps = 1;  /* Stops cmd_processor from iterating further */

    end

    /* Save off the block area definition and command key for possible future */
    /* use via an Again command                                               */

    movb ( &file_area, &last_file_area, sizeof(last_file_area) );
    last_key = key;

    if ( curr_cmd != commands[again_ix] )
       pop_status_line ();

end /* block_cmd */

