[comp.os.vms] LESS-like utils for VMS

kuo@skatter.UUCP (Dr. Peter Kuo) (07/16/88)

Does anyone know or have the source codes (or .EXE) for a utility for VMS
that works similar to the LESS utility on Unix systems? I seem to recall
one was mentioned (posted?) sometime ago but I cann't seem to find it.

For those who is not familiar with LESS, it is a replacement program for
the more commonly used "more" utility. It will display a file screen at
a time, allows searches for characters (backwards or forward), scolls thru
files screen at a time (backwards or forward), "spawn" into editor, etc.
MUCH more than $TYPE/PAGE. Of course one can also emulate this using EDT
but...

Much thanks in advance.

Peter/
-------------------------------------------------------------------------------
Peter Kuo                   | Bitnet (VMS)  : KUO@SASK
Accelerator Laboratory      | Internet      : kuo@skatter.USask.Ca
(a.k.a. The Beam Warehouse) | uucp   (Unix) : !alberta\
Univ. of Saskatchewan       |                 !ihnp4  -- !damask!skatter!kuo
Saskatoon, Saskatchewan     |                 !utcsri /
CANADA  S7N 0W0             |
(Earth)                     | Ma Bell       : (306) 966-8528

Disclaimer: I don't know what I am saying, I'm only a physicist.
            Don't quote me on anything! I speak only for myself.

Opus: "Why, fer cryin' out loud..research physicists need Porsches, TOO!!"

						 -- Bloom County

robert@arizona.edu (Robert J. Drabek) (07/18/88)

In article <363@skatter.UUCP>, kuo@skatter.UUCP (Dr. Peter Kuo) writes:
> Does anyone know or have the source codes (or .EXE) for a utility for VMS
> that works similar to the LESS utility on Unix systems? I seem to recall
> one was mentioned (posted?) sometime ago but I cann't seem to find it.
> 

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.

  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.


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

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;

  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] = malloc((unsigned)strlen(name) + 1);
      strcpy(nargv[nargc], name);
    }
    if (!file_found)
        fprintf (stderr, "Couldn't find file %s\n", argv[i]);
    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("\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;
              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 */

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
University of Arizona
Tucson, AZ  85721