Suppress printing a new prompt when pressing tab with Readline

392 views Asked by At

When using the auto completion with the Readline library in C, the prompt is reprinted when typing the tab key twice:

(prompt) view NAME_OF_F (user presses tab twice)
NAME_OF_FILE1   NAME_OF_FILE2   (suggestions by Readline)
(prompt) view NAME_OF_F 

I'd like to suppress the reprinting of the prompt on the 3rd line by keeping the first line printed with the suggestions below it like such:

(prompt) view NAME_OF_F (user presses tab twice)
NAME_OF_FILE1   NAME_OF_FILE2   (suggestions by Readline)

I'd like the cursor back at the end of the first line that has the prompt.

Compiled with gcc -Wall -O0 -ggdb -fno-builtin rline.c -o rline -lreadline -ltermcap.

Here's a code sample:

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

int execute_line(char *line);
void initialize_readline();
static char **fileman_completion(char *text, int start, int end);
static char *command_generator(char *text, int state);

char *command[] = { "view", "quit", (char *)NULL };

int done; /* When non-zero, this global means the user is done using this program. */

int main(int argc, char **argv)
{
    char *line;
    initialize_readline();  /* Bind our completer. */
    for ( ; done == 0; ) {
        line = readline("> ");

        if (!line) 
            break;

        if (*line) 
            execute_line(line);
        free(line);
    }
    return 0;
}

/* String to pass to system().  This is for the VIEW command. */
static char syscom[1024];

int execute_line(char *line)
{
    int i = 0;
    char *word;
    /* Isolate the command word. */
    while (line[i] && whitespace(line[i]))
        i++;
    word = line + i;

    while (line[i] && !whitespace(line[i])) i++;

    if (line[i]) line[i++] = '\0';

    if (strcmp(word, "quit") == 0) {
        done = 1;
        return 0;
    } else if (strcmp(word, "view")) {
        fprintf(stderr, "%s: Choose only \"view FILE\" or \"quit\" as your command.\n", word);
        return -1;
    }

    /* Get argument to command, if any. */
    while (whitespace(line[i])) i++;

    word = line + i;
    if(!word || !*word) {
        fprintf(stderr, "view: Argument required.\n");
        return -1;
    }
    sprintf(syscom, "more %s", word);
    return system(syscom);
}

void initialize_readline()
{
    rl_readline_name = "rline";
    rl_attempted_completion_function = (rl_completion_func_t *)fileman_completion;
}

static char **fileman_completion(char *text, int start, int end)
{
    if (start == 0)
        return rl_completion_matches(text, (rl_compentry_func_t *)*command_generator);
    return NULL;
}

static char *command_generator(char *text, int state)
{
    static int list_index, len;
    char *name;
    if (!state) {
        list_index = 0;
        len = strlen(text);
    }
    while ((name = command[list_index++]))
        if (strncmp(name, text, len) == 0)
            return strdup(name);
    return NULL;
}

The program only accepts the commands view FILE_NAME to view the contents of a file and quit to exit the program. The example is a shortened version of a sample program found here.

2

There are 2 answers

0
rici On

I don't think that readline has anything like that built in, but it does provide a lot of customisation possibilities if you want to try to write the logic yourself.

You could try writing a custom rl_completion_display_matches_hook to display the completion list. But it's not entirely clear to me how you would restore the cursor position afterwards. I don't think readline has a public interface for either finding or resetting the cursor position. (And, of course, it's possible that the completion list was so big that the original command scrolled off the screen.)

As an alternative, I was able use the hook to print the completion list over top of the current line and then redisplay the prompt after the completion list (although I cheated by assuming that the current input is always just one line). That's not quite what you asked for, but it may be useful for demonstration purposes. I used the following custom match printer:

static void display_matches(char** matches, int len, int max) {
    putp(carriage_return);
    putp(clr_eol);
    putp(cursor_up);
    rl_display_match_list(matches, len, max);
    rl_forced_update_display();
}

I also added the following to the initialisation function:

    rl_completion_display_matches_hook = display_matches;
    setupterm(NULL, 1, (int*)0);
0
tham mes On

Thanks @rici for the inspiration. I got it working with his function with some modifications. In order for this to work properly you need to download the readline library. In the rlprivate.h file from readline, I removed the lines char **lines;, and the line #include "realdine.h" from display.c. Then in your own .c you must have an #include </PATH/TO/display.c>. In that display.c, an #include points to the modified rlprivate.h. All of this so that I can have access to _rl_move_vert(1).

static void display_matches(char** matches, int len, int max)
{
    int saved_point = rl_point;
    char *saved_line = rl_copy_text(0, rl_end);
    rl_save_prompt();
    rl_replace_line("", 0); // Clear the previous text
    putp(cursor_up);
    _rl_move_vert(1);
    rl_display_match_list(matches, len, max);
    putp(cursor_up);
    rl_restore_prompt();
    rl_replace_line(saved_line, 0);
    rl_point = saved_point;
    rl_redisplay();
    putp(cursor_down);
    free(saved_line);
}