ed.xmap.c revision 167465
1/* $Header: /p/tcsh/cvsroot/tcsh/ed.xmap.c,v 3.36 2006/11/29 22:30:09 christos Exp $ */
2/*
3 * ed.xmap.c: This module contains the procedures for maintaining
4 *	      the extended-key map.
5 *
6 * 	      An extended-key (Xkey) is a sequence of keystrokes
7 *	      introduced with an sequence introducer and consisting
8 *	      of an arbitrary number of characters.  This module maintains
9 *	      a map (the Xmap) to convert these extended-key sequences
10 * 	      into input strings (XK_STR), editor functions (XK_CMD), or
11 *	      unix commands (XK_EXE). It contains the
12 *	      following externally visible functions.
13 *
14 *		int GetXkey(ch,val);
15 *		CStr *ch;
16 *		XmapVal *val;
17 *
18 *	      Looks up *ch in map and then reads characters until a
19 *	      complete match is found or a mismatch occurs. Returns the
20 *	      type of the match found (XK_STR, XK_CMD, or XK_EXE).
21 *	      Returns NULL in val.str and XK_STR for no match.
22 *	      The last character read is returned in *ch.
23 *
24 *		void AddXkey(Xkey, val, ntype);
25 *		CStr *Xkey;
26 *		XmapVal *val;
27 *		int ntype;
28 *
29 *	      Adds Xkey to the Xmap and associates the value in val with it.
30 *	      If Xkey is already is in Xmap, the new code is applied to the
31 *	      existing Xkey. Ntype specifies if code is a command, an
32 *	      out string or a unix command.
33 *
34 *	        int DeleteXkey(Xkey);
35 *	        CStr *Xkey;
36 *
37 *	      Delete the Xkey and all longer Xkeys staring with Xkey, if
38 *	      they exists.
39 *
40 *	      Warning:
41 *		If Xkey is a substring of some other Xkeys, then the longer
42 *		Xkeys are lost!!  That is, if the Xkeys "abcd" and "abcef"
43 *		are in Xmap, adding the key "abc" will cause the first two
44 *		definitions to be lost.
45 *
46 *		void ResetXmap();
47 *
48 *	      Removes all entries from Xmap and resets the defaults.
49 *
50 *		void PrintXkey(Xkey);
51 *		CStr *Xkey;
52 *
53 *	      Prints all extended keys prefixed by Xkey and their associated
54 *	      commands.
55 *
56 *	      Restrictions:
57 *	      -------------
58 *	        1) It is not possible to have one Xkey that is a
59 *		   substring of another.
60 */
61/*-
62 * Copyright (c) 1980, 1991 The Regents of the University of California.
63 * All rights reserved.
64 *
65 * Redistribution and use in source and binary forms, with or without
66 * modification, are permitted provided that the following conditions
67 * are met:
68 * 1. Redistributions of source code must retain the above copyright
69 *    notice, this list of conditions and the following disclaimer.
70 * 2. Redistributions in binary form must reproduce the above copyright
71 *    notice, this list of conditions and the following disclaimer in the
72 *    documentation and/or other materials provided with the distribution.
73 * 3. Neither the name of the University nor the names of its contributors
74 *    may be used to endorse or promote products derived from this software
75 *    without specific prior written permission.
76 *
77 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
78 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
79 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
80 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
81 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
82 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
83 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
84 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
85 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
86 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
87 * SUCH DAMAGE.
88 */
89#include "sh.h"
90
91RCSID("$tcsh: ed.xmap.c,v 3.36 2006/11/29 22:30:09 christos Exp $")
92
93#include "ed.h"
94#include "ed.defns.h"
95
96#ifndef NULL
97#define NULL 0
98#endif
99
100/* Internal Data types and declarations */
101
102/* The Nodes of the Xmap.  The Xmap is a linked list of these node
103 * elements
104 */
105typedef struct Xmapnode {
106    Char    ch;			/* single character of Xkey */
107    int     type;
108    XmapVal val; 		/* command code or pointer to string, if this
109				 * is a leaf */
110    struct Xmapnode *next;	/* ptr to next char of this Xkey */
111    struct Xmapnode *sibling;	/* ptr to another Xkey with same prefix */
112} XmapNode;
113
114static XmapNode *Xmap = NULL;	/* the current Xmap */
115
116
117/* Some declarations of procedures */
118static	int       TraverseMap	(XmapNode *, CStr *, XmapVal *);
119static	int       TryNode	(XmapNode *, CStr *, XmapVal *, int);
120static	XmapNode *GetFreeNode	(CStr *);
121static	void	  PutFreeNode	(XmapNode *);
122static	int	  TryDeleteNode	(XmapNode **, CStr *);
123static	int	  Lookup	(struct Strbuf *, const CStr *,
124				 const XmapNode *);
125static	void	  Enumerate	(struct Strbuf *, const XmapNode *);
126static	void	  unparsech	(struct Strbuf *, Char);
127
128
129XmapVal *
130XmapCmd(int cmd)
131{
132    static XmapVal xm;
133    xm.cmd = (KEYCMD) cmd;
134    return &xm;
135}
136
137XmapVal *
138XmapStr(CStr *str)
139{
140    static XmapVal xm;
141    xm.str.len = str->len;
142    xm.str.buf = str->buf;
143    return &xm;
144}
145
146/* ResetXmap():
147 *	Takes all nodes on Xmap and puts them on free list.  Then
148 *	initializes Xmap with arrow keys
149 */
150void
151ResetXmap(void)
152{
153    PutFreeNode(Xmap);
154    Xmap = NULL;
155
156    DefaultArrowKeys();
157    return;
158}
159
160
161/* GetXkey():
162 *	Calls the recursive function with entry point Xmap
163 */
164int
165GetXkey(CStr *ch, XmapVal *val)
166{
167    return (TraverseMap(Xmap, ch, val));
168}
169
170/* TraverseMap():
171 *	recursively traverses node in tree until match or mismatch is
172 * 	found.  May read in more characters.
173 */
174static int
175TraverseMap(XmapNode *ptr, CStr *ch, XmapVal *val)
176{
177    Char    tch;
178
179    if (ptr->ch == *(ch->buf)) {
180	/* match found */
181	if (ptr->next) {
182	    /* Xkey not complete so get next char */
183	    if (GetNextChar(&tch) != 1) {	/* if EOF or error */
184		val->cmd = F_SEND_EOF;
185		return XK_CMD;/* PWP: Pretend we just read an end-of-file */
186	    }
187	    *(ch->buf) = tch;
188	    return (TraverseMap(ptr->next, ch, val));
189	}
190	else {
191	    *val = ptr->val;
192	    if (ptr->type != XK_CMD)
193		*(ch->buf) = '\0';
194	    return ptr->type;
195	}
196    }
197    else {
198	/* no match found here */
199	if (ptr->sibling) {
200	    /* try next sibling */
201	    return (TraverseMap(ptr->sibling, ch, val));
202	}
203	else {
204	    /* no next sibling -- mismatch */
205	    val->str.buf = NULL;
206	    val->str.len = 0;
207	    return XK_STR;
208	}
209    }
210}
211
212void
213AddXkey(const CStr *Xkey, XmapVal *val, int ntype)
214{
215    CStr cs;
216    cs.buf = Xkey->buf;
217    cs.len = Xkey->len;
218    if (Xkey->len == 0) {
219	xprintf(CGETS(9, 1, "AddXkey: Null extended-key not allowed.\n"));
220	return;
221    }
222
223    if (ntype == XK_CMD && val->cmd == F_XKEY) {
224	xprintf(CGETS(9, 2, "AddXkey: sequence-lead-in command not allowed\n"));
225	return;
226    }
227
228    if (Xmap == NULL)
229	/* tree is initially empty.  Set up new node to match Xkey[0] */
230	Xmap = GetFreeNode(&cs);	/* it is properly initialized */
231
232    /* Now recurse through Xmap */
233    (void) TryNode(Xmap, &cs, val, ntype);
234    return;
235}
236
237static int
238TryNode(XmapNode *ptr, CStr *str, XmapVal *val, int ntype)
239{
240    /*
241     * Find a node that matches *string or allocate a new one
242     */
243    if (ptr->ch != *(str->buf)) {
244	XmapNode *xm;
245
246	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
247	    if (xm->sibling->ch == *(str->buf))
248		break;
249	if (xm->sibling == NULL)
250	    xm->sibling = GetFreeNode(str);	/* setup new node */
251	ptr = xm->sibling;
252    }
253
254    str->buf++;
255    str->len--;
256    if (str->len == 0) {
257	size_t len;
258
259	/* we're there */
260	if (ptr->next != NULL) {
261	    PutFreeNode(ptr->next);	/* lose longer Xkeys with this prefix */
262	    ptr->next = NULL;
263	}
264
265	switch (ptr->type) {
266	case XK_STR:
267	case XK_EXE:
268	    xfree(ptr->val.str.buf);
269	    ptr->val.str.len = 0;
270	    break;
271	case XK_NOD:
272	case XK_CMD:
273	    break;
274	default:
275	    abort();
276	    break;
277	}
278
279	switch (ptr->type = ntype) {
280	case XK_CMD:
281	    ptr->val = *val;
282	    break;
283	case XK_STR:
284	case XK_EXE:
285	    ptr->val.str.len = val->str.len;
286	    len = (val->str.len + 1) * sizeof(*ptr->val.str.buf);
287	    ptr->val.str.buf = xmalloc(len);
288	    (void) memcpy(ptr->val.str.buf, val->str.buf, len);
289	    break;
290	default:
291	    abort();
292	    break;
293	}
294    }
295    else {
296	/* still more chars to go */
297	if (ptr->next == NULL)
298	    ptr->next = GetFreeNode(str);	/* setup new node */
299	(void) TryNode(ptr->next, str, val, ntype);
300    }
301    return (0);
302}
303
304void
305ClearXkey(KEYCMD *map, const CStr *in)
306{
307    unsigned char c = (unsigned char) *(in->buf);
308    if ((map[c] == F_XKEY) &&
309	((map == CcKeyMap && CcAltMap[c] != F_XKEY) ||
310	 (map == CcAltMap && CcKeyMap[c] != F_XKEY)))
311	(void) DeleteXkey(in);
312}
313
314int
315DeleteXkey(const CStr *Xkey)
316{
317    CStr s;
318
319    s = *Xkey;
320    if (s.len == 0) {
321	xprintf(CGETS(9, 3, "DeleteXkey: Null extended-key not allowed.\n"));
322	return (-1);
323    }
324
325    if (Xmap == NULL)
326	return (0);
327
328    (void) TryDeleteNode(&Xmap, &s);
329    return (0);
330}
331
332/* Destroys str */
333static int
334TryDeleteNode(XmapNode **inptr, CStr *str)
335{
336    XmapNode *ptr;
337
338    ptr = *inptr;
339    /*
340     * Find a node that matches *string or allocate a new one
341     */
342    if (ptr->ch != *(str->buf)) {
343	XmapNode *xm;
344
345	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
346	    if (xm->sibling->ch == *(str->buf))
347		break;
348	if (xm->sibling == NULL)
349	    return (0);
350	inptr = &xm->sibling;
351	ptr = xm->sibling;
352    }
353
354    str->buf++;
355    str->len--;
356
357    if (str->len == 0) {
358	/* we're there */
359	*inptr = ptr->sibling;
360	ptr->sibling = NULL;
361	PutFreeNode(ptr);
362	return (1);
363    }
364    else if (ptr->next != NULL && TryDeleteNode(&ptr->next, str) == 1) {
365	if (ptr->next != NULL)
366	    return (0);
367	*inptr = ptr->sibling;
368	ptr->sibling = NULL;
369	PutFreeNode(ptr);
370	return (1);
371    }
372    else {
373	return (0);
374    }
375}
376
377/* PutFreeNode():
378 *	Puts a tree of nodes onto free list using free(3).
379 */
380static void
381PutFreeNode(XmapNode *ptr)
382{
383    if (ptr == NULL)
384	return;
385
386    if (ptr->next != NULL) {
387	PutFreeNode(ptr->next);
388	ptr->next = NULL;
389    }
390
391    PutFreeNode(ptr->sibling);
392
393    switch (ptr->type) {
394    case XK_CMD:
395    case XK_NOD:
396	break;
397    case XK_EXE:
398    case XK_STR:
399	xfree(ptr->val.str.buf);
400	break;
401    default:
402	abort();
403	break;
404    }
405    xfree(ptr);
406}
407
408
409/* GetFreeNode():
410 *	Returns pointer to an XmapNode for ch.
411 */
412static XmapNode *
413GetFreeNode(CStr *ch)
414{
415    XmapNode *ptr;
416
417    ptr = xmalloc(sizeof(XmapNode));
418    ptr->ch = ch->buf[0];
419    ptr->type = XK_NOD;
420    ptr->val.str.buf = NULL;
421    ptr->val.str.len = 0;
422    ptr->next = NULL;
423    ptr->sibling = NULL;
424    return (ptr);
425}
426
427
428/* PrintXKey():
429 *	Print the binding associated with Xkey key.
430 *	Print entire Xmap if null
431 */
432void
433PrintXkey(const CStr *key)
434{
435    struct Strbuf buf = Strbuf_INIT;
436    CStr cs;
437
438    if (key) {
439	cs.buf = key->buf;
440	cs.len = key->len;
441    }
442    else {
443	cs.buf = STRNULL;
444	cs.len = 0;
445    }
446    /* do nothing if Xmap is empty and null key specified */
447    if (Xmap == NULL && cs.len == 0)
448	return;
449
450    Strbuf_append1(&buf, '"');
451    cleanup_push(&buf, Strbuf_cleanup);
452    if (Lookup(&buf, &cs, Xmap) <= -1)
453	/* key is not bound */
454	xprintf(CGETS(9, 4, "Unbound extended key \"%S\"\n"), cs.buf);
455    cleanup_until(&buf);
456}
457
458/* Lookup():
459 *	look for the string starting at node ptr.
460 *	Print if last node
461 */
462static int
463Lookup(struct Strbuf *buf, const CStr *str, const XmapNode *ptr)
464{
465    if (ptr == NULL)
466	return (-1);		/* cannot have null ptr */
467
468    if (str->len == 0) {
469	/* no more chars in string.  Enumerate from here. */
470	Enumerate(buf, ptr);
471	return (0);
472    }
473    else {
474	/* If match put this char into buf.  Recurse */
475	if (ptr->ch == *(str->buf)) {
476	    /* match found */
477	    unparsech(buf, ptr->ch);
478	    if (ptr->next != NULL) {
479		/* not yet at leaf */
480		CStr tstr;
481		tstr.buf = str->buf + 1;
482		tstr.len = str->len - 1;
483		return (Lookup(buf, &tstr, ptr->next));
484	    }
485	    else {
486		/* next node is null so key should be complete */
487		if (str->len == 1) {
488		    Strbuf_append1(buf, '"');
489		    Strbuf_terminate(buf);
490		    printOne(buf->s, &ptr->val, ptr->type);
491		    return (0);
492		}
493		else
494		    return (-1);/* mismatch -- string still has chars */
495	    }
496	}
497	else {
498	    /* no match found try sibling */
499	    if (ptr->sibling)
500		return (Lookup(buf, str, ptr->sibling));
501	    else
502		return (-1);
503	}
504    }
505}
506
507static void
508Enumerate(struct Strbuf *buf, const XmapNode *ptr)
509{
510    size_t old_len;
511
512    if (ptr == NULL) {
513#ifdef DEBUG_EDIT
514	xprintf(CGETS(9, 6, "Enumerate: BUG!! Null ptr passed\n!"));
515#endif
516	return;
517    }
518
519    old_len = buf->len;
520    unparsech(buf, ptr->ch); /* put this char at end of string */
521    if (ptr->next == NULL) {
522	/* print this Xkey and function */
523	Strbuf_append1(buf, '"');
524	Strbuf_terminate(buf);
525	printOne(buf->s, &ptr->val, ptr->type);
526    }
527    else
528	Enumerate(buf, ptr->next);
529
530    /* go to sibling if there is one */
531    if (ptr->sibling) {
532	buf->len = old_len;
533	Enumerate(buf, ptr->sibling);
534    }
535}
536
537
538/* PrintOne():
539 *	Print the specified key and its associated
540 *	function specified by val
541 */
542void
543printOne(const Char *key, const XmapVal *val, int ntype)
544{
545    struct KeyFuncs *fp;
546    static const char *fmt = "%s\n";
547
548    xprintf("%-15S-> ", key);
549    if (val != NULL)
550	switch (ntype) {
551	case XK_STR:
552	case XK_EXE: {
553	    unsigned char *p;
554
555	    p = unparsestring(&val->str, ntype == XK_STR ? STRQQ : STRBB);
556	    cleanup_push(p, xfree);
557	    xprintf(fmt, p);
558	    cleanup_until(p);
559	    break;
560	}
561	case XK_CMD:
562	    for (fp = FuncNames; fp->name; fp++)
563		if (val->cmd == fp->func)
564		    xprintf(fmt, fp->name);
565		break;
566	default:
567	    abort();
568	    break;
569	}
570    else
571	xprintf(fmt, CGETS(9, 7, "no input"));
572}
573
574static void
575unparsech(struct Strbuf *buf, Char ch)
576{
577    if (ch == 0) {
578	Strbuf_append1(buf, '^');
579	Strbuf_append1(buf, '@');
580    }
581    else if (Iscntrl(ch)) {
582	Strbuf_append1(buf, '^');
583	if (ch == CTL_ESC('\177'))
584	    Strbuf_append1(buf, '?');
585	else
586#ifdef IS_ASCII
587	    Strbuf_append1(buf, ch | 0100);
588#else
589	    Strbuf_append1(buf, _toebcdic[_toascii[ch]|0100]);
590#endif
591    }
592    else if (ch == '^') {
593	Strbuf_append1(buf, '\\');
594	Strbuf_append1(buf, '^');
595    } else if (ch == '\\') {
596	Strbuf_append1(buf, '\\');
597	Strbuf_append1(buf, '\\');
598    } else if (ch == ' ' || (Isprint(ch) && !Isspace(ch))) {
599	Strbuf_append1(buf, ch);
600    }
601    else {
602	Strbuf_append1(buf, '\\');
603	Strbuf_append1(buf, ((ch >> 6) & 7) + '0');
604	Strbuf_append1(buf, ((ch >> 3) & 7) + '0');
605	Strbuf_append1(buf, (ch & 7) + '0');
606    }
607}
608
609eChar
610parseescape(const Char **ptr)
611{
612    const Char *p;
613    Char c;
614
615    p = *ptr;
616
617    if ((p[1] & CHAR) == 0) {
618	xprintf(CGETS(9, 8, "Something must follow: %c\n"), (char)*p);
619	return CHAR_ERR;
620    }
621    if ((*p & CHAR) == '\\') {
622	p++;
623	switch (*p & CHAR) {
624	case 'a':
625	    c = CTL_ESC('\007');         /* Bell */
626	    break;
627	case 'b':
628	    c = CTL_ESC('\010');         /* Backspace */
629	    break;
630	case 'e':
631	    c = CTL_ESC('\033');         /* Escape */
632	    break;
633	case 'f':
634	    c = CTL_ESC('\014');         /* Form Feed */
635	    break;
636	case 'n':
637	    c = CTL_ESC('\012');         /* New Line */
638	    break;
639	case 'r':
640	    c = CTL_ESC('\015');         /* Carriage Return */
641	    break;
642	case 't':
643	    c = CTL_ESC('\011');         /* Horizontal Tab */
644	    break;
645	case 'v':
646	    c = CTL_ESC('\013');         /* Vertical Tab */
647	    break;
648	case '\\':
649	    c = '\\';
650	    break;
651	case '0':
652	case '1':
653	case '2':
654	case '3':
655	case '4':
656	case '5':
657	case '6':
658	case '7':
659	    {
660		int cnt, val;
661		Char ch;
662
663		for (cnt = 0, val = 0; cnt < 3; cnt++) {
664		    ch = *p++ & CHAR;
665		    if (ch < '0' || ch > '7') {
666			p--;
667			break;
668		    }
669		    val = (val << 3) | (ch - '0');
670		}
671		if ((val & ~0xff) != 0) {
672		    xprintf(CGETS(9, 9,
673			    "Octal constant does not fit in a char.\n"));
674		    return 0;
675		}
676#ifndef IS_ASCII
677		if (CTL_ESC(val) != val && adrof(STRwarnebcdic))
678		    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
679			    "Warning: Octal constant \\%3.3o is interpreted as EBCDIC value.\n", val/*)*/);
680#endif
681		c = (Char) val;
682		--p;
683	    }
684	    break;
685	default:
686	    c = *p;
687	    break;
688	}
689    }
690    else if ((*p & CHAR) == '^' && (Isalpha(p[1] & CHAR) ||
691				    strchr("@^_?\\|[{]}", p[1] & CHAR))) {
692	p++;
693#ifdef IS_ASCII
694	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : ((*p & CHAR) & 0237);
695#else
696	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : _toebcdic[_toascii[*p & CHAR] & 0237];
697	if (adrof(STRwarnebcdic))
698	    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
699		"Warning: Control character ^%c may be interpreted differently in EBCDIC.\n", *p & CHAR /*)*/);
700#endif
701    }
702    else
703	c = *p;
704    *ptr = p;
705    return (c);
706}
707
708
709unsigned char *
710unparsestring(const CStr *str, const Char *sep)
711{
712    unsigned char *buf, *b;
713    Char   p;
714    int l;
715
716    /* Worst-case is "\uuu" or result of wctomb() for each char from str */
717    buf = xmalloc((str->len + 1) * max(4, MB_LEN_MAX));
718    b = buf;
719    if (sep[0])
720#ifndef WINNT_NATIVE
721	*b++ = sep[0];
722#else /* WINNT_NATIVE */
723	*b++ = CHAR & sep[0];
724#endif /* !WINNT_NATIVE */
725
726    for (l = 0; l < str->len; l++) {
727	p = str->buf[l];
728	if (Iscntrl(p)) {
729	    *b++ = '^';
730	    if (p == CTL_ESC('\177'))
731		*b++ = '?';
732	    else
733#ifdef IS_ASCII
734		*b++ = (unsigned char) (p | 0100);
735#else
736		*b++ = _toebcdic[_toascii[p]|0100];
737#endif
738	}
739	else if (p == '^' || p == '\\') {
740	    *b++ = '\\';
741	    *b++ = (unsigned char) p;
742	}
743	else if (p == ' ' || (Isprint(p) && !Isspace(p)))
744	    b += one_wctomb((char *)b, p & CHAR);
745	else {
746	    *b++ = '\\';
747	    *b++ = ((p >> 6) & 7) + '0';
748	    *b++ = ((p >> 3) & 7) + '0';
749	    *b++ = (p & 7) + '0';
750	}
751    }
752    if (sep[0] && sep[1])
753#ifndef WINNT_NATIVE
754	*b++ = sep[1];
755#else /* WINNT_NATIVE */
756	*b++ = CHAR & sep[1];
757#endif /* !WINNT_NATIVE */
758    *b++ = 0;
759    return buf;			/* should check for overflow */
760}
761