1/*
2 * complist.c - completion listing enhancements
3 *
4 * This file is part of zsh, the Z shell.
5 *
6 * Copyright (c) 1999 Sven Wischnowsky
7 * All rights reserved.
8 *
9 * Permission is hereby granted, without written agreement and without
10 * license or royalty fees, to use, copy, modify, and distribute this
11 * software and to distribute modified versions of this software for any
12 * purpose, provided that the above copyright notice and the following
13 * two paragraphs appear in all copies of this software.
14 *
15 * In no event shall Sven Wischnowsky or the Zsh Development Group be liable
16 * to any party for direct, indirect, special, incidental, or consequential
17 * damages arising out of the use of this software and its documentation,
18 * even if Sven Wischnowsky and the Zsh Development Group have been advised of
19 * the possibility of such damage.
20 *
21 * Sven Wischnowsky and the Zsh Development Group specifically disclaim any
22 * warranties, including, but not limited to, the implied warranties of
23 * merchantability and fitness for a particular purpose.  The software
24 * provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
25 * Zsh Development Group have no obligation to provide maintenance,
26 * support, updates, enhancements, or modifications.
27 *
28 */
29
30#include "complist.mdh"
31#include "complist.pro"
32
33
34/* Information about the list shown. */
35
36/*
37 * noselect: 1 if complistmatches indicated we shouldn't do selection.
38 *           Tested in domenuselect.
39 * mselect:  Local copy of the index of the currently selected match.
40 *           Initialised to the gnum entry of the current match for
41 *           each completion.
42 * inselect: 1 if we already selecting matches; tested in complistmatches()
43 * mcol:     The column for the selected completion.  As we never scroll
44 *           horizontally this applies both the screen and the logical array.
45 * mline:    The line for the selected completion in the logical array of
46 *           all matches, not all of which may be on screen at once.
47 * mcols:    Local copy of columns used in sizing arrays.
48 * mlines:   The number of lines in the logical array of all matches,
49 *           initialised from listdat.nlines.
50 */
51static int noselect, mselect, inselect, mcol, mline, mcols, mlines;
52/*
53 * selected: Used to signal between domenucomplete() and menuselect()
54 *           that a selected entry has been found.  Or something.
55 * mlbeg:    The first line of the logical array of all matches that
56 *           fits on screen.  Setting this to -1 forces a redraw.
57 * mlend:    The line after the last that fits on screen.
58 * mscroll:  1 if the scrolling prompt is shown on screen.
59 * mrestlines: The number of screen lines remaining to be processed.
60 */
61static int selected, mlbeg = -1, mlend = 9999999, mscroll, mrestlines;
62/*
63 * mnew: 1 if a new selection menu is being displayed.
64 * mlastcols: stored value of mcols for use in calculating mnew.
65 * mlastlines: stored value of mlines for use in calculating mnew.
66 * mhasstat: Indicates if the status line is present (but maybe not
67 *           yet printed).
68 * mfirstl: The first line of the logical array of all matches to
69 *          be shown on screen, -1 if this has not yet been determined.
70 * mlastm: The index of the selected match in some circumstances; used
71 *         if an explicit number for a match is passed to compprintfmt();
72 *         initialised from the total number of matches.  I realise this
73 *         isn't very illuminating.
74 */
75static int mnew, mlastcols, mlastlines, mhasstat, mfirstl, mlastm;
76/*
77 * mlprinted: Used to signal the number of additional lines printed
78 *            when outputting matches (as argument passing is a bit
79 *            screwy within the completion system).
80 * molbeg:    The last value of mlbeg; -1 if invalid, -42 if, er, very
81 *            invalid.  Used in calculations of how much to draw.
82 * mocol:     The last value of mcol.
83 * moline:    The last value of mline.
84 * mstatprinted: Indicates that the status line has now been printed,
85 *               c.f. mhasstat.
86 */
87static int mlprinted, molbeg = -2, mocol = 0, moline = 0, mstatprinted;
88/*
89 * mstatus: The message printed when scrolling.
90 * mlistp: The message printed when merely listing.
91 */
92static char *mstatus, *mlistp;
93/*
94 * mtab is the logical array of all matches referred to above.  It
95 * contains mcols*mlines entries.  These entries contain a pointer to
96 * the match structure which is in use at a particular point.  Note
97 * that for multiple line entries lines after the first contain NULL.
98 *
99 * mmtabp is a pointer to the selected entry in mtab.
100 */
101static Cmatch **mtab, **mmtabp;
102/*
103 * Used to indicate that the list has changed and needs redisplaying.
104 */
105static int mtab_been_reallocated;
106/*
107 * Array and pointer for the match group in exactly the same layout
108 * as mtab and mmtabp.
109 */
110static Cmgroup *mgtab, *mgtabp;
111#ifdef DEBUG
112/*
113 * Allow us to keep track of pointer arithmetic for mgtab; could
114 * just as well have been for mtab but wasn't.
115 */
116int mgtabsize;
117#endif
118
119/*
120 * Used in mtab/mgtab, for explanations.
121 *
122 * UUUUUUUUUUUUUUURRRRGHHHHHHHHHH!!!!!!!!! --- pws
123 */
124
125#define MMARK       ((unsigned long) 1)
126#define mmarked(v)  (((unsigned long) (v)) & MMARK)
127#define mtmark(v)   ((Cmatch *) (((unsigned long) (v)) | MMARK))
128#define mtunmark(v) ((Cmatch *) (((unsigned long) (v)) & ~MMARK))
129#define mgmark(v)   ((Cmgroup)  (((unsigned long) (v)) | MMARK))
130#define mgunmark(v) ((Cmgroup)  (((unsigned long) (v)) & ~MMARK))
131
132/* Information for in-string colours. */
133
134/* Maximum number of in-string colours supported. */
135
136#define MAX_POS 11
137
138static int nrefs;
139static int begpos[MAX_POS], curisbeg;
140static int endpos[MAX_POS];
141static int sendpos[MAX_POS], curissend; /* sorted end positions */
142static char **patcols, *curiscols[MAX_POS];
143static int curiscol;
144
145/* The last color used. */
146
147static char *last_cap;
148
149
150/* We use the parameters ZLS_COLORS and ZLS_COLOURS in the same way as
151 * the color ls does. It's just that we don't support the `or' file
152 * type. */
153
154
155/*
156 * menu-select widget: used to test if it's already loaded.
157 */
158static Widget w_menuselect;
159/*
160 * Similarly for the menuselect and listscroll keymaps.
161 */
162static Keymap mskeymap, lskeymap;
163
164/* Indixes into the terminal string arrays. */
165
166#define COL_NO  0
167#define COL_FI  1
168#define COL_DI  2
169#define COL_LN  3
170#define COL_PI  4
171#define COL_SO  5
172#define COL_BD  6
173#define COL_CD  7
174#define COL_OR  8
175#define COL_MI  9
176#define COL_SU 10
177#define COL_SG 11
178#define COL_TW 12
179#define COL_OW 13
180#define COL_ST 14
181#define COL_EX 15
182#define COL_LC 16
183#define COL_RC 17
184#define COL_EC 18
185#define COL_TC 19
186#define COL_SP 20
187#define COL_MA 21
188#define COL_HI 22
189#define COL_DU 23
190#define COL_SA 24
191
192#define NUM_COLS 25
193
194/* Names of the terminal strings. */
195
196static char *colnames[] = {
197    "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "or", "mi",
198    "su", "sg", "tw", "ow", "st", "ex",
199    "lc", "rc", "ec", "tc", "sp", "ma", "hi", "du", "sa", NULL
200};
201
202/* Default values. */
203
204static char *defcols[] = {
205    "0", "0", "1;31", "1;36", "33", "1;35", "1;33", "1;33", NULL, NULL,
206    "37;41", "30;43", "30;42", "34;42", "37;44", "1;32",
207    "\033[", "m", NULL, "0", "0", "7", NULL, NULL, "0"
208};
209
210/* This describes a terminal string for a file type. */
211
212typedef struct filecol *Filecol;
213
214struct filecol {
215    Patprog prog;		/* group pattern */
216    char *col;			/* color string */
217    Filecol next;		/* next one */
218};
219
220/* This describes a terminal string for a pattern. */
221
222typedef struct patcol *Patcol;
223
224struct patcol {
225    Patprog prog;
226    Patprog pat;		/* pattern for match */
227    char *cols[MAX_POS + 1];
228    Patcol next;
229};
230
231/* This describes a terminal string for a filename extension. */
232
233typedef struct extcol *Extcol;
234
235struct extcol {
236    Patprog prog;		/* group pattern or NULL */
237    char *ext;			/* the extension */
238    char *col;			/* the terminal color string */
239    Extcol next;		/* the next one in the list */
240};
241
242/* This holds all terminal strings. */
243
244typedef struct listcols *Listcols;
245
246/* values for listcol flags */
247enum {
248    /* ln=target:  follow symlinks to determine highlighting */
249    LC_FOLLOW_SYMLINKS = 0x0001
250};
251
252struct listcols {
253    Filecol files[NUM_COLS];	/* strings for file types */
254    Patcol pats;		/* strings for patterns */
255    Extcol exts;		/* strings for extensions */
256    int flags;			/* special settings, see above */
257};
258
259/*
260 * Contains information about the colours to be used for entries.
261 * Sometimes mcolors is passed as an argument even though it's
262 * available to all the functions.
263 */
264static struct listcols mcolors;
265
266/* Combined length of LC and RC, maximum length of capability strings. */
267
268static int lr_caplen, max_caplen;
269
270/* This parses the value of a definition (the part after the `=').
271 * The return value is a pointer to the character after it. */
272
273static char *
274getcolval(char *s, int multi)
275{
276    char *p, *o = s;
277
278    for (p = s; *s && *s != ':' && (!multi || *s != '='); p++, s++) {
279	if (*s == '\\' && s[1]) {
280	    switch (*++s) {
281	    case 'a': *p = '\007'; break;
282	    case 'n': *p = '\n'; break;
283	    case 'b': *p = '\b'; break;
284	    case 't': *p = '\t'; break;
285	    case 'v': *p = '\v'; break;
286	    case 'f': *p = '\f'; break;
287	    case 'r': *p = '\r'; break;
288	    case 'e': *p = '\033'; break;
289	    case '_': *p = ' '; break;
290	    case '?': *p = '\177'; break;
291	    default:
292		if (*s >= '0' && *s <= '7') {
293		    int i = STOUC(*s);
294
295		    if (*++s >= '0' && *s <= '7') {
296			i = (i * 8) + STOUC(*s);
297			if (*++s >= '0' && *s <= '7')
298			    i = (i * 8) + STOUC(*s);
299		    }
300		    *p = (char) i;
301		} else
302		    *p = *s;
303	    }
304	} else if (*s == '^') {
305	    if ((s[1] >= '@' && s[1] <= '_') ||
306		(s[1] >= 'a' && s[1] <= 'z'))
307		*p = (char) (STOUC(*s) & ~0x60);
308	    else if (s[1] == '?')
309		*p = '\177';
310	    else {
311		*p++ = *s;
312		*p = s[1];
313	    }
314	    s++;
315	} else
316	    *p = *s;
317    }
318    if (p != s)
319	*p = '\0';
320    if ((s - o) > max_caplen)
321	max_caplen = s - o;
322    return s;
323}
324
325/* This parses one definition. Return value is a pointer to the
326 * character after it. */
327
328static char *
329getcoldef(char *s)
330{
331    Patprog gprog = NULL;
332
333    if (*s == '(') {
334	char *p;
335	int l = 0;
336
337	for (p = s + 1, l = 0; *p && (*p != ')' || l); p++)
338	    if (*p == '\\' && p[1])
339		p++;
340	    else if (*p == '(')
341		l++;
342	    else if (*p == ')')
343		l--;
344
345	if (*p == ')') {
346	    char sav = p[1];
347
348	    p[1] = '\0';
349	    tokenize(s);
350	    gprog = patcompile(s, 0, NULL);
351	    p[1]  =sav;
352
353	    s = p + 1;
354	}
355    }
356    if (*s == '*') {
357	Extcol ec, eo;
358	char *n, *p;
359
360	/* This is for an extension. */
361
362	n = ++s;
363	while (*s && *s != '=')
364	    s++;
365	if (!*s)
366	    return s;
367	*s++ = '\0';
368	p = getcolval(s, 0);
369	ec = (Extcol) zhalloc(sizeof(*ec));
370	ec->prog = gprog;
371	ec->ext = n;
372	ec->col = s;
373	ec->next = NULL;
374	if ((eo = mcolors.exts)) {
375	    while (eo->next)
376		eo = eo->next;
377	    eo->next = ec;
378	} else
379	    mcolors.exts = ec;
380	if (*p)
381	    *p++ = '\0';
382	return p;
383    } else if (*s == '=') {
384	char *p = ++s, *t, *cols[MAX_POS];
385	int ncols = 0;
386	Patprog prog;
387
388	/* This is for a pattern. */
389
390	while (*s && *s != '=')
391	    s++;
392	if (!*s)
393	    return s;
394	*s++ = '\0';
395	while (1) {
396	    t = getcolval(s, 1);
397	    if (ncols < MAX_POS)
398		cols[ncols++] = s;
399	    s = t;
400	    if (*s != '=')
401		break;
402	    *s++ = '\0';
403	}
404	tokenize(p);
405	if ((prog = patcompile(p, 0, NULL))) {
406	    Patcol pc, po;
407	    int i;
408
409	    pc = (Patcol) zhalloc(sizeof(*pc));
410	    pc->prog = gprog;
411	    pc->pat = prog;
412	    for (i = 0; i < ncols; i++)
413		pc->cols[i] = cols[i];
414	    pc->cols[i] = NULL;
415	    pc->next = NULL;
416	    if ((po = mcolors.pats)) {
417		while (po->next)
418		    po = po->next;
419		po->next = pc;
420	    } else
421		mcolors.pats = pc;
422	}
423	if (*t)
424	    *t++ = '\0';
425	return t;
426    } else {
427	char *n = s, *p, **nn;
428	int i;
429
430	/* This is for a file type. */
431
432	while (*s && *s != '=')
433	    s++;
434	if (!*s)
435	    return s;
436	*s++ = '\0';
437	for (i = 0, nn = colnames; *nn; i++, nn++)
438	    if (!strcmp(n, *nn))
439		break;
440	/*
441	 * special case:  highlighting link targets
442	 */
443	if (i == COL_LN && strpfx("target", s) &&
444	    (s[6] == ':' || !s[6])) {
445	    mcolors.flags |= LC_FOLLOW_SYMLINKS;
446	    p = s + 6;
447	} else {
448	    p = getcolval(s, 0);
449	    if (*nn) {
450		Filecol fc, fo;
451
452		fc = (Filecol) zhalloc(sizeof(*fc));
453		fc->prog = (i == COL_EC || i == COL_LC || i == COL_RC ?
454			    NULL : gprog);
455		fc->col = s;
456		fc->next = NULL;
457		if ((fo = mcolors.files[i])) {
458		    while (fo->next)
459			fo = fo->next;
460		    fo->next = fc;
461		} else
462		    mcolors.files[i] = fc;
463	    }
464	    if (*p)
465		*p++ = '\0';
466	}
467	return p;
468    }
469}
470
471static Filecol
472filecol(char *col)
473{
474    Filecol fc;
475
476    fc = (Filecol) zhalloc(sizeof(*fc));
477    fc->prog = NULL;
478    fc->col = col;
479    fc->next = NULL;
480
481    return fc;
482}
483
484/*
485 * This initializes the given terminal color structure.
486 */
487
488static void
489getcols()
490{
491    char *s;
492    int i, l;
493
494    max_caplen = lr_caplen = 0;
495    mcolors.flags = 0;
496    queue_signals();
497    if (!(s = getsparam("ZLS_COLORS")) &&
498	!(s = getsparam("ZLS_COLOURS"))) {
499	for (i = 0; i < NUM_COLS; i++)
500	    mcolors.files[i] = filecol("");
501	mcolors.pats = NULL;
502	mcolors.exts = NULL;
503
504	if ((s = tcstr[TCSTANDOUTBEG]) && s[0]) {
505	    mcolors.files[COL_MA] = filecol(s);
506	    mcolors.files[COL_EC] = filecol(tcstr[TCSTANDOUTEND]);
507	} else
508	    mcolors.files[COL_MA] = filecol(defcols[COL_MA]);
509	lr_caplen = 0;
510	if ((max_caplen = strlen(mcolors.files[COL_MA]->col)) <
511	    (l = strlen(mcolors.files[COL_EC]->col)))
512	    max_caplen = l;
513	unqueue_signals();
514	return;
515    }
516    /* Reset the global color structure. */
517    memset(&mcolors, 0, sizeof(mcolors));
518    s = dupstring(s);
519    while (*s)
520	if (*s == ':')
521	    s++;
522	else
523	    s = getcoldef(s);
524    unqueue_signals();
525
526    /* Use default values for those that aren't set explicitly. */
527    for (i = 0; i < NUM_COLS; i++) {
528	if (!mcolors.files[i] || !mcolors.files[i]->col)
529	    mcolors.files[i] = filecol(defcols[i]);
530	if (mcolors.files[i] && mcolors.files[i]->col &&
531	    (l = strlen(mcolors.files[i]->col)) > max_caplen)
532	    max_caplen = l;
533    }
534    lr_caplen = strlen(mcolors.files[COL_LC]->col) +
535	strlen(mcolors.files[COL_RC]->col);
536
537    /* Default for orphan is same as link. */
538    if (!mcolors.files[COL_OR] || !mcolors.files[COL_OR]->col)
539	mcolors.files[COL_OR] = mcolors.files[COL_LN];
540    /* Default for missing files:  currently not used */
541    if (!mcolors.files[COL_MI] || !mcolors.files[COL_MI]->col)
542	mcolors.files[COL_MI] = mcolors.files[COL_FI];
543
544    return;
545}
546
547static void
548zlrputs(char *cap)
549{
550    if (!*last_cap || strcmp(last_cap, cap)) {
551	VARARR(char, buf, lr_caplen + max_caplen + 1);
552
553	strcpy(buf, mcolors.files[COL_LC]->col);
554	strcat(buf, cap);
555	strcat(buf, mcolors.files[COL_RC]->col);
556
557	tputs(buf, 1, putshout);
558
559	strcpy(last_cap, cap);
560    }
561}
562
563static void
564zcputs(char *group, int colour)
565{
566    Filecol fc;
567
568    for (fc = mcolors.files[colour]; fc; fc = fc->next)
569	if (fc->col &&
570	    (!fc->prog || !group || pattry(fc->prog, group))) {
571	    zlrputs(fc->col);
572
573	    return;
574	}
575    zlrputs("0");
576}
577
578/* Turn off colouring. */
579
580static void
581zcoff(void)
582{
583    if (mcolors.files[COL_EC] && mcolors.files[COL_EC]->col) {
584	tputs(mcolors.files[COL_EC]->col, 1, putshout);
585	*last_cap = '\0';
586    } else
587	zcputs(NULL, COL_NO);
588}
589
590
591static void
592initiscol()
593{
594    int i;
595
596    zlrputs(patcols[0]);
597
598    curiscols[curiscol = 0] = *patcols++;
599
600    curisbeg = curissend = 0;
601
602    for (i = 0; i < nrefs; i++)
603	sendpos[i] = 0xfffffff;
604    for (; i < MAX_POS; i++)
605	begpos[i] = endpos[i] = sendpos[i] = 0xfffffff;
606}
607
608static void
609doiscol(int pos)
610{
611    int fi;
612
613    while (pos > sendpos[curissend]) {
614	curissend++;
615	if (curiscol) {
616	    zcputs(NULL, COL_NO);
617	    zlrputs(curiscols[--curiscol]);
618	}
619    }
620    while (((fi = (endpos[curisbeg] < begpos[curisbeg] ||
621		  begpos[curisbeg] == -1)) ||
622	    pos == begpos[curisbeg]) && *patcols) {
623	if (!fi) {
624	    int i, j, e = endpos[curisbeg];
625
626	    /* insert e in sendpos */
627	    for (i = curissend; sendpos[i] <= e; ++i)
628		;
629	    for (j = MAX_POS - 1; j > i; --j)
630		sendpos[j] = sendpos[j-1];
631	    sendpos[i] = e;
632
633	    zcputs(NULL, COL_NO);
634	    zlrputs(*patcols);
635	    curiscols[++curiscol] = *patcols;
636	}
637	++patcols;
638	++curisbeg;
639    }
640}
641
642/* Stripped-down version of printfmt(). But can do in-string colouring. */
643
644static int
645clprintfmt(char *p, int ml)
646{
647    int cc = 0, i = 0, ask, beg;
648
649    initiscol();
650
651    for (; *p; p++) {
652	doiscol(i++);
653	cc++;
654	if (*p == '\n') {
655	    if (mlbeg >= 0 && tccan(TCCLEAREOL))
656		tcout(TCCLEAREOL);
657	    cc = 0;
658	}
659	if (ml == mlend - 1 && (cc % zterm_columns) == zterm_columns - 1)
660	    return 0;
661
662	if (*p == Meta) {
663	    p++;
664	    putc(*p ^ 32, shout);
665	} else
666	    putc(*p, shout);
667	if ((beg = !(cc % zterm_columns)))
668	    ml++;
669	if (mscroll && !(cc % zterm_columns) &&
670	    !--mrestlines && (ask = asklistscroll(ml)))
671	    return ask;
672    }
673    if (mlbeg >= 0 && tccan(TCCLEAREOL))
674	tcout(TCCLEAREOL);
675    return 0;
676}
677
678/*
679 * Local version of nicezputs() with in-string colouring
680 * and scrolling.
681 */
682
683static int
684clnicezputs(int do_colors, char *s, int ml)
685{
686    int i = 0, col = 0, ask, oml = ml;
687    char *t;
688    ZLE_CHAR_T cc;
689#ifdef MULTIBYTE_SUPPORT
690    /*
691     * ums is the untokenized, unmetafied string (length umlen)
692     * uptr is a pointer into it
693     * sptr is the start of the nice character representation
694     * wptr is the point at which the wide character itself starts
695     *  (but may be the end of the string if the character was fully
696     *  prettified).
697     * ret is the return status from the conversion to a wide character
698     * umleft is the remaining length of the unmetafied string to output
699     * umlen is the full length of the unmetafied string
700     * width is the full printing width of a prettified character,
701     *  including both ASCII prettification and the wide character itself.
702     * mbs is the shift state of the conversion to wide characters.
703     */
704    char *ums, *uptr, *sptr, *wptr;
705    int umleft, umlen, eol = 0;
706    size_t width;
707    mbstate_t mbs;
708
709    memset(&mbs, 0, sizeof mbs);
710    ums = ztrdup(s);
711    untokenize(ums);
712    uptr = unmetafy(ums, &umlen);
713    umleft = umlen;
714
715    if (do_colors)
716	initiscol();
717
718    mb_metacharinit();
719    while (umleft > 0) {
720	size_t cnt = eol ? MB_INVALID : mbrtowc(&cc, uptr, umleft, &mbs);
721
722	switch (cnt) {
723	case MB_INCOMPLETE:
724	    eol = 1;
725	    /* FALL THROUGH */
726	case MB_INVALID:
727	    /* This handles byte values that aren't valid wide-character
728	     * sequences. */
729	    sptr = nicechar(*uptr);
730	    /* everything here is ASCII... */
731	    width = strlen(sptr);
732	    wptr = sptr + width;
733	    cnt = 1;
734	    /* Get mbs out of its undefined state. */
735	    memset(&mbs, 0, sizeof mbs);
736	    break;
737	case 0:
738	    /* This handles a '\0' in the input (which is a real char
739	     * to us, not a terminator). */
740	    cnt = 1;
741	    /* FALL THROUGH */
742	default:
743	    sptr = wcs_nicechar(cc, &width, &wptr);
744	    break;
745	}
746
747	umleft -= cnt;
748	uptr += cnt;
749	if (do_colors) {
750	    /*
751	     * The code for the colo[u]ri[s/z]ation is obscure (surprised?)
752	     * but if we do it for every input character, as we do in
753	     * the simple case, we shouldn't go too far wrong.
754	     */
755	    while (cnt--)
756		doiscol(i++);
757	}
758
759	/*
760	 * Loop over characters in the output of the nice
761	 * representation.  This will often correspond to one input
762	 * (possibly multibyte) character.
763	 */
764	for (t = sptr; *t; t++) {
765	    /* Input is metafied... */
766	    int nc = (*t == Meta) ? STOUC(*++t ^ 32) : STOUC(*t);
767	    /* Is the screen full? */
768	    if (ml == mlend - 1 && col == zterm_columns - 1) {
769		mlprinted = ml - oml;
770		return 0;
771	    }
772	    if (t < wptr) {
773		/* outputting ASCII, so single-width */
774		putc(nc, shout);
775		col++;
776		width--;
777	    } else {
778		/* outputting a single wide character, do the lot */
779		putc(nc, shout);
780		/* don't check column until finished */
781		if (t[1])
782		    continue;
783		/* now we've done the entire rest of the representation */
784		col += width;
785	    }
786	    /*
787	     * There might be problems with characters of printing width
788	     * greater than one here.
789	     */
790	    if (col > zterm_columns) {
791		ml++;
792		if (mscroll && !--mrestlines && (ask = asklistscroll(ml))) {
793		    mlprinted = ml - oml;
794		    return ask;
795		}
796		col -= zterm_columns;
797		if (do_colors)
798		    fputs(" \010", shout);
799	    }
800	}
801    }
802
803    free(ums);
804#else
805
806    if (do_colors)
807	initiscol();
808
809    while ((cc = *s++)) {
810	if (do_colors)
811	    doiscol(i++);
812	if (itok(cc)) {
813	    if (cc <= Comma)
814		cc = ztokens[cc - Pound];
815	    else
816		continue;
817	}
818	if (cc == Meta)
819	    cc = *s++ ^ 32;
820
821	for (t = nicechar(cc); *t; t++) {
822	    int nc = (*t == Meta) ? STOUC(*++t ^ 32) : STOUC(*t);
823	    if (ml == mlend - 1 && col == zterm_columns - 1) {
824		mlprinted = ml - oml;
825		return 0;
826	    }
827	    putc(nc, shout);
828	    if (++col > zterm_columns) {
829		ml++;
830		if (mscroll && !--mrestlines && (ask = asklistscroll(ml))) {
831		    mlprinted = ml - oml;
832		    return ask;
833		}
834		col = 0;
835		if (do_colors)
836		    fputs(" \010", shout);
837	    }
838	}
839    }
840#endif
841    mlprinted = ml - oml;
842    return 0;
843}
844
845/* Get the terminal color string for the given match. */
846
847static int
848putmatchcol(char *group, char *n)
849{
850    Patcol pc;
851
852    for (pc = mcolors.pats; pc; pc = pc->next) {
853	nrefs = MAX_POS - 1;
854
855	if ((!pc->prog || !group || pattry(pc->prog, group)) &&
856	    pattryrefs(pc->pat, n, -1, -1, 0, &nrefs, begpos, endpos)) {
857	    if (pc->cols[1]) {
858		patcols = pc->cols;
859
860		return 1;
861	    }
862	    zlrputs(pc->cols[0]);
863
864	    return 0;
865	}
866    }
867
868    zcputs(group, COL_NO);
869
870    return 0;
871}
872
873/* Get the terminal color string for the file with the given name and
874 * file modes. */
875
876static int
877putfilecol(char *group, char *filename, mode_t m, int special)
878{
879    int colour = -1;
880    Extcol ec;
881    Patcol pc;
882    int len;
883
884    for (pc = mcolors.pats; pc; pc = pc->next) {
885	nrefs = MAX_POS - 1;
886
887	if ((!pc->prog || !group || pattry(pc->prog, group)) &&
888	    pattryrefs(pc->pat, filename, -1, -1, 0, &nrefs, begpos, endpos)) {
889	    if (pc->cols[1]) {
890		patcols = pc->cols;
891
892		return 1;
893	    }
894	    zlrputs(pc->cols[0]);
895
896	    return 0;
897	}
898    }
899
900    if (special != -1) {
901	colour = special;
902    } else if (S_ISDIR(m)) {
903	if (m & S_IWOTH)
904	    if (m & S_ISVTX)
905		colour = COL_TW;
906	    else
907		colour = COL_OW;
908	else if (m & S_ISVTX)
909	    colour = COL_ST;
910	else
911	    colour = COL_DI;
912    } else if (S_ISLNK(m))
913	colour = COL_LN;
914    else if (S_ISFIFO(m))
915	colour = COL_PI;
916    else if (S_ISSOCK(m))
917	colour = COL_SO;
918    else if (S_ISBLK(m))
919	colour = COL_BD;
920    else if (S_ISCHR(m))
921	colour = COL_CD;
922    else if (m & S_ISUID)
923	colour = COL_SU;
924    else if (m & S_ISGID)
925	colour = COL_SG;
926    else if (S_ISREG(m) && (m & S_IXUGO))
927	colour = COL_EX;
928
929    if (colour != -1) {
930	zcputs(group, colour);
931	return 0;
932    }
933
934    for (ec = mcolors.exts; ec; ec = ec->next)
935	if (strsfx(ec->ext, filename) &&
936	    (!ec->prog || !group || pattry(ec->prog, group))) {
937	    zlrputs(ec->col);
938
939	    return 0;
940	}
941
942    /* Check for suffix alias */
943    len = strlen(filename);
944    /* shortest valid suffix format is a.b */
945    if (len > 2) {
946	char *suf = filename + len - 1;
947	while (suf > filename+1) {
948	    if (suf[-1] == '.') {
949		if (sufaliastab->getnode(sufaliastab, suf)) {
950		    zcputs(group, COL_SA);
951		    return 0;
952		}
953		break;
954	    }
955	    suf--;
956	}
957    }
958    zcputs(group, COL_FI);
959
960    return 0;
961}
962
963static Cmgroup last_group;
964
965/**/
966static int
967asklistscroll(int ml)
968{
969    Thingy cmd;
970    int i, ret = 0;
971
972    compprintfmt(NULL, 1, 1, 1, ml, NULL);
973
974    fflush(shout);
975    zsetterm();
976    selectlocalmap(lskeymap);
977    if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak))
978	ret = 1;
979    else if (cmd == Th(z_acceptline) ||
980	     cmd == Th(z_downhistory) ||
981	     cmd == Th(z_downlineorhistory) ||
982	     cmd == Th(z_downlineorsearch) ||
983	     cmd == Th(z_vidownlineorhistory))
984	mrestlines = 1;
985    else if (cmd == Th(z_completeword) ||
986		   cmd == Th(z_expandorcomplete) ||
987		   cmd == Th(z_expandorcompleteprefix) ||
988		   cmd == Th(z_menucomplete) ||
989		   cmd == Th(z_menuexpandorcomplete) ||
990		   !strcmp(cmd->nam, "menu-select") ||
991		   !strcmp(cmd->nam, "complete-word") ||
992		   !strcmp(cmd->nam, "expand-or-complete") ||
993		   !strcmp(cmd->nam, "expand-or-complete-prefix") ||
994		   !strcmp(cmd->nam, "menu-complete") ||
995	     !strcmp(cmd->nam, "menu-expand-or-complete"))
996	mrestlines = zterm_lines - 1;
997    else if (cmd == Th(z_acceptsearch))
998	ret = 1;
999    else {
1000	ungetkeycmd();
1001	ret = 1;
1002    }
1003    selectlocalmap(NULL);
1004    settyinfo(&shttyinfo);
1005    putc('\r', shout);
1006    for (i = zterm_columns - 1; i-- > 0; )
1007	putc(' ', shout);
1008    putc('\r', shout);
1009
1010    return ret;
1011}
1012
1013#define dolist(X)   ((X) >= mlbeg && (X) < mlend)
1014#define dolistcl(X) ((X) >= mlbeg && (X) < mlend + 1)
1015#define dolistnl(X) ((X) >= mlbeg && (X) < mlend - 1)
1016
1017/**/
1018static int
1019compprintnl(int ml)
1020{
1021    int ask;
1022
1023    if (mlbeg >= 0 && tccan(TCCLEAREOL))
1024	tcout(TCCLEAREOL);
1025    putc('\n', shout);
1026
1027    if (mscroll && !--mrestlines && (ask = asklistscroll(ml)))
1028	return ask;
1029
1030    return 0;
1031}
1032
1033/* This is used to print the strings (e.g. explanations). *
1034 * It returns the number of lines printed.       */
1035
1036/**/
1037static int
1038compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
1039{
1040    char *p, nc[2*DIGBUFSIZE + 12], nbuf[2*DIGBUFSIZE + 12];
1041    int l = 0, cc = 0, b = 0, s = 0, u = 0, m, ask, beg, stat;
1042
1043    if ((stat = !fmt)) {
1044	if (mlbeg >= 0) {
1045	    if (!(fmt = mstatus)) {
1046		mlprinted = 0;
1047		return 0;
1048	    }
1049	    cc = -1;
1050	} else
1051	    fmt = mlistp;
1052    }
1053    MB_METACHARINIT();
1054    for (p = fmt; *p; ) {
1055	convchar_t cchar;
1056	int len, width;
1057
1058	len = MB_METACHARLENCONV(p, &cchar);
1059#ifdef MULTIBYTE_SUPPORT
1060	if (cchar == WEOF) {
1061	    cchar = (wchar_t)(*p == Meta ? p[1] ^ 32 : *p);
1062	    width = 1;
1063	}
1064	else
1065#endif
1066	    width = WCWIDTH_WINT(cchar);
1067
1068	if (doesc && cchar == ZWC('%')) {
1069	    p += len;
1070	    if (*p) {
1071		int arg = 0, is_fg;
1072
1073		len = MB_METACHARLENCONV(p, &cchar);
1074#ifdef MULTIBYTE_SUPPORT
1075		if (cchar == WEOF)
1076		    cchar = (wchar_t)(*p == Meta ? p[1] ^ 32 : *p);
1077#endif
1078		p += len;
1079
1080		if (idigit(*p))
1081		    arg = zstrtol(p, &p, 10);
1082
1083		m = 0;
1084		switch (cchar) {
1085		case ZWC('%'):
1086		    if (dopr == 1)
1087			putc('%', shout);
1088		    cc++;
1089		    break;
1090		case ZWC('n'):
1091		    if (!stat) {
1092			sprintf(nc, "%d", n);
1093			if (dopr == 1)
1094			    fputs(nc, shout);
1095			/* everything here is ASCII... */
1096			cc += strlen(nc);
1097		    }
1098		    break;
1099		case ZWC('B'):
1100		    b = 1;
1101		    if (dopr)
1102			tcout(TCBOLDFACEBEG);
1103		    break;
1104		case ZWC('b'):
1105		    b = 0; m = 1;
1106		    if (dopr)
1107			tcout(TCALLATTRSOFF);
1108		    break;
1109		case ZWC('S'):
1110		    s = 1;
1111		    if (dopr)
1112			tcout(TCSTANDOUTBEG);
1113		    break;
1114		case ZWC('s'):
1115		    s = 0; m = 1;
1116		    if (dopr)
1117			tcout(TCSTANDOUTEND);
1118		    break;
1119		case ZWC('U'):
1120		    u = 1;
1121		    if (dopr)
1122			tcout(TCUNDERLINEBEG);
1123		    break;
1124		case ZWC('u'):
1125		    u = 0; m = 1;
1126		    if (dopr)
1127			tcout(TCUNDERLINEEND);
1128		    break;
1129		case ZWC('F'):
1130		case ZWC('K'):
1131		    is_fg = (cchar == ZWC('F'));
1132		    /* colours must be ASCII */
1133		    if (*p == '{') {
1134			p++;
1135			arg = match_colour((const char **)&p, is_fg, 0);
1136			if (*p == '}')
1137			    p++;
1138		    } else
1139			arg = match_colour(NULL, is_fg, arg);
1140		    if (arg >= 0 && dopr)
1141			set_colour_attribute(arg, is_fg ? COL_SEQ_FG :
1142					     COL_SEQ_BG, 0);
1143		    break;
1144		case ZWC('f'):
1145		    if (dopr)
1146			set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, 0);
1147		    break;
1148		case ZWC('k'):
1149		    if (dopr)
1150			set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, 0);
1151		    break;
1152		case ZWC('{'):
1153		    if (arg)
1154			cc += arg;
1155		    for (; *p && (*p != '%' || p[1] != '}'); p++)
1156			if (dopr)
1157			    putc(*p == Meta ? *++p ^ 32 : *p, shout);
1158		    if (*p)
1159			p += 2;
1160		    break;
1161		case ZWC('m'):
1162		    if (stat) {
1163			sprintf(nc, "%d/%d", (n ? mlastm : mselect),
1164				listdat.nlist);
1165			m = 2;
1166		    }
1167		    break;
1168		case ZWC('M'):
1169		    if (stat) {
1170			sprintf(nbuf, "%d/%d", (n ? mlastm : mselect),
1171				listdat.nlist);
1172			sprintf(nc, "%-9s", nbuf);
1173			m = 2;
1174		    }
1175		    break;
1176		case ZWC('l'):
1177		    if (stat) {
1178			sprintf(nc, "%d/%d", ml + 1, listdat.nlines);
1179			m = 2;
1180		    }
1181		    break;
1182		case ZWC('L'):
1183		    if (stat) {
1184			sprintf(nbuf, "%d/%d", ml + 1, listdat.nlines);
1185			sprintf(nc, "%-9s", nbuf);
1186			m = 2;
1187		    }
1188		    break;
1189		case ZWC('p'):
1190		    if (stat) {
1191			if (ml == listdat.nlines - 1)
1192			    strcpy(nc, "Bottom");
1193			else if (n ? mfirstl : (mlbeg > 0 || ml != mfirstl))
1194			    sprintf(nc, "%d%%",
1195				    ((ml + 1) * 100) / listdat.nlines);
1196			else
1197			    strcpy(nc, "Top");
1198			m = 2;
1199		    }
1200		    break;
1201		case ZWC('P'):
1202		    if (stat) {
1203			if (ml == listdat.nlines - 1)
1204			    strcpy(nc, "Bottom");
1205			else if (n ? mfirstl : (mlbeg > 0 || ml != mfirstl))
1206			    sprintf(nc, "%2d%%   ",
1207				    ((ml + 1) * 100) / listdat.nlines);
1208			else
1209			    strcpy(nc, "Top   ");
1210			m = 2;
1211		    }
1212		    break;
1213		}
1214		if (m == 2 && dopr == 1) {
1215		    /* nc only contains ASCII text */
1216		    int l = strlen(nc);
1217
1218		    if (l + cc > zterm_columns - 2)
1219			nc[l -= l + cc - (zterm_columns - 2)] = '\0';
1220		    fputs(nc, shout);
1221		    cc += l;
1222		} else if (dopr && m == 1) {
1223		    if (b)
1224			tcout(TCBOLDFACEBEG);
1225		    if (s)
1226			tcout(TCSTANDOUTBEG);
1227		    if (u)
1228			tcout(TCUNDERLINEBEG);
1229		}
1230	    } else
1231		break;
1232	} else {
1233	    cc += width;
1234
1235	    if ((cc >= zterm_columns - 2 || cchar == ZWC('\n')) && stat)
1236		dopr = 2;
1237	    if (cchar == ZWC('\n')) {
1238		if (dopr == 1 && mlbeg >= 0 && tccan(TCCLEAREOL))
1239		    tcout(TCCLEAREOL);
1240		l += 1 + ((cc - 1) / zterm_columns);
1241		cc = 0;
1242	    }
1243	    if (dopr == 1) {
1244		if (ml == mlend - 1 && (cc % zterm_columns) ==
1245		    zterm_columns - 1) {
1246		    dopr = 0;
1247		    p += len;
1248		    continue;
1249		}
1250		while (len--) {
1251		    if (*p == Meta) {
1252			len--;
1253			p++;
1254			putc(*p++ ^ 32, shout);
1255		    } else
1256			putc(*p++, shout);
1257		}
1258		/*
1259		 * TODO: the following doesn't allow for
1260		 * character widths greater than 1.
1261		 */
1262		if ((beg = !(cc % zterm_columns)) && !stat) {
1263		    ml++;
1264                    fputs(" \010", shout);
1265                }
1266		if (mscroll && beg && !--mrestlines && (ask = asklistscroll(ml))) {
1267		    *stop = 1;
1268		    if (stat && n)
1269			mfirstl = -1;
1270		    mlprinted = l + (cc ? ((cc-1) / zterm_columns) : 0);
1271		    return mlprinted;
1272		}
1273	    }
1274	    else
1275		p += len;
1276	}
1277    }
1278    if (dopr) {
1279        if (!(cc % zterm_columns))
1280            fputs(" \010", shout);
1281        if (mlbeg >= 0 && tccan(TCCLEAREOL))
1282            tcout(TCCLEAREOL);
1283    }
1284    if (stat && n)
1285	mfirstl = -1;
1286
1287    /*
1288     * *Not* subtracting 1 from cc at this point appears to be
1289     * correct.  C.f. printfmt in zle_tricky.c.
1290     */
1291    mlprinted = l + (cc / zterm_columns);
1292    return mlprinted;
1293}
1294
1295/* This is like zputs(), but allows scrolling. */
1296
1297/**/
1298static int
1299compzputs(char const *s, int ml)
1300{
1301    int c, col = 0, ask;
1302
1303    while (*s) {
1304	if (*s == Meta)
1305	    c = *++s ^ 32;
1306	else if(itok(*s)) {
1307	    s++;
1308	    continue;
1309	} else
1310	    c = *s;
1311	s++;
1312	putc(c, shout);
1313	if (c == '\n' && mlbeg >= 0 && tccan(TCCLEAREOL))
1314	    tcout(TCCLEAREOL);
1315	if (mscroll && (++col == zterm_columns || c == '\n')) {
1316	    ml++;
1317	    if (!--mrestlines && (ask = asklistscroll(ml)))
1318		return ask;
1319
1320	    col = 0;
1321	}
1322    }
1323    return 0;
1324}
1325
1326/**/
1327static int
1328compprintlist(int showall)
1329{
1330    static int lasttype = 0, lastbeg = 0, lastml = 0, lastinvcount = -1;
1331    static int lastn = 0, lastnl = 0, lastnlnct = -1;
1332    static Cmgroup lastg = NULL;
1333    static Cmatch *lastp = NULL;
1334    static Cexpl *lastexpl = NULL;
1335
1336    Cmgroup g;
1337    Cmatch *p, m;
1338    Cexpl *e;
1339    int pnl = 0, cl, mc = 0, ml = 0, printed = 0, stop = 0, asked = 1;
1340    int lastused = 0;
1341
1342    mfirstl = -1;
1343    if (mnew || lastinvcount != invcount || lastbeg != mlbeg || mlbeg < 0) {
1344	lasttype = 0;
1345	lastg = NULL;
1346	lastexpl = NULL;
1347	lastml = 0;
1348	lastnlnct = -1;
1349    }
1350    cl = (listdat.nlines > zterm_lines - nlnct - mhasstat ?
1351	  zterm_lines - nlnct - mhasstat :
1352	  listdat.nlines) - (lastnlnct > nlnct);
1353    lastnlnct = nlnct;
1354    mrestlines = zterm_lines - 1;
1355    lastinvcount = invcount;
1356
1357    if (cl < 2) {
1358	cl = -1;
1359	if (tccan(TCCLEAREOD))
1360	    tcout(TCCLEAREOD);
1361    } else if (mlbeg >= 0 && !tccan(TCCLEAREOL) && tccan(TCCLEAREOD))
1362	tcout(TCCLEAREOD);
1363
1364    g = ((lasttype && lastg) ? lastg : amatches);
1365    while (g) {
1366	char **pp = g->ylist;
1367
1368#ifdef ZSH_HEAP_DEBUG
1369	if (memory_validate(g->heap_id)) {
1370	    HEAP_ERROR(g->heap_id);
1371	}
1372#endif
1373	if ((e = g->expls)) {
1374	    if (!lastused && lasttype == 1) {
1375		e = lastexpl;
1376		ml = lastml;
1377		lastused = 1;
1378	    }
1379	    while (*e) {
1380		if (((*e)->count || (*e)->always) &&
1381		    (!listdat.onlyexpl ||
1382		     (listdat.onlyexpl & ((*e)->always > 0 ? 2 : 1)))) {
1383		    if (pnl) {
1384			if (dolistnl(ml) && compprintnl(ml))
1385			    goto end;
1386			pnl = 0;
1387			ml++;
1388			if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
1389			    cl = -1;
1390			    if (tccan(TCCLEAREOD))
1391				tcout(TCCLEAREOD);
1392			}
1393		    }
1394		    if (mlbeg < 0 && mfirstl < 0)
1395			mfirstl = ml;
1396		    (void)compprintfmt((*e)->str,
1397				       ((*e)->always ? -1 : (*e)->count),
1398				       dolist(ml), 1, ml, &stop);
1399		    if (mselect >= 0) {
1400			int mm = (mcols * ml), i;
1401
1402			for (i = mcols; i-- > 0; ) {
1403			    DPUTS(mm+i >= mgtabsize, "BUG: invalid position");
1404			    mtab[mm + i] = mtmark(NULL);
1405			    mgtab[mm + i] = mgmark(NULL);
1406			}
1407		    }
1408		    if (stop)
1409			goto end;
1410		    if (!lasttype && ml >= mlbeg) {
1411			lasttype = 1;
1412			lastg = g;
1413			lastbeg = mlbeg;
1414			lastml = ml;
1415			lastexpl = e;
1416			lastp = NULL;
1417			lastused = 1;
1418		    }
1419		    ml += mlprinted;
1420		    if (dolistcl(ml) && cl >= 0 && (cl -= mlprinted) <= 1) {
1421			cl = -1;
1422			if (tccan(TCCLEAREOD))
1423			    tcout(TCCLEAREOD);
1424		    }
1425		    pnl = 1;
1426		}
1427		e++;
1428		if (!mnew && ml > mlend)
1429		    goto end;
1430	    }
1431	}
1432	if (!listdat.onlyexpl && mlbeg < 0 && pp && *pp) {
1433	    if (pnl) {
1434		if (dolistnl(ml) && compprintnl(ml))
1435		    goto end;
1436		pnl = 0;
1437		ml++;
1438		if (cl >= 0 && --cl <= 1) {
1439		    cl = -1;
1440		    if (tccan(TCCLEAREOD))
1441			tcout(TCCLEAREOD);
1442		}
1443	    }
1444	    if (mlbeg < 0 && mfirstl < 0)
1445		mfirstl = ml;
1446	    if (g->flags & CGF_LINES) {
1447		while (*pp) {
1448		    if (compzputs(*pp, ml))
1449			goto end;
1450		    if (*++pp && compprintnl(ml))
1451			goto end;
1452		}
1453	    } else {
1454		int n = g->lcount, nl, nc, i, a;
1455		char **pq;
1456
1457		nl = nc = g->lins;
1458
1459		while (n && nl--) {
1460		    i = g->cols;
1461		    mc = 0;
1462		    pq = pp;
1463		    while (n && i--) {
1464			if (pq - g->ylist >= g->lcount)
1465			    break;
1466			if (compzputs(*pq, mscroll))
1467			    goto end;
1468			if (i) {
1469			    a = (g->widths ? g->widths[mc] : g->width) -
1470				strlen(*pq);
1471			    while (a--)
1472				putc(' ', shout);
1473			}
1474			pq += ((g->flags & CGF_ROWS) ? 1 : nc);
1475			mc++;
1476			n--;
1477		    }
1478		    if (n) {
1479			if (compprintnl(ml))
1480			    goto end;
1481			ml++;
1482			if (cl >= 0 && --cl <= 1) {
1483			    cl = -1;
1484			    if (tccan(TCCLEAREOD))
1485				tcout(TCCLEAREOD);
1486			}
1487		    }
1488		    pp += ((g->flags & CGF_ROWS) ? g->cols : 1);
1489		}
1490	    }
1491	} else if (!listdat.onlyexpl &&
1492		   (g->lcount || (showall && g->mcount))) {
1493	    int n = g->dcount, nl, nc, i, j, wid;
1494	    Cmatch *q;
1495
1496	    nl = nc = g->lins;
1497
1498	    if ((g->flags & CGF_HASDL) &&
1499		(lastused || !lasttype || lasttype == 2)) {
1500		if (!lastused && lasttype == 2) {
1501		    p = lastp;
1502		    ml = lastml;
1503		    n = lastn;
1504		    nl = lastnl;
1505		    lastused = 1;
1506		    pnl = 0;
1507		} else
1508		    p = g->matches;
1509
1510		for (; (m = *p); p++) {
1511		    if (m->disp && (m->flags & CMF_DISPLINE) &&
1512                        (showall || !(m->flags & (CMF_HIDE|CMF_NOLIST)))) {
1513			if (pnl) {
1514			    if (dolistnl(ml) && compprintnl(ml))
1515				goto end;
1516			    pnl = 0;
1517			    ml++;
1518			    if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
1519				cl = -1;
1520				if (tccan(TCCLEAREOD))
1521				    tcout(TCCLEAREOD);
1522			    }
1523			}
1524			if (!lasttype && ml >= mlbeg) {
1525			    lasttype = 2;
1526			    lastg = g;
1527			    lastbeg = mlbeg;
1528			    lastml = ml;
1529			    lastp = p;
1530			    lastn = n;
1531			    lastnl = nl;
1532			    lastused = 1;
1533			}
1534			if (mfirstl < 0)
1535			    mfirstl = ml;
1536			if (dolist(ml))
1537			    printed++;
1538			if (clprintm(g, p, 0, ml, 1, 0))
1539			    goto end;
1540			ml += mlprinted;
1541			if (dolistcl(ml) && (cl -= mlprinted) <= 1) {
1542			    cl = -1;
1543			    if (tccan(TCCLEAREOD))
1544				tcout(TCCLEAREOD);
1545			}
1546			pnl = 1;
1547		    }
1548		    if (!mnew && ml > mlend)
1549			goto end;
1550		}
1551	    }
1552	    if (n && pnl) {
1553		if (dolistnl(ml) && compprintnl(ml))
1554		    goto end;
1555		pnl = 0;
1556		ml++;
1557		if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
1558		    cl = -1;
1559		    if (tccan(TCCLEAREOD))
1560			tcout(TCCLEAREOD);
1561		}
1562	    }
1563	    if (!lastused && lasttype == 3) {
1564		p = lastp;
1565		n = lastn;
1566		nl = lastnl;
1567		ml = lastml;
1568		lastused = 1;
1569	    } else
1570		p = skipnolist(g->matches, showall);
1571
1572	    while (n && nl--) {
1573		if (!lasttype && ml >= mlbeg) {
1574		    lasttype = 3;
1575		    lastg = g;
1576		    lastbeg = mlbeg;
1577		    lastml = ml;
1578		    lastp = p;
1579		    lastn = n;
1580		    lastnl = nl + 1;
1581		    lastused = 1;
1582		}
1583		i = g->cols;
1584		mc = 0;
1585		q = p;
1586		while (n && i--) {
1587		    wid = (g->widths ? g->widths[mc] : g->width);
1588		    if (!(m = *q)) {
1589			if (clprintm(g, NULL, mc, ml, (!i), wid))
1590			    goto end;
1591			break;
1592		    }
1593                    if (clprintm(g, q, mc, ml, (!i), wid))
1594                        goto end;
1595
1596		    if (dolist(ml))
1597			printed++;
1598		    ml += mlprinted;
1599		    if (dolistcl(ml) && (cl -= mlprinted) < 1) {
1600			cl = -1;
1601			if (tccan(TCCLEAREOD))
1602			    tcout(TCCLEAREOD);
1603		    }
1604		    if (mfirstl < 0)
1605			mfirstl = ml;
1606
1607		    if (--n)
1608			for (j = ((g->flags & CGF_ROWS) ? 1 : nc);
1609			     j && *q; j--)
1610			    q = skipnolist(q + 1, showall);
1611		    mc++;
1612		}
1613		while (i-- > 0) {
1614		    if (clprintm(g, NULL, mc, ml, (!i),
1615				 (g->widths ? g->widths[mc] : g->width)))
1616			goto end;
1617		    mc++;
1618		}
1619		if (n) {
1620		    if (dolistnl(ml) && compprintnl(ml))
1621			goto end;
1622		    ml++;
1623		    if (dolistcl(ml) && cl >= 0 && --cl <= 1) {
1624			cl = -1;
1625			if (tccan(TCCLEAREOD))
1626			    tcout(TCCLEAREOD);
1627		    }
1628		    if (nl)
1629			for (j = ((g->flags & CGF_ROWS) ? g->cols : 1);
1630			     j && *p; j--)
1631			    p = skipnolist(p + 1, showall);
1632		}
1633		if (!mnew && ml > mlend)
1634		    goto end;
1635	    }
1636	}
1637	if (g->lcount || (showall && g->mcount))
1638	    pnl = 1;
1639	g = g->next;
1640    }
1641    asked = 0;
1642 end:
1643    mstatprinted = 0;
1644    lastlistlen = 0;
1645    if (nlnct <= 1)
1646	mscroll = 0;
1647    if (clearflag) {
1648	int nl;
1649
1650	/* Move the cursor up to the prompt, if always_last_prompt *
1651	 * is set and all that...                                  */
1652	if (mlbeg >= 0) {
1653	    if ((nl = listdat.nlines + nlnct) >= zterm_lines) {
1654		if (mhasstat) {
1655		    putc('\n', shout);
1656		    compprintfmt(NULL, 0, 1, 1, mline, NULL);
1657                    mstatprinted = 1;
1658		}
1659		nl = zterm_lines - 1;
1660	    } else
1661		nl--;
1662	    tcmultout(TCUP, TCMULTUP, nl);
1663	    showinglist = -1;
1664
1665	    lastlistlen = listdat.nlines;
1666	} else if ((nl = listdat.nlines + nlnct - 1) < zterm_lines) {
1667	    if (mlbeg >= 0 && tccan(TCCLEAREOL))
1668		tcout(TCCLEAREOL);
1669	    tcmultout(TCUP, TCMULTUP, nl);
1670	    showinglist = -1;
1671
1672	    lastlistlen = listdat.nlines;
1673	} else {
1674	    clearflag = 0;
1675	    if (!asked) {
1676		mrestlines = (ml + nlnct > zterm_lines);
1677		compprintnl(ml);
1678	    }
1679	}
1680    } else if (!asked) {
1681	mrestlines = (ml + nlnct > zterm_lines);
1682	compprintnl(ml);
1683    }
1684    listshown = (clearflag ? 1 : -1);
1685    mnew = 0;
1686
1687    return printed;
1688}
1689
1690/**/
1691static int
1692clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width)
1693{
1694    Cmatch m;
1695    int len, subcols = 0, stop = 0, ret = 0;
1696
1697    DPUTS2(mselect >= 0 && ml >= mlines,
1698	   "clprintm called with ml too large (%d/%d)",
1699	   ml, mlines);
1700    if (g != last_group)
1701        *last_cap = '\0';
1702
1703    last_group = g;
1704
1705    if (!mp) {
1706	if (dolist(ml)) {
1707	    zcputs(g->name, COL_SP);
1708	    len = width - 2;
1709	    while (len-- > 0)
1710		putc(' ', shout);
1711	    zcoff();
1712	}
1713	mlprinted = 0;
1714	return 0;
1715    }
1716    m = *mp;
1717
1718    if ((m->flags & CMF_ALL) && (!m->disp || !m->disp[0]))
1719	bld_all_str(m);
1720
1721    mlastm = m->gnum;
1722    if (m->disp && (m->flags & CMF_DISPLINE)) {
1723	if (mselect >= 0) {
1724	    int mm = (mcols * ml), i;
1725
1726            if (m->flags & CMF_DUMMY) {
1727                for (i = mcols; i-- > 0; ) {
1728		    DPUTS(mm+i >= mgtabsize, "BUG: invalid position");
1729                    mtab[mm + i] = mtmark(mp);
1730                    mgtab[mm + i] = mgmark(g);
1731                }
1732            } else {
1733                for (i = mcols; i-- > 0; ) {
1734		    DPUTS(mm+i >= mgtabsize, "BUG: invalid position");
1735                    mtab[mm + i] = mp;
1736                    mgtab[mm + i] = g;
1737                }
1738            }
1739	}
1740	if (!dolist(ml)) {
1741	    mlprinted = printfmt(m->disp, 0, 0, 0);
1742	    return 0;
1743	}
1744	if (m->gnum == mselect) {
1745	    int mm = (mcols * ml);
1746	    DPUTS(mm >= mgtabsize, "BUG: invalid position");
1747	    mline = ml;
1748	    mcol = 0;
1749	    mmtabp = mtab + mm;
1750	    mgtabp = mgtab + mm;
1751	    zcputs(g->name, COL_MA);
1752	} else if ((m->flags & CMF_NOLIST) &&
1753                   mcolors.files[COL_HI] && mcolors.files[COL_HI]->col)
1754	    zcputs(g->name, COL_HI);
1755	else if (mselect >= 0 && (m->flags & (CMF_MULT | CMF_FMULT)) &&
1756                 mcolors.files[COL_DU] && mcolors.files[COL_DU]->col)
1757	    zcputs(g->name, COL_DU);
1758	else
1759	    subcols = putmatchcol(g->name, m->disp);
1760	if (subcols)
1761	    ret = clprintfmt(m->disp, ml);
1762	else {
1763	    compprintfmt(m->disp, 0, 1, 0, ml, &stop);
1764	    if (stop)
1765		ret = 1;
1766	}
1767	zcoff();
1768    } else {
1769	int mx, modec;
1770
1771	if (g->widths) {
1772	    int i;
1773
1774	    for (i = mx = 0; i < mc; i++)
1775		mx += g->widths[i];
1776	} else
1777	    mx = mc * g->width;
1778
1779	if (mselect >= 0) {
1780	    int mm = mcols * ml, i;
1781
1782            if (m->flags & CMF_DUMMY) {
1783                for (i = (width ? width : mcols); i-- > 0; ) {
1784		    DPUTS(mx+mm+i >= mgtabsize, "BUG: invalid position");
1785                    mtab[mx + mm + i] = mtmark(mp);
1786                    mgtab[mx + mm + i] = mgmark(g);
1787                }
1788            } else {
1789                for (i = (width ? width : mcols); i-- > 0; ) {
1790		    DPUTS(mx+mm+i >= mgtabsize, "BUG: invalid position");
1791                    mtab[mx + mm + i] = mp;
1792                    mgtab[mx + mm + i] = g;
1793                }
1794            }
1795	}
1796	if (!dolist(ml)) {
1797	    int nc = ZMB_nicewidth(m->disp ? m->disp : m->str);
1798	    if (nc)
1799		mlprinted = (nc-1) / zterm_columns;
1800	    else
1801		mlprinted = 0;
1802	    return 0;
1803	}
1804	if (m->gnum == mselect) {
1805	    int mm = mcols * ml;
1806	    DPUTS(mx+mm >= mgtabsize, "BUG: invalid position");
1807
1808	    mcol = mx;
1809	    mline = ml;
1810	    mmtabp = mtab + mx + mm;
1811	    mgtabp = mgtab + mx + mm;
1812	    zcputs(g->name, COL_MA);
1813	} else if (m->flags & CMF_NOLIST)
1814	    zcputs(g->name, COL_HI);
1815	else if (mselect >= 0 && (m->flags & (CMF_MULT | CMF_FMULT)))
1816	    zcputs(g->name, COL_DU);
1817	else if (m->mode) {
1818	    /*
1819	     * Symlink is orphaned if we read the mode with lstat
1820	     * but couldn't read one with stat.  That's the
1821	     * only way they can be different so the following
1822	     * test should be enough.
1823	     */
1824	    int orphan_colour = (m->mode && !m->fmode) ? COL_OR : -1;
1825	    if (mcolors.flags & LC_FOLLOW_SYMLINKS) {
1826		subcols = putfilecol(g->name, m->str, m->fmode, orphan_colour);
1827	    } else {
1828		subcols = putfilecol(g->name, m->str, m->mode, orphan_colour);
1829	    }
1830	}
1831	else
1832	    subcols = putmatchcol(g->name, (m->disp ? m->disp : m->str));
1833
1834	ret = clnicezputs(subcols,
1835			  (m->disp ? m->disp : m->str), ml);
1836	if (ret) {
1837	    zcoff();
1838	    return 1;
1839	}
1840	len = ZMB_nicewidth(m->disp ? m->disp : m->str);
1841	mlprinted = len ? (len-1) / zterm_columns : 0;
1842
1843	modec = (mcolors.flags & LC_FOLLOW_SYMLINKS) ? m->fmodec : m->modec;
1844	if ((g->flags & CGF_FILES) && modec) {
1845	    if (m->gnum != mselect) {
1846		zcoff();
1847		zcputs(g->name, COL_TC);
1848	    }
1849	    putc(modec, shout);
1850	    len++;
1851        }
1852	if ((len = width - len - 2) > 0) {
1853	    if (m->gnum != mselect) {
1854		zcoff();
1855		zcputs(g->name, COL_SP);
1856	    }
1857	    while (len-- > 0)
1858		putc(' ', shout);
1859	}
1860	zcoff();
1861	if (!lastc) {
1862	    zcputs(g->name, COL_SP);
1863	    fputs("  ", shout);
1864	    zcoff();
1865	}
1866    }
1867    return ret;
1868}
1869
1870static int
1871singlecalc(int *cp, int l, int *lcp)
1872{
1873    int c = *cp, n, j, first = 1;
1874    Cmatch **p, *op, *mp = mtab[l * zterm_columns + c];
1875
1876    for (n = 0, j = c, p = mtab + l * zterm_columns + c, op = NULL;
1877	 j >= 0;
1878	 j--, p--) {
1879        if (*p == mp)
1880            c = j;
1881        if (!first && *p != op)
1882            n++;
1883        op = *p;
1884        first = 0;
1885    }
1886    *cp = c;
1887    *lcp = 1;
1888    for (p = mtab + l * zterm_columns + c; c < zterm_columns; c++, p++)
1889        if (*p && mp != *p)
1890            *lcp = 0;
1891
1892    return n;
1893}
1894
1895static void
1896singledraw()
1897{
1898    Cmgroup g;
1899    int mc1, mc2, ml1, ml2, md1, md2, mcc1, mcc2, lc1, lc2, t1, t2;
1900
1901    t1 = mline - mlbeg;
1902    t2 = moline - molbeg;
1903
1904    if (t2 < t1) {
1905        mc1 = mocol; ml1 = moline; md1 = t2;
1906        mc2 = mcol; ml2 = mline; md2 = t1;
1907    } else {
1908        mc1 = mcol; ml1 = mline; md1 = t1;
1909        mc2 = mocol; ml2 = moline; md2 = t2;
1910    }
1911    mcc1 = singlecalc(&mc1, ml1, &lc1);
1912    mcc2 = singlecalc(&mc2, ml2, &lc2);
1913
1914    if (md1)
1915        tc_downcurs(md1);
1916    if (mc1)
1917        tcmultout(TCRIGHT, TCMULTRIGHT, mc1);
1918    DPUTS(ml1 * zterm_columns + mc1 >= mgtabsize, "BUG: invalid position");
1919    g = mgtab[ml1 * zterm_columns + mc1];
1920    clprintm(g, mtab[ml1 * zterm_columns + mc1], mcc1, ml1, lc1,
1921             (g->widths ? g->widths[mcc1] : g->width));
1922    if (mlprinted)
1923	(void) tcmultout(TCUP, TCMULTUP, mlprinted);
1924    putc('\r', shout);
1925
1926    if (md2 != md1)
1927        tc_downcurs(md2 - md1);
1928    if (mc2)
1929        tcmultout(TCRIGHT, TCMULTRIGHT, mc2);
1930    DPUTS(ml2 * zterm_columns + mc2 >= mgtabsize, "BUG: invalid position");
1931    g = mgtab[ml2 * zterm_columns + mc2];
1932    clprintm(g, mtab[ml2 * zterm_columns + mc2], mcc2, ml2, lc2,
1933             (g->widths ? g->widths[mcc2] : g->width));
1934    if (mlprinted)
1935	(void) tcmultout(TCUP, TCMULTUP, mlprinted);
1936    putc('\r', shout);
1937
1938    if (mstatprinted) {
1939        int i = zterm_lines - md2 - nlnct;
1940
1941        tc_downcurs(i - 1);
1942        compprintfmt(NULL, 0, 1, 1, mline, NULL);
1943        tcmultout(TCUP, TCMULTUP, zterm_lines - 1);
1944    } else
1945        tcmultout(TCUP, TCMULTUP, md2 + nlnct);
1946
1947    showinglist = -1;
1948    listshown = 1;
1949}
1950
1951static int
1952complistmatches(UNUSED(Hookdef dummy), Chdata dat)
1953{
1954    static int onlnct = -1;
1955    static int extendedglob;
1956
1957    Cmgroup oamatches = amatches;
1958
1959    amatches = dat->matches;
1960#ifdef ZSH_HEAP_DEBUG
1961    if (memory_validate(amatches->heap_id)) {
1962	HEAP_ERROR(amatches->heap_id);
1963    }
1964#endif
1965
1966    noselect = 0;
1967
1968    if ((minfo.asked == 2 && mselect < 0) || nlnct >= zterm_lines) {
1969	showinglist = 0;
1970	amatches = oamatches;
1971	return (noselect = 1);
1972    }
1973
1974    /*
1975     * There's a lot of memory allocation from this function
1976     * for setting up the color display which isn't needed
1977     * after the function exits, so it's worthwhile pushing
1978     * another heap.  As this is called from a hook in the main
1979     * completion handler nothing temporarily allocated from here can be
1980     * useful outside.
1981     */
1982    pushheap();
1983    extendedglob = opts[EXTENDEDGLOB];
1984    opts[EXTENDEDGLOB] = 1;
1985
1986    getcols();
1987
1988    mnew = ((calclist(mselect >= 0) || mlastcols != zterm_columns ||
1989	     mlastlines != listdat.nlines) && mselect >= 0);
1990
1991    if (!listdat.nlines || (mselect >= 0 &&
1992			    !(isset(USEZLE) && !termflags &&
1993			      complastprompt && *complastprompt))) {
1994	showinglist = listshown = 0;
1995	noselect = 1;
1996	amatches = oamatches;
1997	popheap();
1998	opts[EXTENDEDGLOB] = extendedglob;
1999	return 1;
2000    }
2001    if (inselect || mlbeg >= 0)
2002	clearflag = 0;
2003
2004    mscroll = 0;
2005    mlistp = NULL;
2006
2007    queue_signals();
2008    if (mselect >= 0 || mlbeg >= 0 ||
2009	(mlistp = dupstring(getsparam("LISTPROMPT")))) {
2010	unqueue_signals();
2011	if (mlistp && !*mlistp)
2012	    mlistp = "%SAt %p: Hit TAB for more, or the character to insert%s";
2013	trashzle();
2014	showinglist = listshown = 0;
2015
2016	lastlistlen = 0;
2017
2018	if (mlistp) {
2019	    clearflag = (isset(USEZLE) && !termflags && dolastprompt);
2020	    mscroll = 1;
2021	} else {
2022	    clearflag = 1;
2023	    minfo.asked = (listdat.nlines + nlnct <= zterm_lines);
2024	}
2025    } else {
2026	unqueue_signals();
2027	mlistp = NULL;
2028	if (asklist()) {
2029	    amatches = oamatches;
2030	    popheap();
2031	    opts[EXTENDEDGLOB] = extendedglob;
2032	    return (noselect = 1);
2033	}
2034    }
2035    if (mlbeg >= 0) {
2036	mlend = mlbeg + zterm_lines - nlnct - mhasstat;
2037	while (mline >= mlend)
2038	    mlbeg++, mlend++;
2039    } else
2040	mlend = 9999999;
2041
2042    if (mnew) {
2043	int i;
2044
2045    	mtab_been_reallocated = 1;
2046
2047	i = zterm_columns * listdat.nlines;
2048	free(mtab);
2049	mtab = (Cmatch **) zalloc(i * sizeof(Cmatch **));
2050	memset(mtab, 0, i * sizeof(Cmatch **));
2051	free(mgtab);
2052	mgtab = (Cmgroup *) zalloc(i * sizeof(Cmgroup));
2053#ifdef DEBUG
2054	mgtabsize = i;
2055#endif
2056	memset(mgtab, 0, i * sizeof(Cmgroup));
2057	mlastcols = mcols = zterm_columns;
2058	mlastlines = mlines = listdat.nlines;
2059    }
2060    last_cap = (char *) zhalloc(max_caplen + 1);
2061    *last_cap = '\0';
2062
2063    if (!mnew && inselect && onlnct == nlnct && mlbeg >= 0 && mlbeg == molbeg)
2064        singledraw();
2065    else if (!compprintlist(mselect >= 0) || !clearflag)
2066	noselect = 1;
2067
2068    onlnct = nlnct;
2069    molbeg = mlbeg;
2070    mocol = mcol;
2071    moline = mline;
2072
2073    amatches = oamatches;
2074
2075    popheap();
2076    opts[EXTENDEDGLOB] = extendedglob;
2077
2078    return noselect;
2079}
2080
2081static int
2082adjust_mcol(int wish, Cmatch ***tabp, Cmgroup **grp)
2083{
2084    Cmatch **matchtab = *tabp;
2085    int p, n, c;
2086
2087    matchtab -= mcol;
2088
2089    for (p = wish; p >= 0 && (!matchtab[p] || mmarked(matchtab[p])); p--);
2090    for (n = wish; n < mcols && (!matchtab[n] || mmarked(matchtab[n])); n++);
2091    if (n == mcols)
2092	n = -1;
2093
2094    if (p < 0) {
2095	if (n < 0)
2096	    return 1;
2097	c = n;
2098    } else if (n < 0)
2099	c = p;
2100    else
2101	c = ((mcol - p) < (n - mcol) ? p : n);
2102
2103    *tabp = matchtab + c;
2104    if (grp)
2105	*grp = *grp + c - mcol;
2106
2107    mcol = c;
2108
2109    return 0;
2110}
2111
2112typedef struct menustack *Menustack;
2113
2114struct menustack {
2115    Menustack prev;
2116    char *line;
2117    Brinfo brbeg;
2118    Brinfo brend;
2119    int nbrbeg, nbrend;
2120    int cs, acc, nmatches, mline, mlbeg, nolist;
2121    struct menuinfo info;
2122    Cmgroup amatches, pmatches, lastmatches, lastlmatches;
2123    /*
2124     * Status for how line looked like previously.
2125     */
2126    char *origline;
2127    int origcs, origll;
2128    /*
2129     * Status for interactive mode.  status is the line
2130     * printed above the matches saying what the interactive
2131     * completion prefix is.  mode says whether we are in
2132     * interactive or some search mode.
2133     * typed.
2134     */
2135    char *status;
2136    int mode;
2137};
2138
2139typedef struct menusearch *Menusearch;
2140
2141struct menusearch {
2142    Menusearch prev;
2143    char *str;
2144    int line;
2145    int col;
2146    int back;
2147    int state;
2148    Cmatch **ptr;
2149};
2150
2151#define MS_OK       0
2152#define MS_FAILED   1
2153#define MS_WRAPPED  2
2154
2155#define MAX_STATUS 128
2156
2157static char *
2158setmstatus(char *status, char *sline, int sll, int scs,
2159           int *csp, int *llp, int *lenp)
2160{
2161    char *p, *s, *ret = NULL;
2162    int pl, sl, max;
2163
2164    METACHECK();
2165
2166    if (csp) {
2167        *csp = zlemetacs;
2168        *llp = zlemetall;
2169        *lenp = lastend - wb;
2170
2171        ret = dupstring(zlemetaline);
2172
2173        p = (char *) zhalloc(zlemetacs - wb + 1);
2174        strncpy(p, zlemetaline + wb, zlemetacs - wb);
2175        p[zlemetacs - wb] = '\0';
2176        if (lastend < zlemetacs)
2177            s = "";
2178        else {
2179            s = (char *) zhalloc(lastend - zlemetacs + 1);
2180            strncpy(s, zlemetaline + zlemetacs, lastend - zlemetacs);
2181            s[lastend - zlemetacs] = '\0';
2182        }
2183        zlemetacs = 0;
2184        foredel(zlemetall, CUT_RAW);
2185        spaceinline(sll);
2186        memcpy(zlemetaline, sline, sll);
2187        zlemetacs = scs;
2188    } else {
2189        p = complastprefix;
2190        s = complastsuffix;
2191    }
2192    pl = strlen(p);
2193    sl = strlen(s);
2194    max = (zterm_columns < MAX_STATUS ? zterm_columns : MAX_STATUS) - 14;
2195
2196    if (max > 12) {
2197        int h = (max - 2) >> 1;
2198
2199        strcpy(status, "interactive: ");
2200        if (pl > h - 3) {
2201            strcat(status, "...");
2202            strcat(status, p + pl - h - 3);
2203        } else
2204            strcat(status, p);
2205
2206        strcat(status, "[]");
2207        if (sl > h - 3) {
2208            strncat(status, s, h - 3);
2209            strcat(status, "...");
2210        } else
2211            strcat(status, s);
2212    }
2213    return ret;
2214}
2215
2216static Menusearch msearchstack;
2217static char *msearchstr = NULL;
2218static int msearchstate;
2219
2220static void
2221msearchpush(Cmatch **p, int back)
2222{
2223    Menusearch s = (Menusearch) zhalloc(sizeof(struct menusearch));
2224
2225    s->prev = msearchstack;
2226    msearchstack = s;
2227    s->str = dupstring(msearchstr);
2228    s->line = mline;
2229    s->col = mcol;
2230    s->back = back;
2231    s->state = msearchstate;
2232    s->ptr = p;
2233}
2234
2235static Cmatch **
2236msearchpop(int *backp)
2237{
2238    Menusearch s = msearchstack;
2239
2240    if (!s)
2241        return NULL;
2242
2243    if (s->prev)
2244        msearchstack = s->prev;
2245
2246    msearchstr = s->str;
2247    mline = s->line;
2248    mcol = s->col;
2249    msearchstate = s->state;
2250
2251    *backp = s->back;
2252
2253    return s->ptr;
2254}
2255
2256static Cmatch **
2257msearch(Cmatch **ptr, int ins, int back, int rep, int *wrapp)
2258{
2259#ifdef MULTIBYTE_SUPPORT
2260    /* MB_CUR_MAX may not be constant */
2261    VARARR(char, s, MB_CUR_MAX+1);
2262#else
2263    char s[2];
2264#endif
2265    Cmatch **p, *l = NULL, m;
2266    int x = mcol, y = mline;
2267    int ex, ey, wrap = 0, owrap = (msearchstate & MS_WRAPPED);
2268
2269    msearchpush(ptr, back);
2270
2271    if (ins) {
2272#ifdef MULTIBYTE_SUPPORT
2273	if (lastchar_wide_valid)
2274	{
2275	    mbstate_t mbs;
2276	    int len;
2277
2278	    memset(&mbs, 0, sizeof(mbs));
2279	    len = wcrtomb(s, lastchar_wide, &mbs);
2280	    if (len < 0)
2281		len = 0;
2282	    s[len] = '\0';
2283	} else
2284#endif
2285	{
2286	    s[0] = lastchar;
2287	    s[1] = '\0';
2288	}
2289
2290        msearchstr = dyncat(msearchstr, s);
2291    }
2292    if (back) {
2293        ex = mcols - 1;
2294        ey = -1;
2295    } else {
2296        ex = 0;
2297        ey = listdat.nlines;
2298    }
2299    p = mtab + (mline * mcols) + mcol;
2300    if (rep)
2301        l = *p;
2302    while (1) {
2303        if (!rep && mtunmark(*p) && *p != l) {
2304            l = *p;
2305            m = *mtunmark(*p);
2306
2307            if (strstr((m->disp ? m->disp : m->str), msearchstr)) {
2308                mcol = x;
2309                mline = y;
2310
2311                return p;
2312            }
2313        }
2314        rep = 0;
2315
2316        if (back) {
2317            p--;
2318            if (--x < 0) {
2319                x = mcols - 1;
2320                y--;
2321            }
2322        } else {
2323            p++;
2324            if (++x == mcols) {
2325                x = 0;
2326                y++;
2327            }
2328        }
2329        if (x == ex && y == ey) {
2330            if (wrap) {
2331                msearchstate = MS_FAILED | owrap;
2332                break;
2333            }
2334            msearchstate |= MS_WRAPPED;
2335
2336            if (back) {
2337                x = mcols - 1;
2338                y = listdat.nlines - 1;
2339                p = mtab + (y * mcols) + x;
2340            } else {
2341                x = y = 0;
2342                p = mtab;
2343            }
2344            ex = mcol;
2345            ey = mline;
2346            wrap = 1;
2347            *wrapp = 1;
2348        }
2349    }
2350    return NULL;
2351}
2352
2353/*
2354 * Values to assign to mode: interactive, etc.
2355 */
2356#define MM_INTER   1
2357#define MM_FSEARCH 2
2358#define MM_BSEARCH 3
2359
2360static int
2361domenuselect(Hookdef dummy, Chdata dat)
2362{
2363    static Chdata fdat = NULL;
2364    static char *lastsearch = NULL;
2365    Cmatch **p;
2366    Cmgroup *pg;
2367    Thingy cmd = 0;
2368    int     do_last_key = 0;
2369    Menustack u = NULL;
2370    int i = 0, acc = 0, wishcol = 0, setwish = 0, oe = onlyexpl, wasnext = 0;
2371    int space, lbeg = 0, step = 1, wrap, pl = nlnct, broken = 0, first = 1;
2372    int nolist = 0, mode = 0, modecs, modell, modelen, wasmeta;
2373    char *s;
2374    char status[MAX_STATUS], *modeline = NULL;
2375
2376    msearchstack = NULL;
2377    msearchstr = "";
2378    msearchstate = MS_OK;
2379
2380    status[0] = '\0';
2381    queue_signals();
2382    if (fdat || (dummy && (!(s = getsparam("MENUSELECT")) ||
2383			   (dat && dat->num < atoi(s))))) {
2384	if (fdat) {
2385	    fdat->matches = dat->matches;
2386	    fdat->num = dat->num;
2387	    fdat->nmesg = dat->nmesg;
2388	}
2389	unqueue_signals();
2390	return 0;
2391    }
2392    /*
2393     * Lots of the logic here doesn't really make sense if the
2394     * line isn't metafied, but the evidence was that it only used
2395     * to be metafied locally in a couple of places.
2396     * It's horrifically difficult to work out where the line
2397     * is metafied, so I've resorted to the following.
2398     * Unfortunately we need to unmetatfy in zrefresh() when
2399     * we want to display something.  Maybe this function can
2400     * be done better.
2401     */
2402    if (zlemetaline != NULL)
2403	wasmeta = 1;
2404    else {
2405	wasmeta = 0;
2406	metafy_line();
2407    }
2408
2409    if ((s = getsparam("MENUSCROLL"))) {
2410	if (!(step = mathevali(s)))
2411	    step = (zterm_lines - nlnct) >> 1;
2412	else if (step < 0)
2413	    if ((step += zterm_lines - nlnct) < 0)
2414		step = 1;
2415    }
2416    if ((s = getsparam("MENUMODE"))) {
2417        if (!strcmp(s, "interactive")) {
2418            int l = strlen(origline);
2419
2420	    /*
2421	     * In interactive completion mode we don't insert
2422	     * the completion onto the command line, instead
2423	     * we show just what the user has typed and
2424	     * the match so far underneath (stored in "status").
2425	     * So put the command line back to how it
2426	     * was before completion started.
2427	     */
2428            mode = MM_INTER;
2429            zlemetacs = 0;
2430            foredel(zlemetall, CUT_RAW);
2431            spaceinline(l);
2432            strncpy(zlemetaline, origline, l);
2433            zlemetacs = origcs;
2434            setmstatus(status, NULL, 0 , 0, NULL, NULL, NULL);
2435        } else if (strpfx("search", s)) {
2436            mode = (strstr(s, "back") ? MM_BSEARCH : MM_FSEARCH);
2437        }
2438    }
2439    if ((mstatus = dupstring(getsparam("MENUPROMPT"))) && !*mstatus)
2440	mstatus = "%SScrolling active: current selection at %p%s";
2441    unqueue_signals();
2442    mhasstat = (mstatus && *mstatus);
2443    fdat = dat;
2444    selectlocalmap(mskeymap);
2445    noselect = 1;
2446    while ((menuacc &&
2447	    !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr)) ||
2448	   ((*minfo.cur)->flags & CMF_DUMMY) ||
2449	   (((*minfo.cur)->flags & (CMF_NOLIST | CMF_MULT)) &&
2450	    (!(*minfo.cur)->str || !*(*minfo.cur)->str)))
2451	do_menucmp(0);
2452
2453    mselect = (*(minfo.cur))->gnum;
2454    mline = 0;
2455    mlines = 999999;
2456    mlbeg = 0;
2457    molbeg = -42;
2458    mtab_been_reallocated = 0;
2459    for (;;) {
2460	METACHECK();
2461
2462	if (mline < 0 || mtab_been_reallocated) {
2463	    int x, y;
2464	    Cmatch **p = mtab;
2465
2466	    for (y = 0; y < mlines; y++) {
2467		for (x = mcols; x > 0; x--, p++)
2468		    if (*p && !mmarked(*p) && **p && mselect == (**p)->gnum)
2469			break;
2470		if (x) {
2471                    mcol = mcols - x;
2472		    break;
2473                }
2474	    }
2475	    if (y < mlines)
2476		mline = y;
2477	}
2478    	mtab_been_reallocated = 0;
2479	DPUTS(mline < 0,
2480	      "BUG: mline < 0 after re-scanning mtab in domenuselect()");
2481	while (mline < mlbeg)
2482	    if ((mlbeg -= step) < 0) {
2483		mlbeg = 0;
2484		/* Crude workaround for BUG above */
2485		if (mline < 0)
2486		    break;
2487	    }
2488
2489	if (mlbeg && lbeg != mlbeg) {
2490	    Cmatch **p = mtab + ((mlbeg - 1) * zterm_columns), **q;
2491	    int c;
2492
2493	    while (mlbeg) {
2494		for (q = p, c = zterm_columns; c > 0; q++, c--)
2495		    if (*q && !mmarked(*q))
2496			break;
2497		if (c)
2498		    break;
2499		p -= zterm_columns;
2500		mlbeg--;
2501	    }
2502	}
2503	if ((space = zterm_lines - pl - mhasstat))
2504	    while (mline >= mlbeg + space)
2505		if ((mlbeg += step) + space > mlines)
2506		    mlbeg = mlines - space;
2507	if (lbeg != mlbeg) {
2508	    Cmatch **p = mtab + (mlbeg * zterm_columns), **q;
2509	    int c;
2510
2511	    while (mlbeg < mlines) {
2512		for (q = p, c = zterm_columns; c > 0; q++, c--)
2513		    if (*q)
2514			break;
2515		if (c)
2516		    break;
2517		p += zterm_columns;
2518		mlbeg++;
2519	    }
2520	}
2521	lbeg = mlbeg;
2522        onlyexpl = 0;
2523        showinglist = -2;
2524        if (first && !listshown && isset(LISTBEEP))
2525            zbeep();
2526        if (first) {
2527	    /*
2528	     * remember the original data that we will use when
2529	     * performing interactive completion to restore the
2530	     * command line when a menu completion is inserted.
2531	     * this is because menu completion will insert
2532	     * the next match in the loop; for interactive
2533	     * completion we don't want that, we always want to
2534	     * be able to type the next character.
2535	     */
2536	    modeline = dupstring(zlemetaline);
2537            modecs = zlemetacs;
2538            modell = zlemetall;
2539            modelen = minfo.len;
2540        }
2541        first = 0;
2542        if (mode == MM_INTER)
2543	    statusline = status;
2544        else if (mode) {
2545            int l = sprintf(status, "%s%sisearch%s: ",
2546                            ((msearchstate & MS_FAILED) ? "failed " : ""),
2547                            ((msearchstate & MS_WRAPPED) ? "wrapped " : ""),
2548                            (mode == MM_FSEARCH ? "" : " backward"));
2549
2550            strncat(status, msearchstr, MAX_STATUS - l - 1);
2551
2552            statusline = status;
2553        } else {
2554            statusline = NULL;
2555        }
2556        zrefresh();
2557	statusline = NULL;
2558        inselect = 1;
2559        if (noselect) {
2560            broken = 1;
2561            break;
2562        }
2563	selected = 1;
2564	if (!i) {
2565	    i = mcols * mlines;
2566	    while (i--)
2567		if (mtab[i])
2568		    break;
2569	    if (!i)
2570		break;
2571	    i = 1;
2572	}
2573	p = mmtabp;
2574	pg = mgtabp;
2575	minfo.cur = *p;
2576	minfo.group = *pg;
2577	if (setwish)
2578	    wishcol = mcol;
2579	else if (mcol > wishcol) {
2580	    while (mcol > 0 && p[-1] == minfo.cur)
2581		mcol--, p--, pg--;
2582	} else if (mcol < wishcol) {
2583	    while (mcol < mcols - 1 && p[1] == minfo.cur)
2584		mcol++, p++, pg++;
2585	}
2586	setwish = wasnext = 0;
2587
2588    getk:
2589
2590    	if (!do_last_key) {
2591	    cmd = getkeycmd();
2592	    if (mtab_been_reallocated) {
2593		do_last_key = 1;
2594		continue;
2595	    }
2596    	}
2597	do_last_key = 0;
2598
2599	if (!cmd || cmd == Th(z_sendbreak)) {
2600	    zbeep();
2601            molbeg = -1;
2602	    break;
2603	} else if (nolist && cmd != Th(z_undo) &&
2604                   (!mode || (cmd != Th(z_backwarddeletechar) &&
2605                              cmd != Th(z_selfinsert) &&
2606                              cmd != Th(z_selfinsertunmeta)))) {
2607	    ungetkeycmd();
2608	    break;
2609	} else if (cmd == Th(z_acceptline) || cmd == Th(z_acceptsearch)) {
2610            if (mode == MM_FSEARCH || mode == MM_BSEARCH) {
2611                mode = 0;
2612                continue;
2613            }
2614	    acc = 1;
2615	    break;
2616        } else if (cmd == Th(z_viinsert)) {
2617            if (mode == MM_INTER)
2618                mode = 0;
2619            else {
2620                int l = strlen(origline);
2621
2622		/*
2623		 * Entering interactive completion mode:
2624		 * same code as when we enter it on menu selection
2625		 * start.
2626		 */
2627                mode = MM_INTER;
2628                zlemetacs = 0;
2629                foredel(zlemetall, CUT_RAW);
2630                spaceinline(l);
2631                strncpy(zlemetaline, origline, l);
2632                zlemetacs = origcs;
2633                setmstatus(status, NULL, 0, 0, NULL, NULL, NULL);
2634
2635                continue;
2636            }
2637	} else if (cmd == Th(z_acceptandinfernexthistory) ||
2638                   (mode == MM_INTER && (cmd == Th(z_selfinsert) ||
2639                                         cmd == Th(z_selfinsertunmeta)))) {
2640            char *saveline = NULL;
2641            int savell = 0;
2642            int savecs = 0;
2643	    Menustack s = (Menustack) zhalloc(sizeof(*s));
2644
2645	    s->prev = u;
2646	    u = s;
2647	    s->line = dupstring(zlemetaline);
2648	    s->cs = zlemetacs;
2649	    s->mline = mline;
2650	    s->mlbeg = mlbeg;
2651	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
2652	    s->amatches = amatches;
2653#ifdef ZSH_HEAP_DEBUG
2654	    if (memory_validate(amatches->heap_id)) {
2655		HEAP_ERROR(amatches->heap_id);
2656	    }
2657#endif
2658	    s->pmatches = pmatches;
2659	    s->lastmatches = lastmatches;
2660	    s->lastlmatches = lastlmatches;
2661            s->nolist = nolist;
2662	    s->acc = menuacc;
2663	    s->brbeg = dupbrinfo(brbeg, NULL, 1);
2664	    s->brend = dupbrinfo(brend, NULL, 1);
2665	    s->nbrbeg = nbrbeg;
2666	    s->nbrend = nbrend;
2667	    s->nmatches = nmatches;
2668	    s->origline = origline;
2669	    s->origcs = origcs;
2670	    s->origll = origll;
2671            s->status = dupstring(status);
2672	    /*
2673	     * with just the slightest hint of a note of infuriation:
2674	     * mode here is the menu mode, not the file mode, despite
2675	     * the fact we're in a file dealing with file highlighting;
2676	     * but that's OK, because s is a menu stack entry, despite
2677	     * the fact we're in a function declaring s as char *.
2678	     * anyway, in functions we really mean *mode* it's
2679	     * called m, to be clear.
2680	     */
2681            s->mode = mode;
2682	    menucmp = menuacc = hasoldlist = 0;
2683	    minfo.cur = NULL;
2684	    fixsuffix();
2685	    handleundo();
2686	    validlist = 0;
2687	    amatches = pmatches = lastmatches = NULL;
2688	    invalidate_list();
2689	    iforcemenu = 1;
2690	    comprecursive = 1;
2691            if (cmd != Th(z_acceptandinfernexthistory)) {
2692                int l = strlen(origline);
2693
2694		/*
2695		 * Interactive mode: we need to restore the
2696		 * line, add the character, then remember how
2697		 * this new line looks in order to keep
2698		 * the command line as it is with just the
2699		 * characters typed by the user.
2700		 */
2701                zlemetacs = 0;
2702                foredel(zlemetall, CUT_RAW);
2703                spaceinline(l);
2704                strncpy(zlemetaline, origline, l);
2705                zlemetacs = origcs;
2706
2707		/*
2708		 * Horrible quick fix:
2709		 * we shouldn't need to metafy and unmetafy
2710		 * quite as much.  If we kept unmetafied through
2711		 * here we could fix up setmstatus to use unmetafied
2712		 * as well.  This is the only use of setmstatus which
2713		 * restores the line so that should be doable.
2714		 */
2715		unmetafy_line();
2716                if (cmd == Th(z_selfinsert))
2717                    selfinsert(zlenoargs);
2718                else
2719                    selfinsertunmeta(zlenoargs);
2720		metafy_line();
2721		minfo.len++;
2722		minfo.end++;
2723
2724                saveline = (char *) zhalloc(zlemetall);
2725                memcpy(saveline, zlemetaline, zlemetall);
2726                savell = zlemetall;
2727                savecs = zlemetacs;
2728                iforcemenu = -1;
2729            } else
2730                mode = 0;
2731	    /* Nested completion assumes line is unmetafied */
2732	    unmetafy_line();
2733	    menucomplete(zlenoargs);
2734	    metafy_line();
2735	    iforcemenu = 0;
2736
2737            if (cmd != Th(z_acceptandinfernexthistory))
2738                modeline = setmstatus(status, saveline, savell, savecs,
2739                                      &modecs, &modell, &modelen);
2740
2741	    if (nmatches < 1 || !minfo.cur || !*(minfo.cur)) {
2742		nolist = 1;
2743                if (mode == MM_INTER) {
2744                    statusline = status;
2745                } else {
2746		    /* paranoia */
2747		    statusline = NULL;
2748		}
2749		if (nmessages) {
2750		    showinglist = -2;
2751		    zrefresh();
2752		} else {
2753		    trashzle();
2754		    zsetterm();
2755		    if (tccan(TCCLEAREOD))
2756			tcout(TCCLEAREOD);
2757		    fputs("no matches\r", shout);
2758		    fflush(shout);
2759		    tcmultout(TCUP, TCMULTUP, nlnct);
2760		    showinglist = clearlist = 0;
2761		    clearflag = 1;
2762		    zrefresh();
2763		    showinglist = clearlist = 0;
2764		}
2765		statusline = NULL;
2766
2767		goto getk;
2768	    }
2769	    clearlist = listshown = 1;
2770	    mselect = (*(minfo.cur))->gnum;
2771	    setwish = wasnext = 1;
2772	    mline = 0;
2773            molbeg = -42;
2774	    continue;
2775	} else if (cmd == Th(z_acceptandhold) ||
2776		   cmd == Th(z_acceptandmenucomplete)) {
2777	    Menustack s = (Menustack) zhalloc(sizeof(*s));
2778	    int ol;
2779
2780            mode = 0;
2781	    s->prev = u;
2782	    u = s;
2783	    s->line = dupstring(zlemetaline);
2784	    s->cs = zlemetacs;
2785	    s->mline = mline;
2786	    s->mlbeg = mlbeg;
2787	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
2788	    s->amatches = s->pmatches =
2789		s->lastmatches = s->lastlmatches = NULL;
2790            s->nolist = nolist;
2791	    s->acc = menuacc;
2792	    s->brbeg = dupbrinfo(brbeg, NULL, 1);
2793	    s->brend = dupbrinfo(brend, NULL, 1);
2794	    s->nbrbeg = nbrbeg;
2795	    s->nbrend = nbrend;
2796	    s->nmatches = nmatches;
2797	    s->origline = origline;
2798	    s->origcs = origcs;
2799	    s->origll = origll;
2800            s->status = dupstring(status);
2801	    /* see above */
2802            s->mode = mode;
2803	    accept_last();
2804	    handleundo();
2805	    comprecursive = 1;
2806	    do_menucmp(0);
2807	    mselect = (*(minfo.cur))->gnum;
2808
2809	    p -= mcol;
2810	    mcol = 0;
2811	    ol = mline;
2812	    do {
2813		for (mcol = 0; mcol < mcols; mcol++, p++)
2814		    if (*p == minfo.cur)
2815			break;
2816		if (mcol != mcols)
2817		    break;
2818		if (++mline == mlines) {
2819		    mline = 0;
2820		    p -= mlines * mcols;
2821		}
2822	    } while (mline != ol);
2823	    if (*p != minfo.cur) {
2824		noselect = clearlist = listshown = 1;
2825		onlyexpl = 0;
2826		zrefresh();
2827		break;
2828	    }
2829	    setwish = 1;
2830	    continue;
2831	} else if (cmd == Th(z_undo) ||
2832                   (mode == MM_INTER && cmd == Th(z_backwarddeletechar))) {
2833	    int l;
2834
2835	    if (!u)
2836		break;
2837
2838	    handleundo();
2839	    zlemetacs = 0;
2840	    foredel(zlemetall, CUT_RAW);
2841	    spaceinline(l = strlen(u->line));
2842	    strncpy(zlemetaline, u->line, l);
2843	    zlemetacs = u->cs;
2844	    menuacc = u->acc;
2845	    memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
2846	    p = &(minfo.cur);
2847	    mline = u->mline;
2848	    mlbeg = u->mlbeg;
2849	    if (u->lastmatches && lastmatches != u->lastmatches) {
2850		if (lastmatches)
2851		    freematches(lastmatches, 0);
2852		amatches = u->amatches;
2853#ifdef ZSH_HEAP_DEBUG
2854		if (memory_validate(amatches->heap_id)) {
2855		    HEAP_ERROR(amatches->heap_id);
2856		}
2857#endif
2858		pmatches = u->pmatches;
2859		lastmatches = u->lastmatches;
2860		lastlmatches = u->lastlmatches;
2861		nmatches = u->nmatches;
2862		hasoldlist = validlist = 1;
2863	    }
2864	    freebrinfo(brbeg);
2865	    freebrinfo(brend);
2866	    brbeg = dupbrinfo(u->brbeg, &lastbrbeg, 0);
2867	    brend = dupbrinfo(u->brend, &lastbrend, 0);
2868	    nbrbeg = u->nbrbeg;
2869	    nbrend = u->nbrend;
2870	    origline = u->origline;
2871	    origcs = u->origcs;
2872	    origll = u->origll;
2873            strcpy(status, u->status);
2874            mode = u->mode;
2875            nolist = u->nolist;
2876
2877	    u = u->prev;
2878	    clearlist = 1;
2879	    setwish = 1;
2880	    listdat.valid = 0;
2881            molbeg = -42;
2882
2883            if (nolist) {
2884                if (mode == MM_INTER) {
2885                    statusline = status;
2886                } else {
2887		    /* paranoia */
2888		    statusline = NULL;
2889		}
2890                zrefresh();
2891		statusline = NULL;
2892                goto getk;
2893            }
2894            if (mode)
2895                continue;
2896	} else if (cmd == Th(z_redisplay)) {
2897	    redisplay(zlenoargs);
2898            molbeg = -42;
2899	    continue;
2900	} else if (cmd == Th(z_clearscreen)) {
2901	    clearscreen(zlenoargs);
2902            molbeg = -42;
2903	    continue;
2904	} else if (cmd == Th(z_downhistory) ||
2905		   cmd == Th(z_downlineorhistory) ||
2906		   cmd == Th(z_downlineorsearch) ||
2907		   cmd == Th(z_vidownlineorhistory)) {
2908	    int omline;
2909	    Cmatch **op;
2910
2911            mode = 0;
2912	    wrap = 0;
2913
2914	down:
2915
2916	    omline = mline;
2917	    op = p;
2918
2919	    do {
2920		if (mline == mlines - 1) {
2921		    if (wrap & 2) {
2922			mline = omline;
2923			p = op;
2924			break;
2925		    }
2926		    p -= mline * mcols;
2927		    mline = 0;
2928		    wrap |= 1;
2929		} else {
2930		    mline++;
2931		    p += mcols;
2932		}
2933		if (adjust_mcol(wishcol, &p, NULL))
2934		    continue;
2935	    } while (!*p || mmarked(*p));
2936
2937	    if (wrap == 1)
2938		goto right;
2939	} else if (cmd == Th(z_uphistory) ||
2940		   cmd == Th(z_uplineorhistory) ||
2941		   cmd == Th(z_uplineorsearch) ||
2942		   cmd == Th(z_viuplineorhistory)) {
2943	    int omline;
2944	    Cmatch **op;
2945
2946            mode = 0;
2947	    wrap = 0;
2948
2949	up:
2950
2951	    omline = mline;
2952	    op = p;
2953
2954	    do {
2955		if (!mline) {
2956		    if (wrap & 2) {
2957			mline = omline;
2958			p = op;
2959			break;
2960		    }
2961		    mline = mlines - 1;
2962		    p += mline * mcols;
2963		    wrap |= 1;
2964		} else {
2965		    mline--;
2966		    p -= mcols;
2967		}
2968		if (adjust_mcol(wishcol, &p, NULL))
2969		    continue;
2970	    } while (!*p || mmarked(*p));
2971
2972	    if (wrap == 1) {
2973		if (mcol == wishcol)
2974		    goto left;
2975
2976		wishcol = mcol;
2977	    }
2978	} else if (cmd == Th(z_emacsforwardword) ||
2979		   cmd == Th(z_viforwardword) ||
2980		   cmd == Th(z_viforwardwordend) ||
2981		   cmd == Th(z_forwardword)) {
2982	    int i = zterm_lines - pl - 1, oi = i, ll = 0;
2983	    Cmatch **lp = NULL;
2984
2985            mode = 0;
2986	    if (mline == mlines - 1)
2987		goto top;
2988	    while (i > 0) {
2989		if (mline == mlines - 1) {
2990		    if (i != oi && lp)
2991			break;
2992		    goto top;
2993		} else {
2994		    mline++;
2995		    p += mcols;
2996		}
2997		if (adjust_mcol(wishcol, &p, NULL))
2998		    continue;
2999		if (*p && !mmarked(*p)) {
3000		    i--;
3001		    lp = p;
3002		    ll = mline;
3003		}
3004	    }
3005	    p = lp;
3006	    mline = ll;
3007	} else if (cmd == Th(z_emacsbackwardword) ||
3008		   cmd == Th(z_vibackwardword) ||
3009		   cmd == Th(z_backwardword)) {
3010	    int i = zterm_lines - pl - 1, oi = i, ll = 0;
3011	    Cmatch **lp = NULL;
3012
3013            mode = 0;
3014	    if (!mline)
3015		goto bottom;
3016	    while (i > 0) {
3017		if (!mline) {
3018		    if (i != oi && lp)
3019			break;
3020		    goto bottom;
3021		} else {
3022		    mline--;
3023		    p -= mcols;
3024		}
3025		if (adjust_mcol(wishcol, &p, NULL))
3026		    continue;
3027		if (*p || !mmarked(*p)) {
3028		    i--;
3029		    lp = p;
3030		    ll = mline;
3031		}
3032	    }
3033	    p = lp;
3034	    mline = ll;
3035	} else if (cmd == Th(z_beginningofhistory)) {
3036	    int ll;
3037	    Cmatch **lp;
3038
3039            mode = 0;
3040
3041	top:
3042
3043	    ll = mline;
3044	    lp = p;
3045	    while (mline) {
3046		mline--;
3047		p -= mcols;
3048		if (adjust_mcol(wishcol, &p, NULL))
3049		    continue;
3050		if (*p && !mmarked(*p)) {
3051		    lp = p;
3052		    ll = mline;
3053		}
3054	    }
3055	    mline = ll;
3056	    p = lp;
3057	} else if (cmd == Th(z_endofhistory)) {
3058	    int ll;
3059	    Cmatch **lp;
3060
3061            mode = 0;
3062
3063	bottom:
3064
3065	    ll = mline;
3066	    lp = p;
3067	    while (mline < mlines - 1) {
3068		mline++;
3069		p += mcols;
3070		if (adjust_mcol(wishcol, &p, NULL))
3071		    continue;
3072		if (*p && !mmarked(*p)) {
3073		    lp = p;
3074		    ll = mline;
3075		}
3076	    }
3077	    mline = ll;
3078	    p = lp;
3079	} else if (cmd == Th(z_forwardchar) || cmd == Th(z_viforwardchar)) {
3080	    int omcol;
3081	    Cmatch **op;
3082
3083            mode = 0;
3084	    wrap = 0;
3085
3086	right:
3087
3088	    omcol = mcol;
3089	    op = p;
3090
3091	    do {
3092		if (mcol == mcols - 1) {
3093		    if (wrap & 1) {
3094			p = op;
3095			mcol = omcol;
3096			break;
3097		    }
3098		    p -= mcol;
3099		    mcol = 0;
3100		    wrap |= 2;
3101		} else {
3102		    mcol++;
3103		    p++;
3104		}
3105	    } while (!*p || mmarked(*p) || (mcol != omcol && *p == *op));
3106	    wishcol = mcol;
3107
3108	    if (wrap == 2)
3109		goto down;
3110	} else if (cmd == Th(z_backwardchar) || cmd == Th(z_vibackwardchar)) {
3111	    int omcol;
3112	    Cmatch **op;
3113
3114            mode = 0;
3115	    wrap = 0;
3116
3117	left:
3118
3119	    omcol = mcol;
3120	    op = p;
3121
3122	    do {
3123		if (!mcol) {
3124		    if (wrap & 1) {
3125			p = op;
3126			mcol = omcol;
3127			break;
3128		    }
3129		    mcol = mcols - 1;
3130		    p += mcol;
3131		    wrap |= 2;
3132		} else {
3133		    mcol--;
3134		    p--;
3135		}
3136	    } while (!*p || mmarked(*p) || (mcol != omcol && *p == *op));
3137	    wishcol = mcol;
3138
3139	    if (wrap == 2) {
3140		p += mcols - 1 - mcol;
3141		wishcol = mcol = mcols - 1;
3142		adjust_mcol(wishcol, &p, NULL);
3143		goto up;
3144	    }
3145	} else if (cmd == Th(z_beginningofbufferorhistory) ||
3146		   cmd == Th(z_beginningofline) ||
3147		   cmd == Th(z_beginningoflinehist) ||
3148		   cmd == Th(z_vibeginningofline)) {
3149            mode = 0;
3150	    p -= mcol;
3151	    mcol = 0;
3152	    while (!*p || mmarked(*p)) {
3153		mcol++;
3154		p++;
3155	    }
3156	    wishcol = 0;
3157	} else if (cmd == Th(z_endofbufferorhistory) ||
3158		   cmd == Th(z_endofline) ||
3159		   cmd == Th(z_endoflinehist) ||
3160		   cmd == Th(z_viendofline)) {
3161            mode = 0;
3162	    p += mcols - mcol - 1;
3163	    mcol = mcols - 1;
3164	    while (!*p || mmarked(*p)) {
3165		mcol--;
3166		p--;
3167	    }
3168	    wishcol = mcols - 1;
3169	} else if (cmd == Th(z_viforwardblankword) ||
3170		   cmd == Th(z_viforwardblankwordend)) {
3171	    Cmgroup g = *pg;
3172	    int ol = mline;
3173
3174            mode = 0;
3175	    do {
3176		if (mline == mlines - 1) {
3177		    p -= mline * mcols;
3178		    pg -= mline * mcols;
3179		    mline = 0;
3180		} else {
3181		    mline++;
3182		    p += mcols;
3183		    pg += mcols;
3184		}
3185		if (adjust_mcol(wishcol, &p, &pg))
3186		    continue;
3187	    } while (ol != mline && (*pg == g || !*pg || mmarked(*pg)));
3188	} else if (cmd == Th(z_vibackwardblankword)) {
3189	    Cmgroup g = *pg;
3190	    int ol = mline;
3191
3192            mode = 0;
3193	    do {
3194		if (!mline) {
3195		    mline = mlines - 1;
3196		    p += mline * mcols;
3197		    pg += mline * mcols;
3198		} else {
3199		    mline--;
3200		    p -= mcols;
3201		    pg -= mcols;
3202		}
3203		if (adjust_mcol(wishcol, &p, &pg))
3204		    continue;
3205	    } while (ol != mline && (*pg == g || !*pg || mmarked(*pg)));
3206	} else if (cmd == Th(z_completeword) ||
3207		   cmd == Th(z_expandorcomplete) ||
3208		   cmd == Th(z_expandorcompleteprefix) ||
3209		   cmd == Th(z_menucomplete) ||
3210		   cmd == Th(z_menuexpandorcomplete) ||
3211		   !strcmp(cmd->nam, "menu-select") ||
3212		   !strcmp(cmd->nam, "complete-word") ||
3213		   !strcmp(cmd->nam, "expand-or-complete") ||
3214		   !strcmp(cmd->nam, "expand-or-complete-prefix") ||
3215		   !strcmp(cmd->nam, "menu-complete") ||
3216		   !strcmp(cmd->nam, "menu-expand-or-complete")) {
3217            if (mode == MM_INTER) {
3218		/*
3219		 * do_menucmp() has inserted the completion onto
3220		 * the command line.  In interactive mode we
3221		 * don't want that, just what the user typed,
3222		 * so restore the information.
3223		 */
3224                origline = modeline;
3225                origcs = modecs;
3226                origll = modell;
3227                zlemetacs = 0;
3228                foredel(zlemetall, CUT_RAW);
3229                spaceinline(origll);
3230                strncpy(zlemetaline, origline, origll);
3231                zlemetacs = origcs;
3232                minfo.len = modelen;
3233            } else {
3234                mode = 0;
3235                comprecursive = 1;
3236                do_menucmp(0);
3237                mselect = (*(minfo.cur))->gnum;
3238                setwish = 1;
3239                mline = -1;
3240            }
3241	    continue;
3242	} else if (cmd == Th(z_reversemenucomplete) ||
3243		   !strcmp(cmd->nam, "reverse-menu-complete")) {
3244            mode = 0;
3245	    comprecursive = 1;
3246	    unmetafy_line();
3247	    reversemenucomplete(zlenoargs);
3248	    metafy_line();
3249	    mselect = (*(minfo.cur))->gnum;
3250	    setwish = 1;
3251	    mline = -1;
3252	    continue;
3253        } else if (cmd == Th(z_historyincrementalsearchforward) ||
3254                   cmd == Th(z_historyincrementalsearchbackward) ||
3255                   ((mode == MM_FSEARCH || mode == MM_BSEARCH) &&
3256                    (cmd == Th(z_selfinsert) ||
3257                     cmd == Th(z_selfinsertunmeta)))) {
3258            Cmatch **np, **op = p;
3259            int was = (mode == MM_FSEARCH || mode == MM_BSEARCH);
3260            int ins = (cmd == Th(z_selfinsert) || cmd == Th(z_selfinsertunmeta));
3261            int back = (cmd == Th(z_historyincrementalsearchbackward));
3262            int wrap;
3263
3264            do {
3265                if (was) {
3266                    p += wishcol - mcol;
3267                    mcol = wishcol;
3268                }
3269                if (!ins) {
3270                    if (was) {
3271                        if (!*msearchstr && lastsearch) {
3272                            msearchstr = dupstring(lastsearch);
3273                            mode = 0;
3274                        }
3275                    } else {
3276                        msearchstr = "";
3277                        msearchstack = NULL;
3278                    }
3279                }
3280                if (cmd == Th(z_selfinsertunmeta)) {
3281		    fixunmeta();
3282                }
3283                wrap = 0;
3284                np = msearch(p, ins, (ins ? (mode == MM_BSEARCH) : back),
3285                             (was && !ins), &wrap);
3286
3287                if (!ins)
3288                    mode = (back ? MM_BSEARCH : MM_FSEARCH);
3289
3290                if (*msearchstr) {
3291                    zsfree(lastsearch);
3292                    lastsearch = ztrdup(msearchstr);
3293                }
3294                if (np) {
3295                    wishcol = mcol;
3296                    p = np;
3297                }
3298                adjust_mcol(wishcol, &p, NULL);
3299
3300            } while ((back || cmd == Th(z_historyincrementalsearchforward)) &&
3301                     np && !wrap && was && **p == **op);
3302
3303        } else if ((mode == MM_FSEARCH || mode == MM_BSEARCH) &&
3304                   cmd == Th(z_backwarddeletechar)) {
3305            int back = 1;
3306            Cmatch **np = msearchpop(&back);
3307
3308            mode = (back ? MM_BSEARCH : MM_FSEARCH);
3309            wishcol = mcol;
3310            if (np) {
3311                p = np;
3312                adjust_mcol(wishcol, &p, NULL);
3313            }
3314	} else if (cmd == Th(z_undefinedkey)) {
3315            mode = 0;
3316	    continue;
3317	} else {
3318	    ungetkeycmd();
3319	    if (cmd->widget && (cmd->widget->flags & WIDGET_NCOMP)) {
3320		acc = 0;
3321		broken = 2;
3322	    } else
3323		acc = 1;
3324	    break;
3325	}
3326	do_single(**p);
3327	mselect = (**p)->gnum;
3328    }
3329    if (u)
3330	for (; u; u = u->prev)
3331	    if (u->lastmatches != lastmatches)
3332		freematches(u->lastmatches, 0);
3333
3334    selectlocalmap(NULL);
3335    mselect = mlastcols = mlastlines = -1;
3336    mstatus = NULL;
3337    inselect = mhasstat = 0;
3338    if (nolist)
3339        clearlist = listshown = 1;
3340    if (acc && validlist && minfo.cur) {
3341	menucmp = lastambig = hasoldlist = 0;
3342	do_single(*(minfo.cur));
3343    }
3344    if (wasnext || broken) {
3345	menucmp = 2;
3346	showinglist = ((validlist && !nolist) ? -2 : 0);
3347	minfo.asked = 0;
3348	if (!noselect) {
3349	    int nos = noselect;
3350
3351	    zrefresh();
3352	    noselect = nos;
3353	}
3354    }
3355    if (!noselect && (!dat || acc)) {
3356	/*
3357	 * I added the following because in certain cases the zrefresh()
3358	 * here was screwing up the list.  Forcing it to redraw the
3359	 * screen worked.  The case in question (courtesy of
3360	 * "Matt Wozniski" <godlygeek@gmail.com>) is in zsh-workers/24756.
3361	 *
3362	 * *** PLEASE DON'T ASK ME WHY THIS IS NECESSARY ***
3363	 */
3364	mlbeg = -1;
3365	showinglist = ((validlist && !nolist) ? -2 : 0);
3366	onlyexpl = oe;
3367	if (acc && listshown) {
3368	    /*
3369	     * Clear the list without spending sixteen weeks of
3370	     * redrawing it in slightly different states first.
3371	     * The following seems to work.  I'm not sure what
3372	     * the difference is between listshown and showinglist,
3373	     * but listshown looks like the traditional thing to
3374	     * check for in this file at least.
3375	     *
3376	     * showinglist has a normally undocumented value of 1,
3377	     * and an extra-specially undocumented value of -2, which
3378	     * seems to be a force---it appears we need to kick it out
3379	     * of that state, though it worries me that in some places
3380	     * the code actually forces it back into that state.
3381	     */
3382	    clearlist = listshown = showinglist = 1;
3383	} else if (!smatches)
3384	    clearlist = listshown = 1;
3385	zrefresh();
3386    }
3387    mlbeg = -1;
3388    fdat = NULL;
3389
3390    if (!wasmeta)
3391	unmetafy_line();
3392
3393    return (broken == 2 ? 3 :
3394	    ((dat && !broken) ? (acc ? 1 : 2) : (!noselect ^ acc)));
3395}
3396
3397/* The widget function. */
3398
3399static int
3400menuselect(char **args)
3401{
3402    int d = 0;
3403
3404    if (!minfo.cur) {
3405	selected = 0;
3406	menucomplete(args);
3407	if ((minfo.cur && minfo.asked == 2) || selected)
3408	    return 0;
3409	d = 1;
3410    }
3411    if (minfo.cur && (minfo.asked == 2 || domenuselect(NULL, NULL)) && !d)
3412	menucomplete(args);
3413
3414    return 0;
3415}
3416
3417static struct features module_features = {
3418    NULL, 0,
3419    NULL, 0,
3420    NULL, 0,
3421    NULL, 0,
3422    0
3423};
3424
3425/**/
3426int
3427setup_(UNUSED(Module m))
3428{
3429    return 0;
3430}
3431
3432/**/
3433int
3434features_(Module m, char ***features)
3435{
3436    *features = featuresarray(m, &module_features);
3437    return 0;
3438}
3439
3440/**/
3441int
3442enables_(Module m, int **enables)
3443{
3444    return handlefeatures(m, &module_features, enables);
3445}
3446
3447/**/
3448int
3449boot_(Module m)
3450{
3451    mtab = NULL;
3452    mgtab = NULL;
3453    mselect = -1;
3454    inselect = 0;
3455
3456    w_menuselect = addzlefunction("menu-select", menuselect,
3457                                    ZLE_MENUCMP|ZLE_KEEPSUFFIX|ZLE_ISCOMP);
3458    if (!w_menuselect) {
3459	zwarnnam(m->node.nam,
3460		 "name clash when adding ZLE function `menu-select'");
3461	return -1;
3462    }
3463    addhookfunc("comp_list_matches", (Hookfn) complistmatches);
3464    addhookfunc("menu_start", (Hookfn) domenuselect);
3465    mskeymap = newkeymap(NULL, "menuselect");
3466    linkkeymap(mskeymap, "menuselect", 1);
3467    bindkey(mskeymap, "\t", refthingy(t_completeword), NULL);
3468    bindkey(mskeymap, "\n", refthingy(t_acceptline), NULL);
3469    bindkey(mskeymap, "\r", refthingy(t_acceptline), NULL);
3470    bindkey(mskeymap, "\33[A",  refthingy(t_uplineorhistory), NULL);
3471    bindkey(mskeymap, "\33[B",  refthingy(t_downlineorhistory), NULL);
3472    bindkey(mskeymap, "\33[C",  refthingy(t_forwardchar), NULL);
3473    bindkey(mskeymap, "\33[D",  refthingy(t_backwardchar), NULL);
3474    bindkey(mskeymap, "\33OA",  refthingy(t_uplineorhistory), NULL);
3475    bindkey(mskeymap, "\33OB",  refthingy(t_downlineorhistory), NULL);
3476    bindkey(mskeymap, "\33OC",  refthingy(t_forwardchar), NULL);
3477    bindkey(mskeymap, "\33OD",  refthingy(t_backwardchar), NULL);
3478    lskeymap = newkeymap(NULL, "listscroll");
3479    linkkeymap(lskeymap, "listscroll", 1);
3480    bindkey(lskeymap, "\t", refthingy(t_completeword), NULL);
3481    bindkey(lskeymap, " ", refthingy(t_completeword), NULL);
3482    bindkey(lskeymap, "\n", refthingy(t_acceptline), NULL);
3483    bindkey(lskeymap, "\r", refthingy(t_acceptline), NULL);
3484    bindkey(lskeymap, "\33[B",  refthingy(t_downlineorhistory), NULL);
3485    bindkey(lskeymap, "\33OB",  refthingy(t_downlineorhistory), NULL);
3486    return 0;
3487}
3488
3489/**/
3490int
3491cleanup_(Module m)
3492{
3493    free(mtab);
3494    free(mgtab);
3495
3496    deletezlefunction(w_menuselect);
3497    deletehookfunc("comp_list_matches", (Hookfn) complistmatches);
3498    deletehookfunc("menu_start", (Hookfn) domenuselect);
3499    unlinkkeymap("menuselect", 1);
3500    unlinkkeymap("listscroll", 1);
3501    return setfeatureenables(m, &module_features, NULL);
3502}
3503
3504/**/
3505int
3506finish_(UNUSED(Module m))
3507{
3508    return 0;
3509}
3510