[comp.mail.elm] Word wrap for ELM

larry@tapa.uucp (Larry Pajakowski) (09/22/89)

I introduced a PROFS user to elm and he liked it alot.  One thing he did like
better about PROFS was the word wrap feature.

I have not used profs myself but he said you could just keep typing and profs
would break long lines into shorter ones on word boundries.  Now since
keystrokes in profs are not interactive this implies some simple breaking of
long lines into shorter ones after the file was entered.  Seems like this
would be a nice feature for the builtin editor.

Larry
larry@abtcser

aem@ibiza.cs.miami.edu (a.e.mossberg) (09/23/89)

larry@tapa.uucp (Larry Pajakowski) writes:
>I have not used profs myself but he said you could just keep typing and profs
>would break long lines into shorter ones on word boundries.  Now since
>keystrokes in profs are not interactive this implies some simple breaking of
>long lines into shorter ones after the file was entered.  Seems like this
>would be a nice feature for the builtin editor.

That sort of feature belongs in an external editor. The builtin editor 
should be no more functional than using mailx. Auto wrap is available
in vi, emacs, mg, etc etc.  Really people, elm is getting out of hand.
Remember KISS?

aem
a.e.mossberg / aem@mthvax.cs.miami.edu / aem@umiami.BITNET / Pahayokee Bioregion
The man who dies rich dies disgraced.			- Andrew Carnegie

indra@pepsi.amd.com (Indra Singhal) (09/23/89)

In article <1989Sep22.014431.27929@tapa.uucp> larry@tapa.uucp (Larry Pajakowski) writes:
>I have not used profs myself but he said you could just keep typing and profs
>would break long lines into shorter ones on word boundries...

If you use vi as your editor in elm, you can ':set wrapmargin=8' or
abbreviated, ':set wm=8' and vi will do the fancy wordwrapping that you desire.
I use it all the time!!

iNDRA | indra@amdcad.AMD.COM                 (408) 749-5445
      | {ames decwrl gatech pyramid sun uunet}!amdcad!indra
      | MS 167; Box 3453; Sunnyvale, CA 94088

pokey@well.UUCP (Jef Poskanzer) (09/26/89)

In the referenced message, larry@tapa.uucp (Larry Pajakowski) wrote:
}                         you could just keep typing and profs
}would break long lines into shorter ones on word boundries.

I recently added this feature to the Elm editor, for use as a novice
editor on the WELL.  It just adds newlines internally after the line
has been typed.  This opens up a can of worms, since very few systems
will let you type arbitrarily long lines.  On our system you can type
256 characters and then the terminal locks up - any further characters
except CR just ring the bell.  So to solve that, I made versions of
gets() and fgets() which go into CBREAK mode and do their own buffering.
This actually works quite nicely on most types of serial lines, but
we have had a lot of problems getting it to work on our X.25 lines.

Still, if you want word-wrap, this is worth trying.
---
Jef

    Jef Poskanzer  pokey@well.sf.ca.us  {ucbvax, apple, hplabs}!well!pokey
                      "Back off, man - I'm a scientist!"

- - - - - - - - - -

/**				editor.c			**/

/** This is a very simple editor designed for general purpose use.
    
    This program is a sorta subset of the 'editor=none' option in the
    Elm program & the Berkeley mailer, etc, etc.

   (C) Copyright 1986, Dave Taylor

   Heavily modified by Jef Poskanzer, 1989.
**/

/* #define DHAWKISM 1	/* define this for the getenv(EDITOR) weirdness */

#define CBREAKISM 1	/* define this to get the arbitrary-length lines */

/* #define WRAPWARN 1	/* define this to get warnings printed on line-wrap */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sysexits.h>

#define  SLEN	100		/* length of a string */
#define  BUFLEN	50000		/* length of a line of input */

#define  temp	"/tmp/pe"	/* temp file for pipe stuff  */
#define  SHELL	"sh"		/* default shell    	     */
#define  EDITOR	"bbsed"		/* default line editor       */
#define  VISUAL	"vi"		/* default screen editor     */
#define  VISUAL2 "jove"		/* second default screen editor */
#define  ESCAPE	":"		/* default escape character  */

#define  LINES_TO_SHOW 10	/* lines to show if there is text in the file */

char shell[SLEN], editor[SLEN], visual[SLEN], visual2[SLEN], escape[SLEN];
char *getenv( ), *strcpy( );
#ifdef CBREAKISM
char *cb_gets( ), *cb_fgets( );
#else CBREAKISM
char *gets( ), *fgets( );
#endif CBREAKISM

main(argc, argv)
int argc;
char *argv[];
{
	/** assume we're invoked as "<editor> <filename>" **/
	FILE *fd;
	char buffer[BUFLEN];
	int  file_already_exists = 0;
#ifdef CBREAKISM
	int  cont_handler( );
#else CBREAKISM
	int  tstp_handler( ), cont_handler( );
#endif CBREAKISM

	if (argc == 1) {
	  printf("Usage: %s filename\n", argv[0]);
	  exit(1);
	}

	if (getenv("SHELL") != NULL)
	  strcpy(shell, getenv("SHELL"));
	else
	  strcpy(shell, SHELL);
	if (getenv("VISUAL") != NULL)
	  strcpy(visual, getenv("VISUAL"));
	else
	  strcpy(visual, VISUAL);
	if (getenv("VISUAL2") != NULL)
	  strcpy(visual2, getenv("VISUAL2"));
	else
	  strcpy(visual2, VISUAL2);
#ifdef DHAWKISM
        strcpy(editor, EDITOR);
#else DHAWKISM
	if (getenv("EDITOR") != NULL)
	  strcpy(editor, getenv("EDITOR"));
	else
	  strcpy(editor, EDITOR);
#endif DHAWKISM
	if (getenv("MAIL_EDITOR_ESCAPE") != NULL)
	  strcpy(escape, getenv("MAIL_EDITOR_ESCAPE"));
	else
	  strcpy(escape, ESCAPE);

	if (access(argv[1], 00) != -1)
	  if (filesize(argv[1]) > 0) {
	    if ((fd = fopen(argv[1], "r")) == NULL) {
	      printf("Couldn't open the file to show what's already there...??\n");
	    }
	    else {
	      int i;
	      char *r;
	      printf("File already contains information:\n");
	      for (i=0; (r = fgets(buffer, BUFLEN, fd)) != NULL && i < LINES_TO_SHOW; i++)
	       printf("%s", buffer);
	      if (r == NULL)
		printf("[end-of-file]\n");
	      else
		printf("[more, use %sp to see]\n", escape);
	      fclose(fd);
	    }
	    file_already_exists++;
	  }

	if ((fd = fopen(argv[1], "a")) == NULL) {
	  printf("Can't open %s for editing!\n", argv[1]);
	  exit(1);
	}

	printf("%s message, '^D' to end, or %s? for help.\n",
	  file_already_exists? "Continue entering" : "Enter", escape);

#ifdef CBREAKISM
	signal(SIGTSTP, tstp_handler);
#endif CBREAKISM
	signal(SIGCONT, cont_handler);

#ifdef CBREAKISM
	while (cb_fgets(buffer, BUFLEN, stdin) != NULL) {
#else CBREAKISM
	while (fgets(buffer, BUFLEN, stdin) != NULL) {
#endif CBREAKISM
	  if (strcmp(buffer, ".\n") == 0) break;	/* outta here! */
	  else if (buffer[0] == escape[0]) {
	    if (buffer[1] == escape[0]) {
	      visiblebuffer(buffer + 1);
	      formatbuffer(buffer + 1);
	      fputs(buffer + 1, fd);
	    } else {
	      int l;

	      l = strlen(buffer);
	      if (l >= 1 && buffer[l - 1] == '\n')
	        buffer[l - 1] = '\0';

	      switch (buffer[1]) {
	        case '\0' : printf("(Huh?  Try \"%s?\" for help!)\n", escape);
			    break;
	        case '?'  : help( );					break;
	        case 'e'  : invoke(editor, fd, argv[1]);		break;
	        case 'v'  : invoke(visual, fd, argv[1]);		break;
	        case 'o'  : invoke(visual2, fd, argv[1]);		break;
	        case 'r'  : read_in(buffer, fd);         		break;
	        case 'w'  : write_out(buffer, fd, argv[1]);		break;
	        case 'p'  : fclose(fd);
		            fd = fopen(argv[1], "r");
			    while (fgets(buffer, BUFLEN, fd) != NULL)
	                      printf("%s", buffer);
		   	    printf("(continue or  ^C  to abort)\n");
			    fd = fopen(argv[1], "a");			break;
	        case '!'  : if (strlen(buffer) > 2) {
#ifdef CBREAKISM
			      cb_deinit( );
#endif CBREAKISM
			      system((char *) buffer + 2);
			    } else {
#ifdef CBREAKISM
			      cb_deinit( );
#endif CBREAKISM
			      system(shell);
			    }
		   	    printf("(continue or  ^C  to abort)\n");	break;

	        case '|'  : pipe_it((char *) buffer + 2, fd, argv[1]);	break;
	        default   : printf("(don't know what \"%s%c\" means!)\n", 
			    escape, buffer[1]);
			    break;
	      }
	    }
	  }
	  else {
	    visiblebuffer(buffer);
	    formatbuffer(buffer);
	    fputs(buffer, fd);
	  }
	}

	fclose(fd);
#ifdef CBREAKISM
	cb_deinit( );
#endif CBREAKISM
	
 	if (getenv("MAIL_EDITOR_EXIT") != NULL)
	  exit(EX_NOUSER); 
	else
	  exit(0);  
}

visiblebuffer( buffer )
char *buffer;
    {
    /* Turn unlawful control characters into visible sequences.
    ** This expands the string, which is a no-no, but in this case
    ** we know the string is Huge so there won't be a problem. */
    register char *cp1, *cp2, c1, c2;
    int touched = 0;

    for ( cp1 = buffer; *cp1 != '\0'; cp1++ )
	{
	c1 = *cp1;
	/* Check for bad chars.  ^G ^I ^J ^L and ^M are allowed. */
	if ( ( c1 < ' ' && c1 != '\007' && c1 != '\011' && c1 != '\012' &&
	       c1 != '\014' && c1 != '\015' ) ||
	     c1 == '\177')
	    {
	    touched = 1;
	    cp2 = cp1;
	    *cp2++ = '^';
	    c2 = *cp2;
	    if ( c1 == '\177' )
		*cp2 = '?';
	    else
		*cp2 = c1 + 64;
	    c1 = c2;
	    for ( cp2++; *cp2 != '\0'; cp2++ )
		{
		c2 = *cp2;
		*cp2 = c1;
		c1 = c2;
		}
	    *cp2++ = c1;
	    *cp2 = '\0';
	    }
	}
    if ( touched )
	printf( "(making control characters visible, continue)\n" );
    }

formatbuffer( buffer )
char *buffer;
    {
    /* Break long lines at blanks. */
#define MAX_LINE_LENGTH 79
    register char *cp1, *cp2;
    register int l;
    int touched = 0;

    cp1 = buffer;
    l = strlen( cp1 );
    while ( l > MAX_LINE_LENGTH )
	{
	/* Look for nearest preceeding blank. */
	for ( cp2 = cp1 + MAX_LINE_LENGTH; cp2 > cp1; cp2-- )
	    if ( *cp2 == ' ' )
		{
		*cp2 = '\n';
		touched = 1;
		l -= ( cp2 - cp1 ) + 1;
		cp1 = cp2 + 1;
		break;
		}
	if ( *cp2 != '\n' )
	    {
	    /* Hmm, didn't find any blanks looking backwards!  Try forwards. */
	    for ( cp2 = cp1 + MAX_LINE_LENGTH + 1; *cp2 != '\0'; cp2++ )
		{
		if ( *cp2 == ' ' )
		    {
		    *cp2 = '\n';
		    touched = 1;
		    l -= ( cp2 - cp1 ) + 1;
		    cp1 = cp2 + 1;
		    break;
		    }
		}
	    if ( *cp2 != '\n' )
		{
		/* No blanks at all.  Forget it. */
		break;
		}
	    }
	}
#ifdef WRAPWARN
    if ( touched )
	printf(
	    "(reformatting line to %d characters long, continue)\n",
	    MAX_LINE_LENGTH );
#endif WRAPWARN
    }

#ifdef CBREAKISM
tstp_handler( )
    {
    cb_deinit( );
    signal( SIGTSTP, SIG_DFL );
    kill( 0, SIGTSTP );
    }
#endif CBREAKISM

cont_handler( )
    {
#ifdef CBREAKISM
    signal( SIGTSTP, tstp_handler );
#endif CBREAKISM
    printf( "(continue or  ^C  to abort)\n" );
    }

help( )
{
	/** list the possible commands! **/

	printf("(The commands available from here are:\n\
    %s?    list this help menu\n\
    %s!    either give you a shell, or execute the specified command\n\
    %s|    pipe the message written so far through the specified command\n\
    %se    invoke \"%s\" on the response so far\n\
    %so    invoke \"%s\" on the response so far\n\
    %sv    invoke \"%s\" on the response so far\n\
    %sp    print what we've entered so far\n\
    %sr    read in the specified file\n\
    %sw    write out the specified file\n\
    %s%s    enter a line starting with a \"%s\"\n\
To exit from the editor, use <control>-D or a \".\" on a line by itself.)\n",
	escape, escape, escape, escape, editor, escape, visual2, escape,
	visual, escape, escape, escape, escape, escape, escape);
}

invoke(editor, file_descriptor, filename)
char *editor, *filename;
FILE *file_descriptor;
{
	/** invokes the specified editor, closing the file and opening it
	    again when we're done.  If editor = NULL ask the user! **/

	char buffer[SLEN];

	if (strlen(editor) == 0) {
	  printf("Enter the name of the editor to use: ");
	  fflush(stdout);
#ifdef CBREAKISM
	  cb_gets(editor);
#else CBREAKISM
	  gets(editor);
#endif CBREAKISM
	  if (strlen(editor) == 0) goto end_it;
	}
	else {
	  printf("(invoking %s) ", editor);
	  fflush(stdout);
	}

	fclose(file_descriptor);

	sprintf(buffer, "%s %s", editor, filename);

#ifdef CBREAKISM
        cb_deinit( );
#endif CBREAKISM

	system(buffer);

	file_descriptor = fopen(filename, "a");

end_it:
	printf("(continue or  ^C  to abort)\n");

	return;
}

pipe_it(command, file_descriptor, filename)
char *command, *filename;
FILE *file_descriptor;
{
	/** this will either accept a previously entered pipe command,
	    as in ":|fmt", or if none, will prompt for one.  It's really
	    quite a simple routine!
	**/

	char buffer[SLEN];

	if (strlen(command) == 0) {
	  printf("Pipe message through: ");
	  fflush(stdout);
#ifdef CBREAKISM
	  cb_gets(command);
#else CBREAKISM
	  gets(command);
#endif CBREAKISM
	  if (strlen(command) == 0) goto end_it;
	}
	else
	  printf("(piping response through \"%s\")\n", command);

	fclose(file_descriptor);

	sprintf(buffer, "%s < %s > %s.%d ; mv %s.%d %s",
		command, filename, temp, getpid( ), temp, getpid( ),
		filename);

#ifdef CBREAKISM
        cb_deinit( );
#endif CBREAKISM

	system(buffer);

	file_descriptor = fopen(filename, "a");

end_it:

	printf("(continue or  ^C  to abort)\n");

	return;
}

read_in(fname, fd)
char *fname;
FILE *fd;
{
	/** read the specified file in, continuing when done.  **/

	FILE     *newfd;
	register int i, j, lines = 0;
	char     filename[SLEN], buffer[BUFLEN];

	if (strlen(fname) < 3) {
	  printf("Enter name of file to read in: ");
	  fflush(stdout);
#ifdef CBREAKISM
	  cb_gets(filename);
#else CBREAKISM
	  gets(filename);
#endif CBREAKISM
	  if (strlen(filename) == 0) goto end_it;
	}
	else {
	  for (i=2; fname[i] == ' '; i++) 
	    /* count up! */ ;
	  for (j = 0; i < strlen(fname);)
	    filename[j++] = fname[i++];
	  filename[j] = '\0';
	}

	if ((newfd = fopen(filename, "r")) == NULL) {
	  printf("(can't open file \"%s\" for reading!  Continue...)\n", 
		 filename);
	  return;
	}

	while (fgets(buffer, BUFLEN, newfd) != NULL) {
	  visiblebuffer(buffer);
	  formatbuffer(buffer);
	  fputs(buffer, fd);
	  lines++;
	}

	fclose(newfd);

	printf("(read in %d line%s from file \"%s\"   Continue...)\n",
		lines, lines == 1? "" : "s", filename);
	return;

end_it:
	printf("(continue or  ^C  to abort)\n");
	return;
}

write_out(fname, fd, base_filename)
char *fname, *base_filename;
FILE *fd;
{
	/** write a copy of the current message to the specified file! **/

	FILE     *newfd;
	register int i, j, lines = 0;
	char     filename[SLEN], buffer[BUFLEN];

	if (strlen(fname) < 3) {
	  printf("Enter name of file to write to: ");
	  fflush(stdout);
#ifdef CBREAKISM
	  cb_gets(filename);
#else CBREAKISM
	  gets(filename);
#endif CBREAKISM
	  if (strlen(filename) == 0) goto end_it;
	}
	else {
	  for (i=2; fname[i] == ' '; i++) 
	    /* count up! */ ;
	  for (j = 0; i < strlen(fname);)
	    filename[j++] = fname[i++];
	  filename[j] = '\0';
	}

	if ((newfd = fopen(filename, "w")) == NULL) {
	  printf("(Can't open file \"%s\" for writing!  Continue...)\n", 
		 filename);
	  return;
	}

	fclose(fd);	/* close the current file... */

	fd = fopen(base_filename, "r");	/* and open for reading */

	while (fgets(buffer, BUFLEN, fd) != NULL) {
	  fprintf(newfd, "%s", buffer);
	  lines++;
	}

	fclose(newfd);
	fclose(fd);	/* close the current file... */

	fd = fopen(base_filename, "a");	/* and open back up for appending */

	printf("(saved %d line%s to file \"%s\"   Continue...)\n",
		lines, lines == 1? "" : "s", filename);
	return;

end_it:
	printf("(continue or  ^C  to abort)\n");
	return;
}

int
filesize(filename)
char *filename;
{
	struct stat statbuffer;

	if (stat(filename, &statbuffer) != 0)
	  return(0);
	else 
	  return((int) statbuffer.st_size);
}

#ifdef CBREAKISM
/* cbreak versions of fgets and gets */

#include <stdio.h>
#include <sys/file.h>
#include <sys/ioctl.h>

int cb_inited = 0;

struct sgttyb cb_tty;
short cb_save_flags;
int cb_save_lmodes, cb_ifd, cb_ofd;
FILE *cb_if;
int *cb_prevsig;

char *
cb_fgets( s, n, f )
char *s;
int n;
FILE *f;
    {
    int lmodes;
    char c, *r;
    int l;
    int cb_int_handler( );

    if ( cb_inited && f != cb_if )
	cb_inited = 0;
    
    if ( ! cb_inited )
	{
	/* Check for non-tty. */
	cb_if = f;
	cb_ifd = fileno( cb_if );
	if ( ! isatty( cb_ifd ) )
	    return fgets( s, n, cb_if );

	/* Get an output fd for the input device. */
	if ( cb_ifd = 0 && isatty( 1 ) )
	    cb_ofd = 1;
	else
	    {
	    cb_ofd = open( ttyname( cb_ifd ), O_WRONLY );
	    if ( cb_ofd == -1 )
		{
		perror( "opening output tty" );
		return NULL;
		}
	    }

	/* Save tty modes. */
	ioctl( cb_ifd, TIOCGETP, &cb_tty );
	cb_save_flags = cb_tty.sg_flags;
	ioctl( cb_ifd, TIOCLGET, &cb_save_lmodes );

	cb_inited = 1;

	cb_prevsig = (int *) signal( SIGINT, cb_int_handler );
	if ( cb_prevsig == (int *) -1 )
	    {
	    perror( "installing signal handler" );
	    return NULL;
	    }

	/* Set cbreak mode, and unset echo and control-echo. */
	cb_tty.sg_flags |= CBREAK;
	cb_tty.sg_flags &= ~ECHO;
	ioctl( cb_ifd, TIOCSETN, &cb_tty );
	lmodes = cb_save_lmodes & ~LCTLECH;
	ioctl( cb_ifd, TIOCLSET, &lmodes );
	}

    /* Read. */
    r = s;
    l = 0;
    for ( ; ; )
	{
	if ( read( cb_ifd, &c, 1 ) != 1 )
	    {
	    r = NULL;
	    break;
	    }

	if ( c == cb_tty.sg_erase || c == '\010' || c == '\177' )	/* ^H DEL */
	    {
	    if ( l == 0 )
		write( cb_ofd, "\007", 1 );	/* ^G */
	    else
		{
		l--;
		write( cb_ofd, "\010 \010", 3 );	/* ^H ^H */
		}
	    }
	else if ( c == cb_tty.sg_kill )
	    {
	    if ( l == 0 )
		write( cb_ofd, "\007", 1 );	/* ^G */
	    else
		{
		do
		    {
		    l--;
		    write( cb_ofd, "\010 \010", 3 );	/* ^H */
		    }
		while ( l > 0 );
		}
	    }
	else switch ( c )
	    {
	    case '\004':	/* ^D */
	    write( cb_ofd, "^D", 2 );
	    s[l] = '\0';
	    if ( l == 0 )
		r = NULL;
	    goto out;

	    case '\022':	/* ^R */
	    write( cb_ofd, "^R\n", 3 );
	    write( cb_ofd, s, l );
	    break;

	    case '\027':	/* ^W */
	    if ( l == 0 )
		write( cb_ofd, "\007", 1 );	/* ^G */
	    else
		{
		while ( l > 0 && s[l-1] == ' ' || s[l-1] == '\t' )
		    {
		    l--;
		    write( cb_ofd, "\010 \010", 3 );	/* ^H */
		    }
		while ( l > 0 && s[l-1] != ' ' && s[l-1] != '\t' )
		    {
		    l--;
		    write( cb_ofd, "\010 \010", 3 );	/* ^H */
		    }
		}
	    break;

	    case '\r':
	    c = '\n';
	    /* Fall through. */

	    default:
	    s[l++] = c;
	    write( cb_ofd, &c, 1 );
	    if ( l >= n - 1 )
		{
		s[l] = '\0';
		goto out;
		}
	    }

	/* If it was a newline, return. */
	if ( c == '\n' )
	    {
	    s[l] = '\0';
	    goto out;
	    }
	}

out:
    /* Done. */
    return r;
    }

cb_int_handler( )
    {
    cb_deinit( );
    exit( 1 );
    }

cb_deinit( )
    {
    if ( cb_inited )
	{
	/* Restore tty modes. */
	ioctl( cb_ifd, TIOCLSET, &cb_save_lmodes );
	cb_tty.sg_flags = cb_save_flags;
	ioctl( cb_ifd, TIOCSETN, &cb_tty );

	(void) signal( SIGINT, cb_prevsig );

	/* Close output tty. */
	if ( cb_ofd != 1 )
	    close( cb_ofd );

	cb_inited = 0;
	}
    }

char *
cb_gets( s )
char *s;
    {
    char *val;

    val = cb_fgets( s, 32767, stdin );
    if ( val != NULL )
	{
	int l;

	l = strlen( s );
	if ( l >= 1 && s[l - 1] == '\n' )
	    s[l - 1] = '\0';
	}

    return val;
    }
#endif CBREAKISM

pokey@well.UUCP (Jef Poskanzer) (09/26/89)

Oops, apply this patch, I got an #ifdef backwards.

*** b	Mon Sep 25 17:17:03 1989
--- src/mail_editor.c	Mon Sep 25 17:15:47 1989
***************
*** 51,58
  	char buffer[BUFLEN];
  	int  file_already_exists = 0;
  #ifdef CBREAKISM
- 	int  cont_handler( );
- #else CBREAKISM
  	int  tstp_handler( ), cont_handler( );
  #endif CBREAKISM
  

--- 51,56 -----
  	char buffer[BUFLEN];
  	int  file_already_exists = 0;
  #ifdef CBREAKISM
  	int  tstp_handler( ), cont_handler( );
  #else CBREAKISM
  	int  cont_handler( );
***************
*** 54,59
  	int  cont_handler( );
  #else CBREAKISM
  	int  tstp_handler( ), cont_handler( );
  #endif CBREAKISM
  
  	if (argc == 1) {

--- 52,59 -----
  	int  file_already_exists = 0;
  #ifdef CBREAKISM
  	int  tstp_handler( ), cont_handler( );
+ #else CBREAKISM
+ 	int  cont_handler( );
  #endif CBREAKISM
  
  	if (argc == 1) {