Lugaru's Epsilon
Programmer's
Editor

Context:
Epsilon User's Manual and Reference
   Primitives and EEL Subroutines
      . . .
      Control Primitives
         Control Flow
         Character Types
         Examining Strings
         . . .
         Help Subroutines
      Input Primitives
         . . .
         Dialogs
         The Main Loop
         Binding Primitives
      Defining Language Modes
         Language-specific Subroutines

Previous   Up    Next
The Main Loop  Primitives and EEL Subroutines   Defining Language Modes


Epsilon User's Manual and Reference > Primitives and EEL Subroutines > Input Primitives >

Binding Primitives

Epsilon lets each buffer have a different set of key bindings appropriate to editing the type of text in that buffer. For instance, while in a buffer with EEL source code, a certain key could indent the current function. The same key might indent a paragraph in a buffer with text.

A key table stores a set of key bindings. A key table acts like a very large array, with one entry for each key on the keyboard. Each entry in the array contains an index into the name table. (See The Name Table.) If the value of a particular entry is negative or zero, it means the key is undefined according to that table.

Since the key codes that index a key table can be very large numbers, with big gaps between entries, Epsilon doesn't actually store key tables as arrays, but they act like arrays in EEL. (Internally, Epsilon stores a key table as a type of sparse array.) The MAXKEYS macro holds a number bigger than the biggest possible key code.

#define key_t      int
set_range(key_t *table, int i, int value, int cnt)
int get_range(key_t *table, int i, int value)
int find_next_entry(key_t *table, int value, int bound)

EEL code that wants to perform some operation on groups of keys can't simply use a loop like for (i = 0; i < MAXKEYS; i++) to do so; that would be too slow. Instead, there are primitives specifically designed for such purposes.

To set a range of key codes entries to a particular value, use the set_range( ) primitive. It sets the cnt entries starting at index i in the key table table to the specified value. The key_t type represents the type of a key table entry.

To find the length of a run of identical values in a key table array, use the get_range( ) primitive. It returns the number of entries, starting at index i, with the specified value. For instance, if k is a key table, and k[5], k[6], and k[7] equal 123, but k[8] equals 456, then get_range(k, 5, 123) would return 3, and get_range(k, 8, 123) would return 0.

The find_next_entry( ) primitive searches for the index of the next member of the key table array k with the specified value. It starts looking at position bound in the array, skipping past index entries less than or equal to bound. It returns -1 if there are no more entries set to the specified value.

The EEL header file oldkeys.h provides sample implementations of the above primitives. If the number of possible keys were much smaller, you could use them instead of the above primitives. If you write EEL source code that must also work in older versions of Epsilon, you can include that file. Your EEL source code will use the definitions in oldkeys.h when you compile it in old versions of Epsilon; under Epsilon 12 or later, it will use the above primitives.

buffer short *mode_keys;
short *root_keys;
keytable reg_tab, c_tab;

Epsilon uses two key tables in its search for the binding of a key. First it looks in the key table referenced by the buffer-specific variable mode_keys. If the entry for the key is negative, Epsilon considers the command unbound and signals an error. If the entry for the key is 0, as it usually is, Epsilon uses the entry in the key table referenced by the variable root_keys instead. If the resulting entry is zero or negative, Epsilon considers the key unbound. If it finds an entry for the key that is a positive number, Epsilon considers that number the key's binding. The number is actually an index into the name table.

Most entries in a key table refer to commands, but an entry may also refer to a subroutine (if it takes no arguments), to a keyboard macro, or to another key table. For example, the entry for Ctrl-X in the default key table refers to a key table named cx_tab, which contains the Ctrl-X commands. The entry for the find-file command bound to Ctrl-X Ctrl-F appears in the cx_tab key table.

Normally in Epsilon the root_keys variable points to the reg_tab array. The mode_keys variable points to one of the many mode-specific tables, such as c_tab for C mode.

int new_table(char *name)
int make_anon_keytable()            /* control.e */
short *index_table(int index)

Key tables are usually defined with the keytable keyword as described in Key Tables. If a key table's name is not known when the routine is compiled, the new_table( ) primitive can be used. It makes a new key table with the given name. All entries in it are 0.

The make_anon_keytable( ) subroutine defined in control.e calls new_table( ), first choosing an unused name for the table. The index_table( ) function takes a name table index and retrieves the key table it refers to.

fix_key_table(short *ftab, int fval, short *ttab, int tval)
copy_key_table(short *from, short *to)
set_list_keys(short *tab)

The fix_key_table( ) subroutine copies selected key table information from one key table to another. For each key in ftab bound to the function fval, the subroutine binds that key in ttab to the function tval. The copy_key_table( ) subroutine copies an entire key table. The set_list_keys( ) subroutine sets the "n" and "p" keys to move up or down by lines.

do_topkey()
run_topkey()

When Epsilon is ready to execute a key in its main loop, it calls the primitive do_topkey( ). This primitive searches the key tables for the command bound to the current key, as described above. When it has found the name table index, it calls do_command( ), below, to interpret the command.

The run_topkey( ) subroutine provides a wrapper around do_topkey( ) that resets iter and similar variables like the main loop does. An EEL subroutine that wants to retrieve keys itself and execute them as if the user typed them at command level can call this subroutine.

do_command(int index)
user short last_index;

The do_command( ) primitive executes the command or other item with the supplied name table index. If the index is invalid, then the quick_abort( ) primitive is called. Otherwise, the index is copied to the last_index variable, so the help system can find the name of the current command (among other uses).

If the name table index refers to a command or subroutine, Epsilon calls the function. When it returns, Epsilon checks the iter variable. If it is two or more, Epsilon proceeds to call the same function repeatedly, decrementing iter each time, so that it calls the function a total of iter times. See The Main Loop.

key_t *table_keys;
int table_count;
table_prompt()                  /* control.e */

If the entry in the name table that do_command( ) is to execute contains another table, Epsilon gets another key. First, Epsilon updates the primitive array table_keys. It contains the prefix keys entered so far in the current command, and table_count contains their number. Next, Epsilon calls the EEL subroutine table_prompt( ) if it exists to display a prompt for the new key. The version of this subroutine that's provided with Epsilon uses mention( ), so the message may not appear immediately. Epsilon then calls the EEL subroutine getkey( ) to read a new key and clears the echo area of the prompt. Epsilon then interprets the key just as the do_topkey( ) primitive would, but using the new key table. If both mode_keys and root_keys provided a table as the entry for the first key, the values from each are used as the new mode and root key tables.

do_again()

The do_again( ) primitive reinterprets a key using the same pair of mode and root tables that were used previously. The value in the variable key may, of course, be different. Epsilon uses this primitive in commands such as alt-prefix.

Epsilon handles EEL subroutines without parameters in the name table in the same way as commands, as described above. If the entry is for a keyboard macro, the only other legal name table entry, Epsilon goes into a recursive edit level and begins processing the keys in the macro. It saves the macro internally so that future requests for a key will return characters from the macro, as described in Keys. It also saves the value of iter, so the macro will iterate properly. When the macro runs out of keys, Epsilon automatically exits the recursive edit level, and returns from the call to do_again( ). (When macro-runs-immediately is nonzero, running a macro doesn't enter a recursive edit level, but returns immediately. Future key requests will still come from the macro until it's exhausted.)

short ignore_kbd_macro;

Epsilon provides a way for a keyboard macro to suspend itself and get input from the user, then continue. Set the ignore_kbd_macro variable nonzero to get keyboard input even when a macro is running. The pause-macro command uses this variable.

short *ask_key(char *pr, char *keyname, int flags)
int key_binding[30];      // ask_key() puts key info here

The ask_key( ) subroutine defined in basic.e duplicates the logic of the main loop in getting the sequence of keys that make up a command. However, it prompts for the sequence and doesn't run the command at the end. Commands like bind-to-key that ask for a key and accept a sequence of key table keys use it.

The ask_key( ) subroutine returns a pointer to the entry in the key table that was finally reached. The value pointed to is the name table index of the command the key sequence invokes.

This subroutine stores the key sequence in the keyname parameter in text form (as "Ctrl-X f", for example). It also copies the key sequence into the global variable key_binding. The key sequence is in macro format, so in the example of Ctrl-X f, key_binding[1] would hold CTRL('X'), key_binding[2] would hold 'f', and key_binding[0] would hold 3, the total number of entries in the array.

If you pass the 1 flag in flags and the user presses a key like <Backspace> with both a generic and a specific interpretation, Epsilon asks the user which one he wants. Without this flag, Epsilon assumes the specific key is meant.

If you pass the 2 flag and the user presses a key that normally shouldn't be rebound because it self-inserts (such as letter keys or <Enter>), the subroutine asks for confirmation.

full_getkey(char *pr, int code)        /* basic.e */

    /* for full_getkey() */
#define ALTIFY_KEY      1
#define CTRLIFY_KEY     2

The full_getkey( ) subroutine defined in basic.e gets a single key from the keyboard, but recognizes the prefix keys <Esc> and Ctrl-^. The ask_key( ) subroutine uses it, as well as the commands bound to the prefix keys above. It takes a prompt to display and a bit pattern (from eel.h) to make it act as if certain of the above keys had already been typed. For example, the ctrl-prefix command calls this subroutine with the value CTRLIFY_KEY. It leaves the key that results in the key primitive.

name_macro(char *name, key_t *keys)

Epsilon has no internal mechanism for capturing keyboard keys to build a macro (this is done in the getkey( ) subroutine defined in control.e), but once a macro has been built Epsilon can name it and make it accessible with the name_macro( ) function. It takes the name of the macro to create, and the sequence of keys making up the macro in an array of short ints. This array is in the same format that get_keycode( ) uses. That is, the first element of the array contains the number of valid elements in the array (including the first one). The actual keys in the macro follow. The name_macro( ) primitive makes a copy of the macro it is given, so the array can be reused once the macro has been defined.

key_t *get_macro(int index)

The get_macro( ) primitive can retrieve the keys in a defined keyboard macro. It takes the name table index of a macro, and returns a pointer to the array containing the macro, in the same format as name_macro( ).

bind_universally(int k, int f)

The bind_universally( ) subroutine can be useful to bind a function to a key in all modes. When a key has a generic version, a mode that binds the generic version will override a global definition for the non-generic version of the key. For instance, the Tab key has a generic version, Ctrl-I. By default, each time you press Tab, Epsilon converts it to Ctrl-I and uses the current mode's binding for Ctrl-I, unless the current mode has its own definition for the Tab key too. If you want Ctrl-I to behave in a mode-specific manner, but force Tab to always run the same command regardless of the mode, you can call this function. It binds the key k to the function f in all key tables.

int list_bindings(int start, short *modetable,
                  short *roottable, int find)

The list_bindings( ) primitive quickly steps through a pair of key tables, looking for entries that have a certain name table index. It takes mode and root key tables, the name table index to find, and either -1 to start at the beginning of the key tables, or the value it returned on a previous call. It returns the index into the key table, or -1 if there are no more matching entries. For each position in the tables, Epsilon looks at the value in the mode key table, unless it is zero. In that case, it uses the root table.

In addition to the matches, list_bindings( ) also stops on each name table index corresponding to a key table, since these must normally be searched also. For example, the following file defines a command that counts the number of separate bindings of any command.

#include "eel.h"

command count_bindings()
{
    char cmd[80];

    get_cmd(cmd, "Count bindings of command", "");
    if (*cmd)
        say("The %s command has %d bindings", cmd,
            find_some(mode_keys,
                         root_keys, find_index(cmd)));
}

        /* count bindings to index in table */
int find_some(modetable, roottable, index)
        short *modetable, *roottable;
{
    int i, total = 0, found;

    i = list_bindings(-1, modetable, roottable, index);
    while (i != -1) {

        found = (modetable[i]
                        ? modetable[i] : roottable[i]);
        if (found == index)
            total++;
        else
            total += find_some(index_table(found),
                        index_table(found), index);

        i = list_bindings(i, modetable, roottable, index);
    }
    return total;
}



Previous   Up    Next
The Main Loop  Primitives and EEL Subroutines   Defining Language Modes


Lugaru Copyright (C) 1984, 2020 by Lugaru Software Ltd. All rights reserved.