---
 ed.1 | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 ed.c | 250 ++++++++++++++++++++++++++++++++++---------------------------------
 2 files changed, 331 insertions(+), 123 deletions(-)

diff --git a/ed.1 b/ed.1
index 93e3012..520ac5f 100644
--- a/ed.1
+++ b/ed.1
@@ -6,4 +6,206 @@
 .Nd text editor
 .Sh SYNOPSIS
 .Nm
-is the standard text editor.
+.Op Fl s
+.Op Fl p Ar string
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+is the standard text editor. It performs line-oriented operations on a buffer;
+The buffer's contents are manipulated in command mode and text is written to 
the
+buffer in input mode. Command mode is the default. To exit input mode enter a
+dot ('.') on a line of its own.
+
+If
+.Nm
+is invoked with a file as an argument, it will simulate an edit command and 
read
+the file's contents into a buffer. Changes to this buffer are local to
+.Nm
+until a write command is given.
+
+.Nm
+uses the basic regular expression syntax and allows any character but space and
+newline to be used as a delimiter in regular expressions.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl s
+Suppress diagnostic messages
+.It Fl p Ar string
+Use
+.Ar string
+as a prompt when in command mode
+.El
+.Sh EXTENDED DESCRIPTION
+.Ss Addresses
+Commands operate on addresses. Addresses are used to refer to lines
+within the buffer. Address ranges may have spaces before and after the 
separator.
+Unless otherwise specified, 0 is an invalid address. The following symbols are
+valid addresses:
+.Bl -tag -width Ds
+.It n
+The nth line.
+.It .
+The current line, or "dot".
+.It $
+The last line.
+.It +
+The next line.
+.It +n
+The nth next line.
+.It ^ or -
+The previous line.
+.It ^n or -n
+The nth previous line.
+.It x,y
+The range of lines from x to y. The default value of x is 1, and the default
+value of y is $.
+.It x;y
+As above, except that the current line is set to x. Omitting x in this case 
uses
+the current line as the default value.
+.It /re/
+The next line matching re.
+.It ?re?
+The last line matching re.
+.It 'c
+The line marked by c. See k below.
+.El
+.Ss Commands.
+.Nm
+expects to see one command per line, with the following exception: commands may
+be suffixed with either a list, number, or print command. These suffixed
+commands are run after the command they're suffixed to has executed.
+
+The following is the list of commands that
+.Nm
+knows about. The parentheses contain the default addresses that a command uses.
+.Bl -tag -width Ds
+.It (.)a
+Append text after the addressed line. The dot is set to the last line
+entered. If no text was entered, the dot is set to the addressed line. An
+address of 0 appends to the start of the buffer.
+.It (.,.)c
+Delete the addressed lines and then accept input to replace them. The dot
+is set to the last line entered. If no text was entered, the dot is set to
+the line before the deleted lines.
+.It (.,.)d
+Delete the addressed lines. If there is a line after the deleted range, the
+dot is set to it. Otherwise, the dot is set to the line before the deleted 
range.
+.It e Ar file
+Delete the contents of the buffer and load in
+.Ar file
+for editing, printing the bytes read to standard output. If no filename is
+given,
+.Nm
+uses the currently remembered filename. The remembered filename is set to
+.Ar file
+for later use.
+.It E Ar file
+As above, but without warning if the current buffer has unsaved changes.
+.It f Ar file
+Set the currently remembered filename to
+.Ar
+file
+, or print the currently remembered filename if
+.Ar
+file is omitted.
+.It (1,$)g/re/command
+Apply command to lines matching re. The dot is set to the matching line before
+command is executed. When each matching line has been operated on, the dot is
+set to the last line operated on. If no lines match then the dot remains
+unchanged. The command used may not be g, G, v, or V.
+.It (1,$)G/re/
+Interactively edit the range of line addresses that match re. The dot is set to
+the matching line and printed before a command is input. When each matching 
line
+has been operated on, the dot is set to the last line operated on. If no lines
+match then the dot remains unchanged. The command used may not be a, c, i, g,
+G, v, or V.
+.It h
+Print the reason for the most recent error.
+.It H
+Toggle error explanations. If on, the above behaviour is produced on all
+subsequent errors.
+.It (.)i
+Insert text into the buffer before the addressed line. The dot is set to the
+last line entered. If no text was entered, the dot is set to the addressed line
+.It (.,.+1)j
+Join two lines together. If only one address is given, nothing happens. The dot
+is set to the newly joined line.
+.It (.)kc
+Mark the line with the lower case character c. The dot is unchanged.
+.It (.,.)l
+Unambiguously print the addressed lines. The dot is set to the last line 
written.
+.It (.,.)m(.)
+Move lines in the buffer to the line address on the right hand side. An address
+of 0 on the right hand side moves to the start of the buffer. The dot is set to
+the last line moved.
+.It (.,.)n
+Print the addressed lines and their numbers. The dot is set to the last line
+printed.
+.It (.,.)p
+Print the addressed lines. The dot is set to the last line printed.
+.It P
+Toggle the prompt. Defaults to off, but is switched on if the -p flag is used.
+.It q
+Quit
+.Nm
+, warning if there are unsaved changes.
+.It Q
+As above, but without warning if the current buffer has unsaved changes.
+.It ($)r Ar file
+Read in
+.Ar file
+and append it to the current buffer, printing the bytes read to standard 
output.
+The currently remembered filename isn't changed unless it's empty. An address 
of
+0 reads the file into the start of the buffer.
+.It (.,.)s/re/replacement/flags
+Substitute re for replacement in lines matching re. An & within replacement is
+replaced with the whole string matched by re. Backrefs can be used with the 
form
+\\n, where n is a positive non-zero integer. When % is the only character in
+replacement, it is substituted for the replacement string from the last
+substitute command. If a newline is part of replacement then the matched string
+is split into two lines; this cannot be done as part of a g or v command. If
+flags contains an integer n, then the nth match is replaced. If flags contains
+g, all matches are replaced. The dot is set to the last line matched.
+.It (.,.)t(.)
+As m, but copying instead of moving. The dot is set to the last line added.
+.It u
+Undo the last change. The dot is set to whatever it was before the undone
+command was performed.
+.It (1.$)v/re/command
+As with g, but operating on lines that don't match re.
+.It (1.$)V/re/
+As with G, but operating on lines that don't match re.
+.It (1,$)w Ar file
+Write the addressed lines to
+.Ar file
+, overwriting its previous contents if the file exists, and print the number of
+bytes written. If no filename is given the currently remembered filename will 
be
+used instead. The dot is unchanged.
+.It (1,$)W Ar file
+As above, but instead of overwriting the contents of
+.Ar file
+the addressed lines are appended to
+.Ar file
+instead.
+.It (.+1)\\n
+Print the addressed line. Sets the dot to that line.
+.It ($)=
+Print the line number of the addressed line. The dot is unchanged.
+.It &
+Repeat the last command.
+.It ! Ar command
+Execute
+.Ar command
+using sh. If the first character of
+.Ar command
+is '!' then it is replaced with the text of the previous command. An unescaped 
%
+is replaced with the currently remembered filename. ! does not process escape
+characters. When
+.Ar command
+returns a '!' is printed. The dot is unchanged.
+.El
+.Sh BUGS
+g and v operate on single commands rather than lists delimited with '\\'.
+.Sh SEE ALSO
+.Xr sed 1
+.Xr regexp 3
diff --git a/ed.c b/ed.c
index 184ed30..496b24f 100644
--- a/ed.c
+++ b/ed.c
@@ -119,7 +119,8 @@ addchar(char c, char *t, size_t *capacity, size_t *size)
        if (siz >= cap &&
            (cap > SIZE_MAX - LINESIZE ||
             (t = realloc(t, cap += LINESIZE)) == NULL))
-                       error("out of memory");
+               error("out of memory");
+
        t[siz++] = c;
        *size = siz;
        *capacity = cap;
@@ -297,13 +298,17 @@ undo(void)
 }
 
 static void
-inject(char *s)
+inject(char *s, int join)
 {
        int off, k, begin, end;
 
-       begin = getindex(curln);
-       end = getindex(nextln(curln));
-
+       if (join) {
+               begin = getindex(curln-1);
+               end = getindex(nextln(curln-1));
+       } else {
+               begin = getindex(curln);
+               end = getindex(nextln(curln));
+       }
        while (*s) {
                k = makeline(s, &off);
                s += off;
@@ -634,7 +639,7 @@ doread(char *fname)
                        s[n-1] = '\n';
                        s[n] = '\0';
                }
-               inject(s);
+               inject(s, 0);
        }
        if (optdiag)
                printf("%zu\n", cnt);
@@ -751,7 +756,7 @@ append(int num)
        while (getline(&s, &len, stdin) > 0) {
                if (*s == '.' && s[1] == '\n')
                        break;
-               inject(s);
+               inject(s, 0);
        }
        free(s);
 }
@@ -767,7 +772,7 @@ delete(int from, int to)
        lfrom = getindex(prevln(from));
        lto = getindex(nextln(to));
        lastln -= to - from + 1;
-       curln = (from > lastln) ? lastln : from;;
+       curln = (from > lastln) ? lastln : from;
        relink(lto, lfrom, lto, lfrom);
 }
 
@@ -803,18 +808,20 @@ join(void)
        int i;
        char *t, c;
        size_t len = 0, cap = 0;
-       static char *s;
+       char *s;
 
-       free(s);
        for (s = NULL, i = line1; i <= line2; i = nextln(i)) {
                for (t = gettxt(i); (c = *t) != '\n'; ++t)
                        s = addchar(*t, s, &cap, &len);
+                /* prevent infinite loop when there are only two lines */
+               if (i == line2)
+                       break;
        }
 
        s = addchar('\n', s, &cap, &len);
        s = addchar('\0', s, &cap, &len);
        delete(line1, line2);
-       inject(s);
+       inject(s, 1);
        free(s);
 }
 
@@ -841,7 +848,7 @@ copy(int where)
        curln = where;
 
        for (i = line1; i <= line2; ++i)
-               inject(gettxt(i));
+               inject(gettxt(i), 0);
 }
 
 static void
@@ -1021,7 +1028,7 @@ subline(int num, int nth)
        addpost(&s, &cap, &siz);
        delete(num, num);
        curln = prevln(num);
-       inject(s);
+       inject(s, 0);
 }
 
 static void
@@ -1170,9 +1177,8 @@ repeat:
        case 'j':
                chkprint(1);
                deflines(curln, curln+1);
-               if (!line1)
-                       goto bad_address;
-               join();
+               if (line1 != line2)
+                       join();
                break;
        case 'z':
                if (nlines > 1)
@@ -1299,126 +1305,126 @@ chkglobal(void)
        return 1;
 }
 
-static void
-doglobal(void)
-{
-       int i, k;
+       static void
+               doglobal(void)
+       {
+               int i, k;
 
-       skipblank();
-       cmdsiz = 0;
-       gflag = 1;
-       if (uflag)
-               chkprint(0);
-
-       for (i = 1; i <= lastln; i++) {
-               k = getindex(i);
-               if (!zero[k].global)
-                       continue;
-               curln = i;
-               nlines = 0;
-               if (uflag) {
-                       line1 = line2 = i;
-                       pflag = 0;
-                       doprint();
+               skipblank();
+               cmdsiz = 0;
+               gflag = 1;
+               if (uflag)
+                       chkprint(0);
+
+               for (i = 1; i <= lastln; i++) {
+                       k = getindex(i);
+                       if (!zero[k].global)
+                               continue;
+                       curln = i;
+                       nlines = 0;
+                       if (uflag) {
+                               line1 = line2 = i;
+                               pflag = 0;
+                               doprint();
+                       }
+                       docmd();
                }
-               docmd();
+               discard();   /* cover the case of not matching anything */
        }
-       discard();   /* cover the case of not matching anything */
-}
 
-static void
-usage(void)
-{
-       eprintf("usage: %s [-s] [-p] [file]\n", argv0);
-}
-
-static void
-sigintr(int n)
-{
-       signal(SIGINT, sigintr);
-       error("interrupt");
-}
+       static void
+               usage(void)
+       {
+               eprintf("usage: %s [-s] [-p] [file]\n", argv0);
+       }
 
-static void
-sighup(int dummy)
-{
-       int n;
-       char *home = getenv("HOME"), fname[FILENAME_MAX];
+       static void
+               sigintr(int n)
+       {
+               signal(SIGINT, sigintr);
+               error("interrupt");
+       }
 
-       if (modflag) {
-               line1 = nextln(0);
-               line2 = lastln;
-               if (!setjmp(savesp)) {
-                       dowrite("ed.hup", 1);
-               } else if (home && !setjmp(savesp)) {
-                       n = snprintf(fname,
-                                    sizeof(fname), "%s/%s", home, "ed.hup");
-                       if (n < sizeof(fname) && n > 0)
-                               dowrite(fname, 1);
+       static void
+               sighup(int dummy)
+       {
+               int n;
+               char *home = getenv("HOME"), fname[FILENAME_MAX];
+
+               if (modflag) {
+                       line1 = nextln(0);
+                       line2 = lastln;
+                       if (!setjmp(savesp)) {
+                               dowrite("ed.hup", 1);
+                       } else if (home && !setjmp(savesp)) {
+                               n = snprintf(fname,
+                                            sizeof(fname), "%s/%s", home, 
"ed.hup");
+                               if (n < sizeof(fname) && n > 0)
+                                       dowrite(fname, 1);
+                       }
                }
+               exstatus = 1;
+               quit();
        }
-       exstatus = 1;
-       quit();
-}
 
-static void
-edit(void)
-{
-       setjmp(savesp);
-       for (;;) {
-               newcmd = 1;
-               ocurln = curln;
-               cmdsiz = 0;
-               repidx = -1;
-               if (optprompt)
-                       fputs(prompt, stdout);
-               getlst();
-               chkglobal() ? doglobal() : docmd();
+       static void
+               edit(void)
+       {
+               setjmp(savesp);
+               for (;;) {
+                       newcmd = 1;
+                       ocurln = curln;
+                       cmdsiz = 0;
+                       repidx = -1;
+                       if (optprompt)
+                               fputs(prompt, stdout);
+                       getlst();
+                       chkglobal() ? doglobal() : docmd();
+               }
        }
-}
 
-static void
-init(char *fname)
-{
-       size_t len;
+       static void
+               init(char *fname)
+       {
+               size_t len;
 
-       if (setjmp(savesp))
-               return;
-       setscratch();
-       if (!fname)
-               return;
-       if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
-               error("incorrect filename");
-       memcpy(savfname, fname, len);
-       doread(fname);
-       clearundo();
-}
+               if (setjmp(savesp))
+                       return;
+               setscratch();
+               if (!fname)
+                       return;
+               if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
+                       error("incorrect filename");
+               memcpy(savfname, fname, len);
+               doread(fname);
+               clearundo();
+       }
 
-int
-main(int argc, char *argv[])
-{
-       ARGBEGIN {
-       case 'p':
-               prompt = EARGF(usage());
-               optprompt = 1;
-               break;
-       case 's':
-               optdiag = 0;
-               break;
-       default:
-               usage();
-       } ARGEND
+       int
+               main(int argc, char *argv[])
+       {
+               ARGBEGIN {
+               case 'p':
+                       prompt = EARGF(usage());
+                       optprompt = 1;
+                       break;
+               case 's':
+                       optdiag = 0;
+                       break;
+               default:
+                       usage();
+               } ARGEND
 
-       if (argc > 1)
-               usage();
+                         if (argc > 1)
+                                 usage();
 
-       signal(SIGINT, sigintr);
-       signal(SIGHUP, sighup);
-       signal(SIGQUIT, SIG_IGN);
+               signal(SIGINT, sigintr);
+               signal(SIGHUP, sighup);
+               signal(SIGQUIT, SIG_IGN);
 
-       init(*argv);
-       edit();
+               init(*argv);
+               edit();
 
-       /* not reached */
-       return 0;
-}
+               /* not reached */
+               return 0;
+       }
-- 
2.9.2


Reply via email to