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