GNU readline: Implement Custom Auto-complete

GNU readline implement filename auto-complete by default, it will list all the files in the current directory. We can disable it by binds our TAB key to some other operation. In previous post, I simply abort the operation to ignore users hitting TABs.

Auto-complete are useful if only we can customize it. Well, readline allows us do it by assign our own callback functions. First of all, you may want to read up the manual from HERE. It does provides a c sample codes as well but I find it too complicated, here I provide a simplified version that can be compiled under c++.

#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>

static char** my_completion(const char*, int ,int);
char* my_generator(const char*,int);
char * dupstr (char*);
void *xmalloc (int);

char* cmd [] ={ "hello", "world", "hell" ,"word", "quit", " " };

int main()
{
    char *buf;

    rl_attempted_completion_function = my_completion;

    while((buf = readline("\n &gt;&gt; "))!=NULL) {
        //enable auto-complete
        rl_bind_key('\t',rl_complete);

        printf("cmd [%s]\n",buf);
        if (strcmp(buf,"quit")==0)
            break;
        if (buf[0]!=0)
            add_history(buf);
    }

    free(buf);

    return 0;
}

static char** my_completion( const char * text , int start,  int end)
{
    char **matches;

    matches = (char **)NULL;

    if (start == 0)
        matches = rl_completion_matches ((char*)text, &my_generator);
    else
        rl_bind_key('\t',rl_abort);

    return (matches);

}

char* my_generator(const char* text, int state)
{
    static int list_index, len;
    char *name;

    if (!state) {
        list_index = 0;
        len = strlen (text);
    }

    while (name = cmd[list_index]) {
        list_index++;

        if (strncmp (name, text, len) == 0)
            return (dupstr(name));
    }

    /* If no names matched, then return NULL. */
    return ((char *)NULL);

}

char * dupstr (char* s) {
  char *r;

  r = (char*) xmalloc ((strlen (s) + 1));
  strcpy (r, s);
  return (r);
}

void * xmalloc (int size)
{
    void *buf;

    buf = malloc (size);
    if (!buf) {
        fprintf (stderr, "Error: Out of memory. Exiting.'n");
        exit (1);
    }

    return buf;
}

I store my ‘keywords’ targeted for auto-completion in an array cmd. Before calling readline(), I must point my completion callback function to rl_attempted_completion_function.

In my_completion(), if the state==1 meaning auto-completing the first word from readline(). If the state > 1 and my_completion() return NULL, filename auto-complete will be trigger automatically, which I find it damn irritating. So when state !=1, I disable it, and enable it back after next readline(). It is a dirty hack to me, I am still searching for a proper way to disable filename completion entirely.

I wrote xmalloc() myself because extern xmalloc doesn’t seems to work while compiling under c++.

To get better understanding of readline auto-complete, please read up the manual carefully and try it out yourself.

At last, hope you enjoy the post.

9 thoughts on “GNU readline: Implement Custom Auto-complete

  1. Pingback: alldevnet.com

  2. Pingback: GNU Readline « progon progoilut

  3. Thank you so much, this really made life easier.

    When I actually constructed this into my program, I ran across a couple problems. (I’m on a mac, using standard C++)

    First is, my compiler complained it did not know what rl_abort was. I searched the web, couldn’t find anything, checked out the readline doc, and they recommend: rl_insert. Tried that, fixed the compile issue.

    After I got the basic program working with autocomplete I integrated this into my application and got a segfault whenever I hit tab.

    This took a while for me to set up gdb and walk through this but I traced it back to the array:
    char* cmd [] ={ “hello”, “world”, “hell” ,”word”, “quit”, ” ” };

    and

    while ((name = cmd[list_index])

    I’m not sure if this was something wrong with my implementation (I copied your code verbatim, even the cmd string), but the while loop overruns the array. I added a return character to the array, identifying this as the end of the array, and checking for this character before continuing. Ex:

    char* cmd [] ={ “hello”, “world”, “hell” ,”word”, “quit”, ” “, “\r” }; and

    while ((name = cmd[list_index]) && strncmp(cmd[list_index],”\r”, 1) != 0)

    Thanks again, everything is working great now.

  4. to prevent a whitespace at the end of the completed command, just set
    rl_completion_append_character = '';

    okki

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>