GNU Readline is an easy-to-use library for autocompleting using Tab key and bash like history using up/down array keys for interactive programs with command line interface.
In this post, we will build an interactive program in C++ that uses GNU Readline library.
The program will take input from the user a number name and output the corresponding number.
For, e.g. if the user enters one
, the program will output 1
.
The program will prompt for input again and again until the user quits by pressing Ctrl+D
.
First, download and install the GNU Readline library from here. Ubuntu users can easily download from apt using the following command.
$ sudo apt install libreadline-dev
The complete example is given at the end of the post.
First, include the Readline header files that will be used by the program.
#include <readline/history.h>
#include <readline/readline.h>
In the main function of the program, set the pointer rl_attempted_completion_function
to the function that will generate possible matches corresponding to the partial input entered by the user.
This pointer is declared in the Readline header file which will be called by other functions.
int main() {
rl_attempted_completion_function = command_completion;
return 0;
}
Define the command_completion
function. It makes a call to the rl_completion_matches
function which calls a command_generator
function (which will be defined later) repeatedly until the command_generator
function returns a null pointer. The command_generator
function returns a match one at a time. The rl_completion_matches
function returns the array of all those matches.
char **command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 1;
return rl_completion_matches(text, command_generator);
}
Now, define the commad_generator
function. The command_generator
function takes the partial input entered by the user and a state as arguments. The state is zero the first time the command_generator
function is called for a partial input and non-zero otherwise. In our example, we have a static variable match_index
which is reset to zero when the state is zero and incremented when the state is non-zero.
We also have static variable matches
containing all the matches generated when the function was called the first time on the partial input. In subsequent calls, the string whose index is match_index
in matches
vector is returned. The matches are generated by comparing the partial input with the words in the vocabulary
.
std::vector<std::string> vocabulory{"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"};
char *command_generator(const char *text, int state) {
static std::vector<std::string> matches;
static size_t match_index = 0;
if (state == 0) {
matches.clear();
match_index = 0;
std::string textstr(text);
for (auto word : vocabulory) {
if (word.size() >= textstr.size() &&
word.compare(0, textstr.size(), textstr) == 0) {
matches.push_back(word);
}
}
}
if (match_index >= matches.size()) {
return nullptr;
} else {
return strdup(matches[match_index++].c_str());
}
}
At last, complete the main function. The main function calls the readline
function which takes input as the prompt to be displayed on the stdout. The readline
returns a null pointer if Ctrl+D
was entered and the main function exits.
The add_history
function adds the command to the history buffer which will be accessed by the user using up/down arrow keys similar to bash.
int main() {
rl_attempted_completion_function = command_completion;
char *buf;
std::string cmd;
while ((buf = readline("> ")) != nullptr) {
cmd = std::string(buf);
if (cmd.size() > 0) {
add_history(buf);
}
free(buf);
std::stringstream scmd(cmd);
scmd >> cmd;
std::vector<std::string>::iterator itr =
std::find(vocabulory.begin(), vocabulory.end(), cmd);
if (itr != vocabulory.end()) {
std::cout << cmd << ": " << std::distance(vocabulory.begin(), itr)
<< std::endl;
} else {
std::cout << "Invalid number name" << std::endl;
}
}
std::cout << std::endl;
return 0;
}
A call to rl_bind_key('\t', rl_insert);
in the main function will disable the tab completion feature. In the complete example below, we call this function if the user has specified -d
flag as command line argument.
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <readline/history.h>
#include <readline/readline.h>
std::vector<std::string> vocabulory{"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"};
char *command_generator(const char *text, int state) {
static std::vector<std::string> matches;
static size_t match_index = 0;
if (state == 0) {
matches.clear();
match_index = 0;
std::string textstr(text);
for (auto word : vocabulory) {
if (word.size() >= textstr.size() &&
word.compare(0, textstr.size(), textstr) == 0) {
matches.push_back(word);
}
}
}
if (match_index >= matches.size()) {
return nullptr;
} else {
return strdup(matches[match_index++].c_str());
}
}
char **command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 1;
return rl_completion_matches(text, command_generator);
}
int main(int argc, char **argv) {
if (argc > 1 && std::string(argv[1]) == "-d") {
rl_bind_key('\t', rl_insert);
}
rl_attempted_completion_function = command_completion;
char *buf;
std::string cmd;
while ((buf = readline("> ")) != nullptr) {
cmd = std::string(buf);
if (cmd.size() > 0) {
add_history(buf);
}
free(buf);
std::stringstream scmd(cmd);
scmd >> cmd;
std::vector<std::string>::iterator itr =
std::find(vocabulory.begin(), vocabulory.end(), cmd);
if (itr != vocabulory.end()) {
std::cout << cmd << ": " << std::distance(vocabulory.begin(), itr)
<< std::endl;
} else {
std::cout << "Invalid number name" << std::endl;
}
}
std::cout << std::endl;
return 0;
}
Compile the above program with -lreadline
flag to link the program with Readline.
$ g++ -o program file.cpp -lreadline
Run using the below command.
$ ./program
$ man readline