MORE/PAGING routine needed ...

Brad Appleton brad at SSD.CSD.HARRIS.COM
Mon Nov 26 08:10:28 AEST 1990


In article <17293 at netcom.UUCP> avery at netcom.UUCP (Avery Colter) writes:
>salomon at ccu.umanitoba.ca (Dan Salomon) writes:
>
>>MORE is quite a powerful pager, and it can take a lot of
>>effort to duplicate all of its functionality.  If you don't duplicate
>>its operations exactly you will just annoy the users.  
>
>That's partly why I haven't spoken sooner, and put my main more function
>on the back-burner trying to get this regular expression search function
>going...
>
>I'm realizing what a complex set of questions is involved in matching
>like this...

I apologize for posting this but I tried reply directly to Avery via e-mail
6 different times (each with a different path) and every single one bounced.

I had to solve the "pager" problem a few months ago. Basically - if you
want to have MORE as your pager (and you are on Unix) than you can do
this using popen(3S) and pclose(3S). Just tell popen the name of the 
command you want to write to and it will return you the file-pointer
to use for your output.

Only problem is - for paging, you want to know if popen fails BEFORE you
call pclose - the only way I found to do this is to trap the SIGPIPE signal
(since popen only returns non-zero if there was a problem forking the shell,
not with the command you gave it).

Anyway - the following represents my solution to this problem.

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by brad on Sun Nov 25 15:49:27 EST 1990
# Contents:  README Makefile keyboard.h pager.h keyboard.c pager.c vxprintf.c
#	winsize.c
 
echo x - README
sed 's/^@//' > "README" <<'@//E*O*F README//'
_______________________________________________________________________________

                                   LICENSE
                                   =======

This software is not subject to any license  of  the  American  Telephone  and
Telegraph Company or of the Regents of the University of California.

Permission is granted to anyone to use this software for any  purpose  on  any
computer  system,  and  to alter it and redistribute it freely, subject to the
following restrictions:

  1.  Neither  the  authors  of  the  software  nor  their   employers
      (including  any of the employers' subsidiaries and subdivisions)
      are responsible for maintaining & supporting  this  software  or
      for any consequences resulting from the use of this software, no
      matter how awful, even if they arise from flaws in the  software
      (see LIABILITY).

  2.  The origin of this software must not be  misrepresented,  either
      by  explicit  claim  or  by omission.  Since few users ever read
      sources, credits must appear in the documentation.

  3.  Altered versions must be plainly marked as such, and must not be
      misrepresented  as being the original software.  Since few users
      ever read sources, credits must appear in the documentation.

  4.  This notice may not be removed or altered.
_______________________________________________________________________________

                                NO WARRANTY
                                ===========

Because this software is licensed free  of  charge,  we  (the  authors  and/or
distributors  of  this software) provide absolutely no warranty, to the extent
permitted by applicable state law.  Except when otherwise stated  in  writing,
the  authors  and  distributors  of this software and/or other parties provide
this program "as is"  without  warranty  of  any  kind,  either  expressed  or
implied,   including,   but   not   limited  to,  the  implied  warranties  of
merchantability and fitness for a particular purpose.  The entire risk  as  to
the  quality  and performance of this software lies with the recipients and/or
users of this software and not with any of the authors and/or distributors  of
this  software,  nor  with  any  of  their  employers,  including  any  of the
employers' subsidiaries and subdivisions. Should the program prove  defective,
the  recipients/users  of  this  software  assume  the  cost  of all necessary
servicing, repair, or correction.
_______________________________________________________________________________

                                 LIABILITY
                                 =========

In no event  unless  required  by  applicable  law  will  the  authors  and/or
distributors  of  this  software, their employers, and any subsidiaries and/or
subdivisions of their employers, and/or any other party who  may  redistribute
this software as permitted in the LICENSE section of this notice, be liable to
the recipients/users of this software for damages including any lost  profits,
lost  monies,  or  other special, incidental, or consequential damages arising
out of the use or inabilty to use (including but not limited to loss  of  data
or  data  being  rendered inaccurate or lossed sustained by third parties or a
failure of the software to operate with any other software or  hardware)  this
software, even if you have been advised of the possibility of such damages, or
for any claim by any other party.
_______________________________________________________________________________

Now that all thats out of the way ...

This software attempts to provide a minimal C-interface for programs to provide
paged output. On Unix Systems, one may control whether or not curses/terminfo 
or termcap is used to determine window-size by #defining one of USE_CURSES,
USE_TERMINFO, or USE_TERMCAP. Also on Unix - one may #define USE_POPEN to
try and pipe output to a pager (such as "more" or "less") before resorting
to the use of the minimal pager implemented in pager.c.

The rest should be self-explanatory - let me know if its not!!

BTW - I tried to hack this up for VMS Systems but it is NOT tested on VMS!

______________________ "And miles to go before I sleep." ______________________
 Brad Appleton                         Harris Corp., Computer Systems Division
   Software Engineer                   2101 West Cypress Creek Road,  M/S 161 
     brad at ssd.csd.harris.com           Fort Lauderdale, FL  33309-1892  USA
       ...!uunet!hcx1!brad                 Phone: (305) 973-5360
@~~~~~~~~~~~~~~~~~~~~ Disclaimer: I said it, not my company! ~~~~~~~~~~~~~~~~~~~
@//E*O*F README//
chmod u=rw,g=rw,o=r README
 
echo x - Makefile
sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
###
#
# Makefile for the paging library
#
# Created 11/12/90 by Brad Appleton
###

# set up SYSTEM and UNIVERSE
@.UNIVERSE = att
SYSTEM = unix
#OS_DEFS = -D$(SYSTEM) -D$(.UNIVERSE)_universe

# set OPT to debug OR optimize (or both) accordingly
#OPT = -O
OPT = -g

# TYPE should be should be one of LIB, COMMAND, or TEST,
#      COMMAND and TEST should only be used with the "program" target
TYPE = LIB

# DEFINES may include any of the following:
#	 -DUSE_POPEN, -DUSE_CURSES, -DUSE_TERMINFO, and -DUSE_TERMCAP
DEFINES = -DUSE_POPEN

CFLAGS = $(OPT) $(OS_DEFS) -DPAGER_$(TYPE) $(DEFINES)
LINKER = $(CC)

PROGRAM	= pager
LIBRARY = libpager.a

MAKEFILE = Makefile

DOCS = README
HDRS = keyboard.h  pager.h
SRCS = keyboard.c  pager.c  vxprintf.c  winsize.c
OBJS = keyboard.o  pager.o  vxprintf.o  winsize.o

all: $(LIBRARY)

$(LIBRARY):	$(OBJS)
	ar cru $(LIBRARY) $(OBJS)
	ranlib $(LIBRARY)

program: $(PROGRAM)

$(PROGRAM): $(OBJS) $(LIBS)
	$(LINKER) $(LDFLAGS) $(OBJS) $(LIBS) -o $(PROGRAM)

clean:
	rm -f $(OBJS)

clobber: clean
	rm -f $(PROGRAM) $(LIBRARY) core .exrc tags

index: 
	ctags -wx $(HDRS) $(SRCS)

tags: $(HDRS) $(SRCS)
	ctags $(HDRS) $(SRCS)

shar: $(HDRS) $(SRCS) $(DOCS) $(MAKEFILE)
	shar $(DOCS) $(MAKEFILE) $(HDRS) $(SRCS) > $(PROGRAM).shar

collapse: clobber
	shar $(DOCS) $(MAKEFILE) $(HDRS) $(SRCS) > $(PROGRAM).shar  && \
		rm -f $(DOCS) $(MAKEFILE) $(HDRS) $(SRCS)
	compress -v $(PROGRAM).shar
	
###
pager.o: keyboard.h pager.h
@//E*O*F Makefile//
chmod u=rw,g=rw,o=r Makefile
 
echo x - keyboard.h
sed 's/^@//' > "keyboard.h" <<'@//E*O*F keyboard.h//'
/*
* keyboard.h -- include file for keyboard.c
*
* Created 10/09/90 by Brad Appleton
*/

#ifndef KEYBOARD_H
#define KEYBOARD_H

int kbd_open();
int kbd_close();
int kbd_active();
int kbd_getc();

#endif
@//E*O*F keyboard.h//
chmod u=rw,g=rw,o=r keyboard.h
 
echo x - pager.h
sed 's/^@//' > "pager.h" <<'@//E*O*F pager.h//'
/*
* pager.h -- include file for pager.c
*
* Created 10/09/90 by Brad Appleton
*/

#ifndef PG_PAUSE

#define  PG_PAUSE    0
#define  PG_NOPAUSE  1

int pg_open();
int pg_close();
int pg_active();
int pg_putc();
int pg_puts();
int pg_printf();
int pg_pause();

#endif
@//E*O*F pager.h//
chmod u=rw,g=rw,o=r pager.h
 
echo x - keyboard.c
sed 's/^@//' > "keyboard.c" <<'@//E*O*F keyboard.c//'
/**
*
* File:  keyboard.c -- Get a single character directly from keyboard
*
*    this file defines four routines:
*      1) kbd_open() -- open the keyboard; return associated file-descriptor
*                       returns 1 if keyboard was already open, 0 otherwise.
*      2) kbd_close() -- close the keyboard file-descriptor
*                       returns 1 if keyboard was already closed, 0 otherwise.
*      3) kbd_getc() -- get a single key from the keyboard file-descriptor
*                       opens & close keyboard if necessary.
*      4) kbd_active() -- returns 1 if the keyboard is open, 0 otherwise;
*
* Created 10/09/90 by Brad Appleton
**/

#ifndef TRUE
#  define TRUE  (char) 0x01
#  define FALSE (char) 0x00
#endif

static char  KBD_Active = FALSE;
int kbd_active() { return  (int) KBD_Active; }

#if (!defined(unix)  &&  !defined(vms))

#include <stdio.h>

int kbd_open()
{
  if ( KBD_Active )  return  1;
  KBD_Active = TRUE;
  return  0;
}

int kbd_close()
{
  if ( !KBD_Active )  return  1;
  KBD_Active = FALSE;
  return  0;
}

int kbd_getc()
{
  return  fgetc( stdin );
}

#else
# ifdef vms

#include <stdio.h>
#include <iodef.h>
#include <ssdef.h>
#include <descrip.h>

int sys$assign();
int sys$qiow();
int sys$dassgn();

static int  IO_Chan;

int kbd_open()
{
  $DESCRIPTOR( devnam, "SYS$COMMAND" );

  if ( KBD_Active )  return  1;
  KBD_Active = TRUE;
  return  sys$assign( &devnam, &IO_Chan, 0, 0 );
}

int kbd_close()
{
  $DESCRIPTOR( devnam, "SYS$COMMAND" );

  if ( !KBD_Active )  return  1;
  KBD_Active = FALSE;
  return  sys$dassgn( IO_Chan );
}

int kbd_getc()
{
  char need_to_close = FALSE;
  int keystroke = 0;
  int bufsiz = 1;
  int io_func = (IO$_READVBLK | IO$M_NOECHO);
  $DESCRIPTOR( devnam, "SYS$COMMAND" );

  if ( !KBD_Active ) {
    need_to_close = TRUE;
    (void) kbd_open();
  }

  sys$qiow( 0,            /* event flag # */
            IO_Chan,      /* channel to use */
            io_func,      /* i/o function to use */
            0,            /* i/o status block */
            0,            /* AST routine address */
            0,            /* AST parameter */
            &keystroke,   /* input buffer */
            bufsiz,       /* input buffer size */
            0, 0, 0, 0);  /* p3-p6 */

  if ( need_to_close )  (void) kbd_close();

  return  keystroke;
}

# else
	/* pre-open file descriptors */
#define  STDIN  0
#define  STDOUT 1
#define  STDERR 2

#include <stdio.h>
#include <termio.h>

extern int errno;
static int tty_fd;

int  kbd_open()
{
  FILE   *tty_fp;

  if ( KBD_Active )  return  1;

	/* open keyboard for input */
  if ( (tty_fp = fopen("/dev/tty", "r")) == (FILE *)NULL ) {
    perror( "unable to open /dev/tty for reading" );
    exit( errno );
  }

  tty_fd = fileno(tty_fp);
  KBD_Active = TRUE;

  return  0;
}

int  kbd_close()
{
  if ( !KBD_Active )  return  1;
  KBD_Active = FALSE;
  return  close( tty_fd );
}

int  kbd_getc()
{
  char need_to_close = FALSE;
  int    status;
  char   keystroke;
  struct termio  tty_flags, save_flags;

  if ( !KBD_Active ) {
    need_to_close = TRUE;
    (void) kbd_open();
  }


	/* save current tty settings */
  status = ioctl( tty_fd, TCGETA, &save_flags );

  if ( status < 0 ) {
    perror( "unable to retrieve tty settings - bad rc from ioctl()" );
    exit( errno );
  }

	/* get current tty settings and set new flags for raw-mode */
  status = ioctl( tty_fd, TCGETA, &tty_flags );

  if ( status < 0 ) {
    perror( "unable to retrieve tty settings - bad rc from ioctl()" );
    exit( errno );
  }

  tty_flags.c_iflag &= ~ICRNL;
  tty_flags.c_oflag &= ~(TAB3|ONLCR);
  tty_flags.c_lflag &= ~(ECHO|ICANON);
  tty_flags.c_cc[ VMIN ] = 1;  /* only want 1 character */
  tty_flags.c_cc[ VTIME ] = 1;

	/* get the keystroke */
  status = ioctl( tty_fd, TCSETA, &tty_flags );   /* set cbreak mode */

  if ( status < 0 ) {
    perror( "unable to change tty settings - bad rc from ioctl()" );
    exit( errno );
  }

  if ( read( tty_fd, &keystroke, 1 ) != 1 )  { /* get character */
    perror( "unable to read keystroke - bad rc from read()" );
    exit( errno );
  }

  status = ioctl( tty_fd, TCSETA, &save_flags );   /* reset previous mode */

  if ( status < 0 ) {
    perror( "unable to restore tty settings - bad rc from ioctl()" );
    exit( errno );
  }

  if ( need_to_close )  (void) kbd_close();

  return  (int) keystroke;
}

# endif /* not a vms system */
#endif /* not a unix or vms system */



#ifdef GETKEY_TEST

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

static char *map( cc )
  char cc;
{
  static char *a[] = {
	"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
	"BS ", "HT ", "LF ", "VT ", "FF ", "CR ", "SO ", "SI ",
	"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
	"CAN", "EM ", "SUB", "ESC", "FS ", "GS ", "RS ", "US "
  };

  if ( cc == 0177 )
    return  "DEL";
  else
    return  a[cc];
}


main( argc, argv ) int argc; char *argv[];
{
  int  i, ch;

  for ( i = 1 ; i < argc ; i++ )
    fprintf( stderr, "%s ", argv[i] );

  fflush( stderr );

  kbd_open();
  ch = kbd_getc();
  kbd_close();

  if ( isprint( ch ) )
     fprintf( stdout, "%c\n", ch );
  else
     fprintf( stdout, "%s\n", map(ch) );

  exit( 0 );
}

#endif  /* GETKEY_TEST */
@//E*O*F keyboard.c//
chmod u=rw,g=rw,o=r keyboard.c
 
echo x - pager.c
sed 's/^@//' > "pager.c" <<'@//E*O*F pager.c//'
/*
** pager.c -- interface to an internal paging system
**
**  int pg_open( FILE *fp );
**     -- opens the pager. If <fp> is NOT a tty or popen fails (or is not used)
**        then <fp> is the pager file written to by subsequent calls to the
**        pager printing routines.  pg_open() returns 0 if the pager was 
**        successfully opend and returns 1 if it was already open.
**
**  int pg_close( int nopause );
**     -- closes the pager, returns 0 upon success or 1 if the pager
**        was already closed. If nopause is 0, then input is requested
**        from the user before continuing.
**
**  int pg_active( void );
**     -- returns 1 if the pager is active (open) and 0 otherwise.
**
**  int pg_putc( int ch );
**     -- writes a character to the pager
**
**  int pg_puts( char *str );
**     -- writes a string to the pager
** 
**  int pg_printf( char *format, ... );
**     -- prints a formatted string to the pager.
**
**  int pg_pause( void );
**     -- cause the INTERNAL pager to pause for a keystroke and 
**        returns the keystroke given.
** 
**  If USE_POPEN is defined and popen() succeeds, then pager commands
**  are defined by the pager specified by $PAGER or bu /usr/ucb/more.
**  Otherwise an internal pager is used and has the following commands.
**
**      'Q', 'E', 'S', and 'X' will exit the pager (case insensitive).
**
**      On systems where kbd_getc does NOT require a carriage return, a
**      space character yields the next screen of output and a carriage
**      return yields the next line of output; Otherwise a carriage 
**      return yields the next screen of output.
**
**      A formfeed character '\f' will cause the internal pager to pause
**      regardless of how many line are on the screen.
**
**
**  Created 10/10/90 by Brad Appleton
**         (requires keyboard.c, winsize.c, and vxprintf.c)
*/

#include <stdio.h>
#include <varargs.h>
#include "keyboard.h"
#include "pager.h"

#ifdef unix
/* #define  USE_POPEN */

#include <signal.h>
#include <setjmp.h>

/* get #defines for access() call */
#include <sys/file.h>
#ifndef X_OK
#define X_OK  0x01
#endif

#endif

/*
**	Implement a state machine that tries first to use a pager
**	specified by the user, then as a second choice, /usr/ucb/more,
**	if all else fails use the given file-pointer (which we assume to
**  be writable)
**
**	The following numbers define the state machine.
**
*/

#define  PG_CLOSED -1 /* pager not-yet-open */
#define  PG_NONE    0   /* No pager used */
#define  PG_ENV     1   /* $PAGER used */
#define  PG_DFLT    2   /* default pager (more) used */
#define  PG_FILE    3   /* file-pointer used */

#define  DEFAULT_PAGER   "/usr/ucb/more"
#define  INTERNAL_PAGER  "internal pager"
#define  MAXNAMELEN  256
#define  MAXBUFSIZE  256

#ifndef TRUE
#  define  TRUE  1
#endif
#ifndef FALSE
#  define  FALSE 0
#endif

#ifdef USE_POPEN
  static int pg_error();       /* broken-pipe exception handler */
  static jmp_buf pg_recover;   /* jump-buffer for exception handler */
#endif

extern int  errno;
static int  pg_pathname();
static int  Pager_Type = PG_CLOSED;
static int  Pager_Quit = 0;
static FILE *Pager_FP = (FILE *)NULL;
static char Pager_Name[ MAXNAMELEN ];

/*
** is_interactive( int fd )
**
**   returns TRUE if the given file-descriptor corresponds to a tty
**   device and returns FALSE otherwise
*/
static int is_interactive(fd)
	int fd;
{
	int saverr = errno;  /* save errno */

#if (defined(unix)  ||  defined(vms))
	if ( isatty(fd) )
		return  TRUE;
	else {
		errno = saverr;
		return  FALSE;
	}
#else
	return  TRUE;
#endif
}


/*
** pg_open( FILE *fp )
**
**   returns 0 if we open the pager, 1 if it was already open
*/
int pg_open( fp )
  FILE *fp;
{
	int  pg_type;

	*Pager_Name = '\0';

	/* if a pager is already open - ignore this call */
	if ( Pager_Type != PG_CLOSED  ||  Pager_FP )   return  1;

	/*
	** dont page output if it has been redirected
	*/
	if ( !is_interactive(fileno(fp)) ) {
		Pager_Type = PG_FILE;
		Pager_FP = fp;
		return  0;
	}

#ifndef USE_POPEN
	Pager_Type = PG_NONE;
	Pager_FP = fp;

	/* open the keyboard for input */
	kbd_open();

	return  0;
#else

	pg_type = pg_pathname( Pager_Name );
	Pager_FP = (FILE *)NULL;

	/* jump here after pg_error fields SIGPIPE */
	if ( setjmp( pg_recover )) {
		fprintf( stderr, "Unable to pipe output to %s", Pager_Name );
		pclose( Pager_FP );
		Pager_FP = (FILE *)NULL;
		if ( pg_type == PG_ENV ) {
			pg_type = PG_DFLT;
			strcpy( Pager_Name, DEFAULT_PAGER );
		}
		else {
			pg_type = PG_NONE;
			strcpy( Pager_Name, INTERNAL_PAGER );
		}
		fprintf( stderr, " -- trying %s instead.\n", Pager_Name );
		fflush( stderr );
	}

	while ( !Pager_FP ) {

		switch( pg_type ) {
			case PG_DFLT:
				/* jump here if both $PAGER and DEFAULT-PAGER fail */
				if ( setjmp( pg_recover )) {
					fprintf(stderr, "Unable to pipe output to %s", Pager_Name);
					pclose( Pager_FP );
					Pager_FP = (FILE *)NULL;
					pg_type = PG_NONE;
					strcpy( Pager_Name, INTERNAL_PAGER );
					kbd_open();
					fprintf( stderr, " -- trying %s instead.\n", Pager_Name );
					fflush( stderr );
					continue;
				}
				/* fall through to next case */

			case PG_ENV:
				signal( SIGPIPE, pg_error );
				Pager_FP = popen( Pager_Name, "w" );
				if ( !Pager_FP ) {
					if ( pg_type == PG_ENV ) {
						Pager_FP = (FILE *)NULL;
						pg_type = PG_DFLT;
					}
					else {
						Pager_FP = fp;
						pg_type = PG_NONE;
						strcpy( Pager_Name, INTERNAL_PAGER );
						kbd_open();
					}
				}
				else {
					/*
					** Sleep for a bit, just so we block, and the child
					** process can get moving. Then attempt to write to
					** the pager pipeline. If the pager is bad then the
					** pipe will be broken and pg_error() will handle
					** the broken-pipe signal. Othwerwise, the write will
					** succeed and we'll reset the handler to ignore the
					** broken pipe signal.
					*/
#ifdef VERBOSE
					fflush( stdout );
					fflush( stderr );
					fprintf( stderr, "Piping output to %s, please wait ...\n",
							Pager_Name );
					fflush( stderr );
#endif
					sleep( 1 );
					fputc( '\n', Pager_FP );
					fflush( Pager_FP );
					signal( SIGPIPE, SIG_IGN );
				}
				break;
	
			case PG_NONE:
				strcpy( Pager_Name, INTERNAL_PAGER );
				kbd_open();
				Pager_FP = fp;
				break;

			case PG_FILE:
				Pager_FP = fp;
				break;

			default:
				fprintf( stderr, "Unrecognized state [%d] in pg_open()\n",
						pg_type );
				exit( -1 );
		}/*switch*/
	}/*while*/

	Pager_Type = pg_type;
	return  0;
#endif
}

#ifdef USE_POPEN

/*
** pg_error()
**
**     exception-handler for the broken-pipe (SIGPIPE) signal
*/
static
int pg_error()
{
	signal( SIGPIPE, SIG_IGN );
	longjmp( pg_recover, 1 );
}

#endif


#ifdef USE_POPEN

/*
** pg_pathname() 
**
**	   return the name of the pager program to pipe output to
*/
static
int pg_pathname( pager_cmd )
  char *pager_cmd;
{
  char  *env_var, *getenv();
  int   pg_type;

  if ( Pager_Type > PG_NONE )  pg_type = Pager_Type;
  env_var = getenv("PAGER");

  if ( !access(env_var, X_OK) ) {
    pg_type = (strcmp(pager_cmd, DEFAULT_PAGER)) ? PG_ENV : PG_DFLT;
    strcpy( pager_cmd, env_var );
  }
  else {
    pg_type = PG_DFLT;
    strcpy( pager_cmd, DEFAULT_PAGER );
  }

  return  pg_type;
}/* pg_pathname */

#endif


/*
** pg_close()
**
**   returns 0 if we close the pager, 1 if it was already closed
*/
int pg_close( nopause )
  int  nopause;
{
	if ( Pager_Type == PG_CLOSED )
		return  1;

	if ( Pager_Type == PG_FILE  ||  Pager_FP == (FILE *)NULL )
	{
		Pager_FP = (FILE *)NULL;
		Pager_Type = PG_CLOSED;
		Pager_Quit = 0;
		*Pager_Name = '\0';
		return  0;
	}

#ifdef USE_POPEN
	if ( Pager_Type == PG_ENV  ||  Pager_Type == PG_DFLT )
		pclose( Pager_FP );

	signal( SIGPIPE, SIG_IGN );
	wait( (int *) 0 );

		/* reset file pointer for last message */
	if ( is_interactive( fileno(stderr) ) )
		Pager_FP = stderr;
	else if ( is_interactive( fileno(stdout) ) )
		Pager_FP = stdout;
	else
		Pager_FP = (FILE *)NULL;
#endif 

	if ( Pager_FP ) {
		if ( !nopause  &&  !Pager_Quit ) {
			fputs("---end of output (press RETURN to continue)---", Pager_FP);
			fflush( Pager_FP );
			(void) kbd_getc();
			fprintf(Pager_FP, "\r%46s\r", "");
		}
		fflush( Pager_FP );
		Pager_FP = (FILE *)NULL;
	}

	/* close the keyboard */
	if ( Pager_Type == PG_NONE )
		kbd_close();

	Pager_Type = PG_CLOSED;
	Pager_Quit = 0;
	*Pager_Name = '\0';

	return  0;
}


/*
** pg_active() -- returns 1 if the pager is open, FALSE otherwise
*/
int pg_active()
{
  return  ( Pager_Type == PG_CLOSED ) ? 0 : 1;
}

/*
** pg_putc( int ch )  --  write a character to the pager
*/
int pg_putc( ch )
  int  ch;
{
	register int val;

	if ( Pager_Type == PG_CLOSED )  return  EOF;

	if ( Pager_Type == PG_NONE )  {
		val = pg_printf( "%c", ch );
	}
	else  {
		val = fputc( ch, Pager_FP );
		fflush( Pager_FP );
	}

	return  val;
}


/*
** pg_puts( char *str )  --  write a string to the pager
*/
int pg_puts( str )
  char *str;
{
	register int val;

	if ( Pager_Type == PG_CLOSED )  return  EOF;

	if ( Pager_Type == PG_NONE )  {
		val = pg_printf( "%s", str );
	}
	else  {
		val = fputs( str, Pager_FP );
		fflush( Pager_FP );
	}

	return  val;
}


/*
** pg_printf( char *format, ... ) -- write a formatted string to the pager
*/
/*VARARGS1*/
int pg_printf( format, va_alist )
  char *format;
  va_dcl
{
 	va_list  ap;
	register int val;

	if ( Pager_Type == PG_CLOSED )  return  -1;

	va_start(ap);
	if ( Pager_Type == PG_NONE ) {
		val = pg_printbuf( format, ap );
	}
	else {
		val = vfprintf( Pager_FP, format, ap );
		fflush( Pager_FP );
	}
	va_end(ap);
	
	return  val;
}


/*
** pg_pause()  -- cause the internal pager to pause for input
**                (will not work for a pager the was popen'ed!!!)
*/
int  pg_pause()
{
	int  ch;

	fflush( Pager_FP );
	ch = kbd_getc();

	return  ch;
}


/*
** pg_printfbuf() -- private function which handles all the output
**                   to the internal pager.
*/
/*VARARGS1*/
static
int  pg_printbuf(format, ap)
 char *format;
 va_list ap;
{
	char  ch, *p;
	register int val;
	register char *bufptr;
	static  char  pg_buffer[ MAXBUFSIZE ];
	static  int  maxlines = 0, maxcols = 0, nlines = 0, ncols = 0;

	/* get window size if we dont have it already */
	if ( !maxlines && !maxcols )
		get_winsize( fileno(Pager_FP), &maxlines, &maxcols );

	*pg_buffer = '\0';
	val = vsprintf(pg_buffer, format, ap);

	p = pg_buffer;
	for ( bufptr = pg_buffer ; *bufptr ; bufptr++ ) {
		if ( nlines >= (maxlines - 1) ) {
				/* print buffer so far */
			if ( bufptr != pg_buffer ) {
				if ( *bufptr != '\n' ) ++bufptr;
				ch = *bufptr;
				*bufptr = '\0';
				fprintf( Pager_FP, "%s\n", p );
				*bufptr = ch;
				p = bufptr;
			}

				/* prompt for next line of input */
			ncols = 0;

#if (defined(unix)  ||  defined(vms))
			fputs( "---more (SPACE=next-screen, RETURN=next-line, 'q'=Quit)---", Pager_FP );
#else
			fputs( "---more (RETURN=next-screen, 'q'=Quit)---", Pager_FP );
#endif

			fflush( Pager_FP );
			ch = kbd_getc();

#if (defined(unix)  ||  defined(vms))
			fprintf( Pager_FP, "\r%59s\r", " " );
#else
			printf( Pager_FP, "\r%42s\r", " " );
#endif

			fflush( Pager_FP );

				/* interpret single character command */
			switch( ch ) {
				case 'E' : case 'Q' : case 'S' : case 'X' :
				case 'e' : case 'q' : case 's' : case 'x' :	/* exit pager */
					Pager_Quit = 1;
					pg_close();
					return  val;

#if (defined(unix)  ||  defined(vms))
				case '\r' : case '\n' :  /* just print one more line */
					--nlines;
					break;
#endif
				default :  /* print a whole new screenful */
					nlines = 0;
			}/*switch*/
		}/*if*/

		nlines = nlines % maxlines;

		/* update number of lines and columns printed */
		if ( *bufptr == '\n'  ||  ++ncols >= maxcols ) {
			++nlines;
			ncols = 0;
		}

		/* force a pause when we see a form-feed */
		if ( *bufptr == '\f' ) {
			*bufptr = '\n';
			nlines = maxlines - 1;
			ncols = 0;
		}
		ncols = ncols % maxcols;
	}/*for*/

	/* print remainder of buffer */
	if ( p  &&  *p )  fputs( p, Pager_FP );

	fflush( Pager_FP );
	return  val;
}


#ifdef PAGER_TEST

main()
{
  int i;

  pg_open( stdout );

  pg_puts( "see if a formfeed will cause a pause: \f" );
  pg_puts( "try another pause: \f" );

  for ( i = 0; i < 100; i++ )
    pg_printf( "This is line %d\n", i+1 );

  pg_close( PG_NOPAUSE );
}

#endif


#ifdef PAGER_COMMAND

main()
{
  int  c;
  pg_open( stdout );

  while ( !feof(stdin) ) {
    c = getc( stdin );
    pg_putc( c );
  }

  pg_close();
}

#endif
@//E*O*F pager.c//
chmod u=rw,g=rw,o=r pager.c
 
echo x - vxprintf.c
sed 's/^@//' > "vxprintf.c" <<'@//E*O*F vxprintf.c//'
/* Portable vsprintf  by Robert A. Larson <blarson at skat.usc.edu> */

/* Copyright 1989 Robert A. Larson.
 * Distribution in any form is allowed as long as the author
 * retains credit, changes are noted by their author and the
 * copyright message remains intact.  This program comes as-is
 * with no warentee of fitness for any purpose.
 *
 * Thanks to Doug Gwyn, Chris Torek, and others who helped clarify
 * the ansi printf specs.
 *
 * Please send any bug fixes and improvements to blarson at skat.usc.edu .
 * The use of goto is NOT a bug.
 */

/* Feb	7, 1989		blarson		First usenet release */
/* Oct 20, 1990     Brad Appleton -- minimally hacked to fit into my pager */

/* This code implements the vsprintf function, without relying on
 * the existance of _doprint or other system specific code.
 *
 * Define NOVOID if void * is not a supported type.
 *
 * Two compile options are available for efficency:
 *	INTSPRINTF	should be defined if sprintf is int and returns
 *			    the number of chacters formated.
 *	LONGINT		should be defined if sizeof(long) == sizeof(int)
 *
 *	They only make the code smaller and faster, they need not be
 *	defined.
 *
 * UNSIGNEDSPECIAL should be defined if unsigned is treated differently
 * than int in argument passing.  If this is definded, and LONGINT is not,
 * the compiler must support the type unsingned long.
 *
 * Most quirks and bugs of the available sprintf fuction are duplicated,
 * however * in the width and precision fields will work correctly
 * even if sprintf does not support this, as will the n format.
 *
 * Bad format strings, or those with very long width and precision
 * fields (including expanded * fields) will cause undesired results.
 */

#ifdef ucb_universe	/* only need this stuff for BSD Unix */

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

#ifdef OSK		/* os9/68k can take advantage of both */
#define LONGINT
#define INTSPRINTF
#endif

/* This must be a typedef not a #define! */
#ifdef NOVOID
typedef char *pointer;
#else
typedef void *pointer;
#endif

#ifdef	INTSPRINTF
#define Sprintf(string,format,arg)	(sprintf((string),(format),(arg)))
#else
#define Sprintf(string,format,arg)	(\
	sprintf((string),(format),(arg)),\
	strlen(string)\
)
#endif

typedef int *intp;

int vsprintf(dest, format, args)
char *dest;
register char *format;
va_list args;
{
    register char *dp = dest;
    register char c;
    register char *tp;
    char tempfmt[64];
#ifndef LONGINT
    int longflag;
#endif

    tempfmt[0] = '%';
    while( (c = *format++) != 0) {
	if(c=='%') {
	    tp = &tempfmt[1];
#ifndef LONGINT
	    longflag = 0;
#endif
continue_format:
	    switch(c = *format++) {
		case 's':
		    *tp++ = c;
		    *tp = '\0';
		    dp += Sprintf(dp, tempfmt, va_arg(args, char *));
		    break;
		case 'u':
		case 'x':
		case 'o':
		case 'X':
#ifdef UNSIGNEDSPECIAL
		    *tp++ = c;
		    *tp = '\0';
#ifndef LONGINT
		    if(longflag)
			dp += Sprintf(dp, tempfmt, va_arg(args, unsigned long));
		    else
#endif
			dp += Sprintf(dp, tempfmt, va_arg(args, unsigned));
		    break;
#endif
		case 'd':
		case 'c':
		case 'i':
		    *tp++ = c;
		    *tp = '\0';
#ifndef LONGINT
		    if(longflag)
			dp += Sprintf(dp, tempfmt, va_arg(args, long));
		    else
#endif
			dp += Sprintf(dp, tempfmt, va_arg(args, int));
		    break;
		case 'f':
		case 'e':
		case 'E':
		case 'g':
		case 'G':
		    *tp++ = c;
		    *tp = '\0';
		    dp += Sprintf(dp, tempfmt, va_arg(args, double));
		    break;
		case 'p':
		    *tp++ = c;
		    *tp = '\0';
		    dp += Sprintf(dp, tempfmt, va_arg(args, pointer));
		    break;
		case '-':
		case '+':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
		case ' ':
		case '#':
		case 'h':
		    *tp++ = c;
		    goto continue_format;
		case 'l':
#ifndef LONGINT
		    longflag = 1;
		    *tp++ = c;
#endif
		    goto continue_format;
		case '*':
		    tp += Sprintf(tp, "%d", va_arg(args, int));
		    goto continue_format;
		case 'n':
		    *va_arg(args, intp) = dp - dest;
		    break;
		case '%':
		default:
		    *dp++ = c;
		    break;
	    }
	} else *dp++ = c;
    }
    *dp = '\0';
    return dp - dest;
}


int vfprintf(dest, format, args)
FILE *dest;
register char *format;
va_list args;
{
    register char c;
    register char *tp;
    register int count = 0;
    char tempfmt[64];
#ifndef LONGINT
    int longflag;
#endif

    tempfmt[0] = '%';
    while(c = *format++) {
	if(c=='%') {
	    tp = &tempfmt[1];
#ifndef LONGINT
	    longflag = 0;
#endif
continue_format:
	    switch(c = *format++) {
		case 's':
		    *tp++ = c;
		    *tp = '\0';
		    count += fprintf(dest, tempfmt, va_arg(args, char *));
		    break;
		case 'u':
		case 'x':
		case 'o':
		case 'X':
#ifdef UNSIGNEDSPECIAL
		    *tp++ = c;
		    *tp = '\0';
#ifndef LONGINT
		    if(longflag)
			count += fprintf(dest, tempfmt, va_arg(args, unsigned long));
		    else
#endif
			count += fprintf(dest, tempfmt, va_arg(args, unsigned));
		    break;
#endif
		case 'd':
		case 'c':
		case 'i':
		    *tp++ = c;
		    *tp = '\0';
#ifndef LONGINT
		    if(longflag)
			count += fprintf(dest, tempfmt, va_arg(args, long));
		    else
#endif
			count += fprintf(dest, tempfmt, va_arg(args, int));
		    break;
		case 'f':
		case 'e':
		case 'E':
		case 'g':
		case 'G':
		    *tp++ = c;
		    *tp = '\0';
		    count += fprintf(dest, tempfmt, va_arg(args, double));
		    break;
		case 'p':
		    *tp++ = c;
		    *tp = '\0';
		    count += fprintf(dest, tempfmt, va_arg(args, pointer));
		    break;
		case '-':
		case '+':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
		case ' ':
		case '#':
		case 'h':
		    *tp++ = c;
		    goto continue_format;
		case 'l':
#ifndef LONGINT
		    longflag = 1;
		    *tp++ = c;
#endif
		    goto continue_format;
		case '*':
		    tp += Sprintf(tp, "%d", va_arg(args, int));
		    goto continue_format;
		case 'n':
		    *va_arg(args, intp) = count;
		    break;
		case '%':
		default:
		    putc(c, dest);
		    count++;
		    break;
	    }
	} else {
	    putc(c, dest);
	    count++;
	}
    }
    return count;
}

vprintf(format, args)
char *format;
va_list args;
{
    return vfprintf(stdout, format, args);
}

#endif  /* ucb_universe ONLY */


#ifdef VXPRINTF_TEST

/*VARARGS1*/
int foo( fmt, va_alist )
  char *fmt;
  va_dcl
{
  va_list  ap;
  int  rc;

  va_start(ap);
  rc = vprintf( fmt, ap );
  va_end(ap);

  return  rc;
}

void main()
{
  printf( "its a %s %% day in the %d neighborhood for %*s\n",
       "pitiful", 4, 8, "fun" );

  foo( "its a %s %% day in the %d neighborhood for %*s\n",
       "pitiful", 4, 8, "fun" );
}

#endif  /* VXPRINTF_TEST */

@//E*O*F vxprintf.c//
chmod u=rw,g=rw,o=r vxprintf.c
 
echo x - winsize.c
sed 's/^@//' > "winsize.c" <<'@//E*O*F winsize.c//'
/*
** winsize.c -- routine to "portably" detrmine the number of rows
**              and columns that will fit on the user's terminal.
**
** created 10/01/90 by Brad Appleton
*/

#include <stdio.h>

#define  DEFAULT_ROWS  24
#define  DEFAULT_COLS  80

#ifdef vms

#include <stdio.h>
#include <iodef.h>
#include <ssdef.h>
#include <descrip.h>

	/* structure to contain terminal characteristics */
typedef  struct {
    short garb1, cols;
    char  garb2, garb3, garb4, rows;
} termchar_t;

int sys$assign();
int sys$qiow();
int sys$dassgn();

void
get_winsize( fd, nrows, ncols )
  int  fd, *nrows, *ncols;  /* nrows and ncols are passed by reference */
{
  int c, charlen = 8;
  termchar_t  termchar;
  int chan;
  $DESCRIPTOR( devnam,"SYS$COMMAND" );

  sys$assign( &devnam, &chan, 0, 0 );
  sys$qiow( 0, chan, IO$_SENSEMODE, 0, 0, 0, &termchar, &charlen, 0, 0, 0, 0 );
  sys$dassgn( chan );

  *nrows = ( termchar.rows > 0 ) ? (int) termchar.rows : DEFAULT_ROWS;
  *ncols = ( termchar.cols > 0 ) ? (int) termchar.cols : DEFAULT_COLS;
}

#else
#ifdef unix

/*
** we will either try to access terminfo through the termcap-interface
** in the curses library (which would require linking with -lcurses)
** or use termcap directly (which would require linking with -ltermcap)
*/
#ifndef USE_TERMCAP
#if ( defined(USE_TERMINFO) || defined(USE_CURSES) )
#define USE_TERMCAP
#endif
#endif


#ifdef USE_TERMCAP
#define TERMBUFSIZ    1024
#define UNKNOWN_TERM  "unknown"
#define DUMB_TERMBUF  "dumb:co#80:hc:"

extern int  tgetent(), tgetnum();
#endif


/* try to get TIOCGWINSZ from termios.h, then from sys/ioctl.h */
#include <termios.h>
#if ( !defined(TIOCGWINSZ)  &&  !defined(TIOCGSIZE)  &&  !defined(WIOCGETD) )
#include <sys/ioctl.h>
#endif

/* if still dont have TIOCGWINSZ (or TIOCGSIZE) try for WIOCGETD */
#if ( !defined(TIOCGWINSZ)  &&  !defined(TIOCGSIZE)  &&  !defined(WIOCGETD) )
#include <sgtty.h>
#endif

extern  char *getenv();


/*
** get_winsize() -- determine # of rows/columns that will fit on the screen.
**
**     The environment variables $LINES and $COLUMNS will be used if they exist.
**     If not, then the TIOCGWINSZ call to ioctl() is used (if it is defined).
**     If not, then the TIOCGSIZE call to ioctl() is used (if it is defined).
**     If not, then the WIOCGETD call to ioctl() is used (if it is defined).
**     If not, then get the info from terminfo/termcap (if it is there)
**     Otherwise, assume we have a 24x80 screen.
*/
void
get_winsize( fd, nrows, ncols )
  int  fd, *nrows, *ncols;  /* nrows and ncols are passed by reference */
{
	char  *lines_env, *cols_env;
	long  lrow = 0, lcol = 0;
	int   lines = 0, cols = 0;
#ifdef USE_TERMCAP
	char  term_buf[ TERMBUFSIZ ], *term_env;
#endif
#ifdef TIOCGWINSZ
	struct winsize  win;
#else
#ifdef TIOCGSIZE
	struct ttysize  win;
#else
#ifdef WIOCGETD
	struct uwdata  win;
#endif
#endif
#endif

	/* make sure that fd corresponds to a terminal */
	if ( !isatty( fd ) ) {
		*nrows = DEFAULT_ROWS;
		*ncols = DEFAULT_COLS;
		return;
	}

	/* LINES & COLUMNS environment variables override everything else */
	lines_env = getenv( "LINES" );
	if ( lines_env  &&  (lrow = atol(lines_env)) > 0 )
		*nrows = lines = (int) lrow;

	cols_env = getenv( "COLUMNS" );
	if ( cols_env  &&  (lcol = atol(cols_env)) > 0 )
		*ncols = cols = (int) lcol;

#ifdef TIOCGWINSZ
	/* see what ioctl() has to say (overrides terminfo & termcap) */
	if ( (!lines || !cols)  &&  ioctl(fd, TIOCGWINSZ, &win) != -1 ) {
		if ( !lines  &&  win.ws_row > 0 )
			*nrows = lines = (int) win.ws_row;

		if ( !cols  &&  win.ws_col > 0 )
			*ncols = cols = (int) win.ws_col;
	}/*if*/
#else
#ifdef TIOCGSIZE
	/* see what ioctl() has to say (overrides terminfo & termcap) */
	if ( (!lines || !cols)  &&  ioctl(fd, TIOCGSIZE, &win) != -1 ) {
		if ( !lines  &&  win.ts_lines > 0 )
			*nrows = lines = (int) win.ts_lines;

		if ( !cols  &&  win.ts_cols > 0 )
			*ncols = cols = (int) win.ts_cols;
	}/*if*/
#else
#ifdef WIOCGETD
	/* see what ioctl() has to say (overrides terminfo & termcap) */
	if ( (!lines || !cols)  &&  ioctl(fd, WIOCGETD, &win) != -1 ) {
		if ( !lines  &&  win.uw_height > 0 )
			*nrows = lines = (int) (win.uw_height / win.uw_vs);

		if ( !cols  &&  win.uw_width > 0 )
			*ncols = cols = (int) (win.uw_width / win.uw_hs);
	}/*if*/
#endif
#endif
#endif

#ifdef USE_TERMCAP
	/* see what terminfo/termcap has to say */
	if ( !lines || !cols ) {
		if ( (term_env = getenv("TERM")) == (char *)NULL )
			term_env = UNKNOWN_TERM;
		
		if ( (tgetent(term_buf, term_env) <= 0) )
			strcpy( term_buf, DUMB_TERMBUF );

   		if ( !lines  &&  (lrow = tgetnum("li")) > 0 )
				*nrows = lines = (int) lrow;

   		if ( !cols  &&  (lcol = tgetnum("co")) > 0 )
				*ncols = cols = (int) lcol;
	}
#endif

	/* use 80x24 if all else fails */
	if ( !lines )  *nrows = DEFAULT_ROWS;
	if ( !cols )   *ncols = DEFAULT_COLS;

}/*get_winsize()*/

#else

void
get_winsize( fd, nrows, ncols )
  int  fd, *nrows, *ncols;  /* nrows and ncols are passed by reference */
{
	/* just use 80x24 */
	*nrows = DEFAULT_ROWS;
	*ncols = DEFAULT_COLS;
}

#endif  /* not a vms system */
#endif  /* not a unix-system */

#ifdef WINSIZE_TEST

main()
{
	int rows, cols;

	get_winsize( fileno(stderr), &rows, &cols );
	printf( "Output will be %d rows by %d columns\n", rows, cols );
	exit( 0 );
}

#endif
@//E*O*F winsize.c//
chmod u=rw,g=rw,o=r winsize.c
 
exit 0

______________________ "And miles to go before I sleep." ______________________
 Brad Appleton           brad at ssd.csd.harris.com       Harris Computer Systems
                             uunet!hcx1!brad           Fort Lauderdale, FL USA
~~~~~~~~~~~~~~~~~~~~ Disclaimer: I said it, not my company! ~~~~~~~~~~~~~~~~~~~



More information about the Comp.lang.c mailing list