/* This file contains some of the EX commands - mostly ones that deal with
* files, options, etc. -- anything except text.
/* print the selected lines with info on the blocks */
void cmd_debug(frommark
, tomark
, cmd
, bang
, extra
)
/* scan lnum[] to determine which block its in */
for (i
= 1; l
> lnum
[i
]; i
++)
/* fetch text of the block containing that line */
/* calculate its length */
msg("##### hdr[%d]=%d, lnum[%d-1]=%ld, lnum[%d]=%ld (%ld lines)",
i
, hdr
.n
[i
], i
, lnum
[i
-1], i
, lnum
[i
], lnum
[i
] - lnum
[i
- 1]);
msg("##### len=%d, buf=0x%lx, %sdirty",
len
, scan
, ((int *)scan
)[MAXBLKS
+ 1] ? "" : "not ");
} while (i
< MAXBLKS
&& lnum
[i
] && lnum
[i
- 1] < markline(tomark
));
/* This function checks a lot of conditions to make sure they aren't screwy */
void cmd_validate(frommark
, tomark
, cmd
, bang
, extra
)
int nlcnt
; /* used to count newlines */
int len
; /* counts non-NUL characters */
msg("lnum[0] = %ld", lnum
[0]);
for (i
= 1; lnum
[i
] <= nlines
; i
++)
msg("block %d has no NUL at the end", i
);
for (nlcnt
= len
= 0; *scan
; scan
++, len
++)
msg("block %d doesn't end with '\\n' (length %d)", i
, len
);
if (bang
|| nlcnt
!= lnum
[i
] - lnum
[i
- 1])
msg("block %d (line %ld?) has %d lines, but should have %ld",
i
, lnum
[i
- 1] + 1L, nlcnt
, lnum
[i
] - lnum
[i
- 1]);
msg("hdr.n[%d] = %d, but lnum[%d] = %ld",
i
, hdr
.n
[i
], i
, lnum
[i
]);
msg("# = \"%s\", %% = \"%s\"", prevorig
, origname
);
msg("V_from=%ld.%d, cursor=%ld.%d", markline(V_from
), markidx(V_from
), markline(cursor
), markidx(cursor
));
void cmd_mark(frommark
, tomark
, cmd
, bang
, extra
)
/* validate the name of the mark */
/* valid mark names are lowercase ascii characters */
if (!isascii(*extra
) || !islower(*extra
) || extra
[1])
msg("Invalid mark name");
mark
[*extra
- 'a'] = tomark
;
void cmd_write(frommark
, tomark
, cmd
, bang
, extra
)
int append
; /* boolean: write in "append" mode? */
/* if writing to a filter, then let filter() handle it */
filter(frommark
, tomark
, extra
+ 1, FALSE
);
/* if all lines are to be written, use tmpsave() */
if (frommark
== MARK_FIRST
&& tomark
== MARK_LAST
&& cmd
== CMD_WRITE
)
/* see if we're going to do this in append mode or not */
if (extra
[0] == '>' && extra
[1] == '>')
/* either the file must not exist, or we must have a ! or be appending */
if (access(extra
, 0) == 0 && !bang
&& !append
)
msg("File already exists - Use :w! to overwrite");
/* else do it line-by-line, like cmd_print() */
fd
= open(extra
, O_WRONLY
|O_APPEND
);
fd
= open(extra
, O_WRONLY
);
fd
= -1; /* so we know the file isn't open yet */
fd
= creat(extra
, FILEPERMS
);
msg("Can't write to \"%s\"", extra
);
for (l
= markline(frommark
); l
<= markline(tomark
); l
++)
if (twrite(fd
, scan
, i
) < i
)
rptlines
= markline(tomark
) - markline(frommark
) + 1;
void cmd_shell(frommark
, tomark
, cmd
, bang
, extra
)
static char prevextra
[80];
/* special case: ":sh" means ":!sh" */
/* if extra is "!", substitute previous command */
msg("No previous shell command to substitute for '!'");
else if (cmd
== CMD_BANG
&& strlen(extra
) < sizeof(prevextra
) - 1)
strcpy(prevextra
, extra
);
/* warn the user if the file hasn't been saved yet */
if (*o_warn
&& tstflag(file
, MODIFIED
))
msg("Warning: \"%s\" has been modified but not yet saved", origname
);
/* if no lines were specified, just run the command */
else /* pipe lines from the file through the command */
filter(frommark
, tomark
, extra
, TRUE
);
/* resume curses quietly for MODE_EX, but noisily otherwise */
resume_curses(mode
== MODE_EX
);
void cmd_global(frommark
, tomark
, cmd
, bang
, extra
)
char *extra
; /* rest of the command line */
char *cmdptr
; /* the command from the command line */
char cmdln
[100]; /* copy of the command from the command line */
char *line
; /* a line from the file */
long l
; /* used as a counter to move through lines */
long lqty
; /* quantity of lines to be scanned */
long nchanged
; /* number of lines changed */
regexp
*re
; /* the compiled search expression */
/* can't nest global commands */
msg("Can't nest global commands.");
/* ":g! ..." is the same as ":v ..." */
/* make sure we got a search pattern */
if (*extra
!= '/' && *extra
!= '?')
msg("Usage: %c /regular expression/ command", cmd
== CMD_GLOBAL
? 'g' : 'v');
/* parse & compile the search pattern */
cmdptr
= parseptrn(extra
);
msg("Can't use empty regular expression with '%c' command", cmd
== CMD_GLOBAL
? 'g' : 'v');
/* regcomp found & described an error */
/* for each line in the range */
/* NOTE: we have to go through the lines in a forward order,
* otherwise "g/re/p" would look funny. *BUT* for "g/re/d"
* to work, simply adding 1 to the line# on each loop won't
* work. The solution: count lines relative to the end of
* the file. Think about it.
for (l
= nlines
- markline(frommark
),
lqty
= markline(tomark
) - markline(frommark
) + 1L,
lqty
> 0 && nlines
- l
>= 0 && nchanged
>= 0L;
line
= fetchline(nlines
- l
);
/* if it contains the search pattern... */
if ((!regexec(re
, line
, 1)) == (cmd
!= CMD_GLOBAL
))
/* move the cursor to that line */
cursor
= MARK_AT_LINE(nlines
- l
);
/* do the ex command (without mucking up
* the original copy of the command line)
void cmd_file(frommark
, tomark
, cmd
, bang
, extra
)
/* if we're given a new filename, use it as this file's name */
setflag(file
, NOTEDITED
);
msg("\"%s\" %s%s%s %ld lines, line %ld [%ld%%]",
msg("\"%s\" %s%s %ld lines, line %ld [%ld%%]",
*origname
? origname
: "[NO FILE]",
tstflag(file
, MODIFIED
) ? "[MODIFIED]" : "",
tstflag(file
, NOTEDITED
) ?"[NOT EDITED]":"",
tstflag(file
, READONLY
) ? "[READONLY]" : "",
markline(frommark
) * 100 / nlines
);
else if (markline(frommark
) != markline(tomark
))
msg("range \"%ld,%ld\" contains %ld lines",
markline(tomark
) - markline(frommark
) + 1L);
msg("%ld", markline(frommark
));
void cmd_edit(frommark
, tomark
, cmd
, bang
, extra
)
long line
= 1L; /* might be set to prevline */
/* if ":vi", then switch to visual mode, and if no file is named
* then don't switch files.
/* Editing previous file? Then start at previous line */
if (!strcmp(extra
, prevorig
))
/* if we were given an explicit starting line, then start there */
for (init
= ++extra
; !isspace(*extra
); extra
++)
if (line
<= nlines
&& line
>= 1L)
cursor
= MARK_AT_LINE(line
);
msg("Use edit! to abort changes, or w to save changes");
/* so we can say ":e!#" next time... */
/* This code is also used for rewind -- GB */
void cmd_next(frommark
, tomark
, cmd
, bang
, extra
)
/* if extra stuff given, use ":args" to define a new args list */
if (cmd
== CMD_NEXT
&& extra
&& *extra
)
cmd_args(frommark
, tomark
, cmd
, bang
, extra
);
/* move to the next arg */
else if (cmd
== CMD_PREVIOUS
)
else /* cmd == CMD_REWIND */
msg("No %sfiles to edit", cmd
== CMD_REWIND
? "" : "more ");
/* find & isolate the name of the file to edit */
for (j
= i
, scan
= args
; j
> 0; j
--)
/* switch to the next file */
msg("Use :%s! to abort changes, or w to save changes",
cmd
== CMD_NEXT
? "next" :
cmd
== CMD_PREVIOUS
? "previous" :
/* also called from :wq -- always writes back in this case */
void cmd_xit(frommark
, tomark
, cmd
, bang
, extra
)
static long whenwarned
; /* when the user was last warned of extra files */
/* if there are more files to edit, then warn user */
if (argno
>= 0 && argno
+ 1 < nargs
&& whenwarned
!= changes
&& (!bang
|| cmd
!= CMD_QUIT
))
msg("More files to edit -- Use \":n\" to go to next file");
msg("Use q! to abort changes, or wq to save changes");
/* else try to save this file */
oldflag
= tstflag(file
, MODIFIED
);
msg("Could not save file -- use quit! to abort changes, or w filename");
void cmd_args(frommark
, tomark
, cmd
, bang
, extra
)
/* if no extra names given, or just current name, then report the args
if (nargs
== 1 && !*args
)
for (scan
= args
, col
= arg
= 0;
scan
+= width
+ 1, col
+= width
, arg
++)
if (col
+ width
>= COLS
- 4)
/* write a trailing newline */
if ((mode
== MODE_EX
|| mode
== MODE_COLON
|| scrolled
) && col
)
else /* new args list given */
for (scan
= args
, nargs
= 1; *extra
; )
/* reset argno to before the first, so :next will go to first */
msg("%d files to edit", nargs
);
void cmd_cd(frommark
, tomark
, cmd
, bang
, extra
)
/* if current file is modified, and no '!' was given, then error */
if (tstflag(file
, MODIFIED
) && !bang
)
msg("File modified; use \"cd! %s\" to switch anyway", extra
);
/* default directory name is $HOME */
msg("environment variable $HOME not set");
/* go to the directory */
void cmd_map(frommark
, tomark
, cmd
, bang
, extra
)
static char *fnames
[NFKEYS
] =
"#10", "#1", "#2", "#3", "#4",
"#5", "#6", "#7", "#8", "#9",
"#10s", "#1s", "#2s", "#3s", "#4s",
"#5s", "#6s", "#7s", "#8s", "#9s",
"#10c", "#1c", "#2c", "#3c", "#4c",
"#5c", "#6c", "#7c", "#8c", "#9c",
"#10a", "#1a", "#2a", "#3a", "#4a",
"#5a", "#6a", "#7a", "#8a", "#9a",
/* "map" with no extra will dump the map table contents */
dumpkey(bang
? WHEN_EX
|WHEN_VIINP
|WHEN_VIREP
: WHEN_VIINP
|WHEN_VIREP
, TRUE
);
dumpkey(bang
? WHEN_VIINP
|WHEN_VIREP
: WHEN_VICMD
, FALSE
);
/* "extra" is key to map, followed by what it maps to */
/* handle quoting inside the "raw" string */
for (build
= mapto
= extra
;
*mapto
&& (*mapto
!= ' ' && *mapto
!= '\t');
if (*mapto
== ctrl('V') && mapto
[1])
/* skip whitespace, and mark the end of the "raw" string */
while ((*mapto
== ' ' || *mapto
== '\t'))
/* strip ^Vs from the "cooked" string */
for (scan
= build
= mapto
; *scan
; *build
++ = *scan
++)
if (*scan
== ctrl('V') && scan
[1])
/* if the mapped string is '#' and a number, then assume
* the user wanted that function key
if (extra
[0] == '#' && isdigit(extra
[1]))
key
= atoi(extra
+ 1) % 10;
build
= extra
+ strlen(extra
) - 1;
mapkey(FKEY
[key
], mapto
, bang
? WHEN_VIINP
|WHEN_VIREP
: WHEN_VICMD
, fnames
[key
]);
msg("This terminal has no %s key", fnames
[key
]);
if (cmd
== CMD_ABBR
|| cmd
== CMD_UNABBR
)
mapkey(extra
, mapto
, bang
? WHEN_EX
|WHEN_VIINP
|WHEN_VIREP
: WHEN_VIINP
|WHEN_VIREP
, "abbr");
mapkey(extra
, mapto
, bang
? WHEN_VIINP
|WHEN_VIREP
: WHEN_VICMD
, (char *)0);
void cmd_set(frommark
, tomark
, cmd
, bang
, extra
)
dumpopts(FALSE
);/* "FALSE" means "don't dump all" - only set */
else if (!strcmp(extra
, "all"))
dumpopts(TRUE
); /* "TRUE" means "dump all" - even unset vars */
/* That option may have affected the appearence of text */
void cmd_tag(frommark
, tomark
, cmd
, bang
, extra
)
int fd
; /* file descriptor used to read the file */
char *scan
; /* used to scan through the tmpblk.c */
char *cmp
; /* char of tag name we're comparing, or NULL */
char *end
; /* marks the end of chars in tmpblk.c */
char wasmagic
; /* preserves the original state of o_magic */
/* if no tag is given, use the previous tag */
strncpy(prevtag
, extra
, sizeof prevtag
);
prevtag
[sizeof prevtag
- 1] = '\0';
/* use "ref" to look up the tag info for this tag */
sprintf(tmpblk
.c
, "ref -t %s%s %s", (*origname
? "-f" : ""),origname
, prevtag
);
msg("Can't run \"%s\"", tmpblk
.c
);
/* try to read the tag info */
(i
= tread(fd
, scan
, scan
- tmpblk
.c
+ BLKSIZE
)) > 0;
/* close the pipe. abort if error */
if (rpclose(fd
) != 0 || scan
< tmpblk
.c
+ 3)
msg("tag \"%s\" not found", extra
);
#else /* use internal code to look up the tag */
fd
= open(TAGS
, O_RDONLY
);
/* Hmmm... this would have been a lot easier with <stdio.h> */
/* find the line with our tag in it */
for(scan
= end
= tmpblk
.c
, cmp
= extra
; ; scan
++)
/* read a block, if necessary */
end
= tmpblk
.c
+ tread(fd
, tmpblk
.c
, BLKSIZE
);
msg("tag \"%s\" not found", extra
);
/* if we're comparing, compare... */
if (!*cmp
&& *scan
== '\t')
/* failed! skip to newline */
/* if we're skipping to newline, do it fast! */
while (scan
< end
&& *scan
!= '\n')
/* found it! get the rest of the line into memory */
for (cmp
= tmpblk
.c
, scan
++; scan
< end
&& *scan
!= '\n'; )
tread(fd
, cmp
, BLKSIZE
- (int)(cmp
- tmpblk
.c
));
/* we can close the tags file now */
#endif /* INTERNAL_TAGS */
/* extract the filename from the line, and edit the file */
for (scan
= tmpblk
.c
; *scan
!= '\t'; scan
++)
if (strcmp(origname
, tmpblk
.c
) != 0)
msg("Use :tag! to abort changes, or :w to save changes");
/* move to the desired line (or to line 1 if that fails) */
if (cursor
== MARK_UNSET
)
msg("Tag's address is out of date");
/* describe this version of the program */
void cmd_version(frommark
, tomark
, cmd
, bang
, extra
)
msg("Compiled by %s", COMPILED_BY
);
/* make a .exrc file which describes the current configuration */
void cmd_mkexrc(frommark
, tomark
, cmd
, bang
, extra
)
/* the default name for the .exrc file EXRC */
/* create the .exrc file */
fd
= creat(extra
, FILEPERMS
);
msg("Couldn't create a new \"%s\" file", extra
);
msg("Configuration saved");
void cmd_digraph(frommark
, tomark
, cmd
, bang
, extra
)
static char errfile
[256]; /* the name of a file containing an error */
static long errline
; /* the line number for an error */
static int errfd
= -2; /* fd of the errlist file */
/* This static function tries to parse an error message.
* For most compilers, the first word is taken to be the name of the erroneous
* file, and the first number after that is taken to be the line number where
* the error was detected. The description of the error follows, possibly
* preceded by an "error ... :" or "warning ... :" label which is skipped.
* For Coherent, error messages look like "line#: filename: message".
* For non-error lines, or unparsable error lines, this function returns NULL.
* Normally, though, it alters errfile and errline, and returns a pointer to
static char *parse_errmsg(text
)
# if COHERENT || TOS /* any Mark Williams compiler */
/* Get the line number. If no line number, then ignore this line. */
/* Skip to the start of the filename */
while (*text
&& *text
++ != ':')
/* copy the filename to errfile */
for (cpy
= errfile
; *text
&& (*cpy
++ = *text
++) != ':'; )
# else /* not a Mark Williams compiler */
/* the error message is the whole line, by default */
/* skip leading garbage */
while (*text
&& !isalnum(*text
))
/* copy over the filename */
while(isalnum(*text
) || *text
== '.')
/* ignore the name "Error" and filenames that contain a '/' */
if (*text
== '/' || !*errfile
|| !strcmp(errfile
+ 1, "rror") || access(errfile
, 0) < 0)
/* skip garbage between filename and line number */
while (*text
&& !isdigit(*text
))
/* if the number is part of a larger word, then ignore this line */
if (*text
&& isalpha(text
[-1]))
errline
+= (*text
- '0');
/* any line which lacks a filename or line number should be ignored */
if (!errfile
[0] || !errline
)
/* locate the beginning of the error description */
while (*text
&& !isspace(*text
))
/* skip "error #:" and "warning #:" clauses */
if (!strncmp(text
+ 1, "rror ", 5)
|| !strncmp(text
+ 1, "arning ", 7)
|| !strncmp(text
+ 1, "atal error", 10))
} while (*text
&& *text
!= ':');
/* anything other than whitespace or a colon is important */
if (!isspace(*text
) && *text
!= ':')
/* else keep looking... */
# endif /* not COHERENT */
void cmd_errlist(frommark
, tomark
, cmd
, bang
, extra
)
static long endline
;/* original number of lines in this file */
static long offset
; /* offset of the next line in the errlist file */
/* if a new errlist file is named, open it */
errfd
= open(extra
, O_RDONLY
);
/* open the default file */
errfd
= open(ERRLIST
, O_RDONLY
);
/* do we have an errlist file now? */
msg("There is no errlist file");
/* find the next error message in the file */
/* read the next line from the errlist */
if (tread(errfd
, tmpblk
.c
, (unsigned)BLKSIZE
) <= 0)
for (i
= 0; tmpblk
.c
[i
] != '\n'; i
++)
/* look for an error message in the line */
errmsg
= parse_errmsg(tmpblk
.c
);
/* switch to the file containing the error, if this isn't it */
if (strcmp(origname
, errfile
))
msg("Use :er! to abort changes, or :w to save changes");
/* go to the line where the error was detected */
cursor
= MARK_AT_LINE(errline
+ (nlines
- endline
));
/* display the error message */
msg("line %ld(+%ld): %.60s", errline
, nlines
- endline
, errmsg
);
else if (nlines
< endline
)
msg("line %ld(-%ld): %.60s", errline
, endline
- nlines
, errmsg
);
msg("line %ld: %.65s", errline
, errmsg
);
/* remember where the NEXT error line will start */
void cmd_make(frommark
, tomark
, cmd
, bang
, extra
)
/* if the file hasn't been saved, then complain unless ! */
if (tstflag(file
, MODIFIED
) && !bang
)
msg("\"%s\" not saved yet", origname
);
sprintf(buf
.c
, "%s %s %s%s", (cmd
== CMD_CC
? o_cc
: o_make
), extra
, REDIRECT
, ERRLIST
);
/* close the old errlist file, if any */
/* run the command, with curses temporarily disabled */
resume_curses(mode
== MODE_EX
);
/* run the "errlist" command */
cmd_errlist(MARK_UNSET
, MARK_UNSET
, cmd
, bang
, ERRLIST
);
/* figure out the number of text colors we use with this configuration */
/* the attribute bytes used in each of "when"s */
static char bytes
[NCOLORS
];
char *word
; /* a legal word */
int type
; /* what type of word this is */
int val
; /* some other value */
{"normal", 1, A_NORMAL
}, /* all "when" names must come */
{"standout", 1, A_STANDOUT
}, /* at the top of the list. */
{"bold", 1, A_BOLD
}, /* The first 3 must be normal,*/
{"underlined", 1, A_UNDERLINE
}, /* standout, and bold; the */
{"italics", 1, A_ALTCHARSET
}, /* remaining names follow. */
{"visible", 1, A_VISIBLE
},
{"black", 3, 0x00}, /* The color names start right*/
{"blue", 3, 0x01}, /* after the "when" names. */
{"yellow", 3, 0x0E}, /* bright brown */
{"gray", 3, 0x08}, /* bright black? of course! */
void cmd_color(frommark
, tomark
, cmd
, bang
, extra
)
int nowbg
; /* BOOLEAN: is the next color background? */
/* if no args are given, then report the current colors */
/* if no colors are set, then say so */
msg("no colors have been set");
/* report all five color combinations */
for (i
= 0; i
< NCOLORS
; i
++)
case 0x08: qaddstr("gray"); break;
case 0x0e: qaddstr("yellow"); break;
case 0x0f: qaddstr("bright white");break;
qaddstr(words
[(bytes
[i
] & 0x07) + NCOLORS
].word
);
qaddstr(words
[((bytes
[i
] >> 4) & 0x07) + NCOLORS
].word
);
/* The default background color is the same as "normal" chars.
* There is no default foreground color.
attrbyte
= bytes
[0] & 0x70;
/* parse each word in the "extra" text */
for (scan
= extra
; *extra
; extra
= scan
)
/* locate the end of the word */
while (*scan
&& *scan
!= ' ')
/* skip whitespace at the end of the word */
for (i
= 0; words
[i
].word
&& strcmp(words
[i
].word
, extra
); i
++)
/* if not a word, then complain */
msg("Invalid color name: %s", extra
);
attrbyte
|= words
[i
].val
;
attrbyte
= ((attrbyte
& ~0x70) | ((words
[i
].val
& 0x07) << 4));
attrbyte
|= words
[i
].val
;
/* if nowbg isn't set now, then we were never given a foreground color */
msg("usage: color [when] [\"bright\"] [\"blinking\"] foreground [background]");
/* the first ":color" command MUST define the "normal" colors */
/* we should now have a cmode and an attribute byte... */
setcolor(cmode
, attrbyte
);
/* remember what we just did */
/* if the other colors haven't been set yet, then set them to defaults */
/* standout is the opposite of normal */
bytes
[1] = ((attrbyte
<< 4) & 0x70 | (attrbyte
>> 4) & 0x07);
setcolor(A_STANDOUT
, bytes
[1]);
/* if "normal" isn't bright, then bold defaults to normal+bright
* else bold defaults to bright white.
bytes
[2] = attrbyte
| ((attrbyte
& 0x08) ? 0x0f : 0x08);
setcolor(A_BOLD
, bytes
[2]);
/* all others default to the "standout" colors, without blinking */
for (i
= 3; i
< NCOLORS
; i
++)
bytes
[i
] = (bytes
[1] & 0x7f);
setcolor(words
[i
].val
, bytes
[i
]);
/* force a redraw, so we see the new colors */
redraw(MARK_UNSET
, FALSE
);
int fd
; /* file descriptor to write colors to */
/* if no colors are set, then return */
/* save all five color combinations */
for (i
= 0; i
< NCOLORS
; i
++)
strcat(buf
, words
[i
].word
);
strcat(buf
, "blinking ");
case 0x08: strcat(buf
, "gray"); break;
case 0x0e: strcat(buf
, "yellow"); break;
case 0x0f: strcat(buf
, "bright white");break;
strcat(buf
, words
[(bytes
[i
] & 0x07) + NCOLORS
].word
);
strcat(buf
, words
[((bytes
[i
] >> 4) & 0x07) + NCOLORS
].word
);
twrite(fd
, buf
, (unsigned)strlen(buf
));
/* temporarily suspend elvis */
void cmd_suspend(frommark
, tomark
, cmd
, bang
, extra
)
void (*func
)(); /* stores the previous setting of SIGTSTP */
#if !defined(__386BSD__) && defined(ANY_UNIX)
/* the Bourne shell can't handle ^Z */
if (!strcmp(o_shell
, "/bin/sh"))
msg("The /bin/sh shell doesn't support ^Z");
func
= signal(SIGTSTP
, SIG_DFL
);
msg("SIGTSTP is being ignored, you may not suspend the editor", func
);
if (tstflag(file
, MODIFIED
))
addstr("\" modified but not yet saved");
/* was here func = signal(SIGTSTP, SIG_DFL); /* races ??? */
/* the process stops and resumes here */
if (mode
== MODE_VI
|| mode
== MODE_COLON
)
redraw(MARK_UNSET
, FALSE
);