/* * complist.c - completion listing enhancements * * This file is part of zsh, the Z shell. * * Copyright (c) 1999 Sven Wischnowsky * 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 Sven Wischnowsky 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 Sven Wischnowsky and the Zsh Development Group have been advised of * the possibility of such damage. * * Sven Wischnowsky 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 Sven Wischnowsky and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ #include "complist.mdh" #include "complist.pro" /* We use the parameters ZLS_COLORS and ZLS_COLOURS in the same way as * the color ls does. It's just that we don't support the `or' file * type. */ static Widget w_menuselect; static Keymap mskeymap; /* Indixes into the terminal string arrays. */ #define COL_NO 0 #define COL_FI 1 #define COL_DI 2 #define COL_LN 3 #define COL_PI 4 #define COL_SO 5 #define COL_BD 6 #define COL_CD 7 #define COL_EX 8 #define COL_MI 9 #define COL_LC 10 #define COL_RC 11 #define COL_EC 12 #define COL_MA 13 #define NUM_COLS 14 /* Names of the terminal strings. */ static char *colnames[] = { "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "ex", "mi", "lc", "rc", "ec", "ma", NULL }; /* Default values. */ static char *defcols[] = { "0", "0", "1;34", "1;36", "33", "1;35", "1;33", "1;33", "1;32", NULL, "\033[", "m", NULL, "7" }; /* This describes a terminal string for a filename extension. */ typedef struct extcol *Extcol; struct extcol { char *ext; /* the extension */ char *col; /* the terminal color string */ Extcol next; /* the next one in the list */ }; /* This holds all terminal strings. */ typedef struct listcols *Listcols; struct listcols { char *cols[NUM_COLS]; /* strings for file types */ Extcol exts; /* strings for extensions */ }; /* This parses the value of a definition (the part after the `='). * The return value is a pointer to the character after it. */ static char * getcolval(char *s) { char *p; for (p = s; *s && *s != ':'; p++, s++) { if (*s == '\\' && s[1]) { switch (*++s) { case 'a': *p = '\007'; break; case 'n': *p = '\n'; break; case 'b': *p = '\b'; break; case 't': *p = '\t'; break; case 'v': *p = '\v'; break; case 'f': *p = '\f'; break; case 'r': *p = '\r'; break; case 'e': *p = '\033'; break; case '_': *p = ' '; break; case '?': *p = '\177'; break; default: if (*s >= '0' && *s <= '7') { int i = STOUC(*s); if (*++s >= '0' && *s <= '7') { i = (i * 8) + STOUC(*s); if (*++s >= '0' && *s <= '7') i = (i * 8) + STOUC(*s); } *p = (char) i; } else *p = *s; } } else if (*s == '^') { if ((s[1] >= '@' && s[1] <= '_') || (s[1] >= 'a' && s[1] <= 'z')) *p = (char) (STOUC(*s) & ~0x60); else if (s[1] == '?') *p = '\177'; else { *p++ = *s; *p = s[1]; } s++; } else *p = *s; } if (p != s) *p = '\0'; return s; } /* This parses one definition. Return value is a pointer to the * character after it. */ static char * getcoldef(Listcols c, char *s) { if (*s == '*') { Extcol ec; char *n, *p; /* This is for an extension. */ n = ++s; while (*s && *s != '=') s++; if (!*s ) return s; *s++ = '\0'; p = getcolval(s); if (*n) { ec = (Extcol) zhalloc(sizeof(*ec)); ec->ext = n; ec->col = s; ec->next = c->exts; c->exts = ec; } if (*p) *p++ = '\0'; return p; } else { char *n = s, *p, **nn; int i; /* This is for a file type. */ while (*s && *s != '=') s++; if (!*s) return s; *s++ = '\0'; for (i = 0, nn = colnames; *nn; i++, nn++) if (!strcmp(n ,*nn)) break; p = getcolval(s); if (*nn) c->cols[i] = s; if (*p) *p++ = '\0'; return p; } } /* This initializes the given terminal color structure. */ static int getcols(Listcols c) { char *s; int i; if (!(s = getsparam("ZLS_COLORS")) && !(s = getsparam("ZLS_COLOURS"))) { if (!c) return 1; for (i = 0; i < NUM_COLS; i++) c->cols[i] = ""; c->exts = NULL; return 1; } if (!c) return 0; /* We have one of the parameters, use it. */ memset(c, 0, sizeof(*c)); s = dupstring(s); while (*s) s = getcoldef(c, s); /* Use default values for those that aren't set explicitly. */ for (i = 0; i < NUM_COLS; i++) if (!c->cols[i]) c->cols[i] = defcols[i]; /* Default for missing files. */ if (!c->cols[COL_MI]) c->cols[COL_MI] = c->cols[COL_FI]; if (!c->cols[COL_EC]) { char *e = (char *) zhalloc(strlen(c->cols[COL_LC]) + strlen(c->cols[COL_NO]) + strlen(c->cols[COL_RC]) + 1); /* If no `ec' was given, we is `' as the default. */ strcpy(e, c->cols[COL_LC]); strcat(e, c->cols[COL_NO]); strcat(e, c->cols[COL_RC]); c->cols[COL_EC] = e; } return 0; } /* Get the terminal color string for the file with the given name and * file modes. */ static char * getcolstr(Listcols c, char *n, mode_t m) { Extcol e; for (e = c->exts; e; e = e->next) if (strsfx(e->ext, n)) return e->col; if (S_ISDIR(m)) return c->cols[COL_DI]; else if (S_ISLNK(m)) return c->cols[COL_LN]; else if (S_ISFIFO(m)) return c->cols[COL_PI]; else if (S_ISSOCK(m)) return c->cols[COL_SO]; else if (S_ISBLK(m)) return c->cols[COL_BD]; else if (S_ISCHR(m)) return c->cols[COL_CD]; else if (S_ISREG(m) && (m & S_IXUGO)) return c->cols[COL_EX]; return c->cols[COL_FI]; } /* Information about the list shown. */ static int noselect, mselect, inselect, mcol, mline, mcols, mlines; static Cmatch *mmatch, **mtab; static Cmgroup mgroup, *mgtab; /* List the matches. Most of this is just taken from ilistmatches(), * of course. */ static int complistmatches(Hookdef dummy, Chdata dat) { Cmgroup amatches = dat->matches, g; Cmatch *p, m; Cexpl *e; int nlines = 0, ncols, nlist = 0, longest = 1, pnl = 0, opl = 0; int of = isset(LISTTYPES); int mc, ml = 0, cc, hasm = 0; struct listcols col; if (minfo.asked == 2) { showinglist = 0; return (noselect = 1); } getcols(&col); /* Set the cursor below the prompt. */ if (inselect) clearflag = 0; trashzle(); showinglist = listshown = 0; clearflag = (isset(USEZLE) && !termflags && complastprompt && *complastprompt); for (g = amatches; g; g = g->next) { char **pp = g->ylist; int nl = 0, l; if (pp) { /* We have an ylist, lets see, if it contains newlines. */ while (!nl && *pp) nl = !!strchr(*pp++, '\n'); pp = g->ylist; if (nl) { /* Yup, there are newlines, count lines. */ char *nlptr, *sptr; g->flags |= CGF_LINES; noselect = 1; while ((sptr = *pp)) { while (sptr && *sptr) { nlines += (nlptr = strchr(sptr, '\n')) ? 1 + (nlptr-sptr)/columns : strlen(sptr)/columns; sptr = nlptr ? nlptr+1 : NULL; } nlines++; pp++; } nlines--; } else { while (*pp) { if ((l = strlen(*pp)) > longest) longest = l; nlist++; pp++; } } } else { for (p = g->matches; (m = *p); p++) { if (!(m->flags & CMF_NOLIST)) { if ((l = niceztrlen(m->str)) > longest) longest = l; nlist++; } else noselect = 1; } } if ((e = g->expls)) { while (*e) { if ((*e)->count) nlines += 1 + printfmt((*e)->str, (*e)->count, 0); e++; } } } longest += 2 + of; if ((ncols = (columns + 1) / longest)) { for (g = amatches; g; g = g->next) nlines += (g->lcount + ncols - 1) / ncols; } else { ncols = 1; opl = 1; for (g = amatches; g; g = g->next) { char **pp = g->ylist; if (pp) { if (!(g->flags & CGF_LINES)) { while (*pp) { nlines += 1 + (strlen(*pp) / columns); pp++; } } } else for (p = g->matches; (m = *p); p++) if (!(m->flags & CMF_NOLIST)) nlines += 1 + ((1 + niceztrlen(m->str)) / columns); } } /* Maybe we have to ask if the user wants to see the list. */ if ((!minfo.cur || !minfo.asked) && ((complistmax && nlist > complistmax) || (!complistmax && nlines >= lines))) { int qup; zsetterm(); qup = printfmt("zsh: do you wish to see all %n possibilities? ", nlist, 1); fflush(shout); if (getzlequery() != 'y') { if (clearflag) { putc('\r', shout); tcmultout(TCUP, TCMULTUP, qup); if (tccan(TCCLEAREOD)) tcout(TCCLEAREOD); tcmultout(TCUP, TCMULTUP, nlnct); } else putc('\n', shout); noselect = 1; if (minfo.cur) minfo.asked = 2; return 1; } if (clearflag) { putc('\r', shout); tcmultout(TCUP, TCMULTUP, qup); if (tccan(TCCLEAREOD)) tcout(TCCLEAREOD); } else putc('\n', shout); settyinfo(&shttyinfo); if (minfo.cur) minfo.asked = 1; } if (mselect >= 0) { int i; i = ncols * nlines; free(mtab); mtab = (Cmatch **) zalloc(i * sizeof(Cmatch **)); memset(mtab, 0, i * sizeof(Cmatch **)); free(mgtab); mgtab = (Cmgroup *) zalloc(i * sizeof(Cmgroup)); memset(mgtab, 0, i * sizeof(Cmgroup)); mcols = ncols; mlines = nlines; } /* Now print the matches. */ g = amatches; while (g) { char **pp = g->ylist; if ((e = g->expls)) { while (*e) { if ((*e)->count) { if (pnl) { putc('\n', shout); pnl = 0; ml++; } ml += printfmt((*e)->str, (*e)->count, 1); pnl = 1; } e++; } } if (pp && *pp) { if (pnl) { putc('\n', shout); pnl = 0; ml++; } if (g->flags & CGF_LINES) { while (*pp) { zputs(*pp, shout); if (*++pp) putc('\n', shout); } } else { int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, a; char **pq; while (n && nl--) { i = ncols; mc = 0; pq = pp; while (n && i--) { if (pq - g->ylist >= g->lcount) break; zputs(*pq, shout); if (i) { a = longest - strlen(*pq); while (a--) putc(' ', shout); } pq += nc; n--; } if (n) { putc('\n', shout); ml++; } pp++; } } } else if (g->lcount) { int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, j, a = 0; int zt; Cmatch *q; if (n && pnl) { putc('\n', shout); pnl = 0; ml++; } for (p = skipnolist(g->matches); n && nl--;) { i = ncols; mc = 0; q = p; while (n && i--) { fputs(col.cols[COL_LC], shout); if (!(m = *q)) { fputs(col.cols[COL_MI], shout); fputs(col.cols[COL_RC], shout); a = longest - 2; while (a--) putc(' ', shout); fputs(col.cols[COL_EC], shout); break; } hasm = 1; if (mselect >= 0) { mtab[mc + (ncols * ml)] = q; mgtab[mc + (ncols * ml)] = g; } if (m->gnum == mselect) { mcol = mc; mline = ml; mmatch = q; mgroup = g; cc = COL_MA; } else cc = -1; if (m->flags & CMF_FILE) { struct stat buf; char *pb; pb = (char *) zhalloc((m->prpre ? strlen(m->prpre) : 0) + 3 + strlen(m->str)); sprintf(pb, "%s%s", (m->prpre ? m->prpre : "./"), m->str); zt = ztat(pb, &buf, 1); if (cc >= 0) fputs(col.cols[cc], shout); else if (zt) fputs(col.cols[COL_NO], shout); else fputs(getcolstr(&col, pb, buf.st_mode), shout); fputs(col.cols[COL_RC], shout); nicezputs(m->str, shout); if (zt) putc(' ', shout); else putc(file_type(buf.st_mode), shout); } else { fputs(col.cols[cc >= 0 ? cc : COL_NO], shout); fputs(col.cols[COL_RC], shout); nicezputs(m->str, shout); if (of) putc(' ', shout); } a = longest - niceztrlen(m->str) - 2 - of; while (a--) putc(' ', shout); fputs(col.cols[COL_EC], shout); if (i) { fputs(col.cols[COL_LC], shout); fputs(col.cols[COL_NO], shout); fputs(col.cols[COL_RC], shout); fputs(" ", shout); fputs(col.cols[COL_EC], shout); } if (--n) for (j = nc; j && *q; j--) q = skipnolist(q + 1); mc++; } if (i > 0) { fputs(col.cols[COL_LC], shout); fputs(col.cols[COL_MI], shout); fputs(col.cols[COL_RC], shout); a = longest - 2; while (a--) putc(' ', shout); fputs(col.cols[COL_EC], shout); } if (n) { putc('\n', shout); ml++; if (n && nl) p = skipnolist(p + 1); } } } if (g->lcount) pnl = 1; g = g->next; } if (clearflag) { /* Move the cursor up to the prompt, if always_last_prompt * * is set and all that... */ if ((nlines += nlnct - 1) < lines) { tcmultout(TCUP, TCMULTUP, nlines); showinglist = -1; listshown = 1; } else clearflag = 0, putc('\n', shout); } else putc('\n', shout); if (!hasm || nlines >= lines) noselect = 1; return noselect; } typedef struct menustack *Menustack; struct menustack { Menustack prev; char *line; int cs; struct menuinfo info; }; static int domenuselect(Hookdef dummy, Chdata dat) { Cmatch **p; Cmgroup *pg; Thingy cmd; Menustack u = NULL; int i = 0; char *s; if (getcols(NULL) || (dummy && (!(s = getsparam("SELECTMIN")) || (dat && dat->num < atoi(s))))) return 1; selectlocalmap(mskeymap); noselect = 0; mselect = (*(minfo.cur))->gnum; for (;;) { showinglist = -2; zrefresh(); inselect = 1; if (noselect) break; if (!i) { i = mcols * mlines; while (i--) if (mtab[i]) break; if (!i) break; i = 1; } p = mtab + mcol + (mline * mcols); pg = mgtab + mcol + (mline * mcols); minfo.cur = *p; minfo.group = *pg; getk: if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak) || cmd == Th(z_acceptline)) break; else if (cmd == Th(z_acceptandhold) || cmd == Th(z_acceptandmenucomplete)) { Menustack s = (Menustack) zhalloc(sizeof(*s)); s->prev = u; u = s; s->line = dupstring((char *) line); s->cs = cs; memcpy(&(s->info), &minfo, sizeof(struct menuinfo)); acceptlast(); do_menucmp(0); mselect = (*(minfo.cur))->gnum; continue; } else if (cmd == Th(z_undo)) { int l; if (!u) goto getk; cs = 0; foredel(ll); spaceinline(l = strlen(u->line)); strncpy((char *) line, u->line, l); cs = u->cs; memcpy(&minfo, &(u->info), sizeof(struct menuinfo)); p = &(minfo.cur); u = u->prev; } else if (cmd == Th(z_redisplay)) { redisplay(zlenoargs); continue; } else if (cmd == Th(z_clearscreen)) { clearscreen(zlenoargs); continue; } else if (cmd == Th(z_downhistory) || cmd == Th(z_downlineorhistory) || cmd == Th(z_downlineorsearch) || cmd == Th(z_vidownlineorhistory)) { do { if (mline == mlines - 1) { p -= mline * mcols; mline = 0; } else { mline++; p += mcols; } } while (!*p); } else if (cmd == Th(z_uphistory) || cmd == Th(z_uplineorhistory) || cmd == Th(z_uplineorsearch) || cmd == Th(z_viuplineorhistory)) { do { if (!mline) { mline = mlines - 1; p += mline * mcols; } else { mline--; p -= mcols; } } while (!*p); } else if (cmd == Th(z_forwardchar) || cmd == Th(z_viforwardchar)) { do { if (mcol == mcols - 1) { p -= mcol; mcol = 0; } else { mcol++; p++; } } while (!*p); } else if (cmd == Th(z_backwardchar) || cmd == Th(z_vibackwardchar)) { do { if (!mcol) { mcol = mcols - 1; p += mcol; } else { mcol--; p--; } } while (!*p); } else if (cmd == Th(z_beginningofbufferorhistory) || cmd == Th(z_beginningofline) || cmd == Th(z_beginningoflinehist) || cmd == Th(z_vibeginningofline)) { p -= mcol; mcol = 0; while (!*p) { mcol++; p++; } } else if (cmd == Th(z_endofbufferorhistory) || cmd == Th(z_endofline) || cmd == Th(z_endoflinehist) || cmd == Th(z_viendofline)) { p += mcols - mcol - 1; mcol = mcols - 1; while (!*p) { mcol--; p--; } } else if (cmd == Th(z_forwardword) || cmd == Th(z_emacsforwardword) || cmd == Th(z_viforwardword) || cmd == Th(z_viforwardwordend)) { Cmgroup g = *pg; int ol = mline; do { if (mline == mlines - 1) { p -= mline * mcols; pg -= mline * mcols; mline = 0; } else { mline++; p += mcols; pg += mcols; } } while (ol != mline && (*pg == g || !*pg)); } else if (cmd == Th(z_backwardword) || cmd == Th(z_emacsbackwardword) || cmd == Th(z_vibackwardword)) { Cmgroup g = *pg; int ol = mline; do { if (!mline) { mline = mlines - 1; p += mline * mcols; pg += mline * mcols; } else { mline--; p -= mcols; pg -= mcols; } } while (ol != mline && (*pg == g || !*pg)); } else if (cmd == Th(z_completeword) || cmd == Th(z_expandorcomplete) || cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_menucomplete) || cmd == Th(z_menuexpandorcomplete) || !strcmp(cmd->nam, "menu-select") || !strcmp(cmd->nam, "complete-word") || !strcmp(cmd->nam, "expand-or-complete") || !strcmp(cmd->nam, "expand-or-complete-prefix") || !strcmp(cmd->nam, "menu-complete") || !strcmp(cmd->nam, "menu-expand-or-complete")) { do_menucmp(0); mselect = (*(minfo.cur))->gnum; continue; } else if (cmd == Th(z_reversemenucomplete) || !strcmp(cmd->nam, "reverse-menu-complete")) { reversemenucomplete(zlenoargs); mselect = (*(minfo.cur))->gnum; continue; } else { ungetkeycmd(); break; } do_single(**p); mselect = (**p)->gnum; } selectlocalmap(NULL); mselect = -1; inselect = 0; if (!noselect) { showinglist = -2; zrefresh(); } return noselect; } /* The widget function. */ static int menuselect(char **args) { int d = 0; if (!minfo.cur) { menucomplete(args); if ((minfo.cur && minfo.asked == 2) || getsparam("ZLS_SELECT")) return 0; d = 1; } if (minfo.cur && (minfo.asked == 2 || domenuselect(NULL, NULL)) && !d) menucomplete(args); return 0; } /**/ int setup_complist(Module m) { return 0; } /**/ int boot_complist(Module m) { mtab = NULL; mgtab = NULL; mselect = -1; inselect = 0; w_menuselect = addzlefunction("menu-select", menuselect, ZLE_MENUCMP|ZLE_KEEPSUFFIX|ZLE_ISCOMP); if (!w_menuselect) { zwarnnam(m->nam, "name clash when adding ZLE function `menu-select'", NULL, 0); return -1; } addhookfunc("list_matches", (Hookfn) complistmatches); addhookfunc("menu_start", (Hookfn) domenuselect); mskeymap = newkeymap(NULL, "menuselect"); linkkeymap(mskeymap, "menuselect", 1); bindkey(mskeymap, "\t", refthingy(t_completeword), NULL); bindkey(mskeymap, "\n", refthingy(t_acceptline), NULL); bindkey(mskeymap, "\r", refthingy(t_acceptline), NULL); bindkey(mskeymap, "\33[A", refthingy(t_uplineorhistory), NULL); bindkey(mskeymap, "\33[B", refthingy(t_downlineorhistory), NULL); bindkey(mskeymap, "\33[C", refthingy(t_forwardchar), NULL); bindkey(mskeymap, "\33[D", refthingy(t_backwardchar), NULL); bindkey(mskeymap, "\33OA", refthingy(t_uplineorhistory), NULL); bindkey(mskeymap, "\33OB", refthingy(t_downlineorhistory), NULL); bindkey(mskeymap, "\33OC", refthingy(t_forwardchar), NULL); bindkey(mskeymap, "\33OD", refthingy(t_backwardchar), NULL); return 0; } #ifdef MODULE /**/ int cleanup_complist(Module m) { free(mtab); free(mgtab); deletezlefunction(w_menuselect); deletehookfunc("list_matches", (Hookfn) complistmatches); deletehookfunc("menu_start", (Hookfn) domenuselect); unlinkkeymap("menuselect", 1); return 0; } /**/ int finish_complist(Module m) { return 0; } #endif