0

I downloaded the Vim source file (vim74src.zip, for Win32 Console) and tried to find out the way how Vim Editor know the key I pressed? I've searched some keywords like getch, and ReadConsoleInput etc. But, eventually, I failed.

Does anyone know that how does Vim Editor know the key pressed or which function is used to get the key signals pressed by users?

Kevin Dong
  • 5,001
  • 9
  • 29
  • 62
  • 1
    vim is an editor, which means it is event driven. If you look in the source, you'll find a "main-loop" which polls some sort of event source which produces events like "KeyPressDown" and "MouseMovement". On gvim on linux, the user inputs are processed by X and somehow ends up in gvim's event queue via gtk. For console vim, it is probably doing some dark terminal magic to get at the user keystrokes. – user3243135 Jan 16 '14 at 13:38
  • 2
    Why do you need to know? Do you plan to implement something similar yourself? As Vim is multi-platform (and quite old), this might not be the easiest example to learn from. – Ingo Karkat Jan 16 '14 at 13:51
  • @IngoKarkat Yep :-) I'm trying to make a **lite** console text editor. This is my project on [Github](https://github.com/kevin-dong-nai-jia/Console-Text-Editor). It uses `getch()`, but some signals returned by it might overlap. So, I want to find out how does Vim Editor implement. – Kevin Dong Jan 16 '14 at 14:00

2 Answers2

3

Lets take a look at os_win32.c. The first that we see is:

/*
 * os_win32.c
 *
 * Used for both the console version and the Win32 GUI.  A lot of code is for
 * the console version only, so there is a lot of "#ifndef FEAT_GUI_W32".

So for console-only win32 code we should look under this #ifndef.
I'll post here stripped versions of functions - for keyboard input only, without extra handling (mouse, IME, etc).
Now we should find a function for input handling:

/*
 * mch_inchar(): low-level input function.
 * Get one or more characters from the keyboard or the mouse.
 * If time == 0, do not wait for characters.
 * If time == n, wait a short time for characters.
 * If time == -1, wait forever for characters.
 * Returns the number of characters read into buf.
 */
    int
mch_inchar(
    char_u  *buf,
    int     maxlen,
    long    time,
    int     tb_change_cnt)
{

    int     len;
    int     c;
#define TYPEAHEADLEN 20
    static char_u   typeahead[TYPEAHEADLEN];    /* previously typed bytes. */
    static int      typeaheadlen = 0;

    /* First use any typeahead that was kept because "buf" was too small. */
    if (typeaheadlen > 0)
        goto theend;

    if (time >= 0)
    {
        if (!WaitForChar(time))     /* no character available */
            return 0;
    }

    /*
     * Try to read as many characters as there are, until the buffer is full.
     */

    /* we will get at least one key. Get more if they are available. */

    /* Keep looping until there is something in the typeahead buffer and more
     * to get and still room in the buffer (up to two bytes for a char and
     * three bytes for a modifier). */
    while ((typeaheadlen == 0 || WaitForChar(0L))
           && typeaheadlen + 5 <= TYPEAHEADLEN)
    {
        char_u    ch2 = NUL;
        int modifiers = 0;

        c = tgetch(&modifiers, &ch2);

        /* A key may have one or two bytes. */
        typeahead[typeaheadlen] = c;
        if (ch2 != NUL)
        {
            typeahead[typeaheadlen + 1] = ch2;
            ++n;
        }

        if (modifiers != 0)
        {
            /* Prepend modifiers to the character. */
            mch_memmove(typeahead + typeaheadlen + 3,
                         typeahead + typeaheadlen, n);
            typeahead[typeaheadlen++] = K_SPECIAL;
            typeahead[typeaheadlen++] = (char_u)KS_MODIFIER;
            typeahead[typeaheadlen++] =  modifiers;
        }

        typeaheadlen += n;
    }

theend:

    /* Move typeahead to "buf", as much as fits. */
    len = 0;
    while (len < maxlen && typeaheadlen > 0)
    {
        buf[len++] = typeahead[0];
        mch_memmove(typeahead, typeahead + 1, --typeaheadlen);
    }
    return len;

}

The code is throughly commented so it is pretty self-explanatory. The two important functions there are WaitForChar and tgetch. Continuing investigate:

/*
 * Wait until console input from keyboard or mouse is available,
 * or the time is up.
 * Return TRUE if something is available FALSE if not.
 */
    static int
WaitForChar(long msec)
{
    DWORD           dwNow = 0, dwEndTime = 0;
    INPUT_RECORD    ir;
    DWORD           cRecords;
    char_u          ch, ch2;

    if (msec > 0)
        /* Wait until the specified time has elapsed. */
        dwEndTime = GetTickCount() + msec;
    else if (msec < 0)
        /* Wait forever. */
        dwEndTime = INFINITE;

    /* We need to loop until the end of the time period, because
     * we might get multiple unusable mouse events in that time.
     */
    for (;;)
    {

        if (msec > 0)
        {
            /* If the specified wait time has passed, return.  Beware that
             * GetTickCount() may wrap around (overflow). */
            dwNow = GetTickCount();
            if ((int)(dwNow - dwEndTime) >= 0)
                break;
        }
        if (msec != 0)
        {
            DWORD dwWaitTime = dwEndTime - dwNow;

            if (WaitForSingleObject(g_hConIn, dwWaitTime) != WAIT_OBJECT_0)
                    continue;
        }

        cRecords = 0;
        PeekConsoleInput(g_hConIn, &ir, 1, &cRecords);

        if (cRecords > 0)
            if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown)
                if (decode_key_event(&ir.Event.KeyEvent, &ch, &ch2, NULL, FALSE))
                    return TRUE;

        else if (msec == 0)
            break;
    }

    return FALSE;
}
  • GetTickCount and WaitForSingleObject are kind of off-topic here but you can read about them on the links.
  • PeekConsoleInput reads data from the specified console input buffer without removing it from the buffer.
  • g_hConIn is a console input handler, global variable, its initialization will be discussed later.

The next function that was used:

/*
 * Get a keystroke or a mouse event
 */
    static char_u
tgetch(int *pmodifiers, char_u *pch2)
{
    char_u ch;

    for (;;)
    {
        INPUT_RECORD ir;
        DWORD cRecords = 0;

        if (ReadConsoleInput(g_hConIn, &ir, 1, &cRecords) == 0)
        {
            if (did_create_conin)
                read_error_exit();
            create_conin();
            continue;
        }

        if (ir.EventType == KEY_EVENT)
            if (decode_key_event(&ir.Event.KeyEvent, &ch, pch2, pmodifiers, TRUE))
                return ch;
    }
}
  • ReadConsoleInput reads data from a console input buffer and removes it from the buffer.
  • decode_key_event is not much of an interest, it fills arguments by converting chars and modifiers from WinAPI to more usable format.
  • read_error_exit is application exit, defined in ui.c

Now about g_hConIn variable and conin.

This is how g_hConIn defined in global scope:

/* Win32 Console handles for input and output */
static HANDLE g_hConIn  = INVALID_HANDLE_VALUE;

This is how it's initialized:

/*
 * non-GUI version of mch_init().
 */
    void
mch_init(void)
{
    /* Obtain handles for the standard Console I/O devices */
    if (read_cmd_fd == 0)
        g_hConIn =  GetStdHandle(STD_INPUT_HANDLE);
    else
        create_conin();
}
  • GetStdHandle retrieves a handle to the specified standard device (standard input, standard output, or standard error).
  • STD_INPUT_HANDLE The standard input device. Initially, this is the console input buffer, CONIN$.

And the last part:

static int did_create_conin = FALSE;

/*
 * Create the console input.  Used when reading stdin doesn't work.
 */
    static void
create_conin(void)
{
    g_hConIn = CreateFile("CONIN$", GENERIC_READ|GENERIC_WRITE,
                          FILE_SHARE_READ|FILE_SHARE_WRITE,
                          (LPSECURITY_ATTRIBUTES) NULL,
                          OPEN_EXISTING, 0, (HANDLE)NULL);
    did_create_conin = TRUE;
}

But why do we need other CONIN$ handling if it is already done for us by GetStdHandle? The point is that stdin may be redirected whereas CONIN$ can't (See What does CreateFile("CONIN$" ..) do?)

Community
  • 1
  • 1
user
  • 23,260
  • 9
  • 113
  • 101
0

For the windows version of vim (that's what you are looking for, I guess), you might want to take a look at the os_win32.c file. It looks like the function decode_mouse_event processes the mouse events.

dirac3000
  • 746
  • 6
  • 15