ngspice/src/frontend/parser/complete.c

744 lines
18 KiB
C

/**********
Copyright 1990 Regents of the University of California. All rights reserved.
Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
Modified: 1999 Paolo Nenzi
**********/
/*
* Command completion code. We keep a data structure with information on each
* command, to make lookups fast. We also keep NCLASSES (which is sort of
* hardwired as 32) sets of keywords. Each command has an array of NARGS
* bitmasks (also hardwired as 4), stating whether the command takes that
* particular class of keywords in that position. Class 0 always means
* filename completion.
*/
#include "ngspice/ngspice.h"
#include "ngspice/fteext.h"
#include "ngspice/cpdefs.h"
#include "complete.h"
#ifdef HAVE_SYS_DIR_H
#include <sys/types.h>
#include <sys/dir.h>
#else
# ifdef HAVE_DIRENT_H
# include <sys/types.h>
# include <dirent.h>
# ifndef direct
# define direct dirent
# endif
# endif
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#if !defined(__MINGW32__) && !defined(_MSC_VER)
/* MW. We also need ioctl.h here I think */
#include <sys/ioctl.h>
#endif
/* Be sure the ioctls get included in the following */
#ifdef HAVE_SGTTY_H
#include <sgtty.h>
#else
#ifdef HAVE_TERMIO_H
#include <termio.h>
#else
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#endif
#endif
#define CNTRL_D '\004'
#define ESCAPE '\033'
#define NCLASSES 32
bool cp_nocc; /* Don't do command completion. */
static struct ccom *commands = NULL; /* The available commands. */
static struct ccom *keywords[NCLASSES]; /* Keywords. */
#ifdef TIOCSTI /* va, functions used in this branch only */
static struct ccom *getccom(char *first);
static wordlist *ccfilec(char *buf);
static wordlist *ccmatch(char *word, struct ccom **dbase);
static void printem(wordlist *wl);
#endif
static wordlist *cctowl(struct ccom *cc, bool sib);
static struct ccom *clookup(register const char *word, struct ccom **dd, bool pref,
bool create);
/* MW. I need top node in cdelete */
static void cdelete(struct ccom *node, struct ccom **top);
#ifdef TIOCSTI
void
cp_ccom(wordlist *wlist, char *buf, bool esc)
{
struct ccom *cc;
wordlist *a, *pmatches = NULL;
char wbuf[BSIZE_SP], *s;
int i = 0;
int j, arg;
buf = cp_unquote(copy(buf));
if (wlist) { /* Not the first word. */
cc = getccom(wlist->wl_word);
if (cc && cc->cc_invalid)
cc = NULL;
arg = wl_length(wlist) - 1;
if (arg > 3)
arg = 3;
/* First filenames. */
if (cc && (cc->cc_kwords[arg] & 1)) {
pmatches = ccfilec(buf);
s = strrchr(buf, '/');
i = (int) strlen(s ? s + 1 : buf);
if ((*buf == '~') && !strchr(buf, '/'))
i--;
}
/* The keywords. */
for (j = 1; j < NCLASSES; j++)
if (cc && (cc->cc_kwords[arg] & (1 << j))) {
/* Find all the matching keywords. */
a = ccmatch(buf, &keywords[j]);
i = (int) strlen(buf);
if (pmatches)
pmatches = wl_append(pmatches, a);
else
pmatches = a;
}
wl_sort(pmatches);
} else {
pmatches = ccmatch(buf, &commands);
i = (int) strlen(buf);
}
tfree(buf); /*CDHW*/
if (!esc) {
printem(pmatches);
wl_free(pmatches);
return;
}
if (pmatches == NULL) {
(void) putchar('\07');
(void) fflush(cp_out);
return;
}
if (pmatches->wl_next == NULL) {
(void) strcpy(wbuf, &pmatches->wl_word[i]);
goto found;
}
/* Now we know which words might work. Extend the command as much
* as possible, then TIOCSTI the characters out.
*/
for (j = 0;; j++, i++) {
wbuf[j] = pmatches->wl_word[i];
for (a = pmatches->wl_next; a; a = a->wl_next)
if (a->wl_word[i] != wbuf[j]) {
(void) putchar('\07');
(void) fflush(cp_out);
wbuf[j] = '\0';
goto found;
}
if (wbuf[j] == '\0')
goto found;
}
found:
for (i = 0; wbuf[i]; i++)
(void) ioctl(fileno(cp_in), TIOCSTI, &wbuf[i]);
wl_free(pmatches);
}
/* Figure out what the command is, given the name. Returns NULL if there
* is no such command in the command list. This is tricky, because we have
* to do a preliminary history and alias parse. (Or at least we should.)
*/
static struct ccom *
getccom(char *first)
{
struct alias *al;
int ntries = 21;
/* First look for aliases. Just interested in the first word...
* Don't bother doing history yet -- that might get complicated.
*/
while (ntries-- > 0) {
for (al = cp_aliases; al; al = al->al_next)
if (eq(first, al->al_name)) {
first = al->al_text->wl_word;
break;
}
if (al == NULL)
break;
}
if (ntries == 0) {
fprintf(cp_err, "\nError: alias loop.\n");
return (NULL);
}
return (clookup(first, &commands, FALSE, FALSE));
}
/* Figure out what files match the prefix. */
static wordlist *
ccfilec(char *buf)
{
DIR *wdir;
char *lcomp, *dir;
struct direct *de;
wordlist *wl = NULL;
struct passwd *pw;
buf = copy(buf); /* Don't mangle anything... */
lcomp = strrchr(buf, '/');
if (lcomp == NULL) {
dir = ".";
lcomp = buf;
if (*buf == cp_til) { /* User name completion... */
buf++;
while ((pw = getpwent()) != NULL)
if (prefix(buf, pw->pw_name))
wl = wl_cons(copy(pw->pw_name), wl);
(void) endpwent();
return (wl);
}
} else {
dir = buf;
*lcomp = '\0';
lcomp++;
if (*dir == cp_til) {
dir = cp_tildexpand(dir);
if (dir == NULL)
return (NULL);
}
}
if (!(wdir = opendir(dir)))
return (NULL);
while ((de = readdir(wdir)) != NULL)
if ((prefix(lcomp, de->d_name)) && (*lcomp || (*de->d_name != '.')))
wl = wl_cons(copy(de->d_name), wl);
(void) closedir(wdir);
wl_sort(wl);
return (wl);
}
/* See what keywords or commands match the prefix. Check extra also
* for matches, if it is non-NULL. Return a wordlist which is in
* alphabetical order. Note that we have to call this once for each
* class.
*/
static wordlist *
ccmatch(char *word, struct ccom **dbase)
{
wordlist *wl;
register struct ccom *cc;
cc = clookup(word, dbase, TRUE, FALSE);
if (cc) {
if (*word) /* This is a big drag. */
wl = cctowl(cc, FALSE);
else
wl = cctowl(cc, TRUE);
} else {
wl = NULL;
}
return (wl);
}
/* Print the words in the wordlist in columns. They are already
* sorted... This is a hard thing to do with wordlists...
*/
static void
printem(wordlist *wl)
{
wordlist *ww;
int maxl = 0, num, i, j, k, width = 79, ncols, nlines;
(void) putchar('\n');
if (wl == NULL)
return;
num = wl_length(wl);
for (ww = wl; ww; ww = ww->wl_next) {
j = (int) strlen(ww->wl_word);
if (j > maxl)
maxl = j;
}
if (++maxl % 8)
maxl += 8 - (maxl % 8);
ncols = width / maxl;
if (ncols == 0)
ncols = 1;
nlines = num / ncols + (num % ncols ? 1 : 0);
for (k = 0; k < nlines; k++) {
for (i = 0; i < ncols; i++) {
j = i * nlines + k;
if (j < num)
fprintf(cp_out, "%-*s", maxl, wl_nthelem(j, wl)->wl_word);
else
break;
}
(void) putchar('\n');
}
}
#else /* if not TIOCSTI */
void
cp_ccom(wordlist *wlist, char *buf, bool esc)
{
NG_IGNORE(wlist);
NG_IGNORE(buf);
NG_IGNORE(esc);
}
#endif
static wordlist *
cctowl(struct ccom *cc, bool sib)
{
wordlist *wl;
if (!cc)
return (NULL);
wl = cctowl(cc->cc_child, TRUE);
if (!cc->cc_invalid)
wl = wl_cons(copy(cc->cc_name), wl);
if (sib)
wl = wl_append(wl, cctowl(cc->cc_sibling, TRUE));
return (wl);
}
/* We use this in com_device... */
wordlist *
cp_cctowl(struct ccom *stuff)
{
return (cctowl(stuff, TRUE));
}
/* Turn on and off the escape break character and cooked mode. */
void
cp_ccon(bool on)
{
#ifdef TIOCSTI
#ifdef HAVE_SGTTY_H
static bool ison = FALSE;
struct tchars tbuf;
struct sgttyb sbuf;
if (cp_nocc || !cp_interactive || (ison == on))
return;
ison = on;
/* Set the terminal up -- make escape the break character, and
* make sure we aren't in raw or cbreak mode. Hope the (void)
* ioctl's won't fail.
*/
(void) ioctl(fileno(cp_in), TIOCGETC, &tbuf);
if (on)
tbuf.t_brkc = ESCAPE;
else
tbuf.t_brkc = '\0';
(void) ioctl(fileno(cp_in), TIOCSETC, &tbuf);
(void) ioctl(fileno(cp_in), TIOCGETP, &sbuf);
sbuf.sg_flags &= ~(RAW|CBREAK);
(void) ioctl(fileno(cp_in), TIOCSETP, &sbuf);
#else
# ifdef HAVE_TERMIO_H
# define TERM_GET TCGETA
# define TERM_SET TCSETA
static struct termio sbuf;
static struct termio OS_Buf;
# else
# ifdef HAVE_TERMIOS_H
# define TERM_GET TCGETS
# define TERM_SET TCSETS
static struct termios sbuf;
static struct termios OS_Buf;
# endif
# endif
#ifdef TERM_GET
static bool ison = FALSE;
if (cp_nocc || !cp_interactive || (ison == on))
return;
ison = on;
if (ison == TRUE) {
#if HAVE_TCGETATTR
tcgetattr(fileno(cp_in), &OS_Buf);
#else
(void) ioctl(fileno(cp_in), TERM_GET, &OS_Buf);
#endif
sbuf = OS_Buf;
sbuf.c_cc[VEOF] = '\0';
sbuf.c_cc[VEOL] = ESCAPE;
sbuf.c_cc[VEOL2] = CNTRL_D;
#if HAVE_TCSETATTR
tcsetattr(fileno(cp_in), TCSANOW, &sbuf);
#else
(void) ioctl(fileno(cp_in), TERM_SET, &sbuf);
#endif
} else {
#ifdef HAVE_TCSETATTR
tcsetattr(fileno(cp_in), TCSANOW, &OS_Buf);
#else
(void) ioctl(fileno(cp_in), TERM_SET, &OS_Buf);
#endif
}
# endif
#endif
#else
NG_IGNORE(on);
#endif
}
/* The following routines deal with the command and keyword databases.
* Say whether a given word exists in the command database.
*/
bool
cp_comlook(char *word)
{
if (word && *word && clookup(word, &commands, FALSE, FALSE))
return (TRUE);
else
return (FALSE);
}
/* Add a command to the database, with the given keywords and filename
* flag. */
void
cp_addcomm(char *word, long int bits0, long int bits1, long int bits2, long int bits3)
{
struct ccom *cc;
if(cp_nocc)
return;
cc = clookup(word, &commands, FALSE, TRUE);
cc->cc_invalid = 0;
cc->cc_kwords[0] = bits0;
cc->cc_kwords[1] = bits1;
cc->cc_kwords[2] = bits2;
cc->cc_kwords[3] = bits3;
}
/* Remove a command from the database. */
void
cp_remcomm(char *word)
{
struct ccom *cc;
cc = clookup(word, &commands, FALSE, FALSE);
if (cc)
cdelete(cc, &commands);
}
/* Add a keyword to the database. */
void
cp_addkword(int kw_class, char *word)
{
struct ccom *cc;
if(cp_nocc)
return;
if ((kw_class < 1) || (kw_class >= NCLASSES)) {
fprintf(cp_err, "cp_addkword: Internal Error: bad class %d\n",
kw_class);
return;
}
/* word = copy(word); va: not necessary, clookup copies itself (memory leak) */
cc = clookup(word, &keywords[kw_class], FALSE, TRUE);
cc->cc_invalid = 0;
}
void
cp_destroy_keywords(void)
{
int i;
for (i = 0; i < NCLASSES; i++)
throwaway(keywords[i]);
throwaway(commands);
}
/* Remove a keyword from the database. */
void
cp_remkword(int kw_class, const char *word)
{
struct ccom *cc;
if ((kw_class < 1) || (kw_class >= NCLASSES)) {
fprintf(cp_err, "cp_remkword: Internal Error: bad class %d\n",
kw_class);
return;
}
cc = clookup(word, &keywords[kw_class], FALSE, FALSE);
if (cc)
cdelete(cc, &keywords[kw_class]);
}
/* This routine is used when there are several keyword sets that are
* to be switched between rapidly. The return value is the old tree at
* that position, and the keyword class given is set to the argument.
*/
struct ccom *
cp_kwswitch(int kw_class, struct ccom *tree)
{
struct ccom *old;
if ((kw_class < 1) || (kw_class >= NCLASSES)) {
fprintf(cp_err, "cp_addkword: Internal Error: bad class %d\n",
kw_class);
return (NULL);
}
old = keywords[kw_class];
keywords[kw_class] = tree;
return (old);
}
/* Throw away all the stuff and prepare to rebuild it from scratch... */
void
cp_ccrestart(bool kwords)
{
NG_IGNORE(kwords);
/* Ack. */
}
void
throwaway(struct ccom *dbase)
{
if (!dbase)
return; /* va: security first */
if (dbase->cc_child)
throwaway(dbase->cc_child);
if (dbase->cc_sibling)
throwaway(dbase->cc_sibling);
tfree(dbase->cc_name); /* va: also tfree dbase->cc_name (memory leak) */
tfree(dbase);
}
/* Look up a word in the database. Because of the way the tree is set
* up, this also works for looking up all words with a given prefix
* (if the pref arg is TRUE). If create is TRUE, then the node is
* created if it doesn't already exist.
*/
static struct ccom *
clookup(register const char *word, struct ccom **dd, bool pref, bool create)
{
register struct ccom *place = *dd, *tmpc;
int ind = 0, i;
char buf[BSIZE_SP];
if (!place) {
/* This is the first time we were called. */
if (!create) {
return (NULL);
} else {
*dd = place = TMALLOC(struct ccom, 1);
ZERO(place, struct ccom);
buf[0] = *word;
buf[1] = '\0';
place->cc_name = copy(buf);
if (word[0] == '\0') {
fprintf(stderr, "ERROR, internal error, clookup() needs fixing to process the empty string\n");
controlled_exit(EXIT_FAILURE);
}
if (word[1])
place->cc_invalid = 1;
}
}
while (word[ind]) {
/* Walk down the sibling list until we find a node that
* matches 'word' to 'ind' places.
*/
while ((place->cc_name[ind] < word[ind]) && place->cc_sibling)
place = place->cc_sibling;
if (place->cc_name[ind] < word[ind]) {
/* This line doesn't go out that far... */
if (create) {
place->cc_sibling = TMALLOC(struct ccom, 1);
ZERO(place->cc_sibling, struct ccom);
place->cc_sibling->cc_ysibling = place;
place->cc_sibling->cc_parent = place->cc_parent;
place = place->cc_sibling;
place->cc_name = TMALLOC(char, ind + 2);
for (i = 0; i < ind + 1; i++)
place->cc_name[i] = word[i];
place->cc_name[ind + 1] = '\0';
place->cc_invalid = 1;
} else {
return (NULL);
}
} else if (place->cc_name[ind] > word[ind]) {
if (create) {
/* Put this one between place and its pred. */
tmpc = TMALLOC(struct ccom, 1);
ZERO(tmpc, struct ccom);
tmpc->cc_parent = place->cc_parent;
tmpc->cc_sibling = place;
tmpc->cc_ysibling = place->cc_ysibling;
place->cc_ysibling = tmpc;
place = tmpc;
if (tmpc->cc_ysibling)
tmpc->cc_ysibling->cc_sibling = tmpc;
else if (tmpc->cc_parent)
tmpc->cc_parent->cc_child = tmpc;
else
*dd = place;
place->cc_name = TMALLOC(char, ind + 2);
for (i = 0; i < ind + 1; i++)
place->cc_name[i] = word[i];
place->cc_name[ind + 1] = '\0';
place->cc_invalid = 1;
} else {
return (NULL);
}
}
/* place now points to that node that matches the word for
* ind + 1 characters.
*/
if (word[ind + 1]) { /* More to go... */
if (!place->cc_child) {
/* No children, maybe make one and go on. */
if (create) {
tmpc = TMALLOC(struct ccom, 1);
ZERO(tmpc, struct ccom);
tmpc->cc_parent = place;
place->cc_child = tmpc;
place = tmpc;
place->cc_name = TMALLOC(char, ind + 3);
for (i = 0; i < ind + 2; i++)
place->cc_name[i] = word[i];
place->cc_name[ind + 2] = '\0';
if (word[ind + 2])
place->cc_invalid = 1;
} else {
return (NULL);
}
} else {
place = place->cc_child;
}
ind++;
} else {
break;
}
}
if (!pref && !create && place->cc_invalid) {
/* This is no good, we want a real word. */
return (NULL);
}
return (place);
}
/* Delete a node from the tree. Returns the new tree... */
/* MW. It is quite difficoult to free() everything right, but...
* Anyway this could be more optimal, I think */
static void
cdelete(struct ccom *node, struct ccom **top)
{
/* if cc_child exist only mark as deleted */
node->cc_invalid = 1;
if (node->cc_child)
return;
/* fix cc_sibling */
if (node->cc_sibling)
node->cc_sibling->cc_ysibling = node->cc_ysibling;
if (node->cc_ysibling)
node->cc_ysibling->cc_sibling = node->cc_sibling;
/* if we have cc_parent, check if it should not be removed too */
if (node->cc_parent) {
/* this node will be free() */
if (node->cc_parent->cc_child == node) {
if (node->cc_ysibling)
node->cc_parent->cc_child = node->cc_ysibling;
else
node->cc_parent->cc_child = node->cc_sibling;
}
/* free parent only if it is invalid */
if (node->cc_parent->cc_invalid == 1)
cdelete(node->cc_parent, top);
}
/* now free() everything and check the top */
if (node == *top)
*top = node->cc_sibling;
tfree(node->cc_name); /* va: we should allways use tfree */
tfree(node);
}