OpenSolaris_b135/lib/libtecla/common/keytab.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 * of the copyright holder.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "keytab.h"
#include "strngmem.h"
#include "getline.h"
#include "errmsg.h"
#include "hash.h"

/*
 * When allocating or reallocating the key-binding table, how
 * many entries should be added?
 */
#define KT_TABLE_INC 100

/*
 * Define the size of the hash table that is used to associate action
 * names with action functions. This should be a prime number.
 */
#define KT_HASH_SIZE 113

/*
 * Define a binary-symbol-table object.
 */
struct KeyTab {
  ErrMsg *err;            /* Information about the last error */
  int size;               /* The allocated dimension of table[] */
  int nkey;               /* The current number of members in the table */
  KeySym *table;          /* The table of lexically sorted key sequences */
  HashTable *actions;     /* The hash table of actions */
  StringMem *smem;        /* Memory for allocating strings */
};

static int _kt_extend_table(KeyTab *kt);
static int _kt_parse_keybinding_string(const char *keyseq,
				       char *binary, int *nc);
static int _kt_compare_strings(const char *s1, int n1, const char *s2, int n2);
static void _kt_assign_action(KeySym *sym, KtBinder binder, KtKeyFn *keyfn,
			      void *data);
static char _kt_backslash_escape(const char *string, const char **endp);
static int _kt_is_emacs_meta(const char *string);
static int _kt_is_emacs_ctrl(const char *string);
static KtKeyMatch _kt_locate_keybinding(KeyTab *kt, const char *binary_keyseq,
					int nc, int *first, int *last);

/*.......................................................................
 * Create a new key-binding symbol table.
 *
 * Output:
 *  return  KeyTab *  The new object, or NULL on error.
 */
KeyTab *_new_KeyTab(void)
{
  KeyTab *kt;  /* The object to be returned */
/*
 * Allocate the container.
 */
  kt = (KeyTab *) malloc(sizeof(KeyTab));
  if(!kt) {
    errno = ENOMEM;
    return NULL;
  };
/*
 * Before attempting any operation that might fail, initialize the
 * container at least up to the point at which it can safely be passed
 * to del_KeyTab().
 */
  kt->err = NULL;
  kt->size = KT_TABLE_INC;
  kt->nkey = 0;
  kt->table = NULL;
  kt->actions = NULL;
  kt->smem = NULL;
/*
 * Allocate a place to record error messages.
 */
  kt->err = _new_ErrMsg();
  if(!kt->err)
    return _del_KeyTab(kt);
/*
 * Allocate the table.
 */
  kt->table = (KeySym *) malloc(sizeof(kt->table[0]) * kt->size);
  if(!kt->table) {
    errno = ENOMEM;
    return _del_KeyTab(kt);
  };
/*
 * Allocate a hash table of actions.
 */
  kt->actions = _new_HashTable(NULL, KT_HASH_SIZE, IGNORE_CASE, NULL, 0);
  if(!kt->actions)
    return _del_KeyTab(kt);
/*
 * Allocate a string allocation object. This allows allocation of
 * small strings without fragmenting the heap.
 */
  kt->smem = _new_StringMem(KT_TABLE_INC);
  if(!kt->smem)
    return _del_KeyTab(kt);
  return kt;
}

/*.......................................................................
 * Delete a KeyTab object.
 *
 * Input:
 *  kt   KeyTab *  The object to be deleted.
 * Output:
 *  return KeyTab *  The deleted object (always NULL).
 */
KeyTab *_del_KeyTab(KeyTab *kt)
{
  if(kt) {
    if(kt->table)
      free(kt->table);
    kt->actions = _del_HashTable(kt->actions);
    kt->smem = _del_StringMem(kt->smem, 1);
    kt->err = _del_ErrMsg(kt->err);
    free(kt);
  };
  return NULL;
}

/*.......................................................................
 * Increase the size of the table to accomodate more keys.
 *
 * Input:
 *  kt       KeyTab *  The table to be extended.
 * Output:
 *  return      int    0 - OK.
 *                     1 - Error.
 */
static int _kt_extend_table(KeyTab *kt)
{
/*
 * Attempt to increase the size of the table.
 */
  KeySym *newtab = (KeySym *) realloc(kt->table, sizeof(kt->table[0]) *
				      (kt->size + KT_TABLE_INC));
/*
 * Failed?
 */
  if(!newtab) {
    _err_record_msg(kt->err, "Can't extend keybinding table", END_ERR_MSG);
    errno = ENOMEM;
    return 1;
  };
/*
 * Install the resized table.
 */
  kt->table = newtab;
  kt->size += KT_TABLE_INC;
  return 0;
}

/*.......................................................................
 * Add, update or remove a keybinding to the table.
 *
 * Input:
 *  kt           KeyTab *  The table to add the binding to.
 *  binder     KtBinder    The source of the binding.
 *  keyseq   const char *  The key-sequence to bind.
 *  action         char *  The action to associate with the key sequence, or
 *                         NULL to remove the action associated with the
 *                         key sequence.
 * Output:
 *  return          int    0 - OK.
 *                         1 - Error.
 */
int _kt_set_keybinding(KeyTab *kt, KtBinder binder, const char *keyseq,
		       const char *action)
{
  KtKeyFn *keyfn; /* The action function */
  void *data;     /* The callback data of the action function */
/*
 * Check arguments.
 */
  if(kt==NULL || !keyseq) {
    errno = EINVAL;
    if(kt)
      _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
    return 1;
  };
/*
 * Lookup the function that implements the specified action.
 */
  if(!action) {
    keyfn = 0;
    data = NULL;
  } else {
    Symbol *sym = _find_HashSymbol(kt->actions, action);
    if(!sym) {
      _err_record_msg(kt->err, "Unknown key-binding action: ", action,
		      END_ERR_MSG);
      errno = EINVAL;
      return 1;
    };
    keyfn = (KtKeyFn *) sym->fn;
    data = sym->data;
  };
/*
 * Record the action in the table.
 */
  return _kt_set_keyfn(kt, binder, keyseq, keyfn, data);
}

/*.......................................................................
 * Add, update or remove a keybinding to the table, specifying an action
 * function directly.
 *
 * Input:
 *  kt       KeyTab *  The table to add the binding to.
 *  binder KtBinder    The source of the binding.
 *  keyseq     char *  The key-sequence to bind.
 *  keyfn   KtKeyFn *  The action function, or NULL to remove any existing
 *                     action function.
 *  data       void *  A pointer to anonymous data to be passed to keyfn
 *                     whenever it is called.
 * Output:
 *  return     int    0 - OK.
 *                    1 - Error.
 */
int _kt_set_keyfn(KeyTab *kt, KtBinder binder, const char *keyseq,
		  KtKeyFn *keyfn, void *data)
{
  const char *kptr;  /* A pointer into keyseq[] */
  char *binary;      /* The binary version of keyseq[] */
  int nc;            /* The number of characters in binary[] */
  int first,last;    /* The first and last entries in the table which */
                     /*  minimally match. */
  int size;          /* The size to allocate for the binary string */
  int i;
/*
 * Check arguments.
 */
  if(kt==NULL || !keyseq) {
    errno = EINVAL;
    if(kt)
      _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
    return 1;
  };
/*
 * Work out a pessimistic estimate of how much space will be needed
 * for the binary copy of the string, noting that binary meta characters
 * embedded in the input string get split into two characters.
 */
  for(size=0,kptr = keyseq; *kptr; kptr++)
    size += IS_META_CHAR(*kptr) ? 2 : 1;
/*
 * Allocate a string that has the length of keyseq[].
 */
  binary = _new_StringMemString(kt->smem, size + 1);
  if(!binary) {
    errno = ENOMEM;
    _err_record_msg(kt->err, "Insufficient memory to record key sequence",
		    END_ERR_MSG);
    return 1;
  };
/*
 * Convert control and octal character specifications to binary characters.
 */
  if(_kt_parse_keybinding_string(keyseq, binary, &nc)) {
    binary = _del_StringMemString(kt->smem, binary);
    return 1;
  };
/*
 * Lookup the position in the table at which to insert the binding.
 */
  switch(_kt_locate_keybinding(kt, binary, nc, &first, &last)) {
/*
 * If an exact match for the key-sequence is already in the table,
 * simply replace its binding function (or delete the entry if
 * the new binding is 0).
 */
  case KT_EXACT_MATCH:
    if(keyfn) {
      _kt_assign_action(kt->table + first, binder, keyfn, data);
    } else {
      _del_StringMemString(kt->smem, kt->table[first].keyseq);
      memmove(kt->table + first, kt->table + first + 1,
	      (kt->nkey - first - 1) * sizeof(kt->table[0]));
      kt->nkey--;
    };
    binary = _del_StringMemString(kt->smem, binary);
    break;
/*
 * If an ambiguous match has been found and we are installing a
 * callback, then our new key-sequence would hide all of the ambiguous
 * matches, so we shouldn't allow it.
 */
  case KT_AMBIG_MATCH:
    if(keyfn) {
      _err_record_msg(kt->err, "Can't bind \"", keyseq,
		      "\", because it is a prefix of another binding",
		      END_ERR_MSG);
      binary = _del_StringMemString(kt->smem, binary);
      errno = EPERM;
      return 1;
    };
    break;
/*
 * If the entry doesn't exist, create it.
 */
  case KT_NO_MATCH:
/*
 * Add a new binding?
 */
    if(keyfn) {
      KeySym *sym;
/*
 * We will need a new entry, extend the table if needed.
 */
      if(kt->nkey + 1 > kt->size) {
	if(_kt_extend_table(kt)) {
	  binary = _del_StringMemString(kt->smem, binary);
	  return 1;
	};
      };
/*
 * Make space to insert the new key-sequence before 'last'.
 */
      if(last < kt->nkey) {
	memmove(kt->table + last + 1, kt->table + last,
		(kt->nkey - last) * sizeof(kt->table[0]));
      };
/*
 * Insert the new binding in the vacated position.
 */
      sym = kt->table + last;
      sym->keyseq = binary;
      sym->nc = nc;
      for(i=0; i<KTB_NBIND; i++) {
	KtAction *action = sym->actions + i;
	action->fn = 0;
	action->data = NULL;
      };
      sym->binder = -1;
      _kt_assign_action(sym, binder, keyfn, data);
      kt->nkey++;
    };
    break;
  case KT_BAD_MATCH:
    binary = _del_StringMemString(kt->smem, binary);
    return 1;
    break;
  };
  return 0;
}

/*.......................................................................
 * Perform a min-match lookup of a key-binding.
 *
 * Input:
 *  kt          KeyTab *   The keybinding table to lookup in.
 *  binary_keyseq char *   The binary key-sequence to lookup.
 *  nc             int     the number of characters in keyseq[].
 * Input/Output:
 *  first,last     int *   If there is an ambiguous or exact match, the indexes
 *                         of the first and last symbols that minimally match
 *                         will be assigned to *first and *last respectively.
 *                         If there is no match, then first and last will
 *                         bracket the location where the symbol should be
 *                         inserted.
 * Output:
 *  return  KtKeyMatch     One of the following enumerators:
 *                          KT_EXACT_MATCH - An exact match was found.
 *                          KT_AMBIG_MATCH - An ambiguous match was found.
 *                          KT_NO_MATCH    - No match was found.
 *                          KT_BAD_MATCH   - An error occurred while searching.
 */
static KtKeyMatch _kt_locate_keybinding(KeyTab *kt, const char *binary_keyseq,
					int nc, int *first, int *last)
{
  int mid;     /* The index at which to bisect the table */
  int bot;     /* The lowest index of the table not searched yet */
  int top;     /* The highest index of the table not searched yet */
  int test;    /* The return value of strcmp() */
/*
 * Perform a binary search for the key-sequence.
 */
  bot = 0;
  top = kt->nkey - 1;
  while(top >= bot) {
    mid = (top + bot)/2;
    test = _kt_compare_strings(kt->table[mid].keyseq, kt->table[mid].nc,
			   binary_keyseq, nc);
    if(test > 0)
      top = mid - 1;
    else if(test < 0)
      bot = mid + 1;
    else {
      *first = *last = mid;
      return KT_EXACT_MATCH;
    };
  };
/*
 * An exact match wasn't found, but top is the index just below the
 * index where a match would be found, and bot is the index just above
 * where the match ought to be found.
 */
  *first = top;
  *last = bot;
/*
 * See if any ambiguous matches exist, and if so make *first and *last
 * refer to the first and last matches.
 */
  if(*last < kt->nkey && kt->table[*last].nc > nc &&
     _kt_compare_strings(kt->table[*last].keyseq, nc, binary_keyseq, nc)==0) {
    *first = *last;
    while(*last+1 < kt->nkey && kt->table[*last+1].nc > nc &&
	  _kt_compare_strings(kt->table[*last+1].keyseq, nc, binary_keyseq, nc)==0)
      (*last)++;
    return KT_AMBIG_MATCH;
  };
/*
 * No match.
 */
  return KT_NO_MATCH;
}

/*.......................................................................
 * Lookup the sub-array of key-bindings who's key-sequences minimally
 * match a given key-sequence.
 *
 * Input:
 *  kt          KeyTab *   The keybinding table to lookup in.
 *  binary_keyseq char *   The binary key-sequence to lookup.
 *  nc             int     the number of characters in keyseq[].
 * Input/Output:
 *  matches     KeySym **  The array of minimally matching symbols
 *                         can be found in (*matches)[0..nmatch-1], unless
 *                         no match was found, in which case *matches will
 *                         be set to NULL.
 *  nmatch         int     The number of ambiguously matching symbols. This
 *                         will be 0 if there is no match, 1 for an exact
 *                         match, and a number greater than 1 for an ambiguous
 *                         match.
 * Output:
 *  return  KtKeyMatch     One of the following enumerators:
 *                          KT_EXACT_MATCH - An exact match was found.
 *                          KT_AMBIG_MATCH - An ambiguous match was found.
 *                          KT_NO_MATCH    - No match was found.
 *                          KT_BAD_MATCH   - An error occurred while searching.
 */
KtKeyMatch _kt_lookup_keybinding(KeyTab *kt, const char *binary_keyseq,
				 int nc, KeySym **matches, int *nmatch)
{
  KtKeyMatch status;  /* The return status */
  int first,last;     /* The indexes of the first and last matching entry */
                      /* in the symbol table. */
/*
 * Check the arguments.
 */
  if(!kt || !binary_keyseq || !matches || !nmatch || nc < 0) {
    errno = EINVAL;
    if(kt)
      _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
    return KT_BAD_MATCH;
  };
/*
 * Lookup the indexes of the binding-table entries that bracket the
 * target key-sequence.
 */
  status = _kt_locate_keybinding(kt, binary_keyseq, nc, &first, &last);
/*
 * Translate the indexes into the corresponding subarray of matching
 * table entries.
 */
  switch(status) {
  case KT_EXACT_MATCH:
  case KT_AMBIG_MATCH:
    *matches = kt->table + first;
    *nmatch = last - first + 1;
    break;
  default:
    *matches = NULL;
    *nmatch = 0;
    break;
  };
  return status;
}

/*.......................................................................
 * Convert a keybinding string into a uniq binary representation.
 *
 * Control characters can be given directly in their binary form,
 * expressed as either ^ or C-, followed by the character, expressed in
 * octal, like \129 or via C-style backslash escapes, with the addition
 * of '\E' to denote the escape key. Similarly, meta characters can be
 * given directly in binary or expressed as M- followed by the character.
 * Meta characters are recorded as two characters in the binary output
 * string, the first being the escape key, and the second being the key
 * that was modified by the meta key. This means that binding to
 * \EA or ^[A or M-A are all equivalent.
 *
 * Input:
 *  keyseq   char *  The key sequence being added.
 * Input/Output:
 *  binary   char *  The binary version of the key sequence will be
 *                   assigned to binary[], which must have at least
 *                   as many characters as keyseq[] plus the number
 *                   of embedded binary meta characters.
 *  nc        int *  The number of characters assigned to binary[]
 *                   will be recorded in *nc.
 * Output:
 *  return    int    0 - OK.
 *                   1 - Error.
 */
static int _kt_parse_keybinding_string(const char *keyseq, char *binary,
				       int *nc)
{
  const char *iptr = keyseq;   /* Pointer into keyseq[] */
  char *optr = binary;         /* Pointer into binary[] */
  char c;                      /* An intermediate character */
/*
 * Parse the input characters until they are exhausted or the
 * output string becomes full.
 */
  while(*iptr) {
/*
 * Check for special characters.
 */
    switch(*iptr) {
    case '^':        /* A control character specification */
/*
 * Convert the caret expression into the corresponding control
 * character unless no character follows the caret, in which case
 * record a literal caret.
 */
      if(iptr[1]) {
/*
 * Get the next, possibly escaped, character.
 */
	if(iptr[1] == '\\') {
	  c = _kt_backslash_escape(iptr+2, &iptr);
	} else {
	  c = iptr[1];
	  iptr += 2;
	};
/*
 * Convert the character to a control character.
 */
	*optr++ = MAKE_CTRL(c);
      } else {
	*optr++ = *iptr++;
      };
      break;
/*
 * A backslash-escaped character?
 */
    case '\\':
/*
 * Convert the escape sequence to a binary character.
 */
      *optr++ = _kt_backslash_escape(iptr+1, &iptr);
      break;
/*
 * Possibly an emacs-style meta character?
 */
    case 'M':
      if(_kt_is_emacs_meta(iptr)) {
	*optr++ = GL_ESC_CHAR;
	iptr += 2;
      } else {
	*optr++ = *iptr++;
      };
      break;
/*
 * Possibly an emacs-style control character specification?
 */
    case 'C':
      if(_kt_is_emacs_ctrl(iptr)) {
	*optr++ = MAKE_CTRL(iptr[2]);
	iptr += 3;
      } else {
	*optr++ = *iptr++;
      };
      break;
    default:

/*
 * Convert embedded meta characters into an escape character followed
 * by the meta-unmodified character.
 */
      if(IS_META_CHAR(*iptr)) {
	*optr++ = GL_ESC_CHAR;
	*optr++ = META_TO_CHAR(*iptr);
	iptr++;
/*
 * To allow keysequences that start with printable characters to
 * be distinguished from the cursor-key keywords, prepend a backslash
 * to the former. This same operation is performed in gl_interpret_char()
 * before looking up a keysequence that starts with a printable character.
 */
      } else if(iptr==keyseq && !IS_CTRL_CHAR(*iptr) &&
		strcmp(keyseq, "up") != 0 && strcmp(keyseq, "down") != 0 &&
		strcmp(keyseq, "left") != 0 && strcmp(keyseq, "right") != 0) {
	*optr++ = '\\';
	*optr++ = *iptr++;
      } else {
	*optr++ = *iptr++;
      };
    };
  };
/*
 * How many characters were placed in the output array?
 */
  *nc = optr - binary;
  return 0;
}

/*.......................................................................
 * Add, remove or modify an action.
 *
 * Input:
 *  kt     KeyTab *  The key-binding table.
 *  action   char *  The name of the action.
 *  fn    KtKeyFn *  The function that implements the action, or NULL
 *                   to remove an existing action.
 *  data     void *  A pointer to arbitrary callback data to pass to the
 *                   action function whenever it is called.
 * Output:
 *  return    int    0 - OK.
 *                   1 - Error.
 */
int _kt_set_action(KeyTab *kt, const char *action, KtKeyFn *fn, void *data)
{
  Symbol *sym;   /* The symbol table entry of the action */
/*
 * Check the arguments.
 */
  if(!kt || !action) {
    errno = EINVAL;
    if(kt)
      _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
    return 1;
  };
/*
 * If no function was provided, delete an existing action.
 */
  if(!fn) {
    sym = _del_HashSymbol(kt->actions, action);
    return 0;
  };
/*
 * If the action already exists, replace its action function.
 */
  sym = _find_HashSymbol(kt->actions, action);
  if(sym) {
    sym->fn = (void (*)(void))fn;
    sym->data = data;
    return 0;
  };
/*
 * Add a new action.
 */
  if(!_new_HashSymbol(kt->actions, action, 0, (void (*)(void))fn, data, 0)) {
    _err_record_msg(kt->err, "Insufficient memory to record key-binding action",
		    END_ERR_MSG);
    return 1;
  };
  return 0;
}

/*.......................................................................
 * Compare two strings of specified length which may contain embedded
 * ascii NUL's.
 *
 * Input:
 *  s1       char *  The first of the strings to be compared.
 *  n1        int    The length of the string in s1.
 *  s2       char *  The second of the strings to be compared.
 *  n2        int    The length of the string in s2.
 * Output:
 *  return    int    < 0 if(s1 < s2)
 *                     0 if(s1 == s2)
 *                   > 0 if(s1 > s2)
 */
static int _kt_compare_strings(const char *s1, int n1, const char *s2, int n2)
{
  int i;
/*
 * Find the first character where the two strings differ.
 */
  for(i=0; i<n1 && i<n2 && s1[i]==s2[i]; i++)
    ;
/*
 * Did we hit the end of either string before finding a difference?
 */
  if(i==n1 || i==n2) {
    if(n1 == n2)
      return 0;
    else if(n1==i)
      return -1;
    else
      return 1;
  };
/*
 * Compare the two characters that differed to determine which
 * string is greatest.
 */
  return s1[i] - s2[i];
}

/*.......................................................................
 * Assign a given action function to a binding table entry.
 *
 * Input:
 *  sym       KeySym *  The binding table entry to be modified.
 *  binder  KtBinder    The source of the binding.
 *  keyfn    KtKeyFn *  The action function.
 *  data        void *  A pointer to arbitrary callback data to pass to
 *                      the action function whenever it is called.
 */
static void _kt_assign_action(KeySym *sym, KtBinder binder, KtKeyFn *keyfn,
			      void *data)
{
  KtAction *action;   /* An action function/data pair */
  int i;
/*
 * Unknown binding source?
 */
  if(binder < 0 || binder >= KTB_NBIND)
    return;
/*
 * Record the action according to its source.
 */
  action = sym->actions + binder;
  action->fn = keyfn;
  action->data = data;
/*
 * Find the highest priority binding source that has supplied an
 * action. Note that the actions[] array is ordered in order of
 * descreasing priority, so the first entry that contains a function
 * is the one to use.
 */
  for(i=0; i<KTB_NBIND && !sym->actions[i].fn; i++)
    ;
/*
 * Record the index of this action for use during lookups.
 */
  sym->binder = i < KTB_NBIND ? i : -1;
  return;
}

/*.......................................................................
 * Remove all key bindings that came from a specified source.
 *
 * Input:
 *  kt        KeyTab *  The table of key bindings.
 *  binder  KtBinder    The source of the bindings to be cleared.
 */
void _kt_clear_bindings(KeyTab *kt, KtBinder binder)
{
  int oldkey;   /* The index of a key in the original binding table */
  int newkey;   /* The index of a key in the updated binding table */
/*
 * If there is no table, then no bindings exist to be deleted.
 */
  if(!kt)
    return;
/*
 * Clear bindings of the given source.
 */
  for(oldkey=0; oldkey<kt->nkey; oldkey++)
    _kt_assign_action(kt->table + oldkey, binder, 0, NULL);
/*
 * Delete entries that now don't have a binding from any source.
 */
  newkey = 0;
  for(oldkey=0; oldkey<kt->nkey; oldkey++) {
    KeySym *sym = kt->table + oldkey;
    if(sym->binder < 0) {
      _del_StringMemString(kt->smem, sym->keyseq);
    } else {
      if(oldkey != newkey)
	kt->table[newkey] = *sym;
      newkey++;
    };
  };
/*
 * Record the number of keys that were kept.
 */
  kt->nkey = newkey;
  return;
}

/*.......................................................................
 * Translate a backslash escape sequence to a binary character.
 *
 * Input:
 *  string  const char *   The characters that follow the backslash.
 * Input/Output:
 *  endp    const char **  If endp!=NULL, on return *endp will be made to
 *                         point to the character in string[] which follows
 *                         the escape sequence.
 * Output:
 *  return        char     The binary character.
 */
static char _kt_backslash_escape(const char *string, const char **endp)
{
  char c;  /* The output character */
/*
 * Is the backslash followed by one or more octal digits?
 */
  switch(*string) {
  case '0': case '1': case '2': case '3':
  case '4': case '5': case '6': case '7':
    c = strtol(string, (char **)&string, 8);
    break;
  case 'a':
    c = '\a';
    string++;
    break;
  case 'b':
    c = '\b';
    string++;
    break;
  case 'e': case 'E': /* Escape */
    c = GL_ESC_CHAR;
    string++;
    break;
  case 'f':
    c = '\f';
    string++;
    break;
  case 'n':
    c = '\n';
    string++;
    break;
  case 'r':
    c = '\r';
    string++;
    break;
  case 't':
    c = '\t';
    string++;
    break;
  case 'v':
    c = '\v';
    string++;
    break;
  case '\0':
    c = '\\';
    break;
  default:
    c = *string++;
    break;
  };
/*
 * Report the character which follows the escape sequence.
 */
  if(endp)
    *endp = string;
  return c;
}

/*.......................................................................
 * Return non-zero if the next two characters are M- and a third character
 * follows. Otherwise return 0.
 *
 * Input:
 *  string   const char *  The sub-string to scan.
 * Output:
 *  return          int    1 - The next two characters are M- and these
 *                             are followed by at least one character.
 *                         0 - The next two characters aren't M- or no
 *                             character follows a M- pair.
 */
static int _kt_is_emacs_meta(const char *string)
{
  return *string++ == 'M' && *string++ == '-' && *string;
}

/*.......................................................................
 * Return non-zero if the next two characters are C- and a third character
 * follows. Otherwise return 0.
 *
 * Input:
 *  string   const char *  The sub-string to scan.
 * Output:
 *  return          int    1 - The next two characters are C- and these
 *                             are followed by at least one character.
 *                         0 - The next two characters aren't C- or no
 *                             character follows a C- pair.
 */
static int _kt_is_emacs_ctrl(const char *string)
{
  return *string++ == 'C' && *string++ == '-' && *string;
}

/*.......................................................................
 * Merge an array of bindings with existing bindings.
 *
 * Input:
 *  kt                    KeyTab *  The table of key bindings.
 *  binder              KtBinder    The source of the bindings.
 *  bindings  const KtKeyBinding *  The array of bindings.
 *  n                        int    The number of bindings in bindings[].
 * Output:
 *  return                   int    0 - OK.
 *                                  1 - Error.
 */
int _kt_add_bindings(KeyTab *kt, KtBinder binder, const KtKeyBinding *bindings,
		     unsigned n)
{
  int i;
/*
 * Check the arguments.
 */
  if(!kt || !bindings) {
    errno = EINVAL;
    if(kt)
      _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
    return 1;
  };
/*
 * Install the array of bindings.
 */
  for(i=0; i<n; i++) {
    if(_kt_set_keybinding(kt, binder, bindings[i].keyseq, bindings[i].action))
      return 1;
  };
  return 0;
}

/*.......................................................................
 * Lookup the function that implements a given action.
 *
 * Input:
 *  kt          KeyTab *  The table of key bindings.
 *  action  const char *  The name of the action to look up.
 * Input/Output:
 *  fn         KtKeyFn ** If the action is found, the function that
 *                        implements it will be assigned to *fn. Note
 *                        that fn can be NULL.
 *  data          void ** If the action is found, the callback data
 *                        associated with the action function, will be
 *                        assigned to *data. Note that data can be NULL.
 * Output:
 *  return         int    0 - OK.
 *                        1 - Action not found.
 */
int _kt_lookup_action(KeyTab *kt, const char *action,
		      KtKeyFn **fn, void **data)
{
  Symbol *sym;   /* The symbol table entry of the action */
/*
 * Check the arguments.
 */
  if(!kt || !action) {
    errno = EINVAL;
    if(kt)
      _err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
    return 1;
  };
/*
 * Lookup the symbol table entry of the action.
 */
  sym = _find_HashSymbol(kt->actions, action);
  if(!sym)
    return 1;
/*
 * Return the function and ccallback data associated with the action.
 */
  if(fn)
    *fn = (KtKeyFn *) sym->fn;
  if(data)
    *data = sym->data;
  return 0;
}

/*.......................................................................
 * Return extra information (ie. in addition to that provided by errno)
 * about the last error to occur in any of the public functions of this
 * module.
 *
 * Input:
 *  kt          KeyTab *  The table of key bindings.
 * Output:
 *  return  const char *  A pointer to the internal buffer in which
 *                        the error message is temporarily stored.
 */
const char *_kt_last_error(KeyTab *kt)
{
  return kt ? _err_get_msg(kt->err) : "NULL KeyTab argument";
}