/* * zle_thingy.c - thingies * * This file is part of zsh, the Z shell. * * Copyright (c) 1992-1997 Paul Falstad * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any * purpose, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * In no event shall Paul Falstad or the Zsh Development Group be liable * to any party for direct, indirect, special, incidental, or consequential * damages arising out of the use of this software and its documentation, * even if Paul Falstad and the Zsh Development Group have been advised of * the possibility of such damage. * * Paul Falstad and the Zsh Development Group specifically disclaim any * warranties, including, but not limited to, the implied warranties of * merchantability and fitness for a particular purpose. The software * provided hereunder is on an "as is" basis, and Paul Falstad and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ #include "zle.mdh" #include "zle_thingy.pro" /* * Thingies: * * From the user's point of view, a thingy is just a string. Internally, * the thingy is a struct thingy; these structures are in a hash table * indexed by the string the user sees. This hash table contains all * thingies currently referenced anywhere; each has a reference count, * and is deleted when it becomes unused. Being the name of a function * counts as a reference. * * The DISABLED flag on a thingy indicates that it is not the name of a * widget. This makes it easy to generate completion lists; * looking only at the `enabled' nodes makes the thingy table look like * a table of widgets. */ /* Hashtable of thingies. Enabled nodes are those that refer to widgets. */ /**/ mod_export HashTable thingytab; /**********************************/ /* hashtable management functions */ /**********************************/ /**/ static void createthingytab(void) { thingytab = newhashtable(199, "thingytab", NULL); thingytab->hash = hasher; thingytab->emptytable = emptythingytab; thingytab->filltable = NULL; thingytab->cmpnodes = strcmp; thingytab->addnode = addhashnode; thingytab->getnode = gethashnode; thingytab->getnode2 = gethashnode2; thingytab->removenode = removehashnode; thingytab->disablenode = NULL; thingytab->enablenode = NULL; thingytab->freenode = freethingynode; thingytab->printnode = NULL; } /**/ static void emptythingytab(HashTable ht) { /* This will only be called when deleting the thingy table, which * * is only done to unload the zle module. A normal emptytable() * * function would free all the thingies, but we don't want to do * * that because some of them are the known thingies in the fixed * * `thingies' table. As the module cleanup code deletes all the * * keymaps and so on before deleting the thingy table, we can * * just remove the user-defined widgets and then be sure that * * *all* the thingies left are the fixed ones. This has the side * * effect of freeing all resources used by user-defined widgets. */ scanhashtable(thingytab, 0, 0, DISABLED, scanemptythingies, 0); } /**/ static void scanemptythingies(HashNode hn, int flags) { Thingy t = (Thingy) hn; /* Mustn't unbind internal widgets -- we wouldn't want to free the * * memory they use. */ if(!(t->widget->flags & WIDGET_INT)) unbindwidget(t, 1); } /**/ static Thingy makethingynode(void) { Thingy t = (Thingy) zcalloc(sizeof(*t)); t->flags = DISABLED; return t; } /**/ static void freethingynode(HashNode hn) { Thingy th = (Thingy) hn; zsfree(th->nam); zfree(th, sizeof(*th)); } /************************/ /* referencing thingies */ /************************/ /* It is important to maintain the reference counts on thingies. When * * copying a reference to a thingy, wrap the copy in refthingy(), to * * increase its reference count. When removing a reference, * * unrefthingy() it. Both of these functions handle NULL arguments * * correctly. */ /**/ mod_export Thingy refthingy(Thingy th) { if(th) th->rc++; return th; } /**/ void unrefthingy(Thingy th) { if(th && !--th->rc) thingytab->freenode(thingytab->removenode(thingytab, th->nam)); } /* Use rthingy() to turn a string into a thingy. It increases the reference * * count, after creating the thingy structure if necessary. */ /**/ Thingy rthingy(char *nam) { Thingy t = (Thingy) thingytab->getnode2(thingytab, nam); if(!t) thingytab->addnode(thingytab, ztrdup(nam), t = makethingynode()); return refthingy(t); } /***********/ /* widgets */ /***********/ /* * Each widget is attached to one or more thingies. Each thingy * names either zero or one widgets. Thingies that name a widget * are treated as being referenced. The widget type, flags and pointer * are stored in a separate structure pointed to by the thingies. Each * thingy also has a pointer to the `next' thingy (in a circular list) * that references the same widget. The DISABLED flag is unset in these * thingies. */ /* Bind a widget to a thingy. The thingy's reference count must already * * have been incremented. The widget may already be bound to other * * thingies; if it is not, then its `first' member must be NULL. Return * * is 0 on success, or -1 if the thingy has the TH_IMMORTAL flag set. */ /**/ static int bindwidget(Widget w, Thingy t) { if(t->flags & TH_IMMORTAL) { unrefthingy(t); return -1; } if(!(t->flags & DISABLED)) { if(t->widget == w) return 0; unbindwidget(t, 1); } if(w->first) { t->samew = w->first->samew; w->first->samew = t; } else { w->first = t; t->samew = t; } t->widget = w; t->flags &= ~DISABLED; return 0; } /* Unbind a widget from a thingy. This decrements the thingy's reference * * count. The widget will be destroyed if this is its last name. * * TH_IMMORTAL thingies won't be touched, unless override is non-zero. * * Returns 0 on success, or -1 if the thingy is protected. If the thingy * * doesn't actually reference a widget, this is considered successful. */ /**/ static int unbindwidget(Thingy t, int override) { Widget w; if(t->flags & DISABLED) return 0; if(!override && (t->flags & TH_IMMORTAL)) return -1; w = t->widget; if(t->samew == t) freewidget(w); else { Thingy p; for(p = w->first; p->samew != t; p = p->samew) ; w->first = p; /* optimised for deletezlefunction() */ p->samew = t->samew; } t->flags &= ~TH_IMMORTAL; t->flags |= DISABLED; unrefthingy(t); return 0; } /* Free a widget. */ /**/ static void freewidget(Widget w) { if (w->flags & WIDGET_NCOMP) { zsfree(w->u.comp.wid); zsfree(w->u.comp.func); } else if(!(w->flags & WIDGET_INT)) zsfree(w->u.fnnam); zfree(w, sizeof(*w)); } /* Add am internal widget provided by a module. The name given is the * * canonical one, which must not begin with a dot. The widget is first * * bound to the dotted canonical name; if that name is already taken by * * an internal widget, failure is indicated. The same widget is then * * bound to the canonical name, and a pointer to the widget structure * * returned. */ /**/ mod_export Widget addzlefunction(char *name, ZleIntFunc ifunc, int flags) { VARARR(char, dotn, strlen(name) + 2); Widget w; Thingy t; if(name[0] == '.') return NULL; dotn[0] = '.'; strcpy(dotn + 1, name); t = (Thingy) thingytab->getnode(thingytab, dotn); if(t && (t->flags & TH_IMMORTAL)) return NULL; w = zalloc(sizeof(*w)); w->flags = WIDGET_INT | flags; w->first = NULL; w->u.fn = ifunc; t = rthingy(dotn); bindwidget(w, t); t->flags |= TH_IMMORTAL; bindwidget(w, rthingy(name)); return w; } /* Delete an internal widget provided by a module. Don't try to delete * * a widget from the fixed table -- it would be bad. (Thanks, Egon.) */ /**/ mod_export void deletezlefunction(Widget w) { Thingy p, n; p = w->first; while(1) { n = p->samew; if(n == p) { unbindwidget(p, 1); return; } unbindwidget(p, 1); p = n; } } /***************/ /* zle builtin */ /***************/ /* * The available operations are: * * -l list widgets/test for existence * -D delete widget names * -A link the two named widgets (2 arguments) * -C create completion widget (3 arguments) * -N create new user-defined widget (1 or 2 arguments) * invoke a widget (1 argument) */ /**/ int bin_zle(char *name, char **args, Options ops, int func) { static struct opn { char o; int (*func) _((char *, char **, Options, char)); int min, max; } const opns[] = { { 'l', bin_zle_list, 0, -1 }, { 'D', bin_zle_del, 1, -1 }, { 'A', bin_zle_link, 2, 2 }, { 'N', bin_zle_new, 1, 2 }, { 'C', bin_zle_complete, 3, 3 }, { 'R', bin_zle_refresh, 0, -1 }, { 'M', bin_zle_mesg, 1, 1 }, { 'U', bin_zle_unget, 1, 1 }, { 'K', bin_zle_keymap, 1, 1 }, { 'I', bin_zle_invalidate, 0, 0 }, { 'F', bin_zle_fd, 0, 2 }, { 0, bin_zle_call, 0, -1 }, }; struct opn const *op, *opp; int n; /* select operation and ensure no clashing arguments */ for(op = opns; op->o && !OPT_ISSET(ops,STOUC(op->o)); op++) ; if(op->o) for(opp = op; (++opp)->o; ) if(OPT_ISSET(ops,STOUC(opp->o))) { zwarnnam(name, "incompatible operation selection options", NULL, 0); return 1; } /* check number of arguments */ for(n = 0; args[n]; n++) ; if(n < op->min) { zwarnnam(name, "not enough arguments for -%c", NULL, op->o); return 1; } else if(op->max != -1 && n > op->max) { zwarnnam(name, "too many arguments for -%c", NULL, op->o); return 1; } /* pass on the work to the operation function */ return op->func(name, args, ops, op->o); } /**/ static int bin_zle_list(char *name, char **args, Options ops, char func) { if (!*args) { scanhashtable(thingytab, 1, 0, DISABLED, scanlistwidgets, (OPT_ISSET(ops,'a') ? -1 : OPT_ISSET(ops,'L'))); return 0; } else { int ret = 0; Thingy t; for (; *args && !ret; args++) { if (!(t = (Thingy) thingytab->getnode2(thingytab, *args)) || (!OPT_ISSET(ops,'a') && (t->widget->flags & WIDGET_INT))) ret = 1; } return ret; } } /**/ static int bin_zle_refresh(char *name, char **args, Options ops, char func) { char *s = statusline; int sl = statusll, ocl = clearlist; if (!zleactive) return 1; statusline = NULL; statusll = 0; if (*args) { if (**args) { statusline = *args; statusll = strlen(statusline); } if (*++args) { LinkList l = newlinklist(); int zmultsav = zmult; for (; *args; args++) addlinknode(l, *args); zmult = 1; listlist(l); if (statusline) lastlistlen++; showinglist = clearlist = 0; zmult = zmultsav; } else if (OPT_ISSET(ops,'c')) { clearlist = 1; lastlistlen = 0; } } else if (OPT_ISSET(ops,'c')) { clearlist = listshown = 1; lastlistlen = 0; } zrefresh(); clearlist = ocl; statusline = s; statusll = sl; return 0; } /**/ static int bin_zle_mesg(char *name, char **args, Options ops, char func) { if (!zleactive) { zwarnnam(name, "can only be called from widget function", NULL, 0); return 1; } showmsg(*args); if (sfcontext != SFC_WIDGET) zrefresh(); return 0; } /**/ static int bin_zle_unget(char *name, char **args, Options ops, char func) { char *b = *args, *p = b + strlen(b); if (!zleactive) { zwarnnam(name, "can only be called from widget function", NULL, 0); return 1; } while (p > b) ungetkey((int) *--p); return 0; } /**/ static int bin_zle_keymap(char *name, char **args, Options ops, char func) { if (!zleactive) { zwarnnam(name, "can only be called from widget function", NULL, 0); return 1; } return selectkeymap(*args, 0); } /**/ static void scanlistwidgets(HashNode hn, int list) { Thingy t = (Thingy) hn; Widget w = t->widget; if(list < 0) { printf("%s\n", hn->nam); return; } if(w->flags & WIDGET_INT) return; if(list) { printf("zle -%c ", (w->flags & WIDGET_NCOMP) ? 'C' : 'N'); if(t->nam[0] == '-') fputs("-- ", stdout); quotedzputs(t->nam, stdout); if (w->flags & WIDGET_NCOMP) { fputc(' ', stdout); quotedzputs(w->u.comp.wid, stdout); fputc(' ', stdout); quotedzputs(w->u.comp.func, stdout); } else if(strcmp(t->nam, w->u.fnnam)) { fputc(' ', stdout); quotedzputs(w->u.fnnam, stdout); } } else { nicezputs(t->nam, stdout); if (w->flags & WIDGET_NCOMP) { fputs(" -C ", stdout); nicezputs(w->u.comp.wid, stdout); fputc(' ', stdout); nicezputs(w->u.comp.func, stdout); } else if(strcmp(t->nam, w->u.fnnam)) { fputs(" (", stdout); nicezputs(w->u.fnnam, stdout); fputc(')', stdout); } } putchar('\n'); } /**/ static int bin_zle_del(char *name, char **args, Options ops, char func) { int ret = 0; do { Thingy t = (Thingy) thingytab->getnode(thingytab, *args); if(!t) { zwarnnam(name, "no such widget `%s'", *args, 0); ret = 1; } else if(unbindwidget(t, 0)) { zwarnnam(name, "widget name `%s' is protected", *args, 0); ret = 1; } } while(*++args); return ret; } /**/ static int bin_zle_link(char *name, char **args, Options ops, char func) { Thingy t = (Thingy) thingytab->getnode(thingytab, args[0]); if(!t) { zwarnnam(name, "no such widget `%s'", args[0], 0); return 1; } else if(bindwidget(t->widget, rthingy(args[1]))) { zwarnnam(name, "widget name `%s' is protected", args[1], 0); return 1; } return 0; } /**/ static int bin_zle_new(char *name, char **args, Options ops, char func) { Widget w = zalloc(sizeof(*w)); w->flags = 0; w->first = NULL; w->u.fnnam = ztrdup(args[1] ? args[1] : args[0]); if(!bindwidget(w, rthingy(args[0]))) return 0; freewidget(w); zwarnnam(name, "widget name `%s' is protected", args[0], 0); return 1; } /**/ static int bin_zle_complete(char *name, char **args, Options ops, char func) { Thingy t; Widget w, cw; if (!require_module(name, "zsh/complete", 0, 0)) { zwarnnam(name, "can't load complete module", NULL, 0); return 1; } t = rthingy((args[1][0] == '.') ? args[1] : dyncat(".", args[1])); cw = t->widget; unrefthingy(t); if (!cw || !(cw->flags & ZLE_ISCOMP)) { zwarnnam(name, "invalid widget `%s'", args[1], 0); return 1; } w = zalloc(sizeof(*w)); w->flags = WIDGET_NCOMP|ZLE_MENUCMP|ZLE_KEEPSUFFIX; w->first = NULL; w->u.comp.fn = cw->u.fn; w->u.comp.wid = ztrdup(args[1]); w->u.comp.func = ztrdup(args[2]); if (bindwidget(w, rthingy(args[0]))) { freewidget(w); zwarnnam(name, "widget name `%s' is protected", args[0], 0); return 1; } hascompwidgets++; return 0; } /**/ static int bin_zle_call(char *name, char **args, Options ops, char func) { Thingy t; struct modifier modsave; int ret, saveflag = 0; char *wname = *args++; if (!wname) { if (saveflag) zmod = modsave; return (!zleactive || incompctlfunc || incompfunc || sfcontext != SFC_WIDGET); } if(!zleactive || incompctlfunc || incompfunc || sfcontext != SFC_WIDGET) { zwarnnam(name, "widgets can only be called when ZLE is active", NULL, 0); return 1; } while (*args && **args == '-') { char *num; if (!args[0][1] || args[0][1] == '-') { args++; break; } while (*++(*args)) { switch (**args) { case 'n': num = args[0][1] ? args[0]+1 : args[1]; if (!num) { zwarnnam(name, "number expected after -%c", NULL, **args); return 1; } if (!args[0][1]) *++args = "" - 1; modsave = zmod; saveflag = 1; zmod.mult = atoi(num); zmod.flags |= MOD_MULT; break; case 'N': modsave = zmod; saveflag = 1; zmod.mult = 1; zmod.flags &= ~MOD_MULT; break; default: zwarnnam(name, "unknown option: %s", *args, 0); return 1; } } args++; } t = rthingy(wname); ret = execzlefunc(t, args); unrefthingy(t); if (saveflag) zmod = modsave; return ret; } /**/ static int bin_zle_invalidate(char *name, char **args, Options ops, char func) { if (zleactive) { if (!trashedzle) trashzle(); return 0; } else return 1; } /**/ static int bin_zle_fd(char *name, char **args, Options ops, char func) { int fd = 0, i, found = 0; char *endptr; if (*args) { fd = (int)zstrtol(*args, &endptr, 10); if (*endptr || fd < 0) { zwarnnam(name, "Bad file descriptor number for -F: %s", *args, 0); return 1; } } if (OPT_ISSET(ops,'L') || !*args) { /* Listing handlers. */ if (*args && args[1]) { zwarnnam(name, "too many arguments for -FL", NULL, 0); return 1; } for (i = 0; i < nwatch; i++) { if (*args && watch_fds[i] != fd) continue; found = 1; printf("%s -F %d %s\n", name, watch_fds[i], watch_funcs[i]); } /* only return status 1 if fd given and not found */ return *args && !found; } if (args[1]) { /* Adding or replacing a handler */ char *funcnam = ztrdup(args[1]); if (nwatch) { for (i = 0; i < nwatch; i++) { if (watch_fds[i] == fd) { zsfree(watch_funcs[i]); watch_funcs[i] = funcnam; found = 1; break; } } } if (!found) { /* zrealloc handles NULL pointers, so OK for first time through */ int newnwatch = nwatch+1; watch_fds = (int *)zrealloc(watch_fds, newnwatch * sizeof(int)); watch_funcs = (char **)zrealloc(watch_funcs, (newnwatch+1) * sizeof(char *)); watch_fds[nwatch] = fd; watch_funcs[nwatch] = funcnam; watch_funcs[newnwatch] = NULL; nwatch = newnwatch; } } else { /* Deleting a handler */ for (i = 0; i < nwatch; i++) { if (watch_fds[i] == fd) { int newnwatch = nwatch-1; int *new_fds; char **new_funcs; zsfree(watch_funcs[i]); if (newnwatch) { new_fds = zalloc(newnwatch*sizeof(int)); new_funcs = zalloc((newnwatch+1)*sizeof(char*)); if (i) { memcpy(new_fds, watch_fds, i*sizeof(int)); memcpy(new_funcs, watch_funcs, i*sizeof(char *)); } if (i < newnwatch) { memcpy(new_fds+i, watch_fds+i+1, (newnwatch-i)*sizeof(int)); memcpy(new_funcs+i, watch_funcs+i+1, (newnwatch-i)*sizeof(char *)); } new_funcs[newnwatch] = NULL; } else { new_fds = NULL; new_funcs = NULL; } zfree(watch_fds, nwatch*sizeof(int)); zfree(watch_funcs, (nwatch+1)*sizeof(char *)); watch_fds = new_fds; watch_funcs = new_funcs; nwatch = newnwatch; found = 1; break; } } if (!found) { zwarnnam(name, "No handler installed for fd %d", NULL, fd); return 1; } } return 0; } /*******************/ /* initialiasation */ /*******************/ /**/ void init_thingies(void) { Thingy t; createthingytab(); for(t = thingies; t->nam; t++) thingytab->addnode(thingytab, t->nam, t); }