#include<stdio.h> 
#include<stdlib.h>
#include<string.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  : veutils.c
 
                       VERSION : v3.5
 
                       REVISED : 18 July 05
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains a grabbag of generally useful
 
               functions that are required by all of the other modules
 
               of the editor.  They are all assembled in this one   
 
               place for convenience.




*/

/* .page */

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

#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 "../ext/termif.ext"
#include "../ext/kbdif.ext"
#include "../ext/strprim.ext"
#include "../ext/lnklst.ext"
#include "../ext/quitcmd.ext"
#include "../ext/stacks.ext"
#include "../ext/veutils.ext"

/* Function get_curr_line_num (in this module) stores some of the current    */
/* line's attributes here, and each time it is called, it determines if they */
/* have changed. If they have, it recomputes the current line; else it       */
/* returns the currently known one.                                          */

static struct node *curr_bwd_link = NULL;  /* The previous line              */
static int curr_bwd_len           = 0;     /* That line's length             */

/* Status line state indicators */

static int last_left_cx = 0;

/* Reserve memory for ve_alloc (VE alloc). When malloc fails for the first   */
/* time due to a shortage of memory, ve_alloc releases this reserve to       */
/* satisfy the request, but also warns the user. When malloc fails for the   */
/* the second time (some later ve_alloc request) ve_alloc causes VE to issue */
/* a fatal error message and then quit, to avoid damaging the files that are */
/* being edited.                                                             */

static char *reserve_memory;

/* .page */

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

void alloc_reserve_memory ()

/*****************************************************************************
 * This function allocates a block of "reserve memory". VE's malloc routine, *
 * ve_alloc, releases this reserve memory upon the 1st out-of-memory failure *
 * from malloc. This allows ve_alloc to meet the request and still give the  *
 * user a warning of impending exhaustion of available memory. For the       *
 * DOS version of VE, the reserve memory size is set at 16K.                 *
 *****************************************************************************/

begin

#define RESERVE_MEMORY_SIZE 16384

   reserve_memory = malloc ( RESERVE_MEMORY_SIZE );

end /* alloc_reserve_memory */



void release_reserve_memory ()

/*****************************************************************************
 * This function releases the reserve memory segment. This function is used  * 
 * by quit_ve to release the memory reserve at the end of a normal run.      * 
 *****************************************************************************/

begin

   if ( reserve_memory != NULL )
      free ( reserve_memory );

end /* release_reserve_memory */

/* .page */

void truncate_str_left ( input_str, output_str, max_len )

/*****************************************************************************
 *                                                                           *
 *  Copy the input string to the output string, potentially truncating the   *
 *  input string if it is longer than the maximum length allowed for the     *
 *  output_str, which is specified by max_len. If the output_str contains a  *
 *  truncated version of the input string, truncate from the left, not the   *
 *  right, and put ".." as truncation marks in the first two character       *
 *  positions of the output_str, to indicate that the string is a truncated  *
 *  version of the original.                                                 *
 *                                                                           *
 *  This function is used to ensure that file and macro names, no matter how *
 *  long they may actually be, can be displayed in the fixed length fields   *
 *  that VE has in various places for their display. The input parm max_len  *
 *  reflects the maximum size of the display field that input_str will be    *
 *  displayed in.                                                            *
 *                                                                           *
 *  WARNING! output_str is assumed to be at least one char longer than       *
 *  max_len - a '\0' is placed in output_str[max_len] if input_str is        *
 *  max_len long.                                                            *
 *                                                                           *
 *****************************************************************************/

 char input_str [];   /* Input str to fit to max_len chars in output str     */
 char output_str [];  /* Output str for display in max_len field width       */
 int max_len;         /* Size of available field                             */

begin
   int input_str_len; /* Length of input_str                                 */
   int starting_ix;   /* Starting index for copying input to output          */
   register int i;    /* Loop counter                                        */

   input_str_len = strlen ( input_str );
   starting_ix = (input_str_len <= max_len) ? 0 : input_str_len - max_len;

   for ( i=0; i < max_len; i++ )
      output_str[i] = input_str[starting_ix + i];

   if ( starting_ix != 0 ) then            /* Add truncation marks if needed */
      output_str[0] = output_str[1] = output_str[2] = '.';

   output_str[max_len] = '\0';             /* Failsafe string end            */

end /* truncate_str_left */

/* .page */

void update_curr_line ()

/******************************************************************************
 *                                                                            *
 *   Copy the contents of the line buffer into a new line, and link this new  *
 *   line into the list                                                       *
 *                                                                            *
 ******************************************************************************/

begin
    struct node *old_curr_line;
    struct node *cnode, *pnode;

    if (line_buf.length != 0) begin

        /* Check that we haven't exceeded the maximum allowed line size. If  */
        /* we have, truncate the line! Needed for simple self defense!       */

        if ( line_buf.length >= MAX_LINE_SIZE - 2 ) then
           line_buf.length = MAX_LINE_SIZE - 2;

        /* If the contents of the line buffer will fit into the existing      */
        /* allocated line, we will just re-use it. Else we have to release    */
        /* the current line and allocate a larger one.                        */

        if ( line_buf.length > curr_line->length ) begin
   
           old_curr_line = curr_line;
           pnode         = curr_line->bwd_link;

           cnode = delete (&curr_file, curr_line, &status);
           free (cnode);

           curr_line = (struct node *) ve_alloc (line_buf.length + DES_SIZE);
           add (&curr_file, pnode, curr_line, &status);

           if ( curr_screen.top == old_curr_line ) then
                curr_screen.top  = curr_line;

           if ( curr_screen.bottom == old_curr_line ) then
                curr_screen.bottom  = curr_line;
        end

        movb (&line_buf.length, &curr_line->length, line_buf.length + sizeof(int));

        line_buf.length = 0;

    end

end /* update_curr_line */

/* .page */

void load_line_buf ()

/*****************************************************************************
 *                                                                           *
 *   This procedure is the inverse of update_curr_line().  It loads the line *
 *   buffer with the contents of the current line, to allow work on it.      *
 *                                                                           *
 *****************************************************************************/

 begin

    if (line_buf.length == 0) begin

        /* Clear the line buffer to blanks, and then write the curr line    */
        /* into it.                                                         */

        setb (' ', &line_buf.text, MAX_LINE_SIZE);  
        movb (&curr_line->length, &line_buf.length, curr_line->length + sizeof(int) );
    end

end /* load_line_buf */

/* .page */

void *ve_alloc ( segment_size )

/****************************************************************************
 * This function is VE's memory allocator. It calls malloc and then handles *
 * out-of-memory situations if they occur. "ve_alloc" = "ve alloc".           *
 *                                                                          *
 * This function returns a pointer to the allocated memory segment, or NULL *
 * if the allocation fails.                                                 *
 ****************************************************************************/

size_t segment_size;  /* Size of memory segment requested, in bytes         */

begin
   register char dont_care;

   char *segment;  /* Pointer to the allocated segment                      */

   segment = malloc ( segment_size + 4 );  /* CR, LF, "\0" and one extra    */
                                           /* write_file needs these 4      */
   if ( segment == NULL )
   begin
      /* Uh oh, out of memory. If this is the first out of memory occurance */
      /* release the reserve memory, issue a warning message to the user,   */
      /* and retry the allocation. If this is the second out of memory      */
      /* occurance, we are dead. Issue another message to the user and      */
      /* simply quit.                                                       */

      if ( reserve_memory != NULL )
      begin
         write_status_line ( "WARNING: VE is nearly out of memory - please save work and exit" );
         write_divider_msg ( "> press any key to continue", 0, 2 );

         dont_care = read_char ();
         clear_divider_msg ();

         free ( reserve_memory );
         reserve_memory = NULL;
         flush_undo_queue ();

         segment = malloc ( segment_size + 4 );
      end

   end /* malloc failed */

   if ( segment == NULL )
   begin
      /* VE is totally out of memory - issue fatal error message and exit */

      write_status_line ( "ERROR: VE is out of memory -  discarding all files and quitting" );
      write_divider_msg ( "> press any key to continue", 0, 2 );

      dont_care = read_char ();
      clear_divider_msg ();

      quit_ve ( E_MEM );  /* does a discard_all_files () */

   end /* out of memory */

   return ( segment );

end /* ve_alloc */

/* .page */

void write_option_str ()

/****************************************************************************
 * This function ouputs the option string to the status line. It does not   *
 * clear any previous contents there. It returns no value.                  *
 ****************************************************************************/

begin
    int  option_idx = 0;   /* Index into option string                      */
    char option_str [10];  /* The string used to build option output        */

    setb ( 0, &option_str, sizeof(option_str) );  /* NULL terminates str    */
    
    option_str[option_idx++] = '(';

    if ( read_state == RS_LEARNING ) then
         option_str[option_idx++] = 'm';

    if ( options.wrap_text ) then
         option_str[option_idx++] = 'w';

    if ( options.auto_indent ) then
         option_str[option_idx++] = 'i';

    option_str[option_idx++] = ')';

    if ( option_idx > 2 ) then
         mv_writestr ( MODE_X - 5, MODE_Y, FALSE, option_str );

end /* write_option_str */

/* .page */

void write_ve_msg ( msg_str )

/****************************************************************************
 * This function writes out the supplied message to the ve_msg field of the *
 * the status line. This field is shared with the error_msg. ve_msg is in   *
 * in fact a generalization of the error message, and will replace it fully *
 ****************************************************************************/

char msg_str [];

begin
   register int i;  /* loop counter                                         */
   int msg_len;     /* length of new message                                */

   strcpy ( ve_msg_buf, msg_str ); /* load ve_msg_buf with the new message  */
   ve_msg = ve_msg_buf;            /* now just an indicator to clear_ve_msg */

   /* Now pad out the rest of the ve_msg field with spaces                  */

   msg_len = strlen ( msg_str );

   for ( i = msg_len; i < MSG_FIELD_WIDTH; i++ )
      ve_msg_buf[i] = ' ';
   ve_msg_buf[MSG_FIELD_WIDTH] = '\0';
   
   if ( read_state != RS_MACRO && stack_empty(&status_stack) ) then
      mv_writestr ( MSG_X, MSG_Y, TRUE, ve_msg_buf );

end /* write_ve_msg */

/* .page */

void clear_ve_msg ()

/****************************************************************************
 * This function clears the ve message field to its default message, which  *
 * is "ESC-h for Help".                                                     *
 ****************************************************************************/

begin
   /* If there is presently a VE message posted, clear the pointer. If we   */
   /* are not in Macro mode, update the display as well.                    */

   if ( ve_msg != NULL || init_help_shown ) begin

        ve_msg = NULL;
        strcpy ( ve_msg_buf, default_ve_msg );

        if ( init_help_shown ) begin

           /* The init help message (the default ve_msg) is presently    */
           /* written over the Mode display area. This is done one time  */
           /* at startup to provide a new user with a pointer to the     */
           /* help panel. Since clear_ve_msg has been called, the user   */
           /* has entered a keystroke, and will now get the default      */
           /* message from this function, which points them to the help  */
           /* panel. Soooo... restore the Mode display.                  */

           set_status_line ( TRUE, E_OK, ve_mode );
           init_help_shown = FALSE;
        end

        if ( read_state != RS_MACRO ) then
           mv_writestr ( MSG_X, MSG_Y, TRUE, ve_msg_buf );
   end

end /* clear_ve_msg */

/* .page */

void update_scroll_indicator ()

/*****************************************************************************
 *                                                                           *
 *  Check to see if the scroll state has changed from not scrolled to        *
 *  scrolled, or visa versa. Update the indicator if it has changed.         *
 *                                                                           *
 *****************************************************************************/

begin
   register int  new_left_cx;
   register BOOL update_made = FALSE;

   new_left_cx = ti_get_left_cx ();

   if ( ( (last_left_cx == 0) && (new_left_cx >  0) ) ||
        ( (last_left_cx >  0) && (new_left_cx == 0) ) )
   begin
      display_scroll_indicator ();
      restore_cursor ();
   end

   last_left_cx = new_left_cx;

end /* update_scroll_indicator */

/* .page */

void set_status_line (clear, exception, mode)

/*****************************************************************************
 *                                                                           *
 *  Set up the VE status line.  If called for, clear the status line before  *
 *  starting.  If there is an exception to be displayed, display it.         *
 *  Finally, if the currently displayed mode does not equal the new mode,    *
 *  update the display.                                                      *
 *                                                                           *
 *****************************************************************************/

int clear;             /* If TRUE, clear the line before use                 */
int exception;         /* Display this exception message on status line      */
int mode;              /* Set VE's mode to this mode                         */

begin
    BOOL moved_cursor = FALSE;  /* Did we move the cursor?                   */
    char display_fn [82];       /* display version of filename               */

    ve_mode = mode;    /* assign VE's new mode                               */

    if ( read_state == RS_MACRO )
         return;

    if ( stack_empty(&status_stack) || (exception != E_OK) ) 
    begin

        if ( clear )                  /* if requested, clear the line        */
        begin
            ti_move_cursor  ( 0, 0 );
            ti_clear_to_eol ( 0 );

            if ( ve_mode != M_COMMAND ) begin

                /* Refresh filename display and the options indicators, and  */
                /* cause ve_msg to be shown later in this function           */

                truncate_str_left (curr_filename, display_fn, FN_FIELD_WIDTH-1);
                writestr ( display_fn );
                ve_msg = ve_msg_buf;
                write_option_str ();
            end

            else
                writechar ('%');

            displayed_mode = M_NULL;
            moved_cursor   = TRUE;

        end /* if clear */


        /* If there is an exception to be reported, report it                */

        if ( exception != E_OK ) 
        begin

            mv_writestr ( ERR_X, ERR_Y, FALSE, err_msg[exception] );
            moved_cursor = TRUE;

            if ( ve_exception == exception ) then
                 ve_exception = E_OK;
        end

        /* Redisplay the current ve_msg, whatever it was                     */

        else if ( ve_msg != NULL )
        begin
           mv_writestr ( MSG_X, MSG_Y, FALSE, ve_msg_buf );
           moved_cursor   = TRUE;
        end

        /* If the currently displayed mode is not the new mode, update the   */
        /* MODE field of the status line                                     */

        if (displayed_mode != ve_mode) 
        begin

           displayed_mode = ve_mode;
           moved_cursor   = TRUE;

           mv_writestr ( MODE_X, MODE_Y, FALSE, mode_msg[ve_mode] );
           display_scroll_indicator ();
        end

        if ( moved_cursor )
           restore_cursor ();

    end /* Status stack empty */

end /* set_status_line */

/* .page */

void write_status_line (str)

/*****************************************************************************
 *                                                                           *
 *  Clear the status line, and write the supplied string onto it.  Set the   *
 *  status line mode to the supplied mode. This function leaves the cursor   *
 *  on the status line at the end of the string that is written.             *
 *                                                                           *
 *****************************************************************************/

char *str;  /* String to display on the status line.                         */

begin

    if ( read_state == RS_MACRO ) then
         return;

    mv_writestr ( 0, 0, FALSE,  str );
    ti_clear_to_eol ( 0 );

    displayed_mode = M_NULL;

end /* write_status_line */

/* .page */

int update_curr_screen (cy)

/*****************************************************************************
 *                                                                           *
 *  Update the curr_screen data structure "bottom" and "top" fields, based   *
 *  on the current y position of the cursor.  Return the y position.         *
 *  It is implied that cy is on the current line.                            *
 *                                                                           *
 *****************************************************************************/

int cy;  /* Desired Y co-ordinate, on the screen, for the current line.      */

begin
    int remaining_lines;
    int actual, new_cy;

    /* Starting at the current line, move backwards by the correct number,   */
    /* stopping if the head of the file is encountered.  Record the actual   */
    /* movement in the variable new_cy.                                      */

    remaining_lines = cy - 2;
    curr_screen.top = traverse_list (&curr_file, curr_line, '-', &new_cy, 
                                     remaining_lines, &status);

    /* Using the now correct cy value from new_cy, move forward by the       */
    /* correct number of lines, stopping if the end of the file is           */
    /* encountered.                                                          */

    remaining_lines = lines - (new_cy + 2) - 1;
    curr_screen.bottom = traverse_list (&curr_file, curr_line, '+', &actual,
                                        remaining_lines, &status);

    return (new_cy + 2);    /* New y position */

end /* update_curr_screen */

/* .page */

void draw_to_endof_screen ()

/*****************************************************************************
 *                                                                           *
 *  Draw from the current line to the end of the screen (inclusive).         *
 *                                                                           *
 *****************************************************************************/

begin
    int line_num = 0;  /* The number of the line we are putting out          */

    /* Move the cursor to the starting location, and then clear from there   */
    /* to the end of the screen.                                             */

    ti_clear_display ( curr_screen.cy );

    /* Pick up the starting and stopping locations.                          */

    cnode = curr_line;
    pnode = curr_screen.bottom;
    nnode = pnode->fwd_link;

    /* Draw each line from the start to the end of the screen.               */

    while (cnode != nnode) begin

        mv_writeln ( curr_screen.cx, curr_screen.cy+line_num++, FALSE, 
                     &cnode->text, cnode->length, 0);

        cnode = cnode->fwd_link;

    end /* cnode != nnode */

    restore_cursor ();

end /* draw_to_endof_screen */

/* .page */

void draw_curr_screen ()

/*****************************************************************************
 *                                                                           *
 *  Draw the current screen starting at (0, 2).                              *
 *                                                                           *
 *****************************************************************************/

begin
    register int i = 0;

    ti_clear_display ( 2 );

    cnode = curr_screen.top;

    /* Iterate through each line, drawing it onto the screen.  Stop if End-  */
    /* of-file is encountered, or the entire screen is drawn                 */

    while (cnode != EOL && i++ < lines - 2) begin

        mv_writeln ( curr_screen.cx, i+1, FALSE, &cnode->text, cnode->length, 0);

        cnode = cnode->fwd_link;    /* Advance to the next node              */
    end

    restore_cursor ();

end /* draw_curr_screen */

/* .page */

int displayed (line, y)

/*****************************************************************************
 *                                                                           *
 *  Determine if the line supplied is displayed on the current screen.  If   *
 *  it is, return TRUE, and its Y co-ordinate.  Else, return false.          *
 *                                                                           *
 *****************************************************************************/

struct node *line;   /* The line to look for on this screen                  */
int         *y;      /* The return Y value.                                  */

begin
    int this_page;

    *y = 2;
    this_page = FALSE;

    cnode = curr_screen.top;         /* Pick up the top and bottom of screen */
    nnode = curr_screen.bottom;
    nnode = nnode->fwd_link;

    while (cnode != nnode) begin     /* Do from top to bottom of screen      */

        if (cnode == line) begin     /* If curr_line on screen return TRUE   */
            this_page = TRUE;
            break;
        end

        else begin                   /* Else move onto next line of screen   */
            cnode = cnode->fwd_link;
            (*y)++;
        end
    end

    return (this_page);

end /* displayed */

/* .page */

void display_new_screen ( cx )

/*****************************************************************************
 * This function displays the page that has the new current line (just       *
 * selected via a jump command). This function returns no value.             *
 *****************************************************************************/

int cx;

begin
    int y;   /* Line offset containing the current line */

    /* Display the page around the current line        */

    if (displayed (curr_line, &y) ) begin     /* If line displayed, go to it */
        ti_move_cursor (cx, y);

        curr_screen.cx = cx;
        curr_screen.cy = y;
    end

    else begin                                /* Else draw a new page        */
        curr_screen.cx = cx;

        curr_screen.cy = update_curr_screen (11);
        draw_curr_screen ();
    end

end /* display_new_screen */

/* .page */

void after_delete_screen_update ( prev_line, prev_was_displayed, screen_top, cy )

/*****************************************************************************
 *                                                                           *
 * This routine is called to update the screen after one or more line        *
 * deletions that may have deleted the line recorded as curr_screen.top.     *
 * Hence, we must rebuild the curr_screen structure and then update the      *
 * physical display accordingly.                                             *
 *                                                                           *
 *****************************************************************************/

struct node *prev_line;     /* Pointer to line BEFORE current line           */
BOOL   prev_was_displayed;  /* Was the previous line on screen?              */
BOOL   screen_top;          /* Was the current line the top line of screen?  */
int    cy;                  /* The cy of prev_line, if it was on screen      */

begin
  curr_line = prev_line->fwd_link;

  /* Check for the case where we have deleted the last line of the file. In  */
  /* this case, back up by one.                                              */

  if ( curr_line == NULL )
  begin
     curr_line = prev_line;
     cy -= 1;
  end

  if ( prev_was_displayed )
  begin
     /* If the line before the current line was displayed prior to the      */
     /* delete, then the new current line is just one line down. Update the */
     /* screen based on this.                                               */

     curr_screen.cy = update_curr_screen ( ++cy );
     draw_to_endof_screen ();
  end

  else if ( screen_top ) 
  begin
     /* Else if the original next line (first line deleted) the first line  */
     /* of the screen, then we know that the current next line should also  */
     /* be at the first line of the screen. Update accordingly.             */

     curr_screen.cy = update_curr_screen ( 2 );
     draw_curr_screen ();
  end

  else
  begin
     /* If all else fails, simply regenerate the current screen with the    */
     /* current line arbitrarily placed at line 11.                         */

     curr_screen.cy = update_curr_screen ( 11 );
     draw_curr_screen ();
  end

end /* after_delete_screen_update */

/* .page */

void restore_cursor ()

/*****************************************************************************
 *                                                                           *
 *  Restore the cursor to its CX and CY values.                              *
 *                                                                           *
 *****************************************************************************/

begin

    register int x, y;

    x = (curr_screen.cx > columns - 1 ? columns - 1 : curr_screen.cx);
    y = curr_screen.cy;

    ti_move_cursor (x, y);

end /* restore_cursor */

/* .page */

void push_status_line (line)

/*****************************************************************************
 *                                                                           *
 *  Push a new status line string, and then display it.                      *
 *                                                                           *
 *****************************************************************************/

 char *line;   /* Pointer to line to push, display                           */

 begin

    push (&status_stack, line);

    if ( read_state != RS_MACRO ) then
         write_status_line (line);

end /* push_status_line */

/* .page */

void pop_status_line ()

/*****************************************************************************
 *                                                                           *
 *  Pop off the current status line, and display the next one, or set the    *
 *  status line if there is not a next one.                                  *
 *                                                                           *
 *****************************************************************************/

 begin

    char *line;  /* A popped line */

    line = pop (&status_stack);  /* Pop and discard this one                 */

    if (stack_empty(&status_stack)) then

        /* Nothing else: set the status line                                 */

        set_status_line ( TRUE, ve_exception, last_ve_mode );

    else begin

        /* Write the next one                                                */

        line = pop(&status_stack);
        push_status_line (line);

    end /* else */

end /* push_status_line */

/* .page */

void write_divider_line ( cy )

/*****************************************************************************
 *                                                                           *
 *  Place a divider line across the indicated line number of the display.    *
 *                                                                           *
 *****************************************************************************/

int cy;

begin
    char buf [MAX_LINE_SIZE];

    setb (divider_char, buf, physical_cols);
    buf[physical_cols] = '\0';

    mv_writestr ( 0, cy, FALSE, buf );

end /* write_divider_line */

/* .page */

void write_divider_msg ( msg, px, py )

/*****************************************************************************
 *                                                                           *
 *  This function writes a "divider message" onto the screen. A divider      *
 *  message is a message that utilizes the divider line area, plus the two   *
 *  lines immediately below it. The paired function clear_divider_msg        *
 *  restores the divider line and the two lines below back to their normal   *
 *  state.                                                                   *
 *                                                                           *
 *  After writing the divider message, this function "parks" the cursor at   *
 *  the supplied (px, py) co-ord.                                            *
 *                                                                           *
 *****************************************************************************/

char msg [];
int  px, py;

begin
   register char curr_divider_char;

   /* Disable horizontal scrolling and save the current divider char */

   ti_disable_horizontal_scrolling ();
   curr_divider_char = divider_char;

   /* Clear line 2 - lines 1 and 3 will be directly overwritten */

   ti_move_cursor ( 0, 2 );
   ti_clear_to_eol ( 2 );

   /* Overwrite the divider line with a sub-divider */

   divider_char      = '-';
   write_divider_line ( 1 );

   /* Write out the actual message */

   mv_writestr ( 0, 2, FALSE, msg );

   /* Redraw a divider line below the message */

   divider_char = curr_divider_char;
   write_divider_line ( 3 );

   /* Park the cursor and re-enable horizontal scrolling. We are done! */

   ti_move_cursor ( px, py );
   ti_enable_horizontal_scrolling ();

end /* write_divider_msg */

/* .page */

void clear_divider_msg ()

/*****************************************************************************
 *                                                                           *
 *  This function restores screen lines 2 and 3 to their normal state. This  *
 *  is used to restore the space after it has been used for a divider        *
 *  message. This function restores the cursor to the user text area as it   *
 *  completes.                                                               *
 *                                                                           *
 *****************************************************************************/

begin
   register int cx, cy;
   register struct node *this_line;

   cx = ti_get_left_cx ();

   /* Clear lines 2 and 3 - divider line itself will be directly overwritten */

   ti_move_cursor ( cx, 2 );
   for ( cy = 2; cy <= 3; cy++ )
      ti_clear_to_eol ( cy );

   write_divider_line ( 1 );

   this_line = curr_screen.top;

   if ( this_line != NULL )
   begin
      mv_writeln ( cx, 2, FALSE, this_line->text, this_line->length, 0 );

      this_line = this_line->fwd_link;
      if ( this_line != NULL )
         mv_writeln ( cx, 3, FALSE, this_line->text, this_line->length, 0 );
   end

end /* clear_divider_msg */

/* .page */

void create_full_screen_info_page ( title )

/*****************************************************************************
 *                                                                           *
 *  This function sets up a full screen info page, such as is used to list   *
 *  macros, tags, etc. It puts out a title message, and clears the screen.   *
 *                                                                           *
 *****************************************************************************/

char title [];

begin
    int x, len;
    char msg_buf [82];

    /* Start by turning off horizontal scrolling, so that our cursor        */
    /* co-ordinates are not interpreted via the current file scroll state.  */
    /* We want to provide physical screen co-ordinates.                     */

    ti_disable_horizontal_scrolling ();

    /* Create a page heading title                                          */

    strcpy ( msg_buf, ve_masthead );
    strcat ( msg_buf, title );
    len = strlen ( msg_buf );
    x = ( physical_cols - len ) / 2;

    /* Clear the status line, put out the centered header, clear screen      */
    /* and put out column headers                                            */

    ti_move_cursor ( 0, 0 );
    ti_clear_to_eol ( 0 );

    mv_writestr ( x, 0, FALSE, msg_buf );  /* write out the header      */
    ti_clear_display ( 2 );                     /* clear rest of screen      */

end /* create_full_screen_info_page */

/* .page */

void remove_full_screen_info_page ()

/*****************************************************************************
 *                                                                           *
 *  This function removes a full screen info page, such as is used to list   *
 *  macros, tags, etc. It puts out an onscreen navigation message, waits for *
 *  ENTER to be pressed, and then restores the screen.                       *
 *                                                                           *
 *****************************************************************************/

begin
    /* Write out the bottom on-screen navigation guide line                 */

    write_divider_line ( lines - 2 );
    mv_writestr ( 0, lines - 1, FALSE, enter_to_edit_msg );

    /* Home the cursor and wait until the user tells us to carry on */

    ti_move_cursor ( 0, 0 );
    wait_for_enter ();

    /* Re-enable horizontal scrolling so that the user's view of the file   */
    /* is restored to its pre full screen info page state                   */

    ti_enable_horizontal_scrolling ();

    /* Redraw the screen and we are done                                    */

    if ( files_open > 0 ) then
       view_cmd ();                           /* Redraws the current screen */

end /* remove_full_screen_info_page */

/* .page */

void display_curr_position ()

/****************************************************************************
 * This function creates a VE message with information related the users    *
 * current position in the file and displays it.                            *
 ****************************************************************************/

begin
   int percentage;
   char position_msg [80];

    /* Compute what percentage down in the file the user is                 */

    percentage = ( curr_line_num * 100 ) / curr_file.nodes;

    /* Now format and present the cx, cy, percentage and num lines          */

    sprintf ( position_msg, "%s%d %s%d  %d%c %c%d %s", "ln:", curr_line_num, 
              "col:", curr_screen.cx+1, percentage,'%', '(', curr_file.nodes, "lines)");

    write_ve_msg ( position_msg );

end /* display_curr_position */

/* .page */

int get_curr_line_num ()

/****************************************************************************
 *                                                                          *
 *   This function returns the line number of the current line. The first   *
 *   line in the file is line zero by definition. This function sets the    *
 *   ve global var curr_line_num as a side effect.                          *
 *                                                                          *
 *   Determining the current line number "the hard way" involves traversing *
 *   the file lines, from the head until the current line. Since this is a  *
 *   somewhat time consuming step, this function checks for a number of     *
 *   things to see if the currently set line number is still valid. If it   *
 *   is, it simply returns it, rather than doing the traverse.              *
 *                                                                          *
 ****************************************************************************/

begin
    int line_num = 0;
    struct node *cnode;

    /* See if we have moved since last time. If not, just return the        */
    /* current value                                                        */

    if ( curr_line->bwd_link == curr_bwd_link )
       if ( curr_bwd_link->length == curr_bwd_len )
          return ( curr_line_num - 1 );

    /* If the last command executed was the Jump command or the Info        */
    /* command, then the current line information is valid. Just return it  */

    if ( last_cmd_ix == jump_ix || last_cmd_ix == info_ix ) 
       return ( curr_line_num - 1 );

    /* Oh well, have to do it the "hard way" - current line number may not  */
    /* be valid. Traverse the file's line, checking until we hit the        */
    /* current line.                                                        */

    cnode = curr_file.head;

    while ( cnode != EOL ) begin

        if ( cnode == curr_line )
           break;

        line_num += 1;
        cnode = cnode->fwd_link;

    end

    /* We have found the current line - record some attributes for later    */

    curr_line_num = line_num + 1;
    curr_bwd_link = curr_line->bwd_link;
    curr_bwd_len  = curr_bwd_link->length;

    return ( curr_line_num - 1 );

end /* get_curr_line_num */

/* .page */

struct node *goto_line_num ( line_num )

/*****************************************************************************
 *                                                                           *
 *  This routine traverses the curr file, and moves to line indicated by the *
 *  supplied line number. Line numbers start at zero - the first line of the *
 *  file is line zero.                                                       *
 *                                                                           *
 *  This function returns a pointer to the line indicated by the supplied    *
 *  line number.                                                             *
 *                                                                           *
 *****************************************************************************/

int line_num;

begin
   int actual, status;

   return (traverse_list (&curr_file, curr_file.head, '+', &actual, 
           line_num, &status));

end /* goto_line_num */

