ed.xmap.c revision 302408
1/* $Header: /p/tcsh/cvsroot/tcsh/ed.xmap.c,v 3.37 2009/06/25 21:15:37 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.37 2009/06/25 21:15:37 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("%s", 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("%s",
225	    CGETS(9, 2, "AddXkey: sequence-lead-in command not allowed\n"));
226	return;
227    }
228
229    if (Xmap == NULL)
230	/* tree is initially empty.  Set up new node to match Xkey[0] */
231	Xmap = GetFreeNode(&cs);	/* it is properly initialized */
232
233    /* Now recurse through Xmap */
234    (void) TryNode(Xmap, &cs, val, ntype);
235    return;
236}
237
238static int
239TryNode(XmapNode *ptr, CStr *str, XmapVal *val, int ntype)
240{
241    /*
242     * Find a node that matches *string or allocate a new one
243     */
244    if (ptr->ch != *(str->buf)) {
245	XmapNode *xm;
246
247	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
248	    if (xm->sibling->ch == *(str->buf))
249		break;
250	if (xm->sibling == NULL)
251	    xm->sibling = GetFreeNode(str);	/* setup new node */
252	ptr = xm->sibling;
253    }
254
255    str->buf++;
256    str->len--;
257    if (str->len == 0) {
258	size_t len;
259
260	/* we're there */
261	if (ptr->next != NULL) {
262	    PutFreeNode(ptr->next);	/* lose longer Xkeys with this prefix */
263	    ptr->next = NULL;
264	}
265
266	switch (ptr->type) {
267	case XK_STR:
268	case XK_EXE:
269	    xfree(ptr->val.str.buf);
270	    ptr->val.str.len = 0;
271	    break;
272	case XK_NOD:
273	case XK_CMD:
274	    break;
275	default:
276	    abort();
277	    break;
278	}
279
280	switch (ptr->type = ntype) {
281	case XK_CMD:
282	    ptr->val = *val;
283	    break;
284	case XK_STR:
285	case XK_EXE:
286	    ptr->val.str.len = val->str.len;
287	    len = (val->str.len + 1) * sizeof(*ptr->val.str.buf);
288	    ptr->val.str.buf = xmalloc(len);
289	    (void) memcpy(ptr->val.str.buf, val->str.buf, len);
290	    break;
291	default:
292	    abort();
293	    break;
294	}
295    }
296    else {
297	/* still more chars to go */
298	if (ptr->next == NULL)
299	    ptr->next = GetFreeNode(str);	/* setup new node */
300	(void) TryNode(ptr->next, str, val, ntype);
301    }
302    return (0);
303}
304
305void
306ClearXkey(KEYCMD *map, const CStr *in)
307{
308    unsigned char c = (unsigned char) *(in->buf);
309    if ((map[c] == F_XKEY) &&
310	((map == CcKeyMap && CcAltMap[c] != F_XKEY) ||
311	 (map == CcAltMap && CcKeyMap[c] != F_XKEY)))
312	(void) DeleteXkey(in);
313}
314
315int
316DeleteXkey(const CStr *Xkey)
317{
318    CStr s;
319
320    s = *Xkey;
321    if (s.len == 0) {
322	xprintf("%s",
323	        CGETS(9, 3, "DeleteXkey: Null extended-key not allowed.\n"));
324	return (-1);
325    }
326
327    if (Xmap == NULL)
328	return (0);
329
330    (void) TryDeleteNode(&Xmap, &s);
331    return (0);
332}
333
334/* Destroys str */
335static int
336TryDeleteNode(XmapNode **inptr, CStr *str)
337{
338    XmapNode *ptr;
339
340    ptr = *inptr;
341    /*
342     * Find a node that matches *string or allocate a new one
343     */
344    if (ptr->ch != *(str->buf)) {
345	XmapNode *xm;
346
347	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
348	    if (xm->sibling->ch == *(str->buf))
349		break;
350	if (xm->sibling == NULL)
351	    return (0);
352	inptr = &xm->sibling;
353	ptr = xm->sibling;
354    }
355
356    str->buf++;
357    str->len--;
358
359    if (str->len == 0) {
360	/* we're there */
361	*inptr = ptr->sibling;
362	ptr->sibling = NULL;
363	PutFreeNode(ptr);
364	return (1);
365    }
366    else if (ptr->next != NULL && TryDeleteNode(&ptr->next, str) == 1) {
367	if (ptr->next != NULL)
368	    return (0);
369	*inptr = ptr->sibling;
370	ptr->sibling = NULL;
371	PutFreeNode(ptr);
372	return (1);
373    }
374    else {
375	return (0);
376    }
377}
378
379/* PutFreeNode():
380 *	Puts a tree of nodes onto free list using free(3).
381 */
382static void
383PutFreeNode(XmapNode *ptr)
384{
385    if (ptr == NULL)
386	return;
387
388    if (ptr->next != NULL) {
389	PutFreeNode(ptr->next);
390	ptr->next = NULL;
391    }
392
393    PutFreeNode(ptr->sibling);
394
395    switch (ptr->type) {
396    case XK_CMD:
397    case XK_NOD:
398	break;
399    case XK_EXE:
400    case XK_STR:
401	xfree(ptr->val.str.buf);
402	break;
403    default:
404	abort();
405	break;
406    }
407    xfree(ptr);
408}
409
410
411/* GetFreeNode():
412 *	Returns pointer to an XmapNode for ch.
413 */
414static XmapNode *
415GetFreeNode(CStr *ch)
416{
417    XmapNode *ptr;
418
419    ptr = xmalloc(sizeof(XmapNode));
420    ptr->ch = ch->buf[0];
421    ptr->type = XK_NOD;
422    ptr->val.str.buf = NULL;
423    ptr->val.str.len = 0;
424    ptr->next = NULL;
425    ptr->sibling = NULL;
426    return (ptr);
427}
428
429
430/* PrintXKey():
431 *	Print the binding associated with Xkey key.
432 *	Print entire Xmap if null
433 */
434void
435PrintXkey(const CStr *key)
436{
437    struct Strbuf buf = Strbuf_INIT;
438    CStr cs;
439
440    if (key) {
441	cs.buf = key->buf;
442	cs.len = key->len;
443    }
444    else {
445	cs.buf = STRNULL;
446	cs.len = 0;
447    }
448    /* do nothing if Xmap is empty and null key specified */
449    if (Xmap == NULL && cs.len == 0)
450	return;
451
452    Strbuf_append1(&buf, '"');
453    cleanup_push(&buf, Strbuf_cleanup);
454    if (Lookup(&buf, &cs, Xmap) <= -1)
455	/* key is not bound */
456	xprintf(CGETS(9, 4, "Unbound extended key \"%S\"\n"), cs.buf);
457    cleanup_until(&buf);
458}
459
460/* Lookup():
461 *	look for the string starting at node ptr.
462 *	Print if last node
463 */
464static int
465Lookup(struct Strbuf *buf, const CStr *str, const XmapNode *ptr)
466{
467    if (ptr == NULL)
468	return (-1);		/* cannot have null ptr */
469
470    if (str->len == 0) {
471	/* no more chars in string.  Enumerate from here. */
472	Enumerate(buf, ptr);
473	return (0);
474    }
475    else {
476	/* If match put this char into buf.  Recurse */
477	if (ptr->ch == *(str->buf)) {
478	    /* match found */
479	    unparsech(buf, ptr->ch);
480	    if (ptr->next != NULL) {
481		/* not yet at leaf */
482		CStr tstr;
483		tstr.buf = str->buf + 1;
484		tstr.len = str->len - 1;
485		return (Lookup(buf, &tstr, ptr->next));
486	    }
487	    else {
488		/* next node is null so key should be complete */
489		if (str->len == 1) {
490		    Strbuf_append1(buf, '"');
491		    Strbuf_terminate(buf);
492		    printOne(buf->s, &ptr->val, ptr->type);
493		    return (0);
494		}
495		else
496		    return (-1);/* mismatch -- string still has chars */
497	    }
498	}
499	else {
500	    /* no match found try sibling */
501	    if (ptr->sibling)
502		return (Lookup(buf, str, ptr->sibling));
503	    else
504		return (-1);
505	}
506    }
507}
508
509static void
510Enumerate(struct Strbuf *buf, const XmapNode *ptr)
511{
512    size_t old_len;
513
514    if (ptr == NULL) {
515#ifdef DEBUG_EDIT
516	xprintf(CGETS(9, 6, "Enumerate: BUG!! Null ptr passed\n!"));
517#endif
518	return;
519    }
520
521    old_len = buf->len;
522    unparsech(buf, ptr->ch); /* put this char at end of string */
523    if (ptr->next == NULL) {
524	/* print this Xkey and function */
525	Strbuf_append1(buf, '"');
526	Strbuf_terminate(buf);
527	printOne(buf->s, &ptr->val, ptr->type);
528    }
529    else
530	Enumerate(buf, ptr->next);
531
532    /* go to sibling if there is one */
533    if (ptr->sibling) {
534	buf->len = old_len;
535	Enumerate(buf, ptr->sibling);
536    }
537}
538
539
540/* PrintOne():
541 *	Print the specified key and its associated
542 *	function specified by val
543 */
544void
545printOne(const Char *key, const XmapVal *val, int ntype)
546{
547    struct KeyFuncs *fp;
548    static const char *fmt = "%s\n";
549
550    xprintf("%-15S-> ", key);
551    if (val != NULL)
552	switch (ntype) {
553	case XK_STR:
554	case XK_EXE: {
555	    unsigned char *p;
556
557	    p = unparsestring(&val->str, ntype == XK_STR ? STRQQ : STRBB);
558	    cleanup_push(p, xfree);
559	    xprintf(fmt, p);
560	    cleanup_until(p);
561	    break;
562	}
563	case XK_CMD:
564	    for (fp = FuncNames; fp->name; fp++)
565		if (val->cmd == fp->func)
566		    xprintf(fmt, fp->name);
567		break;
568	default:
569	    abort();
570	    break;
571	}
572    else
573	xprintf(fmt, CGETS(9, 7, "no input"));
574}
575
576static void
577unparsech(struct Strbuf *buf, Char ch)
578{
579    if (ch == 0) {
580	Strbuf_append1(buf, '^');
581	Strbuf_append1(buf, '@');
582    }
583    else if (Iscntrl(ch)) {
584	Strbuf_append1(buf, '^');
585	if (ch == CTL_ESC('\177'))
586	    Strbuf_append1(buf, '?');
587	else
588#ifdef IS_ASCII
589	    Strbuf_append1(buf, ch | 0100);
590#else
591	    Strbuf_append1(buf, _toebcdic[_toascii[ch]|0100]);
592#endif
593    }
594    else if (ch == '^') {
595	Strbuf_append1(buf, '\\');
596	Strbuf_append1(buf, '^');
597    } else if (ch == '\\') {
598	Strbuf_append1(buf, '\\');
599	Strbuf_append1(buf, '\\');
600    } else if (ch == ' ' || (Isprint(ch) && !Isspace(ch))) {
601	Strbuf_append1(buf, ch);
602    }
603    else {
604	Strbuf_append1(buf, '\\');
605	Strbuf_append1(buf, ((ch >> 6) & 7) + '0');
606	Strbuf_append1(buf, ((ch >> 3) & 7) + '0');
607	Strbuf_append1(buf, (ch & 7) + '0');
608    }
609}
610
611eChar
612parseescape(const Char **ptr)
613{
614    const Char *p;
615    Char c;
616
617    p = *ptr;
618
619    if ((p[1] & CHAR) == 0) {
620	xprintf(CGETS(9, 8, "Something must follow: %c\n"), (char)*p);
621	return CHAR_ERR;
622    }
623    if ((*p & CHAR) == '\\') {
624	p++;
625	switch (*p & CHAR) {
626	case 'a':
627	    c = CTL_ESC('\007');         /* Bell */
628	    break;
629	case 'b':
630	    c = CTL_ESC('\010');         /* Backspace */
631	    break;
632	case 'e':
633	    c = CTL_ESC('\033');         /* Escape */
634	    break;
635	case 'f':
636	    c = CTL_ESC('\014');         /* Form Feed */
637	    break;
638	case 'n':
639	    c = CTL_ESC('\012');         /* New Line */
640	    break;
641	case 'r':
642	    c = CTL_ESC('\015');         /* Carriage Return */
643	    break;
644	case 't':
645	    c = CTL_ESC('\011');         /* Horizontal Tab */
646	    break;
647	case 'v':
648	    c = CTL_ESC('\013');         /* Vertical Tab */
649	    break;
650	case '\\':
651	    c = '\\';
652	    break;
653	case '0':
654	case '1':
655	case '2':
656	case '3':
657	case '4':
658	case '5':
659	case '6':
660	case '7':
661	    {
662		int cnt, val;
663		Char ch;
664
665		for (cnt = 0, val = 0; cnt < 3; cnt++) {
666		    ch = *p++ & CHAR;
667		    if (ch < '0' || ch > '7') {
668			p--;
669			break;
670		    }
671		    val = (val << 3) | (ch - '0');
672		}
673		if ((val & ~0xff) != 0) {
674		    xprintf("%s", CGETS(9, 9,
675			    "Octal constant does not fit in a char.\n"));
676		    return 0;
677		}
678#ifndef IS_ASCII
679		if (CTL_ESC(val) != val && adrof(STRwarnebcdic))
680		    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
681			    "Warning: Octal constant \\%3.3o is interpreted as EBCDIC value.\n", val/*)*/);
682#endif
683		c = (Char) val;
684		--p;
685	    }
686	    break;
687	default:
688	    c = *p;
689	    break;
690	}
691    }
692    else if ((*p & CHAR) == '^' && (Isalpha(p[1] & CHAR) ||
693				    strchr("@^_?\\|[{]}", p[1] & CHAR))) {
694	p++;
695#ifdef IS_ASCII
696	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : ((*p & CHAR) & 0237);
697#else
698	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : _toebcdic[_toascii[*p & CHAR] & 0237];
699	if (adrof(STRwarnebcdic))
700	    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
701		"Warning: Control character ^%c may be interpreted differently in EBCDIC.\n", *p & CHAR /*)*/);
702#endif
703    }
704    else
705	c = *p;
706    *ptr = p;
707    return (c);
708}
709
710
711unsigned char *
712unparsestring(const CStr *str, const Char *sep)
713{
714    unsigned char *buf, *b;
715    Char   p;
716    int l;
717
718    /* Worst-case is "\uuu" or result of wctomb() for each char from str */
719    buf = xmalloc((str->len + 1) * max(4, MB_LEN_MAX));
720    b = buf;
721    if (sep[0])
722#ifndef WINNT_NATIVE
723	*b++ = sep[0];
724#else /* WINNT_NATIVE */
725	*b++ = CHAR & sep[0];
726#endif /* !WINNT_NATIVE */
727
728    for (l = 0; l < str->len; l++) {
729	p = str->buf[l];
730	if (Iscntrl(p)) {
731	    *b++ = '^';
732	    if (p == CTL_ESC('\177'))
733		*b++ = '?';
734	    else
735#ifdef IS_ASCII
736		*b++ = (unsigned char) (p | 0100);
737#else
738		*b++ = _toebcdic[_toascii[p]|0100];
739#endif
740	}
741	else if (p == '^' || p == '\\') {
742	    *b++ = '\\';
743	    *b++ = (unsigned char) p;
744	}
745	else if (p == ' ' || (Isprint(p) && !Isspace(p)))
746	    b += one_wctomb((char *)b, p & CHAR);
747	else {
748	    *b++ = '\\';
749	    *b++ = ((p >> 6) & 7) + '0';
750	    *b++ = ((p >> 3) & 7) + '0';
751	    *b++ = (p & 7) + '0';
752	}
753    }
754    if (sep[0] && sep[1])
755#ifndef WINNT_NATIVE
756	*b++ = sep[1];
757#else /* WINNT_NATIVE */
758	*b++ = CHAR & sep[1];
759#endif /* !WINNT_NATIVE */
760    *b++ = 0;
761    return buf;			/* should check for overflow */
762}
763