Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!wuarchive!gem.mps.ohio-state.edu!apple!well!pokey
From: pokey@well.UUCP (Jef Poskanzer)
Newsgroups: comp.mail.elm
Subject: Re: Word wrap for ELM
Message-ID: <13805@well.UUCP>
Date: 26 Sep 89 00:03:44 GMT
References: <1989Sep22.014431.27929@tapa.uucp>
Reply-To: Jef Poskanzer
Organization: Paratheo-Anametamystikhood Of Eris Esoteric, Ada Lovelace Cabal
Lines: 762
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
#include
#include
#include
#include
#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 " " **/
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 -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
#include
#include
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