0a374fd7e5
Reduce obfuscation of errno handling. There is only one purpose non-local errno handling is needed for: Inside el_wgets(), several functions call down indirectly to el_wgetc(), many of them via the dispatch table. When el_wgetc() fails, it does properly report failure, but then various cleanup is done which may clobber errno. But when returning due to failure, el_wgets() wants to have errno set to the reason of the original read failure, not to the reason of some subsequent failure of some cleanup operation. So el_wgetc() needs to save errno, and if it's non-zero, el_wgets() needs to restore it on failure. This core logic is currently obscured by the fact that el_errno is set and inspected at some additional places where it isn't needed. Besides, since el_wgetc() and and el_wgets() are both in read.c, el_errno does not need to be in struct editline, it can and should be local to read.c in struct el_read_t. Let's look at what can be simplified. 1. keymacro_get() abuses el_errno instead of having a proper error return code. Adding that error return code is easy because node_trav() already detects the condition and an adequate code is already defined. Returning it, testing for it in read_getcmd(), and returning with error from there removes the need to inspect el_errno from el_wgets() after calling read_getcmd(). Note that resetting lastchar and cursor and clearing buffer[0] is irrelevant. The code returns from el_wgets() right afterwards. Outside el_wgets(), these variables are no longer relevant. When el_wgets() is called the next time, it will call ch_reset() anyway, resetting the two pointers. And as long as lastchar points to the beginning of the buffer, the contents of the buffer won't be used for anything. 2. read_getcmd() doesn't need to set el_errno again after el_wgetc() failure since el_wgetc() already did so. While here, remove the silly "if EOF or error" comments from the el_wgetc() return value tests. It's a public interface documented in a manual, so people working on the implementation can obviously be expected to know how it works. It's a case of count++; /* Increment count. */ 3. In the two code paths of el_wgets() that lead up to "goto noedit", there is no need to save the errno because nothing that might change it happens before returning. For clarity, since el_wgets() is the function restoring the errno, also move initializing it to the same function. Finally, note that restoring errno when the saved value is zero is wrong. No library code is ever allowed to clear a previously set value of errno. Only application programs are allowed to do that, and even they usually don't need to do so, except when using certain ill-designed interfaces like strtol(3). I tested that the behaviour remains sane in the following cases, all during execution of el_wgets(3) and with a signal handler for USR1 installed without SA_RESTART. * Enter some text and maybe move around a bit. Then send a USR1 signal. The signal gets processed, then read_char() resumes reading. Send another USR1 signal. Now el_wgets() sets errno=EINTR and returns -1. * Press Ctrl-V to activate ed-quoted-insert. Then send a USR1 signal. The signal gets processed, then read_char() resumes reading. Send another USR1 signal. ed_quoted_insert() returns ed_end_of_file(), i.e. CC_EOF, and el_wgets() returns 0. * Press a key starting a keyboard macro. Then send a USR1 signal. The signal gets processed, then read_char() resumes reading. Send another USR1 signal. Now el_wgets() sets errno=EINTR and returns -1. * Press : to enter builtin command mode. Start typing a command. Then send a USR1 signal. The signal gets processed, then read_char() resumes reading. Send another USR1 signal. Now c_gets() returns -1, ed_command() beeps and returns CC_REFRESH, and el_wgets() resumes operation as it should. I also tested with "el_set(el, EL_EDITMODE, 0)", and it returns the right value and sets errno correctly.
670 lines
16 KiB
C
670 lines
16 KiB
C
/* $NetBSD: keymacro.c,v 1.23 2016/05/24 15:00:45 christos Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1992, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Christos Zoulas of Cornell University.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#if !defined(lint) && !defined(SCCSID)
|
|
#if 0
|
|
static char sccsid[] = "@(#)key.c 8.1 (Berkeley) 6/4/93";
|
|
#else
|
|
__RCSID("$NetBSD: keymacro.c,v 1.23 2016/05/24 15:00:45 christos Exp $");
|
|
#endif
|
|
#endif /* not lint && not SCCSID */
|
|
|
|
/*
|
|
* keymacro.c: This module contains the procedures for maintaining
|
|
* the extended-key map.
|
|
*
|
|
* An extended-key (key) is a sequence of keystrokes introduced
|
|
* with a sequence introducer and consisting of an arbitrary
|
|
* number of characters. This module maintains a map (the
|
|
* el->el_keymacro.map)
|
|
* to convert these extended-key sequences into input strs
|
|
* (XK_STR) or editor functions (XK_CMD).
|
|
*
|
|
* Warning:
|
|
* If key is a substr of some other keys, then the longer
|
|
* keys are lost!! That is, if the keys "abcd" and "abcef"
|
|
* are in el->el_keymacro.map, adding the key "abc" will cause
|
|
* the first two definitions to be lost.
|
|
*
|
|
* Restrictions:
|
|
* -------------
|
|
* 1) It is not possible to have one key that is a
|
|
* substr of another.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "el.h"
|
|
#include "fcns.h"
|
|
|
|
/*
|
|
* The Nodes of the el->el_keymacro.map. The el->el_keymacro.map is a
|
|
* linked list of these node elements
|
|
*/
|
|
struct keymacro_node_t {
|
|
wchar_t ch; /* single character of key */
|
|
int type; /* node type */
|
|
keymacro_value_t val; /* command code or pointer to str, */
|
|
/* if this is a leaf */
|
|
struct keymacro_node_t *next; /* ptr to next char of this key */
|
|
struct keymacro_node_t *sibling;/* ptr to another key with same prefix*/
|
|
};
|
|
|
|
static int node_trav(EditLine *, keymacro_node_t *, wchar_t *,
|
|
keymacro_value_t *);
|
|
static int node__try(EditLine *, keymacro_node_t *,
|
|
const wchar_t *, keymacro_value_t *, int);
|
|
static keymacro_node_t *node__get(wint_t);
|
|
static void node__free(keymacro_node_t *);
|
|
static void node__put(EditLine *, keymacro_node_t *);
|
|
static int node__delete(EditLine *, keymacro_node_t **,
|
|
const wchar_t *);
|
|
static int node_lookup(EditLine *, const wchar_t *,
|
|
keymacro_node_t *, size_t);
|
|
static int node_enum(EditLine *, keymacro_node_t *, size_t);
|
|
|
|
#define KEY_BUFSIZ EL_BUFSIZ
|
|
|
|
|
|
/* keymacro_init():
|
|
* Initialize the key maps
|
|
*/
|
|
libedit_private int
|
|
keymacro_init(EditLine *el)
|
|
{
|
|
|
|
el->el_keymacro.buf = el_malloc(KEY_BUFSIZ *
|
|
sizeof(*el->el_keymacro.buf));
|
|
if (el->el_keymacro.buf == NULL)
|
|
return -1;
|
|
el->el_keymacro.map = NULL;
|
|
keymacro_reset(el);
|
|
return 0;
|
|
}
|
|
|
|
/* keymacro_end():
|
|
* Free the key maps
|
|
*/
|
|
libedit_private void
|
|
keymacro_end(EditLine *el)
|
|
{
|
|
|
|
el_free(el->el_keymacro.buf);
|
|
el->el_keymacro.buf = NULL;
|
|
node__free(el->el_keymacro.map);
|
|
}
|
|
|
|
|
|
/* keymacro_map_cmd():
|
|
* Associate cmd with a key value
|
|
*/
|
|
libedit_private keymacro_value_t *
|
|
keymacro_map_cmd(EditLine *el, int cmd)
|
|
{
|
|
|
|
el->el_keymacro.val.cmd = (el_action_t) cmd;
|
|
return &el->el_keymacro.val;
|
|
}
|
|
|
|
|
|
/* keymacro_map_str():
|
|
* Associate str with a key value
|
|
*/
|
|
libedit_private keymacro_value_t *
|
|
keymacro_map_str(EditLine *el, wchar_t *str)
|
|
{
|
|
|
|
el->el_keymacro.val.str = str;
|
|
return &el->el_keymacro.val;
|
|
}
|
|
|
|
|
|
/* keymacro_reset():
|
|
* Takes all nodes on el->el_keymacro.map and puts them on free list.
|
|
* Then initializes el->el_keymacro.map with arrow keys
|
|
* [Always bind the ansi arrow keys?]
|
|
*/
|
|
libedit_private void
|
|
keymacro_reset(EditLine *el)
|
|
{
|
|
|
|
node__put(el, el->el_keymacro.map);
|
|
el->el_keymacro.map = NULL;
|
|
return;
|
|
}
|
|
|
|
|
|
/* keymacro_get():
|
|
* Calls the recursive function with entry point el->el_keymacro.map
|
|
* Looks up *ch in map and then reads characters until a
|
|
* complete match is found or a mismatch occurs. Returns the
|
|
* type of the match found (XK_STR or XK_CMD).
|
|
* Returns NULL in val.str and XK_STR for no match.
|
|
* Returns XK_NOD for end of file or read error.
|
|
* The last character read is returned in *ch.
|
|
*/
|
|
libedit_private int
|
|
keymacro_get(EditLine *el, wchar_t *ch, keymacro_value_t *val)
|
|
{
|
|
|
|
return node_trav(el, el->el_keymacro.map, ch, val);
|
|
}
|
|
|
|
|
|
/* keymacro_add():
|
|
* Adds key to the el->el_keymacro.map and associates the value in
|
|
* val with it. If key is already is in el->el_keymacro.map, the new
|
|
* code is applied to the existing key. Ntype specifies if code is a
|
|
* command, an out str or a unix command.
|
|
*/
|
|
libedit_private void
|
|
keymacro_add(EditLine *el, const wchar_t *key, keymacro_value_t *val,
|
|
int ntype)
|
|
{
|
|
|
|
if (key[0] == '\0') {
|
|
(void) fprintf(el->el_errfile,
|
|
"keymacro_add: Null extended-key not allowed.\n");
|
|
return;
|
|
}
|
|
if (ntype == XK_CMD && val->cmd == ED_SEQUENCE_LEAD_IN) {
|
|
(void) fprintf(el->el_errfile,
|
|
"keymacro_add: sequence-lead-in command not allowed\n");
|
|
return;
|
|
}
|
|
if (el->el_keymacro.map == NULL)
|
|
/* tree is initially empty. Set up new node to match key[0] */
|
|
el->el_keymacro.map = node__get(key[0]);
|
|
/* it is properly initialized */
|
|
|
|
/* Now recurse through el->el_keymacro.map */
|
|
(void) node__try(el, el->el_keymacro.map, key, val, ntype);
|
|
return;
|
|
}
|
|
|
|
|
|
/* keymacro_clear():
|
|
*
|
|
*/
|
|
libedit_private void
|
|
keymacro_clear(EditLine *el, el_action_t *map, const wchar_t *in)
|
|
{
|
|
if (*in > N_KEYS) /* can't be in the map */
|
|
return;
|
|
if ((map[(unsigned char)*in] == ED_SEQUENCE_LEAD_IN) &&
|
|
((map == el->el_map.key &&
|
|
el->el_map.alt[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN) ||
|
|
(map == el->el_map.alt &&
|
|
el->el_map.key[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN)))
|
|
(void) keymacro_delete(el, in);
|
|
}
|
|
|
|
|
|
/* keymacro_delete():
|
|
* Delete the key and all longer keys staring with key, if
|
|
* they exists.
|
|
*/
|
|
libedit_private int
|
|
keymacro_delete(EditLine *el, const wchar_t *key)
|
|
{
|
|
|
|
if (key[0] == '\0') {
|
|
(void) fprintf(el->el_errfile,
|
|
"keymacro_delete: Null extended-key not allowed.\n");
|
|
return -1;
|
|
}
|
|
if (el->el_keymacro.map == NULL)
|
|
return 0;
|
|
|
|
(void) node__delete(el, &el->el_keymacro.map, key);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* keymacro_print():
|
|
* Print the binding associated with key key.
|
|
* Print entire el->el_keymacro.map if null
|
|
*/
|
|
libedit_private void
|
|
keymacro_print(EditLine *el, const wchar_t *key)
|
|
{
|
|
|
|
/* do nothing if el->el_keymacro.map is empty and null key specified */
|
|
if (el->el_keymacro.map == NULL && *key == 0)
|
|
return;
|
|
|
|
el->el_keymacro.buf[0] = '"';
|
|
if (node_lookup(el, key, el->el_keymacro.map, (size_t)1) <= -1)
|
|
/* key is not bound */
|
|
(void) fprintf(el->el_errfile, "Unbound extended key \"%ls"
|
|
"\"\n", key);
|
|
return;
|
|
}
|
|
|
|
|
|
/* node_trav():
|
|
* recursively traverses node in tree until match or mismatch is
|
|
* found. May read in more characters.
|
|
*/
|
|
static int
|
|
node_trav(EditLine *el, keymacro_node_t *ptr, wchar_t *ch,
|
|
keymacro_value_t *val)
|
|
{
|
|
|
|
if (ptr->ch == *ch) {
|
|
/* match found */
|
|
if (ptr->next) {
|
|
/* key not complete so get next char */
|
|
if (el_wgetc(el, ch) != 1)
|
|
return XK_NOD;
|
|
return node_trav(el, ptr->next, ch, val);
|
|
} else {
|
|
*val = ptr->val;
|
|
if (ptr->type != XK_CMD)
|
|
*ch = '\0';
|
|
return ptr->type;
|
|
}
|
|
} else {
|
|
/* no match found here */
|
|
if (ptr->sibling) {
|
|
/* try next sibling */
|
|
return node_trav(el, ptr->sibling, ch, val);
|
|
} else {
|
|
/* no next sibling -- mismatch */
|
|
val->str = NULL;
|
|
return XK_STR;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* node__try():
|
|
* Find a node that matches *str or allocate a new one
|
|
*/
|
|
static int
|
|
node__try(EditLine *el, keymacro_node_t *ptr, const wchar_t *str,
|
|
keymacro_value_t *val, int ntype)
|
|
{
|
|
|
|
if (ptr->ch != *str) {
|
|
keymacro_node_t *xm;
|
|
|
|
for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
|
|
if (xm->sibling->ch == *str)
|
|
break;
|
|
if (xm->sibling == NULL)
|
|
xm->sibling = node__get(*str); /* setup new node */
|
|
ptr = xm->sibling;
|
|
}
|
|
if (*++str == '\0') {
|
|
/* we're there */
|
|
if (ptr->next != NULL) {
|
|
node__put(el, ptr->next);
|
|
/* lose longer keys with this prefix */
|
|
ptr->next = NULL;
|
|
}
|
|
switch (ptr->type) {
|
|
case XK_CMD:
|
|
case XK_NOD:
|
|
break;
|
|
case XK_STR:
|
|
if (ptr->val.str)
|
|
el_free(ptr->val.str);
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n",
|
|
ptr->type));
|
|
break;
|
|
}
|
|
|
|
switch (ptr->type = ntype) {
|
|
case XK_CMD:
|
|
ptr->val = *val;
|
|
break;
|
|
case XK_STR:
|
|
if ((ptr->val.str = wcsdup(val->str)) == NULL)
|
|
return -1;
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype));
|
|
break;
|
|
}
|
|
} else {
|
|
/* still more chars to go */
|
|
if (ptr->next == NULL)
|
|
ptr->next = node__get(*str); /* setup new node */
|
|
(void) node__try(el, ptr->next, str, val, ntype);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* node__delete():
|
|
* Delete node that matches str
|
|
*/
|
|
static int
|
|
node__delete(EditLine *el, keymacro_node_t **inptr, const wchar_t *str)
|
|
{
|
|
keymacro_node_t *ptr;
|
|
keymacro_node_t *prev_ptr = NULL;
|
|
|
|
ptr = *inptr;
|
|
|
|
if (ptr->ch != *str) {
|
|
keymacro_node_t *xm;
|
|
|
|
for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
|
|
if (xm->sibling->ch == *str)
|
|
break;
|
|
if (xm->sibling == NULL)
|
|
return 0;
|
|
prev_ptr = xm;
|
|
ptr = xm->sibling;
|
|
}
|
|
if (*++str == '\0') {
|
|
/* we're there */
|
|
if (prev_ptr == NULL)
|
|
*inptr = ptr->sibling;
|
|
else
|
|
prev_ptr->sibling = ptr->sibling;
|
|
ptr->sibling = NULL;
|
|
node__put(el, ptr);
|
|
return 1;
|
|
} else if (ptr->next != NULL &&
|
|
node__delete(el, &ptr->next, str) == 1) {
|
|
if (ptr->next != NULL)
|
|
return 0;
|
|
if (prev_ptr == NULL)
|
|
*inptr = ptr->sibling;
|
|
else
|
|
prev_ptr->sibling = ptr->sibling;
|
|
ptr->sibling = NULL;
|
|
node__put(el, ptr);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* node__put():
|
|
* Puts a tree of nodes onto free list using free(3).
|
|
*/
|
|
static void
|
|
node__put(EditLine *el, keymacro_node_t *ptr)
|
|
{
|
|
if (ptr == NULL)
|
|
return;
|
|
|
|
if (ptr->next != NULL) {
|
|
node__put(el, ptr->next);
|
|
ptr->next = NULL;
|
|
}
|
|
node__put(el, ptr->sibling);
|
|
|
|
switch (ptr->type) {
|
|
case XK_CMD:
|
|
case XK_NOD:
|
|
break;
|
|
case XK_STR:
|
|
if (ptr->val.str != NULL)
|
|
el_free(ptr->val.str);
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ptr->type));
|
|
break;
|
|
}
|
|
el_free(ptr);
|
|
}
|
|
|
|
|
|
/* node__get():
|
|
* Returns pointer to a keymacro_node_t for ch.
|
|
*/
|
|
static keymacro_node_t *
|
|
node__get(wint_t ch)
|
|
{
|
|
keymacro_node_t *ptr;
|
|
|
|
ptr = el_malloc(sizeof(*ptr));
|
|
if (ptr == NULL)
|
|
return NULL;
|
|
ptr->ch = ch;
|
|
ptr->type = XK_NOD;
|
|
ptr->val.str = NULL;
|
|
ptr->next = NULL;
|
|
ptr->sibling = NULL;
|
|
return ptr;
|
|
}
|
|
|
|
static void
|
|
node__free(keymacro_node_t *k)
|
|
{
|
|
if (k == NULL)
|
|
return;
|
|
node__free(k->sibling);
|
|
node__free(k->next);
|
|
el_free(k);
|
|
}
|
|
|
|
/* node_lookup():
|
|
* look for the str starting at node ptr.
|
|
* Print if last node
|
|
*/
|
|
static int
|
|
node_lookup(EditLine *el, const wchar_t *str, keymacro_node_t *ptr,
|
|
size_t cnt)
|
|
{
|
|
ssize_t used;
|
|
|
|
if (ptr == NULL)
|
|
return -1; /* cannot have null ptr */
|
|
|
|
if (!str || *str == 0) {
|
|
/* no more chars in str. node_enum from here. */
|
|
(void) node_enum(el, ptr, cnt);
|
|
return 0;
|
|
} else {
|
|
/* If match put this char into el->el_keymacro.buf. Recurse */
|
|
if (ptr->ch == *str) {
|
|
/* match found */
|
|
used = ct_visual_char(el->el_keymacro.buf + cnt,
|
|
KEY_BUFSIZ - cnt, ptr->ch);
|
|
if (used == -1)
|
|
return -1; /* ran out of buffer space */
|
|
if (ptr->next != NULL)
|
|
/* not yet at leaf */
|
|
return (node_lookup(el, str + 1, ptr->next,
|
|
(size_t)used + cnt));
|
|
else {
|
|
/* next node is null so key should be complete */
|
|
if (str[1] == 0) {
|
|
size_t px = cnt + (size_t)used;
|
|
el->el_keymacro.buf[px] = '"';
|
|
el->el_keymacro.buf[px + 1] = '\0';
|
|
keymacro_kprint(el, el->el_keymacro.buf,
|
|
&ptr->val, ptr->type);
|
|
return 0;
|
|
} else
|
|
return -1;
|
|
/* mismatch -- str still has chars */
|
|
}
|
|
} else {
|
|
/* no match found try sibling */
|
|
if (ptr->sibling)
|
|
return (node_lookup(el, str, ptr->sibling,
|
|
cnt));
|
|
else
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* node_enum():
|
|
* Traverse the node printing the characters it is bound in buffer
|
|
*/
|
|
static int
|
|
node_enum(EditLine *el, keymacro_node_t *ptr, size_t cnt)
|
|
{
|
|
ssize_t used;
|
|
|
|
if (cnt >= KEY_BUFSIZ - 5) { /* buffer too small */
|
|
el->el_keymacro.buf[++cnt] = '"';
|
|
el->el_keymacro.buf[++cnt] = '\0';
|
|
(void) fprintf(el->el_errfile,
|
|
"Some extended keys too long for internal print buffer");
|
|
(void) fprintf(el->el_errfile, " \"%ls...\"\n",
|
|
el->el_keymacro.buf);
|
|
return 0;
|
|
}
|
|
if (ptr == NULL) {
|
|
#ifdef DEBUG_EDIT
|
|
(void) fprintf(el->el_errfile,
|
|
"node_enum: BUG!! Null ptr passed\n!");
|
|
#endif
|
|
return -1;
|
|
}
|
|
/* put this char at end of str */
|
|
used = ct_visual_char(el->el_keymacro.buf + cnt, KEY_BUFSIZ - cnt,
|
|
ptr->ch);
|
|
if (ptr->next == NULL) {
|
|
/* print this key and function */
|
|
el->el_keymacro.buf[cnt + (size_t)used ] = '"';
|
|
el->el_keymacro.buf[cnt + (size_t)used + 1] = '\0';
|
|
keymacro_kprint(el, el->el_keymacro.buf, &ptr->val, ptr->type);
|
|
} else
|
|
(void) node_enum(el, ptr->next, cnt + (size_t)used);
|
|
|
|
/* go to sibling if there is one */
|
|
if (ptr->sibling)
|
|
(void) node_enum(el, ptr->sibling, cnt);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* keymacro_kprint():
|
|
* Print the specified key and its associated
|
|
* function specified by val
|
|
*/
|
|
libedit_private void
|
|
keymacro_kprint(EditLine *el, const wchar_t *key, keymacro_value_t *val,
|
|
int ntype)
|
|
{
|
|
el_bindings_t *fp;
|
|
char unparsbuf[EL_BUFSIZ];
|
|
static const char fmt[] = "%-15s-> %s\n";
|
|
|
|
if (val != NULL)
|
|
switch (ntype) {
|
|
case XK_STR:
|
|
(void) keymacro__decode_str(val->str, unparsbuf,
|
|
sizeof(unparsbuf),
|
|
ntype == XK_STR ? "\"\"" : "[]");
|
|
(void) fprintf(el->el_outfile, fmt,
|
|
ct_encode_string(key, &el->el_scratch), unparsbuf);
|
|
break;
|
|
case XK_CMD:
|
|
for (fp = el->el_map.help; fp->name; fp++)
|
|
if (val->cmd == fp->func) {
|
|
wcstombs(unparsbuf, fp->name, sizeof(unparsbuf));
|
|
unparsbuf[sizeof(unparsbuf) -1] = '\0';
|
|
(void) fprintf(el->el_outfile, fmt,
|
|
ct_encode_string(key, &el->el_scratch), unparsbuf);
|
|
break;
|
|
}
|
|
#ifdef DEBUG_KEY
|
|
if (fp->name == NULL)
|
|
(void) fprintf(el->el_outfile,
|
|
"BUG! Command not found.\n");
|
|
#endif
|
|
|
|
break;
|
|
default:
|
|
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype));
|
|
break;
|
|
}
|
|
else
|
|
(void) fprintf(el->el_outfile, fmt, ct_encode_string(key,
|
|
&el->el_scratch), "no input");
|
|
}
|
|
|
|
|
|
#define ADDC(c) \
|
|
if (b < eb) \
|
|
*b++ = c; \
|
|
else \
|
|
b++
|
|
/* keymacro__decode_str():
|
|
* Make a printable version of the ey
|
|
*/
|
|
libedit_private size_t
|
|
keymacro__decode_str(const wchar_t *str, char *buf, size_t len,
|
|
const char *sep)
|
|
{
|
|
char *b = buf, *eb = b + len;
|
|
const wchar_t *p;
|
|
|
|
b = buf;
|
|
if (sep[0] != '\0') {
|
|
ADDC(sep[0]);
|
|
}
|
|
if (*str == '\0') {
|
|
ADDC('^');
|
|
ADDC('@');
|
|
goto add_endsep;
|
|
}
|
|
for (p = str; *p != 0; p++) {
|
|
wchar_t dbuf[VISUAL_WIDTH_MAX];
|
|
wchar_t *p2 = dbuf;
|
|
ssize_t l = ct_visual_char(dbuf, VISUAL_WIDTH_MAX, *p);
|
|
while (l-- > 0) {
|
|
ssize_t n = ct_encode_char(b, (size_t)(eb - b), *p2++);
|
|
if (n == -1) /* ran out of space */
|
|
goto add_endsep;
|
|
else
|
|
b += n;
|
|
}
|
|
}
|
|
add_endsep:
|
|
if (sep[0] != '\0' && sep[1] != '\0') {
|
|
ADDC(sep[1]);
|
|
}
|
|
ADDC('\0');
|
|
if ((size_t)(b - buf) >= len)
|
|
buf[len - 1] = '\0';
|
|
return (size_t)(b - buf);
|
|
}
|