[comp.os.vms] A VMS version of MORE/LESS

robert@arizona.edu (Robert J. Drabek) (12/09/87)

The following is a MORE (or LESS) file perusal program for VMS.

Its features include one page, half page, and one line steps along with
a back-one-page command.  It can handle wildcard file names and uses
the SMG (screen management) routines to do reverse video, etc.  Command
input is in (almost) raw mode.  Commands (and their alternates):

  <SPACE> ==> print next screen (f)
  <CR>    ==> print next line (e, j, +)
  s       ==> skip over next screenfull
  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


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

  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.

  Commands (and their alternates):
  <SPACE> ==> print next screen (f)
  <CR>    ==> print next line (e, j, +)
  s       ==> skip over next screenfull
  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

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

#include <stdio.h>
#include <ctype.h>
#include <descrip.h>

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

#define EOS '\0'

#define MAXNAME     80
#define MAXWINDOWS 200
#define MAXFILES   100

#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();

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];

  /* 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;

  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];
    while (lib$find_file(&name_desc, &result, &context) & 1) {
      if (nargc >= MAXFILES) {
        fprintf(stderr, "Only %d files will be read\n", MAXFILES);
        break;
      }
      nargv[++nargc] = malloc((unsigned)strlen(name) + 1);
      strcpy(nargv[nargc], name);
    }
    lib$find_file_end(&context);
  }

  for (i = 1; i <= nargc; i++) {
    for (j = 0; nargv[i][j] != EOS; j++)
      if (isupper(nargv[i][j]))
        nargv[i][j] = tolower(nargv[i][j]);
      else if (nargv[i][j] == ' ')
        nargv[i][j] = EOS;
    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;
  long ftell();
  int  find(), fseek();

  win[0] = 0;
  fseek(fp, (long)0, 2);
  file_size = ftell(fp);
  fseek(fp, win[0], 0);

  if (!prwindow(fp, Screen_Height - 1, last_file)) return; /* window #0 */
  wc = 0;
  for (;;) {
    standout();
    printf("--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();
            printf("END OF FILE");
            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;
              fseek(fp, win[wc], 0); 
              standout();
              printf("\nPattern not found.\n");
              standend();
              break;
            } 
            line_count++;
            if (line_count > Screen_Height - 1) {
              line_count = 1;
              win[++wc] = ftell(fp);
            }
        }
        if (pat_found > -1) {
          putchar('\r');
          ClrEOL();
          printf("%s", a_line);
          win[++wc] = ftell(fp);
        }
        if (!prwindow(fp, Screen_Height - 2, last_file)) return;
        break;
      case 't':
      case 'g':
        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();
        printf(" press any key now to continue ");
        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;
    printf("%s", str);
  }
  return got_line && !(feof(fp) && last_file);
}  /* prwindow */

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

  putchar('\r'); ClrEOL();
  if (fgets(str, BUFSIZ, fp) == NULL)
    return FALSE;
  printf("%s", str);
  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()
{
  static char cmd[10]="spawn\0";
  $DESCRIPTOR(cmddesc,cmd);
  printf("Spawning subprocess ... use EOJ to return.");
  lib$spawn(&cmddesc);
}  /* 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
University of Arizona
Tucson, AZ  85721