ngspice/src/frontend/parser/complete.c

737 lines
19 KiB
C
Raw Normal View History

2000-04-27 22:03:57 +02:00
/**********
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 <config.h>
#include "ngspice.h"
#include "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
2002-01-03 23:44:21 +01:00
#ifndef __MINGW32__
2000-04-27 22:03:57 +02:00
/* MW. We also need ioctl.h here I think */
#include <sys/ioctl.h>
2002-01-03 23:44:21 +01:00
#endif
2000-04-27 22:03:57 +02:00
/* 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. */
/* static declarations */
static struct ccom * getccom(char *first);
static wordlist * ccfilec(char *buf);
static wordlist * ccmatch(char *word, struct ccom **dbase);
static void printem(wordlist *wl);
static wordlist * cctowl(struct ccom *cc, bool sib);
static struct ccom * clookup(register 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);
2000-04-27 22:03:57 +02:00
#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));
2000-04-27 22:03:57 +02:00
cp_wstrip(buf);
if (wlist->wl_next) { /* Not the first word. */
cc = getccom(wlist->wl_word);
if (cc && cc->cc_invalid)
cc = NULL;
arg = wl_length(wlist) - 2;
if (arg > 3)
arg = 3;
/* First filenames. */
if (cc && (cc->cc_kwords[arg] & 1)) {
pmatches = ccfilec(buf);
s =strrchr(buf, '/');
i = strlen(s ? s + 1 : buf);
if ((*buf == '~') && !index(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 = strlen(buf);
if (pmatches)
pmatches = wl_append(pmatches, a);
else
pmatches = a;
}
}
wl_sort(pmatches);
} else {
pmatches = ccmatch(buf, &commands);
i = strlen(buf);
}
tfree(buf); /*CDHW*/
2000-04-27 22:03:57 +02:00
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);
return;
}
/* 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;
2000-04-27 22:03:57 +02:00
struct direct *de;
wordlist *wl = NULL, *t;
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())) {
if (prefix(buf, pw->pw_name)) {
if (wl == NULL) {
wl = alloc(struct wordlist);
wl->wl_next = NULL;
wl->wl_prev = NULL;
} else {
t = wl;
wl = alloc(struct wordlist);
wl->wl_prev = NULL;
wl->wl_next = t;
t->wl_prev = wl;
}
wl->wl_word = copy(pw->pw_name);
}
}
(void) endpwent();
return (wl);
}
} else {
dir = buf;
*lcomp = '\0';
lcomp++;
if (*dir == cp_til) {
dir = cp_tildexpand(dir);
2000-04-27 22:03:57 +02:00
if (dir == NULL)
return (NULL);
}
}
if (!(wdir = opendir(dir)))
return (NULL);
while ((de = readdir(wdir)))
if ((prefix(lcomp, de->d_name)) && (*lcomp ||
(*de->d_name != '.'))) {
if (wl == NULL) {
wl = alloc(struct wordlist);
wl->wl_next = NULL;
wl->wl_prev = NULL;
} else {
t = wl;
wl = alloc(struct wordlist);
wl->wl_next = t;
t->wl_prev = wl;
wl->wl_prev = NULL;
}
wl->wl_word = copy(de->d_name);
}
(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.
2000-04-27 22:03:57 +02:00
*/
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...
2000-04-27 22:03:57 +02:00
*/
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 = 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');
}
return;
}
#else /* if not TIOCSTI */
void
cp_ccom(wordlist *wlist, char *buf, bool esc)
{
return;
}
#endif
static wordlist *
cctowl(struct ccom *cc, bool sib)
{
wordlist *wl, *end;
if (!cc)
return (NULL);
if (!cc->cc_invalid) {
wl = alloc(struct wordlist);
wl->wl_word = copy(cc->cc_name);
wl->wl_prev = NULL;
wl->wl_next = cctowl(cc->cc_child, TRUE);
if (wl->wl_next)
wl->wl_next->wl_prev = wl;
} else
wl = cctowl(cc->cc_child, TRUE);
if (sib) {
if (wl) {
for (end = wl; end->wl_next; end = end->wl_next)
;
end->wl_next = cctowl(cc->cc_sibling, TRUE);
if (end->wl_next)
end->wl_next->wl_prev = wl;
} else
wl = cctowl(cc->cc_sibling, TRUE);
}
return (wl);
}
/* We use this in com_device... */
wordlist *
cp_cctowl(char *stuff)
{
return (cctowl((struct ccom *) 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.
2000-04-27 22:03:57 +02:00
*/
(void) ioctl(fileno(cp_in), TIOCGETC, (char *) &tbuf);
if (on)
tbuf.t_brkc = ESCAPE;
else
tbuf.t_brkc = '\0';
(void) ioctl(fileno(cp_in), TIOCSETC, (char *) &tbuf);
(void) ioctl(fileno(cp_in), TIOCGETP, (char *) &sbuf);
sbuf.sg_flags &= ~(RAW|CBREAK);
(void) ioctl(fileno(cp_in), TIOCSETP, (char *) &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
2000-11-13 20:30:00 +01:00
2000-04-27 22:03:57 +02:00
# define TERM_GET TCGETS
# define TERM_SET TCSETS
static struct termios sbuf;
static struct termios OS_Buf;
# endif
# endif
2002-01-03 23:44:21 +01:00
#ifdef TERM_GET
2000-04-27 22:03:57 +02:00
static bool ison = FALSE;
if (cp_nocc || !cp_interactive || (ison == on))
return;
ison = on;
if (ison == TRUE) {
2002-01-03 23:44:21 +01:00
#if HAVE_TCGETATTR
2000-11-13 20:30:00 +01:00
tcgetattr(fileno(cp_in),&OS_Buf);
#else
2000-04-27 22:03:57 +02:00
(void) ioctl(fileno(cp_in), TERM_GET, (char *) &OS_Buf);
2000-11-13 20:30:00 +01:00
#endif
2000-04-27 22:03:57 +02:00
sbuf = OS_Buf;
sbuf.c_cc[VEOF] = 0;
sbuf.c_cc[VEOL] = ESCAPE;
sbuf.c_cc[VEOL2] = CNTRL_D;
2002-01-03 23:44:21 +01:00
#if HAVE_TCSETATTR
2000-11-13 20:30:00 +01:00
tcsetattr(fileno(cp_in),TCSANOW,&sbuf);
#else
2000-04-27 22:03:57 +02:00
(void) ioctl(fileno(cp_in), TERM_SET, (char *) &sbuf);
2000-11-13 20:30:00 +01:00
#endif
2000-04-27 22:03:57 +02:00
} else {
2002-01-03 23:44:21 +01:00
#ifdef HAVE_TCSETATTR
2000-11-13 20:30:00 +01:00
tcsetattr(fileno(cp_in),TCSANOW,&OS_Buf);
#else
2000-04-27 22:03:57 +02:00
(void) ioctl(fileno(cp_in), TERM_SET, (char *) &OS_Buf);
2000-11-13 20:30:00 +01:00
#endif
2000-04-27 22:03:57 +02:00
}
# endif
#endif
#endif
return;
}
/* 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. */
2000-04-27 22:03:57 +02:00
void
cp_addcomm(char *word, long int bits0, long int bits1, long int bits2, long int bits3)
{
struct ccom *cc;
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;
return;
}
/* 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);
2000-04-27 22:03:57 +02:00
return;
}
/* Add a keyword to the database. */
void
cp_addkword(int class, char *word)
{
struct ccom *cc;
if ((class < 1) || (class >= NCLASSES)) {
fprintf(cp_err, "cp_addkword: Internal Error: bad class %d\n",
class);
return;
}
/* word = copy(word); va: not necessary, clookup copies itself (memory leak) */
2000-04-27 22:03:57 +02:00
cc = clookup(word, &keywords[class], FALSE, TRUE);
cc->cc_invalid = 0;
return;
}
/* Remove a keyword from the database. */
void
cp_remkword(int class, char *word)
{
struct ccom *cc;
if ((class < 1) || (class >= NCLASSES)) {
fprintf(cp_err, "cp_remkword: Internal Error: bad class %d\n",
2000-04-27 22:03:57 +02:00
class);
return;
}
cc = clookup(word, &keywords[class], FALSE, FALSE);
if (cc)
cdelete(cc, &keywords[class]);
2000-04-27 22:03:57 +02:00
return;
}
/* 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.
*/
char *
cp_kwswitch(int class, char *tree)
{
char *old;
if ((class < 1) || (class >= NCLASSES)) {
fprintf(cp_err, "cp_addkword: Internal Error: bad class %d\n",
class);
return (NULL);
}
old = (char *) keywords[class];
keywords[class] = (struct ccom *) tree;
return (old);
}
/* Throw away all the stuff and prepare to rebuild it from scratch... */
void
cp_ccrestart(bool kwords)
{
/* Ack. */
return;
}
void
throwaway(struct ccom *dbase)
{
if (!dbase) return; /* va: security first */
2000-04-27 22:03:57 +02:00
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) */
2000-04-27 22:03:57 +02:00
tfree(dbase);
return;
}
/* 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.
2000-04-27 22:03:57 +02:00
*/
static struct ccom *
clookup(register 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 = alloc(struct ccom);
ZERO(place, struct ccom);
buf[0] = *word;
buf[1] = '\0';
place->cc_name = copy(buf);
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 = alloc(struct ccom);
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(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 = alloc(struct ccom);
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(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 = alloc(struct ccom);
ZERO(tmpc, struct ccom);
tmpc->cc_parent = place;
place->cc_child = tmpc;
place = tmpc;
place->cc_name = tmalloc(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 */
2000-04-27 22:03:57 +02:00
static void
cdelete(struct ccom *node, struct ccom **top)
2000-04-27 22:03:57 +02:00
{
/* 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;
}
if (node->cc_parent->cc_invalid == 1)
/* free parent only if it is invalid */
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);
return;
2000-04-27 22:03:57 +02:00
}