1 /*---------------------------------------------------------------------------
2  * Gamedriver - ed compatible editor
3  *
4  * Authors: Brian Beattie, Kees Bot, and others
5  *
6  * Copyright 1987 Brian Beattie Rights Reserved.
7  * Permission to copy or distribute granted under the following conditions:
8  *   1). No charge may be made other than reasonable charges for reproduction.
9  *   2). This notice must remain intact.
10  *   3). No further restrictions may be added.
11  *   4). Except meaningless ones.
12  *
13  * TurboC mods and cleanup 8/17/88 RAMontante.
14  *
15  * Regexp stuff replaced with Spencerian version, sundry other bugfix+speedups
16  *   by Ian Phillipps. Regexps replaced again to transparently use PCRE
17  *   instead of Spencer's regexps by Lars Duening.
18  *
19  * help files and '^' added by Ted Gaunt.
20  * Tab conversion added by Andreas Klauer.
21  *
22  * Original indentation algorithm replaced with adapted version from DGD
23  *   editor by Dworkin (Felix A. Croes), 920510.
24  *   The code may be used freely as long as its author and its origin from
25  *   DGD are clearly stated.
26  *---------------------------------------------------------------------------
27  * Per interactive user there can be one active ed session; stored as
28  * a pointer to the ed_buffer in the interactive structure. The lines of
29  * the text are stored linewise in a double-linked ring.
30  *
31  * TODO: Make it possible to attach an editor to any object, and let
32  * TODO:: the editor communicate via efuns - no direct io.
33  * TODO: In the long run, maybe only offer primitives and let the command
34  * TODO:: interpretation be done by the mudlib. This makes it easier
35  * TODO:: to write non-ed like editors.
36  *---------------------------------------------------------------------------
37  */
38 
39 #define ED_VERSION 6        /* used only in outputs for id */
40 
41 #include "driver.h"
42 #include "typedefs.h"
43 
44 #include <stdio.h>
45 #include <ctype.h>
46 
47 #include "ed.h"
48 #include "actions.h"
49 #include "comm.h"
50 #include "filestat.h"
51 #include "gcollect.h"
52 #include "interpret.h"
53 #include "lex.h"
54 #include "main.h"
55 #include "mregex.h"
56 #include "mstrings.h"
57 #include "object.h"
58 #include "simulate.h"
59 #include "stdstrings.h"
60 #include "svalue.h"
61 #include "xalloc.h"
62 
63 #include "../mudlib/sys/regexp.h"
64 
65 /*-------------------------------------------------------------------------*/
66 
67 /* Default TAB size */
68 # define DEFAULT_TABSIZE   8
69 
70 
71 /* #defines for non-printing ASCII characters */
72 
73 #define NUL  0x00        /* ^@ */
74 #define EOS  0x00        /* end of string */
75 #define SOH  0x01        /* ^A */
76 #define STX  0x02        /* ^B */
77 #define ETX  0x03        /* ^C */
78 #define EOT  0x04        /* ^D */
79 #define ENQ  0x05        /* ^E */
80 #define ACK  0x06        /* ^F */
81 #define BEL  0x07        /* ^G */
82 #define BS   0x08        /* ^H */
83 #define HT   0x09        /* ^I */
84 #define LF   0x0a        /* ^J */
85 #define NL   '\n'
86 #define VT   0x0b        /* ^K */
87 #define FF   0x0c        /* ^L */
88 #define CR   0x0d        /* ^M */
89 #define SO   0x0e        /* ^N */
90 #define SI   0x0f        /* ^O */
91 #define DLE  0x10        /* ^P */
92 #define DC1  0x11        /* ^Q */
93 #define DC2  0x12        /* ^R */
94 #define DC3  0x13        /* ^S */
95 #define DC4  0x14        /* ^T */
96 #define NAK  0x15        /* ^U */
97 #define SYN  0x16        /* ^V */
98 #define ETB  0x17        /* ^W */
99 #define CAN  0x18        /* ^X */
100 #define EM   0x19        /* ^Y */
101 #define SUB  0x1a        /* ^Z */
102 #define ESC  0x1b        /* ^[ */
103 #define FS   0x1c        /* ^\ */
104 #define GS   0x1d        /* ^] */
105 /*#define RS   0x1e           ^^ */
106 #define US   0x1f        /* ^_ */
107 #define SP   0x20        /* space */
108 #define DEL  0x7f        /* DEL*/
109 #define ESCAPE  '\\'
110 
111 
112 /* Characters used in the indentation code */
113 
114 #define TAB '\t'
115 #define LB  '{'
116 #define RB  '}'
117 #define LC  '('
118 #define RC  ')'
119 #define LS  '['
120 #define RS  ']'
121 #define PP  '\"'
122 #define EOL '\0'
123 
124 
125 /*-------------------------------------------------------------------------*/
126 
127 #ifndef FALSE
128 #    define TRUE   1
129 #    define FALSE  0
130 #endif
131 
132 
133 /* Return codes */
134 
135 #define ED_OK     FALSE
136 #define ERR       -2
137 #undef  FATAL     /* (ERR-1) */
138 #define CHANGED   (ERR-2)
139 #define SET_FAIL  (ERR-3)
140 #define SUB_FAIL  (ERR-4)
141 #define MEM_FAIL  (ERR-5)
142 
143 
144 /* Sizes and limits */
145 
146 #define BUFFER_SIZE  2048   /* stream-buffer size, a multible of a disk block */
147 
148 #define MAXLINE  2048         /* max number of chars per line */
149 #define MAXPAT    256         /* max number of chars per replacement pattern */
150 #define MAXFNAME  MAXPATHLEN  /* max file name size */
151 
152 
153 /*-------------------------------------------------------------------------*/
154 
155 /* The whole text is stored linewise in a double-linked ring(!) of 'line'
156  * structures. The text of every line is appended to the end of its
157  * structure.
158  */
159 
160 struct line
161 {
162     int          l_stat;    /* Status of the line */
163     struct line *l_prev;    /* previous line */
164     struct line *l_next;    /* next line */
165     char         l_buff[1]; /* the line's text */
166 };
167 typedef struct line LINE;
168 
169 /* Bitflags of line.l_stat */
170 
171 #define LINFREE  0x01       /* entry not in use */
172 #define LGLOB    0x02       /* line marked global, e.g. by a match-pattern */
173 
174 /*-------------------------------------------------------------------------*/
175 
176 /* The ed_buffer holds all the information for one editor session.
177  */
178 typedef struct ed_buffer_s ed_buffer_t;
179 
180 struct ed_buffer_s
181 {
182     input_t input;             /* It's an input handler. */
183 
184     Bool    diag;              /* True: diagnostic-output?*/
185     Bool    truncflg;          /* True: truncate long line flag
186                                 * Note: not used anywhere */
187     int     nonascii;          /* count of non-ascii chars read */
188     int     nullchar;          /* count of null chars read */
189     int     truncated;         /* count of lines truncated */
190     string_t *fname;           /* name of the file */
191     Bool    fchanged;          /* True: file-changed */
192     int     nofname;
193     int     mark['z'-'a'+1];
194     regexp_t *oldpat;
195     LINE    Line0;             /* anchor of the line buffer */
196     int     CurLn;             /* number of current line */
197     LINE   *CurPtr;            /* CurLn and CurPtr must be kept in sync */
198     int     LastLn;            /* number of last line */
199     int     Line1;             /* command linerange: first line */
200     int     Line2;             /* command linerange: last line */
201     int     nlines;
202     int     flags;             /* flags */
203     Bool    appending;
204     int     moring;            /* used for the wait line of help */
205     int     shiftwidth;        /* shiftwidth, in the range 0..15 */
206     int     leading_blanks;    /* Current number of leading blanks when
207                                   using autoindentation. */
208     int     cur_autoindent;
209     int     lastcmd;           /* The last command */
210     string_t *exit_fn;         /* Function to be called when player exits */
211                                /* TODO: Make this a callback */
212     object_t *exit_ob;         /* Object holding <exit_fn> */
213 };
214 
215 /* ed_buffer.flag values
216  * The lower 4 bits are reserved to hold the shiftwidth value then
217  * the settings are stored/retrieved.
218  */
219 
220 #define SHIFTWIDTH_MASK  0x000f
221 
222 #define NFLG_MASK        0x0010  /* True: number lines */
223 #define LFLG_MASK        0x0020  /* True: mark tabs and line ends */
224 #define PFLG_MASK        0x0040
225 #define EIGHTBIT_MASK    0x0080  /* True: keep 8th bit */
226 #define AUTOINDFLG_MASK  0x0100  /* True: autoindent */
227 #define EXCOMPAT_MASK    0x0200
228 #define TABINDENT_MASK   0x0400
229 #define SMALLNUMBER_MASK 0x0800  /* True: short line numbers */
230 #define ALL_FLAGS_MASK   0x0ff0
231 
232 /* Macros handling the current ed_buffer for less typing */
233 
234 #define ED_BUFFER             (current_ed_buffer)
235 #define EXTERN_ED_BUFFER(ih)  ((ed_buffer_t*) (ih))
236 
237 #define P_DIAG          (ED_BUFFER->diag)
238 #define P_TRUNCFLG      (ED_BUFFER->truncflg)
239 #define P_NONASCII      (ED_BUFFER->nonascii)
240 #define P_NULLCHAR      (ED_BUFFER->nullchar)
241 #define P_TRUNCATED     (ED_BUFFER->truncated)
242 #define P_FNAME         (ED_BUFFER->fname)
243 #define P_FCHANGED      (ED_BUFFER->fchanged)
244 #define P_NOFNAME       (ED_BUFFER->nofname)
245 #define P_MARK          (ED_BUFFER->mark)
246 #define P_OLDPAT        (ED_BUFFER->oldpat)
247 #define P_LINE0         (ED_BUFFER->Line0)
248 #define P_CURLN         (ED_BUFFER->CurLn)
249 #define P_CURPTR        (ED_BUFFER->CurPtr)
250 #define P_LASTLN        (ED_BUFFER->LastLn)
251 #define P_LINE1         (ED_BUFFER->Line1)
252 #define P_LINE2         (ED_BUFFER->Line2)
253 #define P_NLINES        (ED_BUFFER->nlines)
254 #define P_SHIFTWIDTH    (ED_BUFFER->shiftwidth)
255 #define P_FLAGS         (ED_BUFFER->flags)
256 #define P_NFLG          ( P_FLAGS & NFLG_MASK )
257 #define P_LFLG          ( P_FLAGS & LFLG_MASK )
258 #define P_PFLG          ( P_FLAGS & PFLG_MASK )
259 #define P_EIGHTBIT      ( P_FLAGS & EIGHTBIT_MASK )
260 #define P_AUTOINDFLG    ( P_FLAGS & AUTOINDFLG_MASK )
261 #define P_EXCOMPAT      ( P_FLAGS & EXCOMPAT_MASK )
262 #define P_TABINDENT     ( P_FLAGS & TABINDENT_MASK )
263 #define P_SMALLNUMBER   ( P_FLAGS & SMALLNUMBER_MASK )
264 #define P_APPENDING     (ED_BUFFER->appending)
265 #define P_MORE          (ED_BUFFER->moring)
266 #define P_LEADBLANKS    (ED_BUFFER->leading_blanks)
267 #define P_CUR_AUTOIND   (ED_BUFFER->cur_autoindent)
268 #define P_PROMPT        (ED_BUFFER->prompt)
269 #define P_LASTCMD       (ED_BUFFER->lastcmd)
270 
271 
272 /*-------------------------------------------------------------------------*/
273 
274 /* The editor options are described by this structure: */
275 
276 struct tbl
277 {
278     char *t_str;       /* option name, NULL marks the table end */
279     int   t_and_mask;  /* unset mask */
280     int   t_or_mask;   /* set mask */
281 };
282 
283 static struct tbl tbl[]
284   = { {  "number",         ~FALSE,            NFLG_MASK        }
285     , {  "nonumber",       ~NFLG_MASK,        FALSE            }
286     , {  "list",           ~FALSE,            LFLG_MASK        }
287     , {  "nolist",         ~LFLG_MASK,        FALSE            }
288     , {  "print",          ~FALSE,            PFLG_MASK        }
289     , {  "noprint",        ~PFLG_MASK,        FALSE            }
290     , {  "eightbit",       ~FALSE,            EIGHTBIT_MASK    }
291     , {  "noeightbit",     ~EIGHTBIT_MASK,    FALSE            }
292     , {  "autoindent",     ~FALSE,            AUTOINDFLG_MASK  }
293     , {  "noautoindent",   ~AUTOINDFLG_MASK,  FALSE            }
294     , {  "excompatible",   ~FALSE,            EXCOMPAT_MASK    }
295     , {  "noexcompatible", ~EXCOMPAT_MASK,    FALSE            }
296     , {  "tabindent",      ~FALSE,            TABINDENT_MASK   }
297     , {  "notabindent",    ~TABINDENT_MASK,   FALSE            }
298     , {  "smallnumber",    ~FALSE,            SMALLNUMBER_MASK }
299     , {  "nosmallnumber",  ~SMALLNUMBER_MASK, FALSE            }
300     , {  NULL }
301     };
302 
303 /*-------------------------------------------------------------------------*/
304 
305 static ed_buffer_t *current_ed_buffer;
306   /* The current ed_buffer
307    */
308 
309 
310 static char  inlin[MAXLINE];
311 static char *inptr;
312   /* Command input buffer
313    */
314 
315 static int ed_tmp;
316   /* Temporary used by some macros
317    */
318 
319 /*-------------------------------------------------------------------------*/
320 
321 /* Some macros */
322 
323 #ifndef max
324 #    define max(a,b)       ((a) > (b) ? (a) : (b))
325 #endif
326 
327 #ifndef min
328 #    define min(a,b)       ((a) < (b) ? (a) : (b))
329 #endif
330 
331 #define nextln(l)          ((l)+1 > P_LASTLN ? 0 : (l)+1)
332 #define prevln(l)          ((l)-1 < 0 ? P_LASTLN : (l)-1)
333 
334 #define gettxtl(lin)       ((lin)->l_buff)
335 #define gettxt(num)        (gettxtl( getptr(num) ))
336 
337 #define getnextptr(p)      ((p)->l_next)
338 #define getprevptr(p)      ((p)->l_prev)
339 
340 #define _setCurLn( lin )   ( (P_CURPTR = getptr( (lin) )), P_CURLN = (lin) )
341 #define setCurLn( lin )    ( (P_CURPTR = getptr( ed_tmp = (lin) )), P_CURLN = ed_tmp )
342 #define nextCurLn()        ( P_CURLN = nextln(P_CURLN), P_CURPTR = getnextptr( P_CURPTR ) )
343 #define prevCurLn()        ( P_CURLN = prevln(P_CURLN), P_CURPTR = getprevptr( P_CURPTR ) )
344 
345 #define clrbuf()           del(1, P_LASTLN)
346 
347 #define Skip_White_Space   { while (*inptr==SP || *inptr==HT) inptr++; }
348 
349 #define relink(a, x, y, b) { (x)->l_prev = (a); (y)->l_next = (b); }
350 
351 
352 /*-------------------------------------------------------------------------*/
353 
354 /* Forward declarations */
355 
356 static int doprnt(int, int);
357 static int ins(char *);
358 static int deflt(int, int);
359 static void print_help(char arg);
360 static void print_help2(void);
361 static void count_blanks(int line);
362 static void _count_blanks(char *str, int blanks);
363 static LINE *getptr(int num);
364 static void putcntl(char c);
365 static void prntln(char *str, Bool vflg, int lin);
366 static regexp_t *optpat(void);
367 
368 /*-------------------------------------------------------------------------*/
369 size_t
ed_buffer_size(input_t * ih)370 ed_buffer_size (input_t *ih)
371 
372 /* Return the size of the memory allocated for the <buffer>
373  */
374 
375 {
376     ed_buffer_t *buffer = (ed_buffer_t*) ih;
377     size_t sum;
378     long line;
379     LINE *pLine;
380 
381     if (!buffer)
382         return 0;
383 
384     sum = sizeof(*buffer);
385     for (line = 1, pLine = buffer->Line0.l_next
386         ; line < buffer->LastLn
387         ; line++, pLine = pLine->l_next)
388         sum += sizeof(*pLine) + strlen(pLine->l_buff);
389 
390     return sum;
391 } /* ed_buffer_size() */
392 
393 /*-------------------------------------------------------------------------*/
394 static INLINE void
set_ed_prompt(ed_buffer_t * ed_buffer,string_t * prompt)395 set_ed_prompt (ed_buffer_t * ed_buffer, string_t * prompt)
396 
397 /* Reference and set string <prompt> as new prompt in <ed_buffer>.
398  * The prompt svalue must already have been initialized as T_STRING.
399  */
400 
401 {
402     free_mstring(ed_buffer->input.prompt.u.str);
403     ed_buffer->input.prompt.u.str = ref_mstring(prompt);
404 } /* set_ed_prompt() */
405 
406 /*-------------------------------------------------------------------------*/
407 static int
append(int line,Bool glob)408 append (int line, Bool glob)
409 
410 /* Start appending (or inserting) after <line>: set the current line
411  * and print/set the prompt.
412  * Return success.
413  */
414 
415 {
416     if (glob)
417         return(ERR);
418     _setCurLn( line );
419     P_APPENDING = TRUE;
420     if (P_NFLG)
421         add_message(P_SMALLNUMBER ? "%3d " : "%6d. ",P_CURLN+1);
422     if (P_CUR_AUTOIND)
423         add_message("%*s", P_LEADBLANKS, "");
424     set_ed_prompt(ED_BUFFER, STR_ED_APPEND_PROMPT);
425     return ED_OK;
426 }
427 
428 /*-------------------------------------------------------------------------*/
429 static INLINE int /* only used once */
more_append(char * str)430 more_append (char *str)
431 
432 /* User entered a new line <str> in appending mode.
433  * Terminate mode if it is just '.', else append it to the text.
434  * In autoindentation mode, recompute the indentation. Outdentation can
435  * be forced by entering Ctrl-D or CR as the first characters of <str>.
436  * Return success code.
437  */
438 
439 {
440     if(str[0] == '.' && str[1] == '\0')
441     {
442         P_APPENDING = FALSE;
443         set_ed_prompt(ED_BUFFER, STR_ED_PROMPT);
444         return ED_OK;
445     }
446 
447     if (P_NFLG)
448         add_message(P_SMALLNUMBER ? "%3d " : "%6d. ",P_CURLN+2);
449 
450     if (P_CUR_AUTOIND)
451     {
452         int i;
453         int less_indent_flag = 0;
454 
455         while (*str=='\004' || *str == '\013' )
456         {
457             str++;
458             P_LEADBLANKS -= P_SHIFTWIDTH;
459             if (P_LEADBLANKS < 0)
460                 P_LEADBLANKS = 0;
461             less_indent_flag = 1;
462         }
463         for (i = 0; i < P_LEADBLANKS; )
464             inlin[i++]=' ';
465         xstrncpy(inlin+P_LEADBLANKS, str, (size_t)(MAXLINE-P_LEADBLANKS));
466         inlin[MAXLINE-1] = '\0';
467         _count_blanks(inlin, 0);
468         add_message("%*s", P_LEADBLANKS, "");
469         if (!*str && less_indent_flag)
470             return ED_OK;
471         str = inlin;
472     }
473     if (ins(str) < 0)
474         return MEM_FAIL;
475 
476     return ED_OK;
477 }
478 
479 /*-------------------------------------------------------------------------*/
480 static void
count_blanks(int line)481 count_blanks (int line)
482 
483 /* Count the leading blanks in line <line> and set .leadingblanks to
484  * the value.
485  */
486 {
487     _count_blanks(gettxtl(getptr(line)), 0);
488 }
489 
490 /*-------------------------------------------------------------------------*/
491 static void
_count_blanks(char * str,int blanks)492 _count_blanks (char * str, int blanks)
493 
494 /* Count the leading blanks of <str>, add them to <blanks> and set the
495  * result as .leadingblanks.
496  */
497 
498 {
499     for ( ; *str; str++ )
500     {
501         if ( *str == ' ' ) blanks++;
502         else if ( *str == '\t' ) blanks += 8 - blanks % 8 ;
503         else break;
504     }
505     P_LEADBLANKS = blanks < MAXLINE ? blanks : MAXLINE ;
506 }
507 
508 /*-------------------------------------------------------------------------*/
509 static INLINE int /* only used once */
ckglob(void)510 ckglob (void)
511 
512 /* Check if the command starting at <inptr> has a global modifier of the
513  * forms 'g/<expr>/' or 'v/<expr>/'.
514  * If yes, mark all matching lines with LGLOB and return TRUE,
515  * else return FALSE.
516  * Return an error code on failure.
517  */
518 
519 {
520     regexp_t      *glbpat;
521     char        c, delim, *lin;
522     int        num;
523     LINE        *ptr;
524 
525     c = *inptr;
526 
527     if (c != 'g' && c != 'v')
528         return FALSE;
529 
530     if (deflt(1, P_LASTLN) < 0)
531         return ERR;
532 
533     delim = *++inptr;
534     if (delim <= ' ')
535         return ERR;
536 
537     glbpat = optpat();
538     if (*inptr == delim)
539         inptr++;
540 
541     ptr = getptr(1);
542     for (num = 1; num <= P_LASTLN; num++)
543     {
544         ptr->l_stat &= ~LGLOB;
545         if (P_LINE1 <= num && num <= P_LINE2)
546         {
547             /* we might have got a NULL pointer if the
548              * supplied pattern was invalid
549              */
550             if (glbpat)
551             {
552                 int rc;
553                 lin = gettxtl(ptr);
554                 rc = rx_exec_str(glbpat, lin, lin);
555                 if (rc < 0)
556                 {
557                     add_message("ed: %s\n", rx_error_message(rc, glbpat) );
558                     return ERR;
559                 }
560 
561                 if (rc > 0)
562                 {
563                     if (c=='g') ptr->l_stat |= LGLOB;
564                 }
565                 else
566                 {
567                     if (c=='v') ptr->l_stat |= LGLOB;
568                 }
569             }
570         }
571         ptr = getnextptr(ptr);
572     }
573     return TRUE;
574 }
575 
576 /*-------------------------------------------------------------------------*/
577 static int
deflt(int def1,int def2)578 deflt (int def1, int def2)
579 
580 /* Set P_LINE1 & P_LINE2 (the command-range delimiters) if none of them
581  * was supplied with the command; test whether they have valid values.
582  * Return success code.
583  */
584 {
585     if(P_NLINES == 0) {
586         P_LINE1 = def1;
587         P_LINE2 = def2;
588     }
589     return ( (P_LINE1 > P_LINE2 || P_LINE1 <= 0) ? ERR : ED_OK );
590 }
591 
592 
593 /*-------------------------------------------------------------------------*/
594 static int
del(int from,int to)595 del (int from, int to)
596 
597 /* Delete the lines in the range <from> to <to> inclusive. The range is
598  * automatically limited by the text start/end.
599  * Always returns ED_OK.
600  */
601 
602 {
603     LINE    *first, *last, *next, *tmp;
604 
605     if (from < 1)
606         from = 1;
607     first = getprevptr(getptr(from));
608     P_CURLN = prevln(from);
609     P_CURPTR = first;
610     last = getnextptr(getptr(to));
611     next = first->l_next;
612     while(next != last && next != &P_LINE0)
613     {
614         tmp = next->l_next;
615         xfree((char *)next);
616         next = tmp;
617     }
618     relink(first, last, first, last);
619     P_LASTLN -= (to - from)+1;
620     return ED_OK;
621 }
622 
623 
624 /*-------------------------------------------------------------------------*/
625 static INLINE int /* only used once */
dolst(int line1,int line2)626 dolst (int line1, int line2)
627 
628 /* Print the range <line1> to <line2> with tab characters printed
629  * symbolically and line end marked with '$'.
630  * Returns success (currently always ED_OK).
631  */
632 
633 {
634     int oldflags = P_FLAGS;
635     int p;
636 
637     P_FLAGS |= LFLG_MASK;
638     p = doprnt(line1, line2);
639     P_FLAGS = oldflags;
640     return p;
641 }
642 
643 
644 /*-------------------------------------------------------------------------*/
645 #if 0 /* unused */
646 
647 int
648 esc (char **s)
649 
650 /* Map escape sequences into their equivalent symbols.  Returns the
651  * correct ASCII character.  If no escape prefix is present then s
652  * is untouched and *s is returned, otherwise **s is advanced to point
653  * at the escaped character and the translated character is returned.
654  */
655 
656 {
657     register int    rval;
658 
659     if (**s != ESCAPE)
660     {
661         rval = **s;
662     }
663     else
664     {
665         (*s)++;
666         switch(islower(**s) ? toupper(**s) : **s)
667         {
668         case '\000':
669             rval = ESCAPE;    break;
670         case 'S':
671             rval = ' ';    break;
672         case 'N':
673             rval = '\n';    break;
674         case 'T':
675             rval = '\t';    break;
676         case 'B':
677             rval = '\b';    break;
678         case 'R':
679             rval = '\r';    break;
680         default:
681             rval = **s;    break;
682         }
683     }
684     return rval;
685 }
686 #endif
687 
688 
689 /*-------------------------------------------------------------------------*/
690 static int
doprnt(int from,int to)691 doprnt (int from, int to)
692 
693 /* Print the text in the range <from> to <to>, using the current
694  * settings of P_LFLG and P_NFLG.
695  * Return ED_OK.
696  */
697 
698 {
699     from = (from < 1) ? 1 : from;
700     to = (to > P_LASTLN) ? P_LASTLN : to;
701 
702     if (to != 0 && from <= P_LASTLN)
703     {
704         _setCurLn( from );
705         while( P_CURLN <= to )
706         {
707             prntln( gettxtl( P_CURPTR ), P_LFLG, (P_NFLG ? P_CURLN : 0));
708             if( P_CURLN == to )
709                 break;
710             nextCurLn();
711         }
712     }
713     return ED_OK;
714 }
715 
716 
717 /*-------------------------------------------------------------------------*/
718 static void
prntln(char * str,Bool vflg,int lin)719 prntln (char *str, Bool vflg, int lin)
720 
721 /* Print the line <str>, optionally prepended by the number <lin> (if not 0).
722  * If <vflg> is true, tab characters are printed symbolically and line end
723  * marked with '$'.
724  */
725 
726 {
727     if (lin)
728         add_message(P_SMALLNUMBER ? "%3d " : "%7d " ,lin);
729 
730     while(*str && *str != NL)
731     {
732         if (*str < ' ' || *str >= 0x7f)
733         {
734             switch(*str)
735             {
736             case '\t':
737                 if (vflg)
738                     putcntl(*str);
739                 else
740                     add_message("%c", *str);
741                 break;
742 
743             case DEL:
744                 add_message("^?");
745                 break;
746 
747             default:
748                 putcntl(*str);
749                 break;
750             }
751             str++;
752         }
753         else
754         {
755             char *start;
756 
757             start = str;
758             do str++; while(*str >= ' ' && *str < 0x7f);
759             if (*str)
760                 add_message("%.*s", (int)(str - start), start);
761             else
762                 add_message("%s", start);
763         }
764     }
765     if (vflg)
766         add_message("$");
767     add_message("\n");
768 }
769 
770 
771 /*-------------------------------------------------------------------------*/
772 static void
putcntl(char c)773 putcntl (char c)
774 
775 /* Print the control character <c> symbolically (e.g. '^C' for a Control-C).
776  */
777 
778 {
779     add_message("^%c",(c&31)|'@');
780 }
781 
782 
783 /*-------------------------------------------------------------------------*/
784 static INLINE int /* only used once */
egets(char * str,int size,FILE * stream)785 egets (char *str, int size, FILE * stream)
786 
787 /* Safe version of fgets(): get a line of text from <stream> into <str>,
788  * but at max <size> characters, and return the number characters read.
789  */
790 
791 {
792     int   c, count;
793     char *cp;
794 
795     /* assert(size); if there is any chance of size == 0 */
796     count = 0;
797     cp = str;
798     do {
799         c = getc(stream);
800         if(c == EOF)
801         {
802             *cp = EOS;
803             if(count)
804                 add_message("[Incomplete last line]\n");
805             return(count);
806         }
807         else if(c == NL) {
808             *cp = EOS;
809             return(++count);
810         }
811         else if (c == 0)
812             P_NULLCHAR++;    /* count nulls */
813         else
814         {
815             if (c > 127) {
816                 if (!P_EIGHTBIT) /* if not saving eighth bit */
817                     c = c&127;   /* strip eigth bit */
818                 P_NONASCII++;    /* count it */
819             }
820             *cp++ = (char)c;    /* not null, keep it */
821             count++;
822         }
823     } while (size > count);
824 
825     str[count-1] = EOS;
826     if (c != NL) {
827         add_message("truncating line\n");
828         P_TRUNCATED++;
829         while ((c = getc(stream)) != EOF)
830             if (c == NL)
831                 break;
832     }
833     return count;
834 }  /* egets() */
835 
836 
837 /*-------------------------------------------------------------------------*/
838 static int
doread(int lin,string_t * fname)839 doread (int lin, string_t *fname)
840 
841 /* Read the file <fname> and insert the lines at line <lin>
842  * Return success code.
843  */
844 
845 {
846     FILE          *fp;
847     int            err;
848     unsigned long  bytes;
849     unsigned int   lines;
850     char    str[MAXLINE];
851 
852     err = 0;
853     P_NONASCII = P_NULLCHAR = P_TRUNCATED = 0;
854 
855     if (P_DIAG) add_message("\"%s\" ",get_txt(fname));
856     if ((fp = fopen(get_txt(fname), "r")) == NULL )
857     {
858         if (!P_DIAG) add_message("\"%s\" ",get_txt(fname));
859         add_message(" isn't readable.\n");
860         return ERR ;
861     }
862     FCOUNT_READ(get_txt(fname));
863     _setCurLn( lin );
864     for(lines = 0, bytes = 0;(err = egets(str,MAXLINE,fp)) > 0;) {
865         bytes += err;
866         if (ins(str) < 0) {
867             err = MEM_FAIL;
868             break;
869         }
870         lines++;
871     }
872     fclose(fp);
873 
874     if(err < 0)
875         return err;
876 
877     if (P_DIAG)
878     {
879         add_message("%u lines %lu bytes",lines,bytes);
880         if(P_NONASCII)
881             add_message(" [%d non-ascii]",P_NONASCII);
882         if(P_NULLCHAR)
883             add_message(" [%d nul]",P_NULLCHAR);
884         if(P_TRUNCATED)
885             add_message(" [%d lines truncated]",P_TRUNCATED);
886         add_message("\n");
887     }
888     return err;
889 }  /* doread */
890 
891 
892 /*-------------------------------------------------------------------------*/
893 static int
dowrite(int from,int to,string_t * fname,Bool apflg)894 dowrite (int from, int to, string_t *fname, Bool apflg)
895 
896 /* Write the lines <from> to <to> into the file <fname>. If the <apflg>
897  * is true, the file is opened for appending, else it is written
898  * from scratch.
899  * Return success code.
900  */
901 
902 {
903     FILE          *fp;
904     int            lin, err;
905     unsigned int   lines;
906     unsigned long  bytes;
907     char          *str;
908     LINE          *lptr;
909 
910     err = 0;
911     lines = bytes = 0;
912 
913     add_message("\"%s\" ",get_txt(fname));
914     if ((fp = fopen(get_txt(fname),(apflg?"a":"w"))) == NULL)
915     {
916         add_message(" can't be opened for writing!\n");
917         return ERR;
918     }
919     FCOUNT_WRITE(get_txt(fname));
920 
921     lptr = getptr(from);
922     for (lin = from; lin <= to; lin++)
923     {
924         str = lptr->l_buff;
925         lines++;
926         bytes += strlen(str) + 1;    /* str + '\n' */
927         if(fputs(str, fp) == EOF)
928         {
929             add_message("file write error\n");
930             err++;
931             break;
932         }
933         fputc('\n', fp);
934         lptr = lptr->l_next;
935     }
936     add_message("%u lines %lu bytes\n", lines, bytes);
937     fclose(fp);
938     return err;
939 }  /* dowrite */
940 
941 
942 /*-------------------------------------------------------------------------*/
943 static INLINE int /* only used once */
find(regexp_t * pat,Bool dir)944 find (regexp_t *pat, Bool dir)
945 
946 /* Find the <pat>tern in the text, starting 'after' the current line.
947  * If <dir> is false, the search is carried out forward, else backwards.
948  * Return the number of the line where the pattern is found first, else
949  * the result code ERR.
950  */
951 
952 {
953     int    i, num;
954     LINE    *lin;
955 
956     dir ? nextCurLn() : prevCurLn() ;
957     num = P_CURLN;
958     lin = P_CURPTR;
959 
960     /* if the typed in pattern was invalid we have a NULL pointer! */
961     if (!pat)
962         return ERR;
963 
964     for (i = 0; i < P_LASTLN; i++ )
965     {
966         int rc;
967         char *line_start = gettxtl(lin);
968         rc = rx_exec_str(pat, line_start, line_start);
969         if (rc < 0)
970         {
971             add_message("ed: %s\n", rx_error_message(rc, pat) );
972             return ERR;
973         }
974         if (rc)
975             return(num);
976         if( dir )
977             num = nextln(num), lin = getnextptr(lin);
978         else
979             num = prevln(num), lin = getprevptr(lin);
980     }
981     return ERR;
982 }
983 
984 /*-------------------------------------------------------------------------*/
985 #if 0 /* unused */
986 
987 static int
988 findg (regexp_t *pat, Bool dir)
989 
990 /* Find the <pat>tern in the text, starting 'after' the current line
991  * and print matching lines (like a grep).
992  * If <dir> is false, the search is carried out forward, else backwards.
993  * Return the numbers of matching lines, or ERR on failure.
994  */
995 
996 {
997     int    i, num,count;
998     LINE    *lin;
999 
1000     count = 0;
1001     num = P_CURLN;
1002     lin = P_CURPTR;
1003 
1004     /* if the typed in pattern was invalid we have a NULL pointer! */
1005     if (!pat)
1006         return ERR;
1007 
1008     for (i = 0; i < P_LASTLN; i++ )
1009     {
1010         int rc;
1011         rc = rx_exec_str(pat, gettxtl(lin), gettxtl(lin)))
1012         if (rc < 0)
1013         {
1014             add_message("ed: %s\n", rx_error_message(rc, pat) );
1015             return ERR;
1016         }
1017         if (rc > 0)
1018         {
1019             prntln( gettxtl( lin ), P_LFLG, (P_NFLG ? P_CURLN : 0));
1020              count++;
1021         }
1022         if (dir)
1023             num = nextln(num), lin = getnextptr(lin);
1024         else
1025             num = prevln(num), lin = getprevptr(lin);
1026     }
1027 
1028     return count ? count : ERR;
1029 }
1030 #endif /* 0 */
1031 
1032 /*-------------------------------------------------------------------------*/
1033 static string_t *
getfn(Bool writeflg)1034 getfn (Bool writeflg)
1035 
1036 /* Get a filename from the input buffer, create a string from it and return
1037  * the pointer to it (counts as ref). Relative filenames are made absolute by
1038  * master->make_path_absolute(). If there is no filename, set P_NOFNAME to
1039  * true and set the filename to '/'+P_FNAME. In either case the filename
1040  * is validated by check_valid_path().
1041  *
1042  * Return NULL on an error.
1043  */
1044 
1045 {
1046     string_t *file; /* TODO: make this ed_buffer based? */
1047     char     *cp;
1048     string_t *file2;
1049     svalue_t *ret;
1050 
1051     if (*inptr == NL)
1052     {
1053         P_NOFNAME = TRUE;
1054 
1055         if (!P_FNAME)
1056         {
1057             /* I have no idea how this can happen, but it did.
1058              * So be save.
1059              */
1060             add_message("bad file name\n");
1061             return NULL;
1062         }
1063 
1064         file = alloc_mstring(1+mstrsize(P_FNAME));
1065         if (!file)
1066         {
1067             add_message("Out of memory (%lu bytes) for filename.\n"
1068                        , (unsigned long) (1+mstrsize(P_FNAME)));
1069             return NULL;
1070         }
1071         get_txt(file)[0] = '/';
1072         memcpy(get_txt(file)+1, get_txt(P_FNAME), mstrsize(P_FNAME));
1073     }
1074     else
1075     {
1076         size_t len;
1077         char *tmp;
1078 
1079         P_NOFNAME = FALSE;
1080         Skip_White_Space;
1081 
1082         for (len = 0, tmp = inptr
1083             ; *tmp && *tmp != NL && *tmp != SP && *tmp != HT
1084             ; tmp++, len++) NOOP;
1085 
1086         file = alloc_mstring(len);
1087         if (!file)
1088         {
1089             add_message("Out of memory (%lu bytes) for filename.\n"
1090                        , (unsigned long)len);
1091             return NULL;
1092         }
1093 
1094         cp = get_txt(file);
1095         while (*inptr && *inptr != NL && *inptr != SP && *inptr != HT)
1096             *cp++ = *inptr++;
1097     }
1098 
1099     if (mstrsize(file) == 0)
1100     {
1101         add_message("bad file name\n");
1102         free_mstring(file);
1103         return NULL;
1104     }
1105 
1106     if (get_txt(file)[0] != '/')
1107     {
1108         push_ref_string(inter_sp, file);
1109         ret = apply_master(STR_ABS_PATH, 1);
1110         if (!ret || (ret->type == T_NUMBER && ret->u.number == 0))
1111         {
1112             free_mstring(file);
1113             return NULL;
1114         }
1115 
1116         if (ret->type == T_STRING)
1117         {
1118             free_mstring(file);
1119             file = ref_mstring(ret->u.str);
1120         }
1121     }
1122 
1123     /* add_message() / apply() might have nasty effects */
1124     if (!command_giver || command_giver->flags & O_DESTRUCTED)
1125     {
1126         free_mstring(file);
1127         return NULL;
1128     }
1129 
1130     file2 = check_valid_path(file, command_giver, STR_ED_START, writeflg);
1131     free_mstring(file);
1132 
1133     if (!file2)
1134     {
1135         return NULL;
1136     }
1137 
1138     if (mstrsize(file2) == 0) {
1139         add_message("no file name\n");
1140         free_mstring(file2);
1141         return NULL;
1142     }
1143 
1144     return file2;
1145 }  /* getfn */
1146 
1147 /*-------------------------------------------------------------------------*/
1148 static int
getnum(int first)1149 getnum (int first)
1150 
1151 /* Parse a line designation from the command input and return it.
1152  * If no line is given, return EOF or, if <first> is true, 1.
1153  * Allowed designations are:
1154  *   [0-9]*: line number
1155  *   .     : current line
1156  *   $     : last line
1157  *   /<pat>: line matching <pat>, searched forwards
1158  *   ?<pat>: line matching <pat>, searched backwards
1159  *   [+-]  : <first> true ? first line : cur. line; inptr is not incremented!
1160  *   \[a-z]: line of given mark
1161  */
1162 {
1163     regexp_t *srchpat;
1164     int    num;
1165     char    c;
1166 
1167     Skip_White_Space;
1168 
1169     if (isdigit((unsigned char)*inptr)) /* line number */
1170     {
1171         for (num = 0; isdigit((unsigned char)*inptr); ++inptr) {
1172             num = (num * 10) + (*inptr - '0');
1173         }
1174         return num;
1175     }
1176 
1177     switch(c = *inptr)
1178     {
1179     case '.':
1180         inptr++;
1181         return P_CURLN;
1182 
1183     case '$':
1184         inptr++;
1185         return P_LASTLN;
1186 
1187     case '/':
1188     case '?':
1189         srchpat = optpat();
1190         if (*inptr == c)
1191             inptr++;
1192         return find(srchpat, (c == '/') );
1193 
1194 #if 0
1195     case '^':            /* for grep-like searching */
1196     case '&':
1197         srchpat = optpat();
1198         if (*inptr == c)
1199             inptr++;
1200         return findg(srchpat, (c == '^'));
1201 #endif
1202 
1203     case '-':
1204     case '+':
1205         return first ? P_CURLN : 1;
1206 
1207     case '\'':
1208         inptr++;
1209         if (*inptr < 'a' || *inptr > 'z')
1210             return(EOF);
1211         return P_MARK[ *inptr++ - 'a' ];
1212 
1213     default:
1214         return first ? EOF : 1;    /* unknown address */
1215     }
1216 }  /* getnum */
1217 
1218 
1219 /*-------------------------------------------------------------------------*/
1220 static int
getone(void)1221 getone (void)
1222 
1223 /* Parse a line number off the commandline. It can be any additive
1224  * expression of the numbers accepted by getnum().
1225  * Return the resulting line number, or EOF/ERR on failure.
1226  */
1227 
1228 {
1229     int    c, i, num;
1230 
1231 #   define FIRST 1
1232 #   define NOTFIRST 0
1233 
1234     if ((num = getnum(FIRST)) >= 0)
1235     {
1236         for (;;)
1237         {
1238             Skip_White_Space;
1239 
1240             if (*inptr != '+' && *inptr != '-')
1241                 break;    /* exit infinite loop */
1242 
1243             c = *inptr++;
1244             if ((i = getnum(NOTFIRST)) < 0)
1245                 return ( i );
1246             if (c == '+')
1247                 num += i;
1248             else
1249                 num -= i;
1250         }
1251     }
1252     return num > P_LASTLN ? ERR : num;
1253 
1254 #   undef FIRST
1255 #   undef NOTFIRST
1256 }  /* getone */
1257 
1258 
1259 /*-------------------------------------------------------------------------*/
1260 static
getlst(void)1261 int getlst (void)
1262 
1263 /* Get a range designation of lines and store it in P_LINE1 and P_LINE2
1264  * (defaults are 0). The number of designations (0, 1 or 2) is stored
1265  * in P_NLINES.
1266  * Accepted line designations are:
1267  *   <num>         : defines P_LINE1
1268  *   <num1>,<num2> : defines P_LINE1 and P_LINE2
1269  *   <num1>;<num2> : defines P_LINE1 and P_LINE2, current line is set
1270  *                   to <num2>
1271  * <num> is everything getone() accepts.
1272  *
1273  * Return the number of line designations, or ERR on failure.
1274  */
1275 
1276 {
1277     int    num;
1278 
1279     P_LINE2 = 0;
1280     for(P_NLINES = 0; (num = getone()) >= 0;)
1281     {
1282         P_LINE1 = P_LINE2;
1283         P_LINE2 = num;
1284         P_NLINES++;
1285         if(*inptr != ',' && *inptr != ';')
1286             break;
1287         if(*inptr == ';')
1288             _setCurLn( num );
1289         inptr++;
1290     }
1291 
1292     P_NLINES = min(P_NLINES, 2);
1293 
1294     if (P_NLINES == 0)
1295         P_LINE2 = P_CURLN;
1296     if (P_NLINES <= 1)
1297         P_LINE1 = P_LINE2;
1298 
1299     return (num == ERR) ? num : P_NLINES;
1300 }  /* getlst */
1301 
1302 
1303 /*-------------------------------------------------------------------------*/
1304 static LINE *
getptr(int num)1305 getptr (int num)
1306 
1307 /* Find and return the structure for line <num>.
1308  * Simple partitioning gives the function a complexity of O(N/4).
1309  */
1310 
1311 {
1312     LINE *ptr;
1313     int j, cur;
1314 
1315     cur = P_CURLN;
1316     if (num >= cur)
1317     {
1318         if (2*num - cur > P_LASTLN && num <= P_LASTLN)
1319         {
1320             /* high line numbers */
1321             ptr = P_LINE0.l_prev;
1322             for (j = P_LASTLN - num; --j >= 0; )
1323                 ptr = ptr->l_prev;
1324         }
1325         else
1326         {
1327             ptr = P_CURPTR;
1328             for (j = num - cur; --j >= 0; )
1329                 ptr = ptr->l_next;
1330         }
1331     }
1332     else
1333     {
1334         if (2*num <= cur) {
1335             /* low line numbers */
1336             ptr = &P_LINE0;
1337             for (j = num; --j >= 0; )
1338                 ptr = ptr->l_next;
1339         }
1340         else
1341         {
1342             ptr = P_CURPTR;
1343             for (j = cur - num; --j >= 0; )
1344                 ptr = ptr->l_prev;
1345         }
1346     }
1347 
1348     return ptr;
1349 }
1350 
1351 
1352 /*-------------------------------------------------------------------------*/
1353 static INLINE int /* only used once */
getrhs(char * sub)1354 getrhs (char *sub)
1355 
1356 /* Parse the replacement pattern for a search-and-replace command and
1357  * put it into sub. inptr is expected to point to the leading delimiter
1358  * of the pattern. Special replacement designators are:
1359  *
1360  *   \r, \t, \n, \b: the ASCII characters CR, TAB, NL and BEL
1361  *   \\            : the '\' character itself
1362  *   \0xxx         : the ASCII character with octal code xxx
1363  *   \&, \[0-9]    : the matched (sub)pattern
1364  *
1365  * Return TRUE for a 'g'lobal replacement, and FALSE for a singular one.
1366  * Return ERR on failure.
1367  */
1368 {
1369     char delim = *inptr++;
1370     char *outmax = sub + MAXPAT;
1371 
1372     if( delim == NL || *inptr == NL)    /* check for eol */
1373         return( ERR );
1374 
1375     while (*inptr != delim && *inptr != NL)
1376     {
1377         if ( sub > outmax )
1378             return ERR;
1379         if ( *inptr == ESCAPE )
1380         {
1381             switch ( *++inptr )
1382             {
1383             case 'r':
1384                 *sub++ = '\r';
1385                 inptr++;
1386                 break;
1387 #if 0
1388             case ESCAPE:
1389                 *sub++ = ESCAPE;
1390                 *sub++ = ESCAPE;
1391                 inptr++;
1392 #endif
1393             case 'n':
1394                 *sub++ = '\n';
1395                 inptr++;
1396                 break;
1397             case 'b':
1398                 *sub++ = '\b';
1399                 inptr++;
1400                 break;
1401             case 't':
1402                 *sub++ = '\t';
1403                 inptr++;
1404                 break;
1405             case '0':
1406             {
1407                 int i=3;
1408                 *sub = 0;
1409                 do {
1410                     if (*++inptr<'0' || *inptr >'7')
1411                         break;
1412                     *sub = (char)((*sub<<3) | (*inptr-'0'));
1413                 } while (--i!=0);
1414                 sub++;
1415                 } break;
1416 #if 0
1417             default:
1418                 if ( *inptr != delim )
1419                     *sub++ = ESCAPE;
1420 #else
1421             case '&':
1422             case '1':
1423             case '2':
1424             case '3':
1425             case '4':
1426             case '5':
1427             case '6':
1428             case '7':
1429             case '8':
1430             case '9':
1431             case '\\':
1432                 *sub++ = ESCAPE; /* fall through */
1433             default:
1434 #endif
1435                 *sub++ = *inptr;
1436                 if ( *inptr != NL )
1437                     inptr++;
1438             }
1439         }
1440         else *sub++ = *inptr++;
1441     }
1442     *sub = '\0';
1443 
1444     inptr++;        /* skip over delimter */
1445     Skip_White_Space;
1446     if (*inptr == 'g')
1447     {
1448         inptr++;
1449         return TRUE;
1450     }
1451     return FALSE;
1452 }
1453 
1454 /*-------------------------------------------------------------------------*/
1455 static int
ins(char * str)1456 ins (char *str)
1457 
1458 /* Insert <str> as new line(s) after the current line. <str> can be
1459  * several lines, separated by \n.
1460  * Return TRUE on success, MEM_FAIL if out of memory.
1461  */
1462 
1463 {
1464     char    *cp;
1465     LINE    *new, *nxt;
1466     size_t   len;
1467 
1468     do
1469     {
1470         for ( cp = str; *cp && *cp != NL; cp++ ) NOOP;
1471         len = (size_t)(cp - str);
1472         /* cp now points to end of first or only line */
1473 
1474         if ((new = (LINE *)xalloc(sizeof(LINE)+len)) == NULL)
1475             return( MEM_FAIL );     /* no memory */
1476 
1477         new->l_stat = 0;
1478         strncpy(new->l_buff, str, len);    /* build new line */
1479         new->l_buff[len] = EOS;
1480         nxt = getnextptr(P_CURPTR);    /* get next line */
1481         relink(P_CURPTR, new, new, nxt);    /* add to linked list */
1482         relink(new, nxt, P_CURPTR, new);
1483         P_LASTLN++;
1484         P_CURLN++;
1485         P_CURPTR = new;
1486         str = cp + 1;
1487     }
1488     while( *cp != EOS );
1489 
1490     return TRUE;
1491 }
1492 
1493 
1494 /*-------------------------------------------------------------------------*/
1495 static INLINE int /* only used once */
join(int first,int last)1496 join (int first, int last)
1497 
1498 /* Join the lines <first> to <last> into one line and insert it at the
1499  * current line. The original lines are deleted.
1500  * Return success code.
1501  */
1502 
1503 {
1504     char buf[MAXLINE];
1505     char *cp = buf, *str;
1506     LINE *lin;
1507     int num;
1508 
1509     if (first <= 0 || first > last || last > P_LASTLN)
1510         return ERR;
1511 
1512     if (first==last)
1513     {
1514         _setCurLn(first);
1515         return ED_OK;
1516     }
1517 
1518     lin = getptr(first);
1519     for (num=first; num<=last; num++)
1520     {
1521         str=gettxtl(lin);
1522         while (*str)
1523         {
1524             if (cp >= buf + MAXLINE-1 )
1525             {
1526                 add_message("line too long\n");
1527                 return ERR;
1528             }
1529             *cp++ = *str++;
1530         }
1531         lin = getnextptr(lin);
1532     }
1533     *cp = EOS;
1534 
1535     del(first, last);
1536 
1537     if( ins(buf) < 0 )
1538         return MEM_FAIL;
1539 
1540     P_FCHANGED = TRUE;
1541     return ED_OK;
1542 }
1543 
1544 
1545 /*-------------------------------------------------------------------------*/
1546 static INLINE int /* only used once */
move(int num)1547 move (int num)
1548 
1549 /* Move the block of lines P_LINE1 to P_LINE2 after the line <num> which
1550  * must not be in the moved range.
1551  * Return TRUE on success, else an error code.
1552  */
1553 
1554 {
1555     LINE *dest, *before, *first, *last, *after;
1556 
1557     dest = getptr(num);
1558     if (num < P_LINE1)
1559         num += P_LINE2 - P_LINE1 + 1;
1560     else if (num <= P_LINE2)
1561         return ERR;
1562 
1563     first = getptr(P_LINE1);
1564     before = first->l_prev;
1565     last = getptr(P_LINE2);
1566     after = last->l_next;
1567 
1568     relink(before, after, before, after);
1569 
1570     before = dest;
1571     after = dest->l_next;
1572     relink(before, first, last, after);
1573     relink(last, after, before, first);
1574     P_CURPTR = last;
1575     P_CURLN = num;
1576 
1577     return TRUE;
1578 }
1579 
1580 
1581 /*-------------------------------------------------------------------------*/
1582 static INLINE int /* only used once */
transfer(int num)1583 transfer (int num)
1584 
1585 /* Copy the block of lines P_LINE1 to P_LINE2 after the line <num> which
1586  * may be in the moved range.
1587  * Return TRUE on success, else an error code.
1588  */
1589 
1590 {
1591     int count1, count2;
1592     struct line *ptr;
1593 
1594     /* The caller made sure that (P_LINE1 > 0 && P_LINE1 <= P_LINE2)
1595      * by calling deflt()
1596      */
1597 
1598     if (num >= P_LINE1 && num < P_LINE2)
1599     {
1600         count1 = num - P_LINE1; /* loop has one iteration more */
1601         count2 = P_LINE2 - num;
1602     }
1603     else
1604     {
1605         count1 = P_LINE2 - P_LINE1;
1606         count2 = 0;
1607     }
1608 
1609     _setCurLn( num );
1610     ptr = getptr(P_LINE1);
1611     do
1612     {
1613         if (ins(gettxtl(ptr)) < 0)
1614             return MEM_FAIL;
1615         ptr = getnextptr(ptr);
1616     } while (--count1 >= 0);
1617 
1618     if (count2)
1619     {
1620         ptr = getnextptr(P_CURPTR);
1621         do
1622         {
1623             if (ins(gettxtl(ptr)) < 0)
1624                 return MEM_FAIL;
1625             ptr = getnextptr(ptr);
1626         } while(--count2);
1627     }
1628 
1629     return TRUE;
1630 }
1631 
1632 
1633 /*-------------------------------------------------------------------------*/
1634 static regexp_t *
optpat(void)1635 optpat (void)
1636 
1637 /* Parse a search- or replace-match pattern from the command input, compile
1638  * it and return the compiled regular expression. inptr is expected
1639  * to point to the leading delimiter, and is left pointing to the trailing
1640  * delimiter.
1641  *
1642  * The pattern is also stored as P_OLDPAT, so it can be reused by simply
1643  * entering a leading delimiter only.
1644  *
1645  * Return NULL on failure, else the pointer to the compiled pattern.
1646  */
1647 
1648 {
1649     char delim, str[MAXPAT], *cp;
1650     string_t *buf;
1651 
1652     delim = *inptr;
1653     if (delim == NL)
1654         return P_OLDPAT;
1655 
1656     inptr++;
1657 
1658     cp = str;
1659     while (*inptr != delim
1660         && *inptr != NL
1661         && *inptr != EOS
1662         && cp < str + MAXPAT - 1)
1663     {
1664         if (*inptr == ESCAPE && inptr[1] != NL)
1665             *cp++ = *inptr++;
1666         *cp++ = *inptr++;
1667     }
1668 
1669     *cp = EOS;
1670     if (*str == EOS)
1671         return(P_OLDPAT);
1672     if(P_OLDPAT)
1673         free_regexp(P_OLDPAT);
1674 
1675     memsafe(buf = new_mstring(str), strlen(str), "regexp pattern string");
1676     P_OLDPAT = rx_compile(buf, P_EXCOMPAT ? RE_EXCOMPATIBLE : 0, MY_TRUE);
1677     free_mstring(buf);
1678     return P_OLDPAT;
1679 }
1680 
1681 /*-------------------------------------------------------------------------*/
1682 static INLINE int /* only used once */
set(void)1683 set (void)
1684 
1685 /* Process the 'set' command and return the success code.
1686  * The options are defined in tbl[].
1687  */
1688 
1689 {
1690     char word[16];
1691       /* the longest valid set keyword is 14 characters long. Add one char
1692        * for EOS, and another to get 4-byte-alignmnt
1693        */
1694     int    i;
1695     struct tbl *t;
1696 
1697     if (*(++inptr) != 't')
1698     {
1699         if(*inptr != SP && *inptr != HT && *inptr != NL)
1700             return ERR;
1701     } else
1702         inptr++;
1703 
1704     /* No arguments: print the settings */
1705 
1706     if ( *inptr == NL)
1707     {
1708         add_message("ed version %d.%d\n", ED_VERSION/100, ED_VERSION%100);
1709         for (t = tbl; t->t_str; t+=2)
1710         {
1711             add_message(    "%s:%s ",t->t_str,
1712                 P_FLAGS & t->t_or_mask ?"ON":"OFF");
1713         }
1714         add_message("\nshiftwidth:%d\n",P_SHIFTWIDTH);
1715         return(0);
1716     }
1717 
1718     /* Parse the name of the option to be set */
1719 
1720     Skip_White_Space;
1721     for (i = 0; *inptr != SP && *inptr != HT && *inptr != NL;)
1722     {
1723         /* leave space for EOS too */
1724         if (i == sizeof word - 2) {
1725             add_message("Too long argument to 'set'!\n");
1726             return ED_OK;
1727         }
1728         word[i++] = *inptr++;
1729     }
1730     word[i] = EOS;
1731 
1732     /* Look for the option. If found, set the flag. */
1733 
1734     for(t = tbl; t->t_str; t++) {
1735         if (strcmp(word,t->t_str) == 0) {
1736             P_FLAGS = (P_FLAGS & t->t_and_mask) | t->t_or_mask;
1737             return ED_OK;
1738         }
1739     }
1740 
1741     /* Option not found in table, try the special ones. */
1742 
1743     if ( !strcmp(word,"save") ) {
1744         svalue_t *ret;
1745         push_ref_object(inter_sp, command_giver, "save ed");
1746         push_number(inter_sp, P_SHIFTWIDTH | P_FLAGS );
1747         ret = apply_master(STR_SAVE_ED,2);
1748         if ( ret && ret->type==T_NUMBER && ret->u.number > 0 )
1749             return ED_OK;
1750     }
1751 
1752     if ( !strcmp(word,"shiftwidth") ) {
1753         Skip_White_Space;
1754         if ( isdigit((unsigned char)*inptr) ) {
1755             P_SHIFTWIDTH = *inptr-'0';
1756             return ED_OK;
1757         }
1758     }
1759 
1760     /* Option not recognized */
1761 
1762     return SET_FAIL;
1763 }
1764 
1765 /*-------------------------------------------------------------------------*/
1766 #ifndef relink
1767 static void
relink(LINE * a,LINE * x,LINE * y,LINE * b)1768 relink (LINE *a, LINE *x, LINE *y, LINE *b)
1769 
1770 {
1771     x->l_prev = a;
1772     y->l_next = b;
1773 }
1774 #endif
1775 
1776 
1777 /*-------------------------------------------------------------------------*/
1778 static INLINE void /* only used once */
set_ed_buf(void)1779 set_ed_buf (void)
1780 
1781 /* Initialize the line parameters in the ed buffer.
1782  */
1783 
1784 {
1785     relink(&P_LINE0, &P_LINE0, &P_LINE0, &P_LINE0);
1786     P_CURLN = P_LASTLN = 0;
1787     P_CURPTR = &P_LINE0;
1788 }
1789 
1790 
1791 /*-------------------------------------------------------------------------*/
1792 static INLINE int /* only used once */
subst(regexp_t * pat,char * sub,Bool gflg,Bool pflag)1793 subst (regexp_t *pat, char *sub, Bool gflg, Bool pflag)
1794 
1795 /* Scan the range P_LINE1 to P_LINE2 and replace in every line matching <pat>
1796  * the matched pattern by <sub>. If <gflg> is true, all matching patterns
1797  * in the line are changed. If <pflg> is true, the changed lines are printed.
1798  * Return the number of changed lines, or a failure code.
1799  */
1800 
1801 {
1802     int    nchngd = 0;
1803     char  *new, *old, buf[MAXLINE];
1804     Bool   still_running = TRUE;
1805     LINE  *lastline = getptr( P_LINE2 );
1806 
1807     if(P_LINE1 <= 0)
1808         return SUB_FAIL;
1809 
1810     nchngd = 0;        /* reset count of lines changed */
1811 
1812     for (setCurLn( prevln( P_LINE1 ) ); still_running; )
1813     {
1814         char    *start, *current;
1815         int    space, rc;
1816 
1817         nextCurLn();
1818         new = buf;
1819         if ( P_CURPTR == lastline )
1820             still_running = FALSE;
1821 
1822         current = start = gettxtl(P_CURPTR);
1823         rc = rx_exec_str(pat, current, start);
1824         if (rc < 0)
1825         {
1826             add_message("ed: %s\n", rx_error_message(rc, pat) );
1827             return SUB_FAIL;
1828         }
1829         if ( rc )
1830         {
1831             space = MAXLINE;
1832             do
1833             {
1834                 /* Copy leading text */
1835                 size_t mstart, mend;
1836                 size_t diff;
1837                 string_t * substr;
1838 
1839                 rx_get_match_str(pat, start, &mstart, &mend);
1840                 diff = start + mstart - current;
1841                 if ( (space -= diff) < 0)
1842                     return SUB_FAIL;
1843                 strncpy( new, current, diff );
1844                 new += diff;
1845                 /* Do substitution */
1846                 old = new;
1847                 substr = rx_sub_str( pat, current, sub);
1848                 if (!substr)
1849                     return SUB_FAIL;
1850                 if ((space-= mstrsize(substr)) < 0)
1851                 {
1852                     free_mstring(substr);
1853                     return SUB_FAIL;
1854                 }
1855                 memcpy(new, get_txt(substr), mstrsize(substr));
1856                 new += mstrsize(substr);
1857                 free_mstring(substr);
1858 
1859                 if (current == start + mend)
1860                 {
1861                     /* prevent infinite loop */
1862                     if (!*current)
1863                         break;
1864                     if (--space < 0)
1865                         return SUB_FAIL;
1866                     *new++ = *current++;
1867                 }
1868                 else
1869                     current = start + mend;
1870             } while(gflg
1871                  && !rx_reganch(pat)
1872                  && (rc = rx_exec_str(pat, current, start)) > 0);
1873 
1874             if (rc < 0)
1875             {
1876                 add_message("ed: %s\n", rx_error_message(rc, pat) );
1877                 return SUB_FAIL;
1878             }
1879 
1880             /* Copy trailing chars */
1881             if ( (space -= strlen(current)+1 ) < 0)
1882                 return SUB_FAIL;
1883             strcpy(new, current);
1884             del (P_CURLN,P_CURLN);
1885             if (ins(buf) < 0)
1886                 return MEM_FAIL;
1887             nchngd++;
1888             if (pflag)
1889                 doprnt(P_CURLN, P_CURLN);
1890         } /* if(rc) */
1891     } /* for() */
1892     return (( nchngd == 0 && !gflg ) ? SUB_FAIL : nchngd);
1893 } /* subst() */
1894 
1895 /*-------------------------------------------------------------------------*/
1896 static void
detab_line(char * buf,int tabsize)1897 detab_line (char *buf, int tabsize)
1898 
1899 /* replace all possible '\t'ab characters with whitespace ' '
1900  * in the given string <buf> and replace the current line with
1901  * the result. <tabsize> is the desired tab spacing.
1902  */
1903 
1904 {
1905     int i;                /* i: index of buffer */
1906     int h;                 /* h: index of result */
1907     int space;             /* counter for whitspace */
1908     char result[MAXLINE];  /* the detabbed result */
1909 
1910     for (i = 0, h = 0; buf[i] != '\0'; i++)
1911     {
1912         if (h == MAXLINE )
1913         {
1914             add_message("line too long.\n");
1915             return;
1916         }
1917 
1918         switch (buf[i])
1919         {
1920         case '\t':
1921             /* replace \t by up tu tabsize spaces, depending on position */
1922 
1923             for (space = tabsize - (h % tabsize); space--; h++)
1924             {
1925               if (h == MAXLINE)
1926               {
1927                   add_message("line too long.\n");
1928                   return;
1929               }
1930 
1931               result[h] = ' ';
1932             }
1933 
1934             break;
1935 
1936         default:
1937             result[h] = buf[i];
1938             h++;
1939             break;
1940         }
1941     }
1942 
1943     /* terminate result string */
1944 
1945     result[h] = '\0';
1946 
1947     /* replace current line by result */
1948 
1949     del(P_CURLN,P_CURLN);
1950     ins(result);
1951 } /* detab_line() */
1952 
1953 /*-------------------------------------------------------------------------*/
1954 static void
tab_line(char * buf,int tabsize)1955 tab_line (char *buf, int tabsize)
1956 
1957 /* replace whitespace ' ' with '\t'ab-characters where it makes sense
1958  * in the given string <buf> and replace the current line with the result.
1959  * the result. <tabsize> is the desired tab spacing.
1960  *
1961  * TODO: whitespace to tab replacement makes only sense if the '\t'ab-char
1962  * TODO:: replaces more than one whitespace ' '. Not everyone may share
1963  * TODO:: this opinion, so it maybe this should be optional.
1964  */
1965 
1966 {
1967     int i;                 /* i: index of buffer */
1968     int h;                 /* h: index of result */
1969     int space, pos;        /* whitespace & position counter */
1970     char result[MAXLINE];  /* the tabbed result */
1971 
1972     for (i = 0, h = 0, space = 0, pos = 0; buf[i] != '\0'; i++)
1973     {
1974         switch (buf[i])
1975         {
1976         case ' ':
1977             pos++;
1978             space++;
1979 
1980             if (! (pos % tabsize))
1981             {
1982                 if (space == 1)
1983                 {
1984                     /* makes no sense to replace 1 space by '\t'ab */
1985                     result[h] = ' ';
1986                     h++;
1987                 }
1988                 else
1989                 {
1990                     result[h] = '\t';
1991                     h++;
1992                 }
1993 
1994                 pos = 0;
1995                 space = 0;
1996             }
1997             break;
1998 
1999         case '\t':
2000             if (!space && (pos % tabsize) == tabsize - 1)
2001             {
2002                 /* remove unnecessary tabs */
2003                 result[h] = ' ';
2004                 h++;
2005                 pos++;
2006             }
2007             else
2008             {
2009                 /* don't put unnecessary spaces in result */
2010                 result[h] = '\t';
2011                 h++;
2012                 pos = 0;
2013                 space = 0;
2014             }
2015             break;
2016 
2017         default:
2018             /* add spaces which couldn't be replaced */
2019             for (; space--; h++)
2020             {
2021                 result[h] = ' ';
2022             }
2023 
2024             result[h] = buf[i];
2025             h++;
2026 
2027             pos++;
2028             space = 0;
2029 
2030             break;
2031         }
2032     }
2033 
2034     /* terminate result string */
2035 
2036     result[h] = '\0';
2037 
2038     /* replace current line by result */
2039 
2040     del(P_CURLN,P_CURLN);
2041     ins(result);
2042 } /* tab_line() */
2043 
2044 /*-------------------------------------------------------------------------*/
2045 static int
tab_conversion(int from,int to,int tabsize,Bool do_detab)2046 tab_conversion (int from, int to, int tabsize, Bool do_detab)
2047 
2048 /* Perform tab character conversion on the given range [<from>, <to>].
2049  * <tabsize> is the desired tab spacing, or 0 for the default.
2050  * <do_detab> is TRUE for the Tab->Whitespace conversion, and FALSE
2051  * for the Whitespace->Tab conversion.
2052  */
2053 
2054 {
2055     from = (from < 1) ? 1 : from;
2056     to = (to > P_LASTLN) ? P_LASTLN : to;
2057 
2058     if (tabsize <= 0)
2059     {
2060         tabsize = DEFAULT_TABSIZE;
2061     }
2062 
2063     if (to != 0)
2064     {
2065         _setCurLn( from );
2066         while( P_CURLN <= to )
2067         {
2068             if (do_detab)
2069             {
2070                 detab_line( gettxtl( P_CURPTR ), tabsize );
2071             }
2072 
2073             else
2074             {
2075                 tab_line( gettxtl( P_CURPTR ), tabsize );
2076             }
2077 
2078             if( P_CURLN == to )
2079                 break;
2080 
2081             nextCurLn();
2082         }
2083     }
2084 
2085     return ED_OK;
2086 } /* tab_conversion() */
2087 
2088 /*=========================================================================*/
2089 /*
2090  * Adapted indent code from DGD editor (v0.1).
2091  * No attempt has been made to optimize for this editor.
2092  *  -- Dworkin 920510
2093  */
2094 
2095 # define add_errorf(s)        { add_message(s, lineno); errs++; return; }
2096 
2097 static int lineno, errs;
2098 static int shi;        /* the current shift (negative for left shift) */
2099 static int full_shift, small_shift;
2100 
2101 # define STACKSZ    1024    /* size of indent stack */
2102 
2103 /* token definitions in indent */
2104 # define SEMICOLON   0
2105 # define LBRACKET    1
2106 # define RBRACKET    2
2107 # define LOPERATOR   3
2108 # define ROPERATOR   4
2109 # define LHOOK       5
2110 # define LHOOK2      6
2111 # define RHOOK       7
2112 # define TOKEN       8
2113 # define ELSE        9
2114 # define IF         10
2115 # define FOR        11
2116 # define WHILE      12
2117 # define DO         13
2118 # define XEOT       14
2119 
2120 static char *stack, *stackbot;    /* token stack */
2121 static int *ind, *indbot;    /* indent stack */
2122 static char quote;        /* ' or " */
2123 static Bool in_ppcontrol, in_comment, after_keyword;    /* status */
2124 
2125 /*-------------------------------------------------------------------------*/
2126 static void
shift(char * text)2127 shift (char *text)
2128 
2129 /* Shift a line left or right according to "shi".
2130  */
2131 
2132 {
2133     register int indent_index;
2134 
2135     /* first determine the number of leading spaces */
2136     indent_index = 0;
2137     while (*text == ' ' || *text == '\t')
2138     {
2139         if (*text++ == ' ')
2140         {
2141             indent_index++;
2142         }
2143         else
2144         {
2145             indent_index = (indent_index + 8) & ~7;
2146         }
2147     }
2148 
2149     if (*text != '\0') /* don't shift lines with ws only */
2150     {
2151         indent_index += shi;
2152         if (indent_index < MAXLINE)
2153         {
2154             char buffer[MAXLINE];
2155             register char *p;
2156 
2157             p = buffer;
2158             /* fill with leading ws */
2159             if (P_TABINDENT) while (indent_index >= 8)
2160             {
2161                 *p++ = '\t';
2162                 indent_index -= 8;
2163             }
2164 
2165             while (indent_index > 0)
2166             {
2167                 *p++ = ' ';
2168                 --indent_index;
2169             }
2170 
2171             if (p - buffer + strlen(text) < MAXLINE)
2172             {
2173                 strcpy(p, text);
2174                 del(lineno, lineno);
2175                 ins(buffer);
2176                 return;
2177             }
2178         }
2179 
2180         add_errorf("Result of shift would be too long, line %d\n");
2181     }
2182 }
2183 
2184 /*-------------------------------------------------------------------------*/
2185 static void
indent(char * buf)2186 indent (char *buf)
2187 
2188 /* Parse and indent a line of text. This isn't perfect, as
2189  * keywords could be defined as macros, comments are very hard to
2190  * handle properly, (, [ and ({ will match any of ), ] and }),
2191  * and last but not least everyone has his own taste of
2192  * indentation.
2193  */
2194 
2195 {
2196 /*                      ;  {  }  (  )  [  ([ ]  tok el if fo whi do xe   */
2197 /*                      (  ({ )  en  se    r  le     ot   */
2198     static char f[] = { 7, 1, 7, 1, 2, 1, 1, 6, 4,  2, 6, 7, 7,  2, 0, };
2199     static char g[] = { 2, 2, 1, 7, 1, 5, 5, 1, 3,  6, 2, 2, 2,  2, 0, };
2200     char text[MAXLINE], ident[MAXLINE];
2201     register char *p, *sp;
2202     register int *ip;
2203     register long indent_index;
2204     register int top, token;
2205     char *start;
2206     Bool do_indent;
2207 
2208     /*
2209      * Problem: in this editor memory for deleted lines is reclaimed. So
2210      * we cannot shift the line and then continue processing it, as in
2211      * DGD ed. Instead make a copy of the line, and process the copy.
2212      */
2213     strcpy(text, buf);
2214 
2215     do_indent = FALSE;
2216     indent_index = 0;
2217     p = text;
2218 
2219     /* process status vars */
2220     if (quote != '\0')
2221     {
2222         shi = 0;    /* in case a comment starts on this line */
2223     }
2224     else if (in_ppcontrol || (*p == '#' && p[1] != '\'') )
2225     {
2226         while (*p != '\0')
2227         {
2228             if (*p == '\\' && *++p == '\0')
2229             {
2230                 in_ppcontrol = TRUE;
2231                 return;
2232             }
2233             p++;
2234         }
2235         in_ppcontrol = FALSE;
2236         return;
2237     }
2238     else
2239     {
2240         /* count leading ws */
2241         while (*p == ' ' || *p == '\t')
2242         {
2243             if (*p++ == ' ')
2244             {
2245                 indent_index++;
2246             }
2247             else
2248             {
2249                 indent_index = (indent_index + 8) & ~7;
2250             }
2251         }
2252         if (*p == '\0')
2253         {
2254             del(lineno, lineno);
2255             ins(p);
2256             return;
2257         }
2258         else if (in_comment)
2259         {
2260             shift(text);    /* use previous shi */
2261         }
2262         else
2263         {
2264             do_indent = TRUE;
2265         }
2266     }
2267 
2268     /* process this line */
2269     start = p;
2270     while (*p != '\0') {
2271 
2272         /* lexical scanning: find the next token */
2273         ident[0] = '\0';
2274         if (in_comment)
2275         {
2276             /* comment */
2277             while (*p != '*')
2278             {
2279                 if (*p == '\0')
2280                 {
2281                     return;
2282                 }
2283                 p++;
2284             }
2285             while (*p == '*')
2286             {
2287                 p++;
2288             }
2289             if (*p == '/')
2290             {
2291                 in_comment = FALSE;
2292                 p++;
2293             }
2294             continue;
2295 
2296         }
2297         else if (quote != '\0')
2298         {
2299             /* string or character constant */
2300             for (;;)
2301             {
2302                 if (*p == quote)
2303                 {
2304                     quote = '\0';
2305                     p++;
2306                     break;
2307                 }
2308                 else if (*p == '\0')
2309                 {
2310                     add_errorf("Unterminated string in line %d\n");
2311                 }
2312                 else if (*p == '\\' && *++p == '\0')
2313                 {
2314                     break;
2315                 }
2316                 p++;
2317             }
2318             token = TOKEN;
2319         }
2320         else
2321         {
2322             switch (*p++)
2323             {
2324             case ' ':    /* white space */
2325             case '\t':
2326                 continue;
2327 
2328             case '\'':
2329                 if (isalunum(*p) && p[1] && p[1] != '\'')
2330                 {
2331                     do ++p; while (isalunum(*p));
2332                     token = TOKEN;
2333                     break;
2334                 }
2335                 if (*p == '(' && p[1] == '{')
2336                 {
2337                     /* treat quoted array like an array */
2338                     token = TOKEN;
2339                     break;
2340                 }
2341                 /* FALLTHROUGH */
2342             case '"':    /* start of string */
2343                 quote = p[-1];
2344                 continue;
2345 
2346             case '/':
2347                 if (*p == '*')     /* start of comment */
2348                 {
2349                     in_comment = TRUE;
2350                     if (do_indent)
2351                     {
2352                         /* this line hasn't been indented yet */
2353                         shi = *ind - indent_index;
2354                         shift(text);
2355                         do_indent = FALSE;
2356                     }
2357                     else
2358                     {
2359                         register char *q;
2360                         register int index2;
2361 
2362                         /* find how much the comment has shifted, so the same
2363                          * shift can be used if the coment continues on the
2364                          * next line
2365                          */
2366                         index2 = *ind;
2367                         for (q = start; q < p - 1;)
2368                         {
2369                             if (*q++ == '\t')
2370                             {
2371                                 indent_index = (indent_index + 8) & ~7;
2372                                 index2 = (index2 + 8) & ~7;
2373                             }
2374                             else
2375                             {
2376                                 indent_index++;
2377                                 index2++;
2378                             }
2379                         }
2380                         shi = index2 - indent_index;
2381                     }
2382                     p++;
2383                     continue;
2384                 }
2385                 if (*p == '/')     /* start of C++ style comment */
2386                 {
2387                     p = strchr(p, '\0');
2388                 }
2389                 token = TOKEN;
2390                 break;
2391 
2392             case '{':
2393                 token = LBRACKET;
2394                 break;
2395 
2396             case '(':
2397                 if (after_keyword)
2398                 {
2399                     /* LOPERATOR & ROPERATOR are a kludge. The operator
2400                      * precedence parser that is used could not work if
2401                      * parenthesis after keywords was not treated specially.
2402                      */
2403                     token = LOPERATOR;
2404                     break;
2405                 }
2406                 if (*p == '{' || *p == '[')
2407                 {
2408                     p++;    /* ({ , ([ each are one token */
2409                     token = LHOOK2;
2410                     break;
2411                 }
2412                 /* FALLTHROUGH */
2413             case '[':
2414                 token = LHOOK;
2415                 break;
2416 
2417             case '}':
2418                 if (*p != ')')
2419                 {
2420                     token = RBRACKET;
2421                     break;
2422                 }
2423                 /* }) is one token */
2424                 p++;
2425                 token = RHOOK;
2426                 break;
2427 
2428             case ']':
2429                 if (*p == ')'
2430                  && (*stack == LHOOK2 || (*stack != XEOT
2431                  && ( stack[1] == LHOOK2
2432                      || ( stack[1] == ROPERATOR && stack[2] == LHOOK2) ) ) ) )
2433                 {
2434                     p++;
2435                 }
2436                 /* FALLTHROUGH */
2437             case ')':
2438                 token = RHOOK;
2439                 break;
2440 
2441             case ';':
2442                 token = SEMICOLON;
2443                 break;
2444 
2445             case '#':
2446                 if (*p == '\'')
2447                 {
2448                     ++p;
2449                     if (isalunum(*p))
2450                     {
2451                         do ++p; while (isalunum(*p));
2452                     }
2453                     else
2454                     {
2455                         const char *end;
2456 
2457                         if (symbol_operator(p, &end) < 0)
2458                         {
2459                             add_errorf("Missing function name after #' in line %d\n");
2460                         }
2461                         p = (char *)end;
2462                     }
2463                     token = TOKEN;
2464                     break;
2465                 }
2466                 /* FALLTHROUGH */
2467 
2468             default:
2469                 if (isalpha((unsigned char)*--p) || *p == '_')
2470                 {
2471                     register char *q;
2472 
2473                     /* Identifier. See if it's a keyword. */
2474                     q = ident;
2475                     do
2476                     {
2477                         *q++ = *p++;
2478                     } while (isalnum((unsigned char)*p) || *p == '_');
2479                     *q = '\0';
2480 
2481                     if      (strcmp(ident, "if"   ) == 0)    token = IF;
2482                     else if (strcmp(ident, "else" ) == 0)    token = ELSE;
2483                     else if (strcmp(ident, "for"  ) == 0)    token = FOR;
2484                     else if (strcmp(ident, "while") == 0)    token = WHILE;
2485                     else if (strcmp(ident, "do"   ) == 0)    token = DO;
2486                     else    /* not a keyword */              token = TOKEN;
2487                 }
2488                 else
2489                 {
2490                     /* anything else is a "token" */
2491                     p++;
2492                     token = TOKEN;
2493                 }
2494                 break;
2495             }
2496         }
2497 
2498         /* parse */
2499 
2500         sp = stack;
2501         ip = ind;
2502         for (;;)
2503         {
2504             top = *sp;
2505             if (top == LOPERATOR && token == RHOOK)
2506             {
2507                 /* ) after LOPERATOR is ROPERATOR */
2508                 token = ROPERATOR;
2509             }
2510 
2511             if (f[top] <= g[token])     /* shift the token on the stack */
2512             {
2513                 register int i;
2514 
2515                 if (sp == stackbot)
2516                 {
2517                     /* out of stack */
2518                     add_errorf("Nesting too deep in line %d\n");
2519                 }
2520 
2521                 /* handle indentation */
2522                 i = *ip;
2523                 /* if needed, reduce indentation prior to shift */
2524                 if ((token == LBRACKET
2525                      && (*sp == ROPERATOR || *sp == ELSE || *sp == DO))
2526                  || token == RBRACKET || (token == IF && *sp == ELSE))
2527                 {
2528                     /* back up */
2529                     i -= full_shift;
2530                 }
2531                 else if (token == RHOOK || token == ROPERATOR)
2532                 {
2533                     i -= small_shift;
2534                 }
2535                 /* shift the current line, if appropriate */
2536                 if (do_indent)
2537                 {
2538                     shi = i - indent_index;
2539                     if (token == TOKEN && *sp == LBRACKET
2540                      && (   strcmp(ident, "case") == 0
2541                          || strcmp(ident, "default") == 0))
2542                     {
2543                         /* back up if this is a switch label */
2544                         shi -= full_shift;
2545                     }
2546                     shift(text);
2547                     do_indent = FALSE;
2548                 }
2549 
2550                 /* change indentation after current token */
2551                 switch (token)
2552                 {
2553                 case LBRACKET: case ROPERATOR: case ELSE: case DO:
2554                     /* add indentation */
2555                     i += full_shift;
2556                     break;
2557 
2558                 case LOPERATOR: case LHOOK: case LHOOK2:
2559                     /* half indent after ( [ ({ ([ */
2560                     i += small_shift;
2561                     break;
2562 
2563                 case SEMICOLON:
2564                     /* in case it is followed by a comment */
2565                     if (*sp == ROPERATOR || *sp == ELSE)
2566                     {
2567                         i -= full_shift;
2568                     }
2569                     break;
2570                 }
2571 
2572                 *--sp = (char)token;
2573                 *--ip = i;
2574                 break;
2575             }
2576 
2577             /* reduce handle */
2578             do
2579             {
2580                 top = *sp++;
2581                 ip++;
2582             } while (f[(int)*sp] >= g[top]);
2583         }
2584         stack = sp;
2585         ind = ip;
2586         after_keyword = (token >= IF);    /* but not after ELSE */
2587     }
2588 }
2589 
2590 /*-------------------------------------------------------------------------*/
2591 static int
indent_code(int from,int to)2592 indent_code (int from, int to)
2593 
2594 /* Indent the code in the range <from> to <to>.
2595  * Return success code.
2596  */
2597 
2598 {
2599     char s[STACKSZ];
2600     int i[STACKSZ];
2601 
2602     /* setup stacks */
2603     stackbot = s;
2604     indbot = i;
2605     stack = stackbot + STACKSZ - 1;
2606     *stack = XEOT;
2607     ind = indbot + STACKSZ - 1;
2608     *ind = 0;
2609 
2610     quote = '\0';
2611     in_ppcontrol = FALSE;
2612     in_comment = FALSE;
2613 
2614     P_FCHANGED = TRUE;
2615     errs = 0;
2616     full_shift = P_SHIFTWIDTH;
2617     small_shift = full_shift / 2;
2618 
2619     for (lineno = from; lineno <= to; lineno++)
2620     {
2621         _setCurLn(lineno);
2622         indent(gettxtl(P_CURPTR));
2623         if (errs != 0)
2624         {
2625             return ERR;
2626         }
2627     }
2628 
2629     return ED_OK;
2630 }
2631 
2632 # undef error
2633 
2634 /* End of indent code */
2635 /*=========================================================================*/
2636 
2637 /*-------------------------------------------------------------------------*/
2638 static int
docmd(Bool glob)2639 docmd (Bool glob)
2640 
2641 /* Read the command letter from the input buffer and execute the command.
2642  * All other command parameters are expected to be set up by now.
2643  * If <glob> is true and the line designation is a pattern, the command
2644  * is applied to all lines, else only to the first.
2645  * Return success code, with TRUE meaning 'just changed current line'.
2646  */
2647 
2648 {
2649     static char  rhs[MAXPAT];
2650     regexp_t    *subpat;
2651     int          c, err, line3, lastcmd;
2652     int          apflg, pflag, gflag;
2653     int          nchng;
2654     string_t    *fptr;
2655 
2656     pflag = FALSE;
2657     Skip_White_Space;
2658 
2659     lastcmd = P_LASTCMD;
2660 
2661     P_LASTCMD = c = *inptr++;
2662     switch(c)
2663     {
2664     case NL:
2665         if (P_NLINES == 0 && (P_LINE2 = nextln(P_CURLN)) == 0 )
2666             return ERR;
2667         setCurLn( P_LINE2 );
2668         return TRUE;
2669 
2670     case '=':
2671         add_message("%d\n",P_LINE2);
2672         break;
2673 
2674     case 'a':
2675     case 'A':
2676         P_CUR_AUTOIND = c=='a' ? P_AUTOINDFLG : !P_AUTOINDFLG;
2677         if (*inptr != NL || P_NLINES > 1)
2678             return ERR;
2679 
2680         if ( P_CUR_AUTOIND ) count_blanks(P_LINE1);
2681         if (append(P_LINE1, glob) < 0)
2682             return ERR;
2683         P_FCHANGED = TRUE;
2684         break;
2685 
2686     case 'c':
2687         if (*inptr != NL)
2688             return ERR;
2689 
2690         if (deflt(P_CURLN, P_CURLN) < 0)
2691             return ERR;
2692 
2693         P_CUR_AUTOIND = P_AUTOINDFLG;
2694         if (P_AUTOINDFLG ) count_blanks(P_LINE1);
2695         if (del(P_LINE1, P_LINE2) < 0)
2696             return ERR;
2697         if (append(P_CURLN, glob) < 0)
2698             return ERR;
2699         P_FCHANGED = TRUE;
2700         break;
2701 
2702     case 'd':
2703         if (*inptr != NL)
2704             return ERR;
2705 
2706         if (deflt(P_CURLN, P_CURLN) < 0)
2707             return ERR;
2708 
2709         if (del(P_LINE1, P_LINE2) < 0)
2710             return ERR;
2711         if (nextln(P_CURLN) != 0)
2712             nextCurLn();
2713         if (P_PFLG)
2714             doprnt(P_CURLN, P_CURLN);
2715         P_FCHANGED = TRUE;
2716         break;
2717 
2718     case 'e':
2719         if (P_NLINES > 0)
2720             return ERR;
2721         if (P_FCHANGED)
2722             return CHANGED;
2723         /*FALL THROUGH*/
2724     case 'E':
2725         if (P_NLINES > 0)
2726             return ERR;
2727 
2728         if (*inptr != ' ' && *inptr != HT && *inptr != NL)
2729             return ERR;
2730 
2731         if ((fptr = getfn(MY_FALSE)) == NULL)
2732             return ERR;
2733 
2734         clrbuf();
2735         (void)doread(0, fptr);
2736 
2737         P_FNAME = fptr;
2738         P_FCHANGED = FALSE;
2739         break;
2740 
2741     case 'f':
2742         if (P_NLINES > 0)
2743             return ERR;
2744 
2745         if (*inptr != ' ' && *inptr != HT && *inptr != NL)
2746             return ERR;
2747 
2748         fptr = getfn(MY_FALSE);
2749 
2750         if (P_NOFNAME)
2751         {
2752             if (P_FNAME)
2753                 add_message("%s\n", get_txt(P_FNAME));
2754             else
2755                 add_message("<no file>\n");
2756         }
2757         else
2758         {
2759             if (fptr == NULL)
2760                 return ERR;
2761             P_FNAME = fptr;
2762         }
2763         break;
2764 
2765     case 'i':
2766         if (*inptr != NL || P_NLINES > 1)
2767             return ERR;
2768 
2769         P_CUR_AUTOIND = P_AUTOINDFLG;
2770         if (P_AUTOINDFLG ) count_blanks(P_LINE1);
2771         if (append(prevln(P_LINE1), glob) < 0)
2772             return ERR;
2773         P_FCHANGED = TRUE;
2774         break;
2775 
2776     case 'j':
2777         if (*inptr != NL || deflt(P_CURLN, P_CURLN+1)<0)
2778             return(ERR);
2779 
2780         if (join(P_LINE1, P_LINE2) < 0)
2781             return ERR;
2782         break;
2783 
2784     case 'k':
2785         Skip_White_Space;
2786 
2787         if (*inptr < 'a' || *inptr > 'z')
2788             return ERR;
2789         c= *inptr++;
2790 
2791         if (*inptr != ' ' && *inptr != HT && *inptr != NL)
2792             return ERR;
2793 
2794         P_MARK[c-'a'] = P_LINE1;
2795         break;
2796 
2797     case 'l':
2798         if (*inptr != NL)
2799             return ERR;
2800         if (deflt(P_CURLN,P_CURLN) < 0)
2801             return ERR;
2802         if (dolst(P_LINE1,P_LINE2) < 0)
2803             return ERR;
2804         break;
2805 
2806     case 'm':
2807         if ((line3 = getone()) < 0)
2808             return ERR;
2809         if (deflt(P_CURLN,P_CURLN) < 0)
2810             return ERR;
2811         if (move(line3) < 0)
2812             return ERR;
2813         P_FCHANGED = TRUE;
2814         break;
2815 
2816     case 'M':
2817       {
2818         regexp_t * pat;
2819 
2820         if (deflt(1, P_LASTLN) < 0)
2821             return ERR;
2822         if (*inptr != NL)
2823             return ERR;
2824 
2825 
2826         pat = rx_compile(STR_CRPATTERN, P_EXCOMPAT ? RE_EXCOMPATIBLE : 0, MY_TRUE);
2827         nchng = subst(pat, "", 0, 0);
2828         free_regexp(pat);
2829 
2830         if (nchng < 0)
2831             return ERR;
2832         P_FCHANGED = TRUE;
2833         break;
2834       }
2835 
2836     case 'n':
2837         if (P_NFLG)
2838             P_FLAGS &= ~( NFLG_MASK | LFLG_MASK );
2839         else
2840             P_FLAGS |=  ( NFLG_MASK | LFLG_MASK );
2841         P_DIAG=!P_DIAG;
2842         add_message("number %s, list %s\n"
2843                    , P_NFLG?"ON":"OFF", P_LFLG?"ON":"OFF");
2844         break;
2845 
2846     case 'I':
2847         if (deflt(1, P_LASTLN) < 0)
2848             return ERR ;
2849         if (*inptr != NL)
2850             return ERR;
2851         if (!P_NLINES)
2852             add_message("Indenting entire code...\n");
2853         if (indent_code(P_LINE1, P_LINE2))
2854             add_message("Indention halted.\n");
2855         else
2856             add_message("Done indenting.\n");
2857         break;
2858 
2859     case 'H':
2860     case 'h':
2861         print_help(*(inptr++));
2862         break;
2863 
2864     case 'P':
2865     case 'p':
2866         if (*inptr != NL)
2867             return ERR;
2868         if (deflt(P_CURLN,P_CURLN) < 0)
2869             return ERR;
2870         if (doprnt(P_LINE1,P_LINE2) < 0)
2871             return ERR;
2872         break;
2873 
2874     case 'q':
2875         if (P_FCHANGED)
2876             return CHANGED;
2877         /*FALL THROUGH*/
2878     case 'Q':
2879         if (*inptr != NL || glob)
2880             return ERR;
2881         clrbuf();
2882         if (*inptr == NL && P_NLINES == 0 && !glob)
2883             return EOF;
2884         else /* Just in case clrbuf() fails */
2885             return ERR;
2886 
2887     case 'r':
2888         if (P_NLINES > 1)
2889             return ERR;
2890 
2891         if (P_NLINES == 0)
2892             P_LINE2 = P_LASTLN;
2893 
2894         if (*inptr != ' ' && *inptr != HT && *inptr != NL)
2895             return ERR;
2896 
2897         if ((fptr = getfn(MY_FALSE)) == NULL)
2898             return ERR;
2899 
2900         if ((err = doread(P_LINE2, fptr)) < 0)
2901         {
2902             free_mstring(fptr);
2903             return err;
2904         }
2905         free_mstring(fptr);
2906         P_FCHANGED = TRUE;
2907         break;
2908 
2909     case 's':
2910         if(*inptr == 'e')
2911             return set();
2912         Skip_White_Space;
2913         if((subpat = optpat()) == NULL)
2914             return ERR;
2915         if((gflag = getrhs(rhs)) < 0)
2916             return ERR;
2917         if(*inptr == 'p')
2918             pflag++;
2919         if(deflt(P_CURLN, P_CURLN) < 0)
2920             return ERR;
2921         if((nchng = subst(subpat, rhs, gflag, pflag)) < 0)
2922             return ERR;
2923         if(nchng)
2924             P_FCHANGED = TRUE;
2925         if (nchng == 1 && P_PFLG )
2926         {
2927             if(doprnt(P_CURLN, P_CURLN) < 0)
2928                 return ERR;
2929         }
2930         break;
2931 
2932     case 't':
2933         if ((line3 = getone()) < 0)
2934             return ERR;
2935         if (deflt(P_CURLN,P_CURLN) < 0)
2936             return ERR;
2937         if (transfer(line3) < 0)
2938             return ERR;
2939         P_FCHANGED = TRUE;
2940         break;
2941 
2942     case 'T':
2943       {
2944         int tabsize;
2945         Bool do_detab;
2946 
2947         switch(*inptr)
2948         {
2949           case '+':
2950               do_detab = MY_FALSE;
2951               break;
2952 
2953           case '-':
2954               do_detab = MY_TRUE;
2955               break;
2956 
2957           default:
2958               return ERR;
2959         }
2960 
2961         inptr++;
2962         tabsize = atoi(inptr);
2963 
2964         if (deflt(P_CURLN,P_CURLN) < 0)
2965             return ERR;
2966 
2967         if (tab_conversion(P_LINE1, P_LINE2, tabsize, do_detab) < 0)
2968             return ERR;
2969 
2970         break;
2971       }
2972 
2973     case 'W':
2974     case 'w':
2975         apflg = (c=='W');
2976 
2977         if (*inptr != ' ' && *inptr != HT && *inptr != NL)
2978             return ERR;
2979 
2980         if ((fptr = getfn(MY_TRUE)) == NULL)
2981             return ERR;
2982 
2983         if (deflt(1, P_LASTLN) < 0)
2984         {
2985             free_mstring(fptr);
2986             return ERR;
2987         }
2988         if (dowrite(P_LINE1, P_LINE2, fptr, apflg) < 0)
2989         {
2990             free_mstring(fptr);
2991             return ERR;
2992         }
2993         free_mstring(fptr);
2994         P_FCHANGED = FALSE;
2995         break;
2996 
2997     case 'x':
2998         if (*inptr == NL && P_NLINES == 0 && !glob)
2999         {
3000             if ((fptr = getfn(MY_TRUE)) == NULL)
3001                 return ERR;
3002             if (dowrite(1, P_LASTLN, fptr, 0) >= 0
3003              && command_giver && command_giver->flags & O_SHADOW)
3004             {
3005                 free_mstring(fptr);
3006                 return EOF;
3007             }
3008             free_mstring(fptr);
3009         }
3010         return ERR;
3011 
3012     case 'z':
3013       {
3014         int dfln;
3015 
3016         switch(*inptr)
3017         {
3018         case '-':
3019             dfln = P_CURLN;
3020             if (deflt(dfln,dfln) < 0)
3021                 return ERR;
3022             if (doprnt(P_LINE1-21,P_LINE1) < 0)
3023                 return ERR;
3024             break;
3025 
3026         case '.':
3027             dfln = P_CURLN;
3028             if (deflt(dfln,dfln) < 0)
3029                 return ERR;
3030             if (doprnt(P_LINE1-11,P_LINE1+10) < 0)
3031                 return ERR;
3032             break;
3033 
3034         case '+':
3035         case '\n':
3036             if (lastcmd == 'z' || lastcmd == 'Z')
3037                 dfln = P_CURLN != 1 ? P_CURLN + 1 : 1;
3038             else
3039                 dfln = P_CURLN;
3040             if (deflt(dfln,dfln) < 0)
3041                 return ERR;
3042             if (doprnt(P_LINE1,P_LINE1+21) < 0)
3043                 return ERR;
3044             break;
3045         }
3046         break;
3047       }
3048 
3049     case 'Z':
3050       {
3051         int dfln;
3052 
3053         switch(*inptr)
3054         {
3055         case '-':
3056             dfln = P_CURLN;
3057             if (deflt(dfln,dfln) < 0)
3058                 return ERR;
3059             if (doprnt(P_LINE1-41,P_LINE1) < 0)
3060                 return ERR;
3061             break;
3062 
3063         case '.':
3064             dfln = P_CURLN;
3065             if (deflt(dfln,dfln) < 0)
3066                 return ERR;
3067             if (doprnt(P_LINE1-21,P_LINE1+20) < 0)
3068                 return ERR;
3069             break;
3070 
3071         case '+':
3072         case '\n':
3073             if (lastcmd == 'z' || lastcmd == 'Z')
3074                 dfln = P_CURLN != 1 ? P_CURLN + 1 : 1;
3075             else
3076                 dfln = P_CURLN;
3077             if (deflt(dfln,dfln) < 0)
3078                 return ERR;
3079             if (doprnt(P_LINE1,P_LINE1+41) < 0)
3080                 return ERR;
3081             break;
3082         }
3083         break;
3084       }
3085 
3086     default:
3087         return ERR;
3088     }
3089 
3090     return ED_OK;
3091 }  /* docmd */
3092 
3093 
3094 /*-------------------------------------------------------------------------*/
3095 static INLINE int /* only used once */
doglob(void)3096 doglob (void)
3097 
3098 /* Call docmd(TRUE) for every line marked with LGLOB, clearing that mark
3099  * in this.
3100  * Return the number of the last current line, or an error code on failure.
3101  */
3102 
3103 {
3104     int    lin, status;
3105     char    *cmd;
3106     LINE    *ptr;
3107 
3108     cmd = inptr;
3109 
3110     for (;;)
3111     {
3112         ptr = getptr(1);
3113         for (lin=1; lin<=P_LASTLN; lin++)
3114         {
3115             if (ptr->l_stat & LGLOB)
3116                 break;
3117             ptr = getnextptr(ptr);
3118         }
3119         if (lin > P_LASTLN)
3120             break;
3121 
3122         ptr->l_stat &= ~LGLOB;
3123         P_CURLN = lin; P_CURPTR = ptr;
3124         inptr = cmd;
3125         if ((status = getlst()) < 0)
3126             return status;
3127         if ((status = docmd(TRUE)) < 0)
3128             return status;
3129     }
3130     return P_CURLN;
3131 }  /* doglob */
3132 
3133 
3134 /*-------------------------------------------------------------------------*/
3135 static void
ed_start(string_t * file_arg,string_t * exit_fn,object_t * exit_ob)3136 ed_start (string_t *file_arg, string_t *exit_fn, object_t *exit_ob)
3137 
3138 /* Start the editor on file <file_arg>. Because several players can edit
3139  * simultaneously, they will each need a separate editor data block.
3140  *
3141  * If an <exit_fn> and <exit_ob> is given, then call <exit_ob>-><exit_fn>()
3142  * at exit of editor. The purpose is to make it possible for external LPC
3143  * code to maintain a list of locked files.
3144  */
3145 
3146 {
3147     string_t *new_path;
3148     svalue_t *setup;
3149     ed_buffer_t *old_ed_buffer;
3150     interactive_t *ip;
3151 
3152     if (!command_giver || !(O_SET_INTERACTIVE(ip, command_giver)))
3153         errorf("Tried to start an ed session on a non-interative player.\n");
3154 
3155     /* Check for read on startup, since the buffer is read in. But don't
3156      * check for write, since we may want to change the file name.
3157      */
3158     new_path = check_valid_path(file_arg, command_giver, STR_ED_START, MY_FALSE);
3159     if (!file_arg && !new_path)
3160         return;
3161 
3162     /* Never trust the master... it might be as paranoid as ourselves...
3163      */
3164     if (!command_giver
3165      || !(command_giver->flags & O_SHADOW)
3166      || command_giver->flags & O_DESTRUCTED
3167        )
3168     {
3169         return;
3170     }
3171 
3172     old_ed_buffer = ED_BUFFER;
3173     ED_BUFFER = (ed_buffer_t *)xalloc(sizeof (ed_buffer_t));
3174 
3175     memset(ED_BUFFER, '\0', sizeof (ed_buffer_t));
3176 
3177     ED_BUFFER->input.type = INPUT_ED;
3178 
3179     ED_BUFFER->truncflg = MY_TRUE;
3180     ED_BUFFER->flags |= EIGHTBIT_MASK | TABINDENT_MASK;
3181     ED_BUFFER->shiftwidth= 4;
3182     put_ref_string(&(ED_BUFFER->input.prompt), STR_ED_PROMPT);
3183     ED_BUFFER->CurPtr = &ED_BUFFER->Line0;
3184 
3185     add_input_handler(ip, &(ED_BUFFER->input), MY_FALSE);
3186 
3187     if (exit_fn)
3188     {
3189         ED_BUFFER->exit_fn = ref_mstring(exit_fn);
3190         ref_object(exit_ob, "ed_start");
3191     }
3192     else
3193     {
3194         ED_BUFFER->exit_fn = NULL;
3195     }
3196     ED_BUFFER->exit_ob = exit_ob;
3197 
3198     set_ed_buf();
3199     push_apply_value();
3200     push_ref_object(inter_sp, command_giver, "retr ed");
3201 
3202     setup = apply_master(STR_RETR_ED,1);
3203     if ( setup && setup->type==T_NUMBER && setup->u.number )
3204     {
3205         ED_BUFFER->flags      = setup->u.number & ALL_FLAGS_MASK;
3206         ED_BUFFER->shiftwidth = setup->u.number & SHIFTWIDTH_MASK;
3207     }
3208 
3209     /* It is possible to toggle P_DIAG in retrieve_ed_setup(), by issueing
3210      * an 'n' command(), which will cause add_message() to be called in
3211      * do_read(); add_message might in turn call apply() via
3212      * shadow_catch_message(), thus new_path needs to stay pushed.
3213      */
3214 
3215     if (new_path && !doread(0, new_path))
3216     {
3217         _setCurLn( 1 );
3218     }
3219 
3220     if (new_path)
3221     {
3222         P_FNAME = new_path;
3223         add_message("/%s, %d lines\n", get_txt(new_path), P_LASTLN);
3224     }
3225     else
3226     {
3227         add_message("No file.\n");
3228     }
3229     pop_apply_value();
3230     ED_BUFFER = old_ed_buffer;
3231 }
3232 
3233 
3234 #ifdef GC_SUPPORT
3235 
3236 /*-------------------------------------------------------------------------*/
3237 void
clear_ed_buffer_refs(input_t * ih)3238 clear_ed_buffer_refs (input_t *ih)
3239 
3240 /* GC Support: Clear all references from ed_buffer <b>.
3241  */
3242 
3243 {
3244     ed_buffer_t *b = (ed_buffer_t*) ih;
3245     object_t *ob;
3246 
3247     if (b->fname)
3248         clear_string_ref(b->fname);
3249 
3250     if (b->exit_fn)
3251     {
3252         clear_string_ref(b->exit_fn);
3253         if ( NULL != (ob = b->exit_ob) )
3254         {
3255             clear_object_ref(ob);
3256         }
3257     }
3258 
3259     /* For the RE cache */
3260     clear_regexp_ref(b->oldpat);
3261 
3262     clear_ref_in_vector(&b->input.prompt, 1);
3263 }
3264 
3265 /*-------------------------------------------------------------------------*/
3266 void
count_ed_buffer_refs(input_t * ih)3267 count_ed_buffer_refs (input_t *ih)
3268 
3269 /* GC Support: Count all references from ed_buffer <b>.
3270  */
3271 
3272 {
3273     ed_buffer_t *b = (ed_buffer_t*) ih;
3274     object_t *ob;
3275     LINE *line;
3276 
3277     if (b->LastLn)
3278     {
3279         line = b->Line0.l_next;
3280         while(line != &b->Line0)
3281         {
3282             note_malloced_block_ref((char *)line);
3283             line = line->l_next;
3284         }
3285     }
3286 
3287     if (b->fname)
3288         count_ref_from_string(b->fname);
3289 
3290     if (b->exit_fn)
3291     {
3292         count_ref_from_string(b->exit_fn);
3293         if ( NULL != (ob = b->exit_ob) )
3294         {
3295             if (ob->flags & O_DESTRUCTED)
3296             {
3297                 reference_destructed_object(ob);
3298                 b->exit_ob = NULL;
3299             }
3300             else
3301             {
3302                 ob->ref++;
3303             }
3304         }
3305     }
3306 
3307     if (b->oldpat)
3308         count_regexp_ref(b->oldpat);
3309     count_ref_in_vector(&b->input.prompt, 1);
3310 }
3311 
3312 #endif /* GC_SUPPORT */
3313 
3314 #ifdef DEBUG
3315 /*-------------------------------------------------------------------------*/
3316 void
count_ed_buffer_extra_refs(input_t * ih)3317 count_ed_buffer_extra_refs (input_t *ih)
3318 
3319 /* Count refs in ed_buffer <b> to debug refcounts.
3320  */
3321 
3322 {
3323     ed_buffer_t *b = (ed_buffer_t*) ih;
3324     object_t *ob;
3325 
3326     if ( NULL != (ob = b->exit_ob) )
3327         ob->extra_ref++;
3328 }
3329 
3330 #endif /* DEBUG */
3331 
3332 /*-------------------------------------------------------------------------*/
3333 void
free_ed_buffer(input_t * ih)3334 free_ed_buffer (input_t *ih)
3335 
3336 /* Deallocate the ed_buffer of the command_giver and call the exit function
3337  * if set.
3338  *
3339  * ED_BUFFER won't be referenced unless set anew. There must be no errors
3340  * here, because there might be a call from remove_interactive().
3341  */
3342 
3343 {
3344     string_t *name;
3345     object_t *ob;
3346     interactive_t *ip;
3347 
3348     ED_BUFFER = EXTERN_ED_BUFFER(ih);
3349 
3350     clrbuf();
3351     ob   = ED_BUFFER->exit_ob;
3352     name = ED_BUFFER->exit_fn;
3353     free_svalue(&ED_BUFFER->input.prompt);
3354 
3355     if(P_OLDPAT)
3356     {
3357         free_regexp(P_OLDPAT);
3358         P_OLDPAT = NULL;
3359     }
3360 
3361     if (P_FNAME)
3362         free_mstring(P_FNAME);
3363     if (O_SET_INTERACTIVE(ip, command_giver))
3364         remove_input_handler(ip, ih);
3365     xfree(ED_BUFFER);
3366 
3367     if (name)
3368     {
3369         if (!ob || ob->flags & O_DESTRUCTED)
3370         {
3371             debug_message("%s ed: exit_ob destructed at eof.\n", time_stamp());
3372         }
3373         else
3374         {
3375             object_t *save = current_object;
3376 
3377             current_object = ob;
3378             secure_apply(name, ob, 0);
3379             current_object = save;
3380         }
3381         if (ob)
3382             free_object(ob, "ed EOF");
3383         free_mstring(name);
3384     }
3385     else
3386     {
3387         add_message("Exit from ed.\n");
3388     }
3389 }
3390 
3391 /*-------------------------------------------------------------------------*/
3392 void
ed_cmd(char * str,input_t * ih)3393 ed_cmd (char *str, input_t *ih)
3394 
3395 /* Called from the backend with a new line of player input in <str>.
3396  */
3397 
3398 {
3399     int status;
3400     ed_buffer_t *old_ed_buffer;
3401 
3402     old_ed_buffer = ED_BUFFER;
3403     ED_BUFFER = EXTERN_ED_BUFFER(ih);
3404     if (P_MORE)
3405     {
3406         print_help2();
3407         ED_BUFFER = old_ed_buffer;
3408         return;
3409     }
3410 
3411     if (P_APPENDING)
3412     {
3413         more_append(str);
3414         ED_BUFFER = old_ed_buffer;
3415         return;
3416     }
3417 
3418     if (strlen(str) < MAXLINE)
3419         strcat(str, "\n");
3420 
3421     xstrncpy(inlin, str, MAXLINE-1);
3422     inlin[MAXLINE-1] = 0;
3423     inptr = inlin;
3424 
3425     if( (status = getlst()) >= 0)
3426     {
3427         if((status = ckglob()) != 0)
3428         {
3429             if(status >= 0 && (status = doglob()) >= 0)
3430             {
3431                 _setCurLn( status );
3432                 ED_BUFFER = old_ed_buffer;
3433                 return;
3434             }
3435         }
3436         else
3437         {
3438             if((status = docmd(MY_FALSE)) >= 0)
3439             {
3440                 if(status == 1)
3441                     doprnt(P_CURLN, P_CURLN);
3442                 ED_BUFFER = old_ed_buffer;
3443                 return;
3444             }
3445         }
3446     }
3447 
3448     switch (status)
3449     {
3450     case EOF:
3451         free_ed_buffer(&(ED_BUFFER->input));
3452         ED_BUFFER = old_ed_buffer;
3453         return;
3454 
3455 #ifdef FATAL
3456     case FATAL:
3457         if (ED_BUFFER->exit_fn)
3458         {
3459             xfree(ED_BUFFER->exit_fn);
3460             free_object(ED_BUFFER->exit_ob, "ed FATAL");
3461         }
3462         xfree((char *)ED_BUFFER);
3463         EXTERN_ED_BUFFER = 0;
3464         add_message("FATAL ERROR\n");
3465         ED_BUFFER = old_ed_buffer;
3466         return;
3467 #endif
3468 
3469     case CHANGED:
3470         add_message("File has been changed.\n");
3471         break;
3472 
3473     case SET_FAIL:
3474         add_message("`set' command failed.\n");
3475         break;
3476 
3477     case SUB_FAIL:
3478         add_message("string substitution failed.\n");
3479         break;
3480 
3481     case MEM_FAIL:
3482         add_message("Out of memory: text may have been lost.\n" );
3483         break;
3484 
3485     default:
3486         add_message("Unrecognized or failed command.\n");
3487         /*  Unrecognized or failed command (this is SOOOO much better
3488          * than "?" :-)
3489          */
3490     }
3491     ED_BUFFER = old_ed_buffer;
3492 }
3493 
3494 /*-------------------------------------------------------------------------*/
3495 void
save_ed_buffer(input_t * ih)3496 save_ed_buffer (input_t *ih)
3497 
3498 /* Called when the command_giver is destructed in an edit session.
3499  * The function calls master->get_ed_buffer_save_file() to get a filename
3500  * to store the current buffer contents in. If the master doesn't return
3501  * a filename, the buffer contents are simply discarded.
3502  */
3503 
3504 {
3505     svalue_t *stmp;
3506     string_t *fname;
3507     interactive_t *save;
3508 
3509     (void)O_SET_INTERACTIVE(save, command_giver);
3510     ED_BUFFER = EXTERN_ED_BUFFER(ih);
3511     push_ref_string(inter_sp, P_FNAME ? P_FNAME : STR_EMPTY);
3512     stmp = apply_master(STR_GET_ED_FNAME,1);
3513     if (save)
3514     {
3515         save->catch_tell_activ = MY_FALSE;
3516         command_giver = save->ob;
3517     }
3518     if (stmp && stmp->type == T_STRING) {
3519         fname = ref_mstring(stmp->u.str);
3520         if (*get_txt(fname) == '/')
3521         {
3522             string_t *tmp;
3523             tmp = new_n_mstring(get_txt(fname)+1, mstrsize(fname)-1);
3524             if (!tmp)
3525             {
3526                 add_message("(ed) Out of memory (%lu bytes) for filename.\n"
3527                            , (unsigned long)(mstrsize(fname)-1));
3528                 free_mstring(fname);
3529                 return;
3530             }
3531             free_mstring(fname);
3532             fname = tmp;
3533         }
3534         dowrite(1, P_LASTLN, fname , MY_FALSE);
3535         free_mstring(fname);
3536     }
3537     free_ed_buffer(ih);
3538 }
3539 
3540 /*-------------------------------------------------------------------------*/
3541 svalue_t *
v_ed(svalue_t * sp,int num_arg)3542 v_ed (svalue_t *sp, int num_arg)
3543 
3544 /* EFUN ed()
3545  *
3546  *   int ed()
3547  *   int ed(string file)
3548  *   int ed(string file, string func)
3549  *
3550  * Calling without arguments will start the editor ed with the
3551  * name of the error file, that was returned by
3552  * master->valid_read(0, geteuid(this_player()), "ed_start",
3553  * this_player()), usually something like ~/.err. If that file is
3554  * empty, ed will immediatly exit again.
3555  * Calling ed() with argument file will start the editor on the
3556  * file. If the optional argument func is given, this function
3557  * will be called after exiting the editor.
3558  *
3559  * Result is 1 if the editor could be started, else 0. TODO: ???
3560  */
3561 
3562 {
3563     if (current_object->flags & O_DESTRUCTED)
3564     {
3565         /* could confuse the master... */
3566         errorf("Calling ed from destructed object.\n");
3567     }
3568 
3569     if (num_arg == 0)
3570     {
3571         ed_start(NULL, NULL, NULL);
3572         push_number(sp, 1);
3573     }
3574     else if (num_arg == 1)
3575     {
3576         ed_start(sp->u.str, NULL, NULL);
3577         free_svalue(sp);
3578         put_number(sp, 1);
3579     }
3580     else /* num_arg == 2 */
3581     {
3582         if (sp->type == T_STRING)
3583             ed_start((sp-1)->u.str, sp->u.str, current_object);
3584         else /* sp is number 0 */
3585             ed_start((sp-1)->u.str, NULL, NULL);
3586         free_svalue(sp--);
3587         free_svalue(sp);
3588         put_number(sp, 1);
3589     }
3590 
3591     return sp;
3592 } /* f_ed() */
3593 
3594 /*-------------------------------------------------------------------------*/
3595 svalue_t *
f_query_editing(svalue_t * sp)3596 f_query_editing (svalue_t *sp)
3597 
3598 /* EFUN: query_editing()
3599  *
3600  *   mixed query_editing (object ob)
3601  *
3602  * Returns 1 if the ob is interactive and currently editing
3603  * with ed(). If ed() was called with a function name as
3604  * second argument, the object where ed() was called is returned,
3605  * else 0.
3606  */
3607 
3608 {
3609     object_t      *ob;
3610     interactive_t *ip;
3611     input_t       *ih;
3612 
3613     ob = sp->u.ob;
3614     deref_object(ob, "query_editing");
3615 
3616     if (O_SET_INTERACTIVE(ip, ob)
3617      && (ih = get_input_handler(ip, INPUT_ED)) != NULL)
3618     {
3619         if ( NULL != (ob = ((ed_buffer_t*) ih)->exit_ob) )
3620             sp->u.ob = ref_object(ob, "query_editing");
3621         else
3622             put_number(sp, 1);
3623     }
3624     else
3625     {
3626         put_number(sp, 0);
3627     }
3628     return sp;
3629 }
3630 
3631 /*-------------------------------------------------------------------------*/
3632 static void
print_help(char arg)3633 print_help (char arg)
3634 
3635 /* Print the help for command 'arg'.
3636  */
3637 
3638 {
3639     switch (arg)
3640     {
3641     case 'I':
3642         add_message(
3643 "This command indents your entire file under the\n"
3644 "assumption that it is LPC code.  It is only useful\n"
3645 "for files that are not yet indented, since the\n"
3646 "indentation style is unlikely to satisfy anyone.\n"
3647 "Originally from DGD ed.\n"
3648                );
3649         break;
3650 
3651 #if 0
3652     case '^':
3653         add_message(
3654 "Command: ^   Usage: ^pattern\n"
3655 "This command is similiar to grep, in that it searches the\n"
3656 "entire file, printing every line that contains the specified\n"
3657 "pattern.  To get the line numbers of found lines, turn on line\n"
3658 "number printing with the 'n' command.\n"
3659                );
3660         break;
3661 #endif
3662 
3663     case 'n':
3664         add_message(
3665 "Command: n   Usage: n\n"
3666 "This command toggles the internal flag which will cause line\n"
3667 "numbers to be printed whenever a line is listed.\n"
3668                    );
3669         break;
3670 
3671     case 'a':
3672         add_message(
3673 "Command: a   Usage: a\n"
3674 "Append causes the editor to enter input mode, inserting all text\n"
3675 "starting AFTER the current line. Use a '.' on a blank line to exit\n"
3676 "this mode.\n"
3677                    );
3678         break;
3679 
3680     case 'A':
3681         add_message(
3682 "Command: A   Usage: A\n"
3683 "Like the 'a' command, but uses inverse autoindent mode.\n"
3684                    );
3685         break;
3686 
3687     case 'i':
3688         add_message(
3689 "Command: i   Usage: i\n"
3690 "Insert causes the editor to enter input mode, inserting all text\n"
3691 "starting BEFORE the current line. Use a '.' on a blank line to exit\n"
3692 "this mode.\n"
3693                    );
3694         break;
3695 
3696     case 'c':
3697         add_message(
3698 "Command: c   Usage: c\n"
3699 "Change command causes the current line to be wiped from memory.\n"
3700 "The editor enters input mode and all text is inserted where the previous\n"
3701 "line existed.\n"
3702                    );
3703         break;
3704 
3705     case 'd':
3706         add_message(
3707 "Command: d   Usage: d  or [range]d\n"
3708 "Deletes the current line unless preceeded with a range of lines,\n"
3709 "then the entire range will be deleted.\n"
3710                    );
3711         break;
3712 
3713     case 'e':
3714         add_message(
3715 "Commmand: e  Usage: e filename\n"
3716 "Causes the current file to be wiped from memory, and the new file\n"
3717 "to be loaded in.\n"
3718                    );
3719         break;
3720 
3721     case 'E':
3722         add_message(
3723 "Commmand: E  Usage: E filename\n"
3724 "Causes the current file to be wiped from memory, and the new file\n"
3725 "to be loaded in.  Different from 'e' in the fact that it will wipe\n"
3726 "the current file even if there are unsaved modifications.\n"
3727                    );
3728         break;
3729 
3730     case 'f':
3731         add_message(
3732 "Command: f  Usage: f  or f filename\n"
3733 "Display or set the current filename.   If  filename is given as\n"
3734 "an argument, the file (f) command changes the current filename to\n"
3735 "filename; otherwise, it prints  the current filename.\n"
3736                    );
3737         break;
3738 
3739     case 'g':
3740         add_message(
3741 "Command: g  Usage: g/re/p\n"
3742 "Search in all lines for expression 're', and print\n"
3743 "every match. Command 'l' can also be given\n"
3744 "Unlike in unix ed, you can also supply a range of lines\n"
3745 "to search in\n"
3746 "Compare with command 'v'.\n"
3747                    );
3748         break;
3749 
3750     case 'h':
3751         add_message("Command: h    Usage:  h  or hc (where c is a command)\n");
3752         break;
3753 
3754     case 'j':
3755         add_message(
3756 "Command: j    Usage: j or [range]j\n"
3757 "Join Lines. Remove the NEWLINE character  from  between the  two\n"
3758 "addressed lines.  The defaults are the current line and the line\n"
3759 "following.  If exactly one address is given,  this  command does\n"
3760 "nothing.  The joined line is the resulting current line.\n"
3761                    );
3762         break;
3763 
3764     case 'k':
3765         add_message(
3766 "Command: k   Usage: kc  (where c is a character)\n"
3767 "Mark the addressed line with the name c,  a  lower-case\n"
3768 "letter.   The  address-form,  'c,  addresses  the  line\n"
3769 "marked by c.  k accepts one address; the default is the\n"
3770 "current line.  The current line is left unchanged.\n"
3771                    );
3772         break;
3773 
3774     case 'l':
3775         add_message(
3776 "Command: l   Usage: l  or  [range]l\n"
3777 "List the current line or a range of lines in an unambiguous\n"
3778 "way such that non-printing characters are represented as\n"
3779 "symbols (specifically New-Lines).\n"
3780                    );
3781         break;
3782 
3783     case 'm':
3784         add_message(
3785 "Command: m   Usage: mADDRESS or [range]mADDRESS\n"
3786 "Move the current line (or range of lines if specified) to a\n"
3787 "location just after the specified ADDRESS.  Address 0 is the\n"
3788 "beginning of the file and the default destination is the\n"
3789 "current line.\n"
3790                    );
3791         break;
3792 
3793     case 'M':
3794         add_message(
3795 "Command: M   Usage: M or [range]M\n"
3796 "The command removes in the whole file (or in the range of lines if\n"
3797 "specified) any trailing ^M from the line end. This change converts MS-DOS\n"
3798 "line ends into Unix-style lineends.\n"
3799                    );
3800         break;
3801 
3802     case 'p':
3803         add_message(
3804 "Command: p    Usage: p  or  [range]p\n"
3805 "Print the current line (or range of lines if specified) to the\n"
3806 "screen. See the commands 'n' and 'set' if line numbering is desired.\n"
3807                    );
3808         break;
3809 
3810     case 'q':
3811         add_message(
3812 "Command: q    Usage: q\n"
3813 "Quit the editor. Note that you can't quit this way if there\n"
3814 "are any unsaved changes.  See 'w' for writing changes to file.\n"
3815                    );
3816         break;
3817 
3818     case 'Q':
3819         add_message(
3820 "Command: Q    Usage: Q\n"
3821 "Force Quit.  Quit the editor even if the buffer contains unsaved\n"
3822 "modifications.\n"
3823                );
3824         break;
3825 
3826     case 'r':
3827         add_message(
3828 "Command: r    Usage: r filename\n"
3829 "Reads the given filename into the current buffer starting\n"
3830 "at the current line.\n"
3831                    );
3832         break;
3833 
3834     case 't':
3835         add_message(
3836 "Command: t   Usage: tADDRESS or [range]tADDRESS\n"
3837 "Transpose a copy of the current line (or range of lines if specified)\n"
3838 "to a location just after the specified ADDRESS.  Address 0 is the\n"
3839 "beginning of the file and the default destination\nis the current line.\n"
3840                    );
3841         break;
3842 
3843     case 'T':
3844         add_message(
3845 "Command: T  Usage: T{+|-}[width] or [range]T{+|-}[width]\n"
3846 "Replace whitespace or tabs in the current line or in the specified range.\n"
3847 "T+ means that whitespace will be replaced by tabs,\n"
3848 "T- means that tabs will be replaced by whitespace.\n"
3849 "The width option specifies the number of spaces a tab character represents.\n"
3850 "(Default value for width: 8)\n"
3851                    );
3852         break;
3853 
3854     case 'v':
3855         add_message(
3856 "Command: v   Usage: v/re/p\n"
3857 "Search in all lines without expression 're', and print\n"
3858 "every match. Other commands than 'p' can also be given\n"
3859 "Compare with command 'g'.\n"
3860                    );
3861         break;
3862 
3863     case 'z':
3864         add_message(
3865 "Command: z   Usage: z  or  z-  or z.\n"
3866 "Displays 20 lines starting at the current line.\n"
3867 "If the command is 'z.' then 20 lines are displayed being\n"
3868 "centered on the current line. The command 'z-' displays\n"
3869 "the 20 lines before the current line.\n"
3870                    );
3871         break;
3872 
3873     case 'Z':
3874         add_message(
3875 "Command: Z   Usage: Z  or  Z-  or Z.\n"
3876 "Displays 40 lines starting at the current line.\n"
3877 "If the command is 'Z.' then 40 lines are displayed being\n"
3878 "centered on the current line. The command 'Z-' displays\n"
3879 "the 40 lines before the current line.\n"
3880                    );
3881         break;
3882 
3883     case 'x':
3884         add_message(
3885 "Command: x   Usage: x\n"
3886 "Save file under the current name, and then exit from ed.\n"
3887                    );
3888         break;
3889 
3890     case 's':
3891         if ( *inptr=='e' && *(inptr+1)=='t' )
3892         {
3893             add_message(
3894 "Without arguments: show current settings.\n"
3895 "'set save' will preserve the current settings for subsequent invocations\n"
3896 "of ed.\n"
3897 "Options:\n"
3898 "\n"
3899 "number       will print line numbers before printing or inserting a lines\n"
3900 "list         will print control characters in p(rint) and z command\n"
3901 "               like in l(ist)\n"
3902 "print        will show current line after a single substitution or deletion\n"
3903 "eightbit     will preserve the highbit of characters\n"
3904 "autoindent   will preserve current indentation while entering text,\n"
3905 "               use ^D or ^K to get back one step back to the right.\n"
3906 "excompatible will exchange the meaning of \\( and ( as well as \\) and )\n"
3907 "\n"
3908 "An option can be cleared by prepending it with 'no' in the set command, e.g.\n"
3909 "'set nolist' to turn off the list option.\n"
3910 "\n"
3911 "set shiftwidth <digit> will store <digit> in the shiftwidth variable, which\n"
3912 "determines how much blanks are removed from the current indentation when\n"
3913 "typing ^D or ^K in the autoindent mode.\n"
3914                        );
3915             break;
3916         }
3917         else
3918         {
3919             add_message("TODO: document the 's' command\n"
3920 "Command: s   Usage: s/pat/sub/[g]\n"
3921 "Replace regular expression <pat> by the text <sub>. If 'g' is given, all\n"
3922 "occurences of <pat> in a line are replaced, else just the first.\n"
3923 "<sub> may reference subexpressions of <pat> with '\\0'..'\\9', or to the\n"
3924 "whole matched pattern with '\\&'. The special characters '\\t', '\\b',\n"
3925 "'\\r' and '\\n' are recognized, as is '\\0xxx' for arbitrary characters.\n"
3926 "Any character but '/' may be used as delimiter as long as it is consistent\n"
3927 "within one command.\n"
3928                        );
3929             break;
3930         }
3931     case 'w':
3932         add_message(
3933 "Command: w   Usage: w filename\n"
3934 "Write the text into the file <filename>, overwriting the old content.\n"
3935 "If <filename> is omitted, the name of the originating file is used.\n"
3936                    );
3937         break;
3938 
3939     case 'W':
3940         add_message(
3941 "Command: W   Usage: w filename\n"
3942 "Appends the text to the file <filename>.\n"
3943 "If <filename> is omitted, the name of the originating file is used.\n"
3944                    );
3945         break;
3946 
3947     case '/':
3948         add_message(
3949 "Command: /   Usage: /pattern\n"
3950 "Locates the first line matching <pattern>, searching forward from the\n"
3951 "current line. If the pattern is omitted, the last pattern is reused.\n"
3952                    );
3953         break;
3954 
3955     case '?':
3956         add_message(
3957 "Command: ?   Usage: ?pattern\n"
3958 "Locates the first line matching <pattern>, searching backwards from the\n"
3959 "current line. If the pattern is omitted, the last pattern is reused.\n"
3960                    );
3961         break;
3962 
3963     default:
3964         add_message("       Help for Ed  (V %d.%d)\n", ED_VERSION / 100, ED_VERSION % 100);
3965         add_message(
3966 "---------------------------------\n"
3967 "\n\nCommands\n--------\n"
3968 "/\tsearch forward for pattern\n"
3969 "?\tsearch backward for a pattern\n"
3970 /* "^\tglobal search and print for pattern\n" */
3971 "=\tshow current line number\n"
3972 "a\tappend text starting after this line\n"
3973 "A\tlike 'a' but with inverse autoindent mode\n"
3974 "c\tchange current line, query for replacement text\n"
3975 "d\tdelete line(s)\n"
3976 "e\treplace this file with another file\n"
3977 "E\tsame as 'e' but works if file has been modified\n"
3978 "f\tshow/change current file name\n"
3979 "g\tSearch and execute command on any matching line.\n"
3980 "h\thelp file (display this message)\n"
3981 "i\tinsert text starting before this line\n"
3982 "I\tindent the entire file (from DGD ed v0.1)\n"
3983 "\n--Return to continue--"
3984                    );
3985         P_MORE=1;
3986         break;
3987     }
3988 }
3989 
3990 /*-------------------------------------------------------------------------*/
3991 static void
print_help2(void)3992 print_help2 (void)
3993 
3994 /* Print the second page of the generic help.
3995  */
3996 {
3997     P_MORE=0;
3998     add_message(
3999 "j\tjoin lines together\n"
4000 "k\tmark this line with a character - later referenced as 'a\n"
4001 "l\tline line(s) with control characters displayed\n"
4002 "m\tmove line(s) to specified line\n"
4003 "M\tremove all ^M at lineends (DOS -> Unix lineconversion)\n"
4004 "n\ttoggle line numbering\n"
4005 "p\tprint line(s) in range\n"
4006 "q\tquit editor\n"
4007 "Q\tquit editor even if file modified and not saved\n"
4008 "r\tread file into editor at end of file or behind the given line\n"
4009 "s\tsearch and replace\n"
4010 "set\tquery, change or save option settings\n"
4011 "t\tmove copy of line(s) to specified line\n"
4012 "T\ttab / detab line(s), see help\n"
4013 "v\tSearch and execute command on any non-matching line.\n"
4014 "x\tsave file and quit\n"
4015 "w\twrite to current file (or specified file)\n"
4016 "W\tlike the 'w' command but appends instead\n"
4017 "z\tdisplay 20 lines, possible args are . + -\n"
4018 "Z\tdisplay 40 lines, possible args are . + -\n"
4019 "\n"
4020 "For further information type 'hc' where c is the command\n"
4021 "that help is desired for.\n"
4022                );
4023 }
4024 
4025 /***************************************************************************/
4026 
4027