corrected termination condition for fill character introduced in last version
[mmh] / uip / prompter.c
1
2 /*
3  * prompter.c -- simple prompting editor front-end
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 #include <h/mh.h>
13 #include <fcntl.h>
14 #include <h/signals.h>
15 #include <errno.h>
16 #include <signal.h>
17 #include <setjmp.h>
18
19 #ifdef HAVE_TERMIOS_H
20 # include <termios.h>
21 #else
22 # ifdef HAVE_TERMIO_H
23 #  include <termio.h>
24 # else
25 #  include <sgtty.h>
26 # endif
27 #endif
28
29 #define QUOTE '\\'
30
31 #ifndef CKILL
32 # define CKILL '@'
33 #endif
34
35 #ifndef CERASE
36 # define CERASE '#'
37 #endif
38
39 static struct swit switches[] = {
40 #define ERASESW 0
41     { "erase chr", 0 },
42 #define KILLSW  1
43     { "kill chr", 0 },
44 #define PREPSW  2
45     { "prepend", 0 },
46 #define NPREPSW 3
47     { "noprepend", 0 }, 
48 #define RAPDSW  4
49     { "rapid", 0 },
50 #define NRAPDSW 5
51     { "norapid", 0 },
52 #define BODYSW  6
53     { "body", -4 },
54 #define NBODYSW 7
55     { "nobody", -6 },
56 #define DOTSW   8
57     { "doteof", 0 },
58 #define NDOTSW  9
59     { "nodoteof", 0 },
60 #define VERSIONSW 10
61     { "version", 0 },
62 #define HELPSW  11
63     { "help", 0 },
64     { NULL, 0 }
65 };
66
67 extern int errno;
68
69 #ifdef HAVE_TERMIOS_H
70 static struct termios tio;
71 # define ERASE tio.c_cc[VERASE]
72 # define KILL  tio.c_cc[VKILL]
73 # define INTR  tio.c_cc[VINTR]
74 #else
75 # ifdef HAVE_TERMIO_H
76 static struct termio tio;
77 #  define ERASE tio.c_cc[VERASE]
78 #  define KILL  tio.c_cc[VKILL]
79 #  define INTR  tio.c_cc[VINTR]
80 # else
81 static struct sgttyb tio;
82 static struct tchars tc;
83 #  define ERASE tio.sg_erase
84 #  define KILL  tio.sg_kill
85 #  define INTR  tc.t_intrc
86 # endif
87 #endif
88
89 static int wtuser = 0;
90 static int sigint = 0;
91 static jmp_buf sigenv;
92
93 /*
94  * prototypes
95  */
96 int getln (char *, int);
97 static int chrcnv (char *);
98 static void chrdsp (char *, char);
99 static RETSIGTYPE intrser (int);
100
101
102 int
103 main (int argc, char **argv)
104 {
105     int body = 1, prepend = 1, rapid = 0;
106     int doteof = 0, fdi, fdo, i, state;
107     char *cp, *drft = NULL, *erasep = NULL;
108     char *killp = NULL, name[NAMESZ], field[BUFSIZ];
109     char buffer[BUFSIZ], tmpfil[BUFSIZ];
110     char **arguments, **argp;
111     FILE *in, *out;
112
113 #ifdef LOCALE
114     setlocale(LC_ALL, "");
115 #endif
116     invo_name = r1bindex (argv[0], '/');
117
118     /* read user profile/context */
119     context_read();
120
121     arguments = getarguments (invo_name, argc, argv, 1);
122     argp = arguments;
123
124     while ((cp = *argp++))
125         if (*cp == '-') {
126             switch (smatch (++cp, switches)) {
127                 case AMBIGSW: 
128                     ambigsw (cp, switches);
129                     done (1);
130                 case UNKWNSW: 
131                     adios (NULL, "-%s unknown", cp);
132
133                 case HELPSW: 
134                     snprintf (buffer, sizeof(buffer), "%s [switches] file",
135                         invo_name);
136                     print_help (buffer, switches, 1);
137                     done (1);
138                 case VERSIONSW:
139                     print_version(invo_name);
140                     done (1);
141
142                 case ERASESW: 
143                     if (!(erasep = *argp++) || *erasep == '-')
144                         adios (NULL, "missing argument to %s", argp[-2]);
145                     continue;
146                 case KILLSW: 
147                     if (!(killp = *argp++) || *killp == '-')
148                         adios (NULL, "missing argument to %s", argp[-2]);
149                     continue;
150
151                 case PREPSW: 
152                     prepend++;
153                     continue;
154                 case NPREPSW: 
155                     prepend = 0;
156                     continue;
157
158                 case RAPDSW: 
159                     rapid++;
160                     continue;
161                 case NRAPDSW: 
162                     rapid = 0;
163                     continue;
164
165                 case BODYSW: 
166                     body++;
167                     continue;
168                 case NBODYSW: 
169                     body = 0;
170                     continue;
171
172                 case DOTSW: 
173                     doteof++;
174                     continue;
175                 case NDOTSW: 
176                     doteof = 0;
177                     continue;
178             }
179         } else {
180             if (!drft)
181                 drft = cp;
182         }
183
184     if (!drft)
185         adios (NULL, "usage: %s [switches] file", invo_name);
186     if ((in = fopen (drft, "r")) == NULL)
187         adios (drft, "unable to open");
188
189     strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil));
190     if ((out = fopen (tmpfil, "w")) == NULL)
191         adios (tmpfil, "unable to create");
192     chmod (tmpfil, 0600);
193
194     /*
195      * Are we changing the kill or erase character?
196      */
197     if (killp || erasep) {
198 #ifdef HAVE_TERMIOS_H
199         cc_t save_erase, save_kill;
200 #else
201         int save_erase, save_kill;
202 #endif
203
204         /* get the current terminal attributes */
205 #ifdef HAVE_TERMIOS_H
206         tcgetattr(0, &tio);
207 #else
208 # ifdef HAVE_TERMIO_H
209         ioctl(0, TCGETA, &tio);
210 # else
211         ioctl (0, TIOCGETP, (char *) &tio);
212         ioctl (0, TIOCGETC, (char *) &tc);
213 # endif
214 #endif
215
216         /* save original kill, erase character for later */
217         save_kill = KILL;
218         save_erase = ERASE;
219
220         /* set new kill, erase character in terminal structure */
221         KILL = killp ? chrcnv (killp) : save_kill;
222         ERASE = erasep ? chrcnv (erasep) : save_erase;
223
224         /* set the new terminal attributes */
225 #ifdef HAVE_TERMIOS_H
226          tcsetattr(0, TCSADRAIN, &tio);
227 #else
228 # ifdef HAVE_TERMIO_H
229         ioctl(0, TCSETAW, &tio);
230 # else
231         ioctl (0, TIOCSETN, (char *) &tio);
232 # endif
233 #endif
234
235         /* print out new kill erase characters */
236         chrdsp ("erase", ERASE);
237         chrdsp (", kill", KILL);
238         chrdsp (", intr", INTR);
239         putchar ('\n');
240         fflush (stdout);
241
242         /*
243          * We set the kill and erase character back to original
244          * setup in terminal structure so we can easily
245          * restore it upon exit.
246          */
247         KILL = save_kill;
248         ERASE = save_erase;
249     }
250
251     sigint = 0;
252     SIGNAL2 (SIGINT, intrser);
253
254     /*
255      * Loop through the lines of the draft skeleton.
256      */
257     for (state = FLD;;) {
258         switch (state = m_getfld (state, name, field, sizeof(field), in)) {
259             case FLD: 
260             case FLDEOF: 
261             case FLDPLUS: 
262                 /*
263                  * Check if the value of field contains anything
264                  * other than space or tab.
265                  */
266                 for (cp = field; *cp; cp++)
267                     if (*cp != ' ' && *cp != '\t')
268                         break;
269
270                 /* If so, just add header line to draft */
271                 if (*cp++ != '\n' || *cp != 0) {
272                     printf ("%s:%s", name, field);
273                     fprintf (out, "%s:%s", name, field);
274                     while (state == FLDPLUS) {
275                         state =
276                             m_getfld (state, name, field, sizeof(field), in);
277                         printf ("%s", field);
278                         fprintf (out, "%s", field);
279                     }
280                 } else {
281                     /* Else, get value of header field */
282                     printf ("%s: ", name);
283                     fflush (stdout);
284                     i = getln (field, sizeof(field));
285                     if (i == -1) {
286 abort:
287                         if (killp || erasep) {
288 #ifdef HAVE_TERMIOS_H
289                             tcsetattr(0, TCSADRAIN, &tio);
290 #else
291 # ifdef HAVE_TERMIO
292                             ioctl (0, TCSETA, &tio);
293 # else
294                             ioctl (0, TIOCSETN, (char *) &tio);
295 # endif
296 #endif
297                         }
298                         unlink (tmpfil);
299                         done (1);
300                     }
301                     if (i != 0 || (field[0] != '\n' && field[0] != 0)) {
302                         fprintf (out, "%s:", name);
303                         do {
304                             if (field[0] != ' ' && field[0] != '\t')
305                                 putc (' ', out);
306                             fprintf (out, "%s", field);
307                         } while (i == 1
308                                     && (i = getln (field, sizeof(field))) >= 0);
309                         if (i == -1)
310                             goto abort;
311                     }
312                 }
313
314                 if (state == FLDEOF) {  /* moby hack */
315                     fprintf (out, "--------\n");
316                     printf ("--------\n");
317                     if (!body)
318                         break;
319                     goto no_body;
320                 }
321                 continue;
322
323             case BODY: 
324             case BODYEOF:
325             case FILEEOF: 
326                 if (!body)
327                     break;
328                 fprintf (out, "--------\n");
329                 if (field[0] == 0 || !prepend)
330                     printf ("--------\n");
331                 if (field[0]) {
332                     if (prepend && body) {
333                         printf ("\n--------Enter initial text\n\n");
334                         fflush (stdout);
335                         for (;;) {
336                             getln (buffer, sizeof(buffer));
337                             if (doteof && buffer[0] == '.' && buffer[1] == '\n')
338                                 break;
339                             if (buffer[0] == 0)
340                                 break;
341                             fprintf (out, "%s", buffer);
342                         }
343                     }
344
345                     do {
346                         fprintf (out, "%s", field);
347                         if (!rapid && !sigint)
348                             printf ("%s", field);
349                     } while (state == BODY &&
350                             (state = m_getfld (state, name, field, sizeof(field), in)));
351                     if (prepend || !body)
352                         break;
353                     else
354                         printf ("\n--------Enter additional text\n\n");
355                 }
356 no_body:
357                 fflush (stdout);
358                 for (;;) {
359                     getln (field, sizeof(field));
360                     if (doteof && field[0] == '.' && field[1] == '\n')
361                         break;
362                     if (field[0] == 0)
363                         break;
364                     fprintf (out, "%s", field);
365                 }
366                 break;
367
368             default: 
369                 adios (NULL, "skeleton is poorly formatted");
370         }
371         break;
372     }
373
374     if (body)
375         printf ("--------\n");
376
377     fflush (stdout);
378     fclose (in);
379     fclose (out);
380     SIGNAL (SIGINT, SIG_IGN);
381
382     if (killp || erasep) {
383 #ifdef HAVE_TERMIOS_H
384          tcsetattr(0, TCSADRAIN, &tio);
385 #else
386 # ifdef HAVE_TERMIO_H
387         ioctl (0, TCSETAW, &tio);
388 # else
389         ioctl (0, TIOCSETN, (char *) &tio);
390 # endif
391 #endif
392     }
393
394     if ((fdi = open (tmpfil, O_RDONLY)) == NOTOK)
395         adios (tmpfil, "unable to re-open");
396     if ((fdo = creat (drft, m_gmprot ())) == NOTOK)
397         adios (drft, "unable to write");
398     cpydata (fdi, fdo, tmpfil, drft);
399     close (fdi);
400     close (fdo);
401     unlink (tmpfil);
402
403     context_save ();    /* save the context file */
404     return done (0);
405 }
406
407
408 int
409 getln (char *buffer, int n)
410 {
411     int c;
412     char *cp;
413
414     cp = buffer;
415     *cp = 0;
416
417     switch (setjmp (sigenv)) {
418         case OK: 
419             wtuser = 1;
420             break;
421
422         case DONE: 
423             wtuser = 0;
424             return 0;
425
426         default: 
427             wtuser = 0;
428             return NOTOK;
429     }
430
431     for (;;) {
432         switch (c = getchar ()) {
433             case EOF: 
434                 clearerr (stdin);
435                 longjmp (sigenv, DONE);
436
437             case '\n': 
438                 if (cp[-1] == QUOTE) {
439                     cp[-1] = c;
440                     wtuser = 0;
441                     return 1;
442                 }
443                 *cp++ = c;
444                 *cp = 0;
445                 wtuser = 0;
446                 return 0;
447
448             default: 
449                 if (cp < buffer + n)
450                     *cp++ = c;
451                 *cp = 0;
452         }
453     }
454 }
455
456
457 static RETSIGTYPE
458 intrser (int i)
459 {
460 #ifndef RELIABLE_SIGNALS
461     SIGNAL (SIGINT, intrser);
462 #endif
463
464     if (wtuser)
465         longjmp (sigenv, NOTOK);
466     sigint++;
467 }
468
469
470 static int
471 chrcnv (char *cp)
472 {
473     return (*cp != QUOTE ? *cp : m_atoi (++cp));
474 }
475
476
477 static void
478 chrdsp (char *s, char c)
479 {
480     printf ("%s ", s);
481     if (c < ' ' || c == 0177)
482         printf ("^%c", c ^ 0100);
483     else
484         printf ("%c", c);
485 }