From:	CRDGW2::CRDGW2::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX" 27-APR-1989 20:57
To:	MRGATE::"ARISIA::EVERHART"
Subj:	Re: MORE utility for VMS

Received: From KL.SRI.COM by CRVAX.SRI.COM with TCP; Thu, 27 APR 89 12:14:00 PDT
Received: from ucbvax.Berkeley.EDU by KL.SRI.COM with TCP; Thu, 27 Apr 89 12:08:46 PDT
Received: by ucbvax.Berkeley.EDU (5.61/1.36)
	id AA27982; Thu, 27 Apr 89 11:42:26 -0700
Received: from USENET by ucbvax.Berkeley.EDU with netnews
	for info-vax@kl.sri.com (info-vax@kl.sri.com)
	(contact usenet@ucbvax.Berkeley.EDU if you have questions)
Date: 27 Apr 89 18:32:56 GMT
From: robert@arizona.edu  (Robert J. Drabek)
Organization: U of Arizona CS Dept, Tucson
Subject: Re: MORE utility for VMS
Message-Id: <10509@megaron.arizona.edu>
References: <50880@philabs.Philips.Com>
Sender: info-vax-request@kl.sri.com
To: info-vax@kl.sri.com

In article <50880@philabs.Philips.Com>, tal@frank.Philips.Com (Tal Barnea) writes:
> Does anyone out there happen to have a MORE utility (as in Unix) for VMS?
> I have one written by Shan Xuning and it is great, except that now that we
> have upgraded to VMS 5.0, it refuses to work (even after recompiling/linking).
> If some good soul has a version that works, and cares to post it/email it,
> or else point me in the direction where it can be found, I would be grateful.
> 

Shan Xuning's MORE as modified to include new commands and to work
under VMS 5.0 follows:

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  A simple MORE/LESS-like program for VMS.

  Shan Xuning, Dept. of Chem. Engr, JHU   18/11/87

  Modified by Robert Drabek
              Computer Science Department
              University of Arizona, Tucson

    December 1987:
      Added wild-card file searching.
      Added screen routines using SMG.
      Added raw key input.
      Fixed window counting.
      Fixed string search.
      Fixed a multitude of other bugs and styled it up a bit.
      Made it more like the Unix version.
      Added #, N, and D commands.

  Modified by M. Warner Losh
	      System Manager HydroVAX
	      New Mexico Tech, Socorro NM.
	      ...!lanl!unm-la!unmvax!warner%hydrovax

    December 1987

    Fixed both t and b commands so that when they are at the top of the
        file, they don't do anything
    Fixed the spawn command so that it only spawns one job, instead of
	spawning a spawn.
    If no files are found, then it says no files were found.

  Modified by Aaron Leonard
          System Manager UACCIT VAXcluster
          University of Arizona
          leonard@rvax.ccit.arizona.edu

    July 1988

    Unsupported use of fseek() and ftell() in module more broke with
    VMS V5.0 VAXC Run-Time Library.  Fixed by replacing with call to
    fstat().  Also refrained from exiting on FALSE status returned from
    Terminal_Open, so as to work on UNKNOWN type terminals.

  Modified by Jan Trumbo
	 University of Arizona systems programmer
	 trumbo@rvax.ccit.arizona.edu

    November 1988

    Refrain from exiting if search string not found.


  Commands (and their alternates):
  <SPACE> ==> print next screen (f)
  <CR>    ==> print next line (e, j, +)
  s       ==> skip over next screen
  t       ==> top of the file (g)
  b       ==> skip backward
  h       ==> help message (?)
  #       ==> go to next file (if more than one specified)
  q       ==> quit
  !       ==> spawn subprocess
  /string ==> search forward for string. The search is case
              sensitive
  n       ==> next occurrence of last search string


  Compile using  CC MORE
                 LINK MORE
  Create a "foreign command" with
                 $ MORE :== $mydisk:[mydir]MORE.EXE
  where  mydisk  and  mydir  are the names of your disk drive and
  directory where MORE.EXE will reside.  Use SHOW DEFAULT if you
  don't know what they are.

   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include stdio
#include ctype
#include descrip
#include unixio
#include stat

typedef char bool;
#define TRUE  1
#define FALSE 0

#define EOS '\0'

#define MAXNAME    255
#define MAXWINDOWS 200
#define MAXFILES   200

#define lower(c) (isupper(c) ? _tolower(c) : c)

bool Terminal_Open();
void ClrEOL();
void Ding();
void standend();
void standout();
int  getkey();

void more();
bool prwindow();
bool prline();
void fixstring();

int  Screen_Height = 24; /* may be changed below */
int  Screen_Width  = 80;

main(argc, argv)
int argc;
char *argv[];
{
  int i, j;
  char *pn, *malloc();
  FILE *fp;
  int nargc; char *nargv[MAXFILES + 1];
  bool file_found;

  /* next few lines to deal with lib$find_file() */
  static char name[MAXNAME];
  struct dsc$descriptor_s name_desc;
  struct dsc$descriptor_s result =
    {MAXNAME, DSC$K_DTYPE_T, DSC$K_CLASS_S, name};
  long context;

/*
  if (!Terminal_Open()) {
    fprintf(stderr, "Could not open terminal definition properly.\n");
    exit();
  }
 */

  Terminal_Open();

  for (i = 1, nargc = 0; i < argc && nargc <= MAXFILES; i++) {
    context = 0;
    name_desc.dsc$w_length = strlen(argv[i]);
    name_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    name_desc.dsc$b_class = DSC$K_CLASS_S;
    name_desc.dsc$a_pointer = argv[i];
    file_found = FALSE;
    while (lib$find_file(&name_desc, &result, &context) & 1) {
      file_found = TRUE;
      if (nargc >= MAXFILES) {
        fprintf(stderr, "Only %d files will be read\n", MAXFILES);
        break;
      }
      nargv[++nargc] = calloc(1, result.dsc$w_length + 1);
      strncpy(nargv[nargc], name, result.dsc$w_length);
    }
    if (!file_found)
        fprintf (stderr, "Couldn't find file %s\n", argv[i]);
    lib$find_file_end(&context);
  }

  for (i = 1; i <= nargc; i++)
  {
    fixstring(nargv[i], MAXNAME);       /* lowercase & null-terminate string */
    if ((fp = fopen(nargv[i], "r")) == NULL) {
      perror(nargv[i]);
      continue;
    }
    for (pn = nargv[i]; *pn != EOS && *pn != ']'; pn++)
      ;
    if (*pn == ']') pn++;
    more(fp, pn, i == nargc);
    fclose(fp);
  }

}  /* main */

void more(fp, fname, last_file)
FILE *fp;
char *fname;
bool  last_file;
{
  char pattern[BUFSIZ], a_line[BUFSIZ];
  int  i, ch, wc, wc_save, line_count, pat_found;
  long win[MAXWINDOWS], file_size;
  stat_t status_block;                  /* status block for info from fstat */
  long ftell();
  int  find(), fseek();

  win[0] = 0;
  fstat(fileno(fp), &status_block);     /* find out info about this file */
  file_size = status_block.st_size;

  if (!prwindow(fp, Screen_Height - 1, last_file)) return; /* window #0 */
  wc = 0;
  for (;;) {
    standout();
    printf("\r--More--(%s, %d%%)", fname,
      feof(fp) ? 100 : (int)(2+(double)ftell(fp)/(double)file_size*100.0));
    standend();
    ch = getkey();
    switch ( lower(ch) ) {
      case '\r':
      case '+':
      case 'e':
      case 'j':
        win[++wc] = ftell(fp);
        if (!prline(fp)) return;
        break;
      case ' ':
      case 'f':
        win[++wc] = ftell(fp);
        if (!prwindow(fp, Screen_Height - 2, last_file)) return;
        break;
      case 'd':
        win[++wc] = ftell(fp);
        if (!prwindow(fp, Screen_Height / 2, last_file)) return;
        break;
      case 's':
        standout();
        printf("\nSkipping forward %d lines.\n", Screen_Height - 1);
        standend();
        win[++wc] = ftell(fp);
        for (i = 0; i < Screen_Height - 2; i++)
          if (fgets(a_line, BUFSIZ, fp)==NULL) {
            standout();
            fputs("END OF FILE", stdout);
            standend();
            return;
          }
        win[++wc] = ftell(fp);
        if (!prwindow(fp, Screen_Height - 1, last_file)) return;
        break;
      case '/':
        putchar('\r');
        ClrEOL();
        putchar('/');
        get_pattern(pattern);
      case 'n':
        wc_save = wc;
        fgets(a_line, BUFSIZ, fp);
        line_count = 1;
        while ((pat_found=find(pattern, a_line)) < 0) {
            if (fgets(a_line, BUFSIZ, fp) == NULL) {
              wc = wc_save;
	      clearerr (fp);			/* reset EOF flag */
              fseek(fp, win[wc], 0); 
              standout();
              fputs("\nPattern not found.\n", stdout);
              standend();
              break;
            } 
            line_count++;
            if (line_count > Screen_Height - 1) {
              line_count = 1;
              win[++wc] = ftell(fp);
            }
        }
        if (pat_found > -1) {
          putchar('\r');
          ClrEOL();
          fputs(a_line, stdout);
          win[++wc] = ftell(fp);
        }
        if (!prwindow(fp, Screen_Height - 2, last_file)) return;
        break;
      case 't':
      case 'g':
        if (wc > 0) {
          wc = 0;
          fseek(fp, win[wc], 0);
          if (!prwindow(fp, Screen_Height - 1, last_file)) return;
        }
        break;
      case 'b':
        if (wc > 0) {
          wc--;
          fseek(fp, win[wc], 0);
          if (!prwindow(fp, Screen_Height - 1, last_file)) return;
        }
        break;
      case 'h':
      case '?':
        printf("\n\n\t<SPACE>\t\tnext %d lines\n", Screen_Height - 2);
        puts("\t<RETURN>\tnext line");
        puts("\td\t\thalf a screen");
        puts("\tb\t\tskip backward");
        puts("\tt\t\ttop of the file");
        printf("\ts\t\tskip over next %d lines\n", Screen_Height - 2);
        puts("\th\t\tprint this message");
        puts("\t/string\t\tsearch forward");
        puts("\tn\t\trepeat last search");
        puts("\t!\t\tspawn subprocess");
        puts("\t#\t\tgo to the next file");
        puts("\tq\t\tquit\n");
        standout();
        fputs(" press any key now to continue ", stdout);
        standend();
        putchar(' ');
        getkey();
        fseek(fp, win[wc], 0);
        if (!prwindow(fp, Screen_Height - 1, last_file)) return;
        break;
      case '!':
        spawn();
        fseek(fp, win[wc], 0);
        if (!prwindow(fp, Screen_Height - 1, last_file)) return;
        break;
      case '#':
        putchar('\n');
        return;
      case 'q':
        putchar('\n');
        exit();
      default:
        Ding();
        putchar('\r');
        break;      
    }
  } 
}  /* more */

bool prwindow(fp, height, last_file)
FILE *fp;
int height;
bool last_file;
{
  int i;
  char *fgets(), str[BUFSIZ];
  bool got_line = FALSE;

  putchar('\r'); ClrEOL();
  for (i = 0; i < height; i++) {
    if (fgets(str, BUFSIZ, fp) == NULL)
      break;
    got_line = TRUE;
    fputs(str, stdout);
  }
  return got_line && !(feof(fp) && last_file);
}  /* prwindow */

/* fixstring
 *   "fix" a string - i.e. lowercase it & make sure it's null-terminated.
 *   Buffer to fix is hopefully 1 char longer than data size to hold final
 *   null.
 */

void fixstring(str, siz)
char *str;
int  siz;
{
  char *nullpos;
  char *strptr;

  if ((nullpos = memchr(str, ' ', (size_t) siz)) == NULL)
    str[siz + 1] = EOS;
  else
    *nullpos = EOS;

  for (strptr = str; *strptr != EOS; strptr++)
    *strptr = lower(*strptr);

} /* fixstring */

bool prline(fp)
FILE *fp;
{
  char *fgets(), str[BUFSIZ];

  putchar('\r'); ClrEOL();
  if (fgets(str, BUFSIZ, fp) == NULL)
    return FALSE;
  fputs(str, stdout);
  return TRUE;
}  /* prline */

/* get_pattern
     Read pattern to be search for from the user's keyboard.
*/
get_pattern(pat)
char *pat;
{
  int i = 0;

  while ((pat[i++] = getchar()) != '\n')
    ;
  pat[i - 1] = EOS;
}  /* get_pattern */

/* find
     Find starting index of the first string within the second.
     Return -1 if not there.  Horribly slow!
*/
int find(t, s)
char *t, *s;
{
  int i, n;

  n = strlen(t);
  for (i = 0; s[i] != EOS; i++)
    if (strncmp(s+i, t, n) == 0)
      return i;
  return -1;

}  /* find */

spawn()
{
  puts("\nSpawning subprocess ... use EOJ to return.");
  lib$spawn();
}  /* spawn */


/*  -------------------------------------------------------------------

    Contains terminal-handling routines using the SMG routines.

    Author: Robert J. Drabek, Curtis Smith

    Date: Winter 1987

    -------------------------------------------------------------------  */

#define NULL_STR (char *)0
#define BEL_STR  "\07"

/*  These would normally come from iodef.h and ttdef.h  */
#define IO$_SENSEMODE 0x27    /* Sense mode of terminal */
#define TT$_UNKNOWN 0x00    /* Unknown terminal   */

/* Local routines. */
static void  vmsgtty();
static char *tgetstr();
static void  tputs();
static void  tputone();

/* -------------------------------------------------------------------- */
static char  *CE = NULL_STR;
static char  *SO = NULL_STR;
static char  *SE = NULL_STR;
static unsigned int Kb;

static int termtype;

#define SMG$K_BEGIN_REVERSE     0x1bf
#define SMG$K_END_REVERSE       0x1d6
#define SMG$K_ERASE_TO_END_LINE 0x1d9


/***
 *  tgetstr  -  Get an SMG string capability by name
 *
 *  Returns:  Escape sequence
 *    NULL  No escape sequence available
 ***/ 
static char *tgetstr(request_code)
int request_code;
{
  register char * result;
  static char seq_storage[1024];
  static char * buffer = seq_storage;
  static int arg_list[2] = { 1, 1 };
  int max_buffer_length, ret_length;

  /*  Precompute buffer length */

  max_buffer_length = (seq_storage + sizeof(seq_storage)) - buffer;

  /* Get terminal commands sequence from master table */

  if ((smg$get_term_data(     /* Get terminal data   */
           &termtype,         /* Terminal table address  */
           &request_code,     /* Request code    */
           &max_buffer_length,/* Maximum buffer length */
           &ret_length,       /* Return length   */
           buffer,            /* Capability data buffer  */
           arg_list)          /* Argument list array */

             & 1) == 0 && 
           /* If that didn't work, try again with no arguments */

      (smg$get_term_data(     /* Get terminal data    */
           &termtype,         /* Terminal table address */
           &request_code,     /* Request code     */
           &max_buffer_length,/* Maximum buffer length */
           &ret_length,       /* Return length    */
           buffer)            /* Capability data buffer */
  
             & 1) == 0)
        /* Return NULL pointer if capability is not available */
        return NULL;

  /* Check for empty result */
  if (ret_length == 0)
    return NULL;

  /* Save current position so we can return it to caller */
  result = buffer;

  /* NIL terminate the sequence for return */
  buffer[ret_length] = 0;

  /* Advance buffer */
  buffer += ret_length + 1;

  /* Return capability to user */
  return result;
}  /* tgetstr */


/** I/O information block definitions **/
typedef struct {    /* I/O status block     */
  short i_cond;     /* Condition value      */
  short i_xfer;     /* Transfer count     */
  long  i_info;     /* Device information     */
} iosb;

typedef struct {    /* Terminal characteristics   */
  char  t_class;    /* Terminal class     */
  char  t_type;     /* Terminal type      */
  short t_width;    /* Terminal width in characters   */
  long  t_mandl;    /* Terminal's mode and length   */
  long  t_extend;   /* Extended terminal characteristics  */
} termchar;

static termchar tc; /* Terminal characteristics   */


/***
 *  vmsgtty - Get terminal type from system control block
 *
 *  Nothing returned
 ***/
static void vmsgtty()
{   
  short fd;
  int status;
  iosb iostatus;

  $DESCRIPTOR(devnam, "SYS$COMMAND");

  /* Assign input to a channel */
  status = sys$assign(&devnam, &fd, 0, 0);
  if ((status & 1) == 0)
    exit(status);

  /* Get terminal characteristics */
  status = sys$qiow(    /* Queue and wait   */
                0,               /* Wait on event flag zero  */
                fd,              /* Channel to input terminal  */
                IO$_SENSEMODE,   /* Get current characteristic */
                &iostatus,       /* Status after operation */
                0, 0,            /* No AST service   */
                &tc,             /* Terminal characteristics buf */
                sizeof(tc),      /* Size of the buffer   */
                0, 0, 0, 0);     /* P3-P6 unused     */

  /* De-assign the input device */
  if ((sys$dassgn(fd) & 1) == 0)
    exit(status);

  /* Jump out if bad status */
  if ((status & 1) == 0)
    exit(status);
  if ((iostatus.i_cond & 1) == 0)
    exit(iostatus.i_cond);

}  /* vmsgtty */


/* Terminal_Open
 *   Get terminal type and open terminal.
*/
bool Terminal_Open()
{

  smg$create_virtual_keyboard(&Kb);

  /* Get terminal type */
  vmsgtty();
  if (tc.t_type == TT$_UNKNOWN)
    return FALSE;

  /* Access the system terminal definition table for the  */
  /* information of the terminal type returned by IO$_SENSEMODE */
  if ((smg$init_term_table_by_type(&tc.t_type, &termtype) & 1) == 0)
    return FALSE;

  /* Set sizes */
  Screen_Height = ((unsigned int) tc.t_mandl >> 24);
  Screen_Width = tc.t_width;

  /* Get some capabilities */
  SO = tgetstr(SMG$K_BEGIN_REVERSE);
  SE = tgetstr(SMG$K_END_REVERSE);
  CE = tgetstr(SMG$K_ERASE_TO_END_LINE);

  setbuf(stdout, 0);

  return TRUE;

}  /* Terminal_Open */


/* ClrEOL
     Clears from (and including) the current cursor position to the
     end of the line.
*/
void ClrEOL()
{
  if (CE != NULL)
    tputs(CE);
  else {
    int i;
    for (i = 0; i < Screen_Width - 1; i++)
      tputone(' ');
    tputone('\r');
  }
}  /* ClrEOL */


/* Ding
     Rings the terminal's bell.
*/
void Ding()
{
  tputs(BEL_STR);
}  /* Ding */


/* standout
*/
void standout()
{
  if (SO != NULL) tputs(SO);
}  /* standout */


/* standend
*/
void standend()
{
  if (SE != NULL) tputs(SE);
}  /* standend */


/* tputs
*/
static void tputs(str)
char *str;
{
  int i;

  if (str != NULL) {
    i = 0;
    while (str[i])
      (void) write(1, &str[i++], 1);
  }
}  /* tputs */


/* tputone
*/
static void tputone(ch)
char ch;
{
  (void) write(1, &ch, 1);
}  /* tputone */


/* -------------------------------------------------------------------- */

/* getkey
    Read in a keystroke in raw noecho mode.
*/
int getkey()
{
  static short int ch;

  smg$read_keystroke(&Kb, &ch);
  return ch;

}  /* getkey */


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
-- 
Robert J. Drabek
Department of Computer Science
The University of Arizona
Tucson, AZ  85721

