* Copyright (c) 1980 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
static char *sccsid
= "@(#)ex_cmdsub.c 7.7 (Berkeley) %G%";
* Command mode subroutines implementing
* append, args, copy, delete, join, move, put,
* shift, tag, yank, z and undo
* Append after line a lines returned by function f.
* Be careful about intermediate states to avoid scramble
* if an interrupt comes in.
register line
*a1
, *a2
, *rdot
;
if(FIXUNDO
&& !inopen
&& f
!=getsub
) {
undap1
= undap2
= dot
+ 1;
if (truedol
>= endcore
) {
if (FIXUNDO
&& f
== getsub
) {
error("Out of memory@- too many lines in file");
for (rdot
= dot
; a1
> rdot
;)
* Print out the argument list, with []'s around the current name.
register char **av
= argv0
, *as
= args0
;
for (ac
= 0; ac
< argc0
; ac
++) {
if (ac
+ argc
== argc0
- 1)
if (ac
+ argc
== argc0
- 1)
as
= av
? *++av
: strend(as
) + 1;
* Delete lines; two cases are if we are really deleting,
* more commonly we are just moving lines to the undo save area.
register int (*dsavint
)();
dsavint
= signal(SIGINT
, SIG_IGN
);
* Crush out the undo save area, moving the open/visual
* save area down in its place.
register line
*a1
= dol
+ 1, *a2
= unddol
+ 1, *a3
= truedol
+ 1;
* Join lines. Special hacks put in spaces, two spaces if
* preceding line ends with '.', or no spaces if next line starts with ).
static int jcount
, jnoop();
for (a1
= addr1
; a1
<= addr2
; a1
++) {
if (a1
!= addr1
&& c
== 0) {
while (*cp1
== ' ' || *cp1
== '\t')
if (*cp1
&& cp
> genbuf
&& cp
[-1] != ' ' && cp
[-1] != '\t') {
if (cp
> &genbuf
[LBSIZE
-2])
error("Line overflow|Result line of join would be too long");
ignore(append(jnoop
, --addr1
));
* Move and copy lines. Hard work is done by move1 which
* is also called by undo.
markpr(addr2
== dot
? addr1
- 1 : addr2
+ 1);
serror("%s where?|%s requires a trailing address", Command
);
register line
*adt
, *ad1
, *ad2
;
lines
= (addr2
- addr1
) + 1;
ignore(append(getcopy
, ad1
++));
for (ad1
= addr1
; ad1
<= ad2
;)
if (adt
+ 1 == ad1
&& !cflag
&& !inglobal
)
error("That move would do nothing!");
error("Move to a moved line");
* Put lines in the buffer from the undo save area.
error("Cannot put inside global/macro");
if (cnt
&& inopen
&& pkill
[0] && pkill
[1]) {
ignore(append(getput
, addr2
));
* A tricky put, of a group of lines in the middle
* of an existing line. Only from open/visual.
* Argument says pkills have meaning, e.g. called from
* put; it is 0 on calls from putreg.
register char *gp
= &genbuf
[cursor
- linebuf
];
* This kind of stuff is TECO's forte.
* We just grunge along, since it cuts
* across our line-oriented model of the world
* almost scrambling our addled brain.
* Shift lines, based on c.
* If c is neither < nor >, then this is a lisp aligning =.
save12(), undkind
= UNDCHANGE
;
cnt
*= value(SHIFTWIDTH
);
for (addr
= addr1
; addr
<= addr2
; addr
++) {
if (c
== '=' && addr
== addr1
&& addr
!= addr2
)
cp
= i
> 0 ? genindent(i
) : genbuf
;
if (cp
+ strlen(dp
= vpastwh(linebuf
)) >= &genbuf
[LBSIZE
- 2])
error("Line too long|Result line after shift would be too long");
* Find a tag in the tags file.
* Most work here is in parsing the tags file itself.
long mid
; /* assumed byte offset */
long top
, bot
; /* length of tag file */
register char *lp
= lasttag
;
while (!iswhite(peekchar()) && !endcmd(peekchar()))
if (lp
< &lasttag
[sizeof lasttag
- 2])
error("Bad tag|Give one tag per line");
} else if (lasttag
[0] == 0)
error("No previous tag");
* Loop once for each file in tags "path".
CP(tagfbuf
, svalue(TAGS
));
while (*fne
&& *fne
!= ' ')
fne
= 0; /* done, quit after this time */
*fne
= 0; /* null terminate filename */
* Avoid stdio and scan tag file linearly.
if (fstat(io
, &sbuf
) < 0)
/* loop for each tags file entry */
register char *cp
= linebuf
;
register char *lp
= lasttag
;
if (mid
> 0) /* to get first tag in file to work */
if(tgets(linebuf
, sizeof linebuf
, iof
)==NULL
)
/* get the line itself */
if(tgets(linebuf
, sizeof linebuf
, iof
)==NULL
)
printf("tag: %o %o %o %s\n", bot
, mid
, top
, linebuf
);
while (*cp
&& *lp
== *cp
)
if ((*lp
|| !iswhite(*cp
)) && (value(TAGLENGTH
)==0 ||
lp
-lasttag
< value(TAGLENGTH
))) {
/* Not this tag. Try the next */
* We found the tag. Decode the line in the file.
/* Rest of tag if abbreviated */
while (*cp
&& iswhite(*cp
))
serror("%s: Bad tags file entry", lasttag
);
while (*cp
&& *cp
!= ' ' && *cp
!= '\t') {
if (lp
< &filebuf
[sizeof filebuf
- 2])
* Save current position in 't for ^^ in visual.
names
['t'-'a'] = *dot
&~ 01;
extern char *ncols
['z'-'a'+2];
if (strcmp(filebuf
, savedfile
) || !edited
) {
char cmdbuf2
[sizeof filebuf
+ 10];
/* Different file. Do autowrite & get it. */
error("No write@since last change (:tag! overrides)");
strcat(cmdbuf2
, filebuf
);
* Look for pattern in the current file.
* BUG: if it isn't found (user edited header
* line) we get left in nomagic mode.
} /* end of "for each tag in file" */
* No such tag in this file. Close it and try the next.
} /* end of "for each file in path" */
serror("%s: No such tag@in tags file", lasttag
);
* Save lines from addr1 thru addr2 as though
error("Can't yank inside global/macro");
killcnt(addr2
- addr1
+ 1);
* z command; print windows of text in the file.
* If this seems unreasonably arcane, the reasons
* are historical. This is one of the first commands
* added to the first ex (then called en) and the
* number of facilities here were the major advantage
* of en over ed since they allowed more use to be
* made of fast terminals w/o typing .,.22p all the time.
register int c
, lines
, op
;
switch (c
= op
= getchar()) {
while (peekchar() == op
) {
lines
= op
== EOF
? value(SCROLL
) : excl
? LINES
- 1 : 2*value(SCROLL
);
if (inopen
|| c
!= EOF
) {
if (addr2
== 0 && dot
< dol
&& op
== 0)
addr2
+= lines
* zweight
;
addr2
-= lines
* zweight
;
if (op
== EOF
&& zhadpr
) {
} else if (znoclear
== 0 && CL
!= NOSTR
&& !inopen
) {
plines(addr1
, split
- 1, 0);
for (l
= COLUMNS
> 80 ? 40 : COLUMNS
/ 2; l
> 0; l
--)
plines(adr1
, adr2
, movedot
)
for (addr
= adr1
; addr
<= adr2
; addr
++) {
if (inopen
&& Outchar
!= termchar
) {
* Dudley doright to the rescue.
* Undo saves the day again.
* A tip of the hatlo hat to Warren Teitleman
* who made undo as useful as do.
* Command level undo works easily because
* the editor has a unique temporary file
* index for every line which ever existed.
* We don't have to save large blocks of text,
* only the indices which are small. We do this
* by moving them to after the last line in the
* line buffer array, and marking down info
* about whence they came.
* Undo is its own inverse.
line
*dolp1
, *newdol
, *newadot
;
if (inglobal
&& inopen
<= 0)
error("Can't undo in global@commands");
if (undkind
== UNDMOVE
) {
* Command to be undone is a move command.
* This is handled as a special case by noting that
* a move "a,b m c" can be inverted by another move.
if ((i
= (jp
= unddel
) - undap2
) > 0) {
* when c > b inverse is a+(c-b),c m a-1
addr1
= (jp
= undap1
) + i
;
* when b > c inverse is c+1,c+1+(b-a) m b
addr2
= jp
+ ((unddel
= undap2
) - undap1
);
* Command to be undone is a non-move.
* All such commands are treated as a combination of
* a delete command and a append command.
* We first move the lines appended by the last command
* from undap1 to undap2-1 so that they are just before the
if ((i
= (kp
= undap2
) - (jp
= undap1
)) > 0) {
* Account for possible backward motion of target
* for restoration of saved deleted lines.
* For the case where no lines are restored, dot
* is the line before the first line deleted.
* Now put the deleted lines, if any, back where they were.
* Basic operation is: dol+1,unddol m unddel
if ((i
= (kp
= unddol
) - dol
) > 0) {
* Account for possible forward motion of the target
* for restoration of the deleted lines.
* Dot is the first resurrected line.
* Clean up so we are invertible
* Defensive programming - after a munged undadot.
* Also handle empty buffer case.
if ((dot
<= zero
|| dot
> dol
) && dot
!= dol
)
* Be (almost completely) sure there really
* was a change, before claiming to undo.
if (undap1
== undap2
&& dol
== unddol
)
if (unddol
- dol
!= lineDOL())
for (ip
= one
, jp
= dol
+ 1; ip
<= dol
; ip
++, jp
++)
if ((*ip
&~ 01) != (*jp
&~ 01))
error("Nothing to undo");
error("Nothing changed|Last undoable command didn't change anything");
int un
; /* true if this is unmap command */
int ab
; /* true if this is abbr command */
char lhs
[100], rhs
[100]; /* max sizes resp. */
register int c
; /* mjm: char --> int */
struct maps
*mp
; /* the map structure we are working on */
mp
= ab
? abbrevs
: exclam() ? immacs
: arrows
;
/* print current mapping values */
for (i
=0; mp
[i
].mapto
; i
++)
lprintf("%s", mp
[i
].descr
);
lprintf("%s", mp
[i
].cap
);
lprintf("%s", mp
[i
].mapto
);
} else if (!un
&& any(c
, " \t")) {
} else if (endcmd(c
) && c
!='"') {
addmac(lhs
, NOSTR
, NOSTR
, mp
);
} else if (endcmd(c
) && c
!='"') {
* Special hack for function keys: #1 means key f1, etc.
* If the terminal doesn't have function keys, we just use #1.
fnkey
= fkey(lhs
[1] - '0');
funkey
[0] = 'f'; funkey
[1] = lhs
[1]; funkey
[2] = 0;
addmac(lhs
,rhs
,dname
,mp
);
* Add a macro definition to those that already exist. The sequence of
* chars "src" is mapped into "dest". If src is already mapped into something
* this overrides the mapping. There is no recursion. Unmap is done by
* using NOSTR for dest. Dname is what to show in listings. mp is
* the structure to affect (arrows, etc).
addmac(src
,dest
,dname
,mp
)
register char *src
, *dest
, *dname
;
register struct maps
*mp
;
fprintf(trace
, "addmac(src='%s', dest='%s', dname='%s', mp=%x\n", src
, dest
, dname
, mp
);
if (dest
&& mp
==arrows
) {
/* Make sure user doesn't screw himself */
* Prevent tail recursion. We really should be
* checking to see if src is a suffix of dest
* but this makes mapping involving escapes that
if (src
[1] == 0 && src
[0] == dest
[strlen(dest
)-1])
error("No tail recursion");
* We don't let the user rob himself of ":", and making
* multi char words is a bad idea so we don't allow it.
* Note that if user sets mapinput and maps all of return,
* linefeed, and escape, he can screw himself. This is
* so weird I don't bother to check for it.
if (isalpha(src
[0]) && src
[1] || any(src
[0],":"))
error("Too dangerous to map that");
/* check for tail recursion in input mode: fussier */
if (eq(src
, dest
+strlen(dest
)-strlen(src
)))
error("No tail recursion");
* If the src were null it would cause the dest to
* be mapped always forever. This is not good.
if (src
== NOSTR
|| src
[0] == 0)
/* see if we already have a def for src */
for (slot
=0; mp
[slot
].mapto
; slot
++) {
if (eq(src
, mp
[slot
].cap
) || eq(src
, mp
[slot
].mapto
))
break; /* if so, reuse slot */
zer
= slot
; /* remember an empty slot */
error("Not mapped|That macro wasn't mapped");
/* reuse empty slot, if we found one and src isn't already defined */
if (zer
>= 0 && mp
[slot
].mapto
== 0)
/* if not, append to end */
error("Too many macros");
if (msnext
== 0) /* first time */
/* Check is a bit conservative, we charge for dname even if reusing src */
if (msnext
- mapspace
+ strlen(dest
) + strlen(src
) + strlen(dname
) + 3 > MAXCHARMACS
)
error("Too much macro text");
msnext
+= strlen(src
) + 1; /* plus 1 for null on the end */
msnext
+= strlen(dest
) + 1;
msnext
+= strlen(dname
) + 1;
/* default descr to string user enters */
* Implements macros from command mode. c is the buffer to
regbuf(c
, macbuf
, sizeof(macbuf
));
for (ad
=a1
; ad
<=a2
; ad
++) {