// ************************************************************************** /** @class Readline @brief C++ wrapper for GNU's readline library This class is meant as a C++ wrapper around GNU's readline library. Note that because readline uses a global namespace only one instance of this class can exist at a time. Instantiating a second object after a first one was deleted might show unexpected results. When the object is instantiated readline's history is read from a file. At destruction the history in memory is copied back to that file. The history file will be truncated to fMaxLines. By overloading the Readline class the function used for auto-completion can be overwritten. Simple example: \code Readline rl("MyProg"); // will read the history from "MyProg.his" while (1) { string txt = rl.Prompt("prompt> "); if (txt=="quit) break; // ... do something ... rl.AddHistory(txt); } // On destruction the history will be written to the file \endcode Simpler example (you need to implement the Process() function) \code Readline rl("MyProg"); // will read the history from "MyProg.his" rl.Run("prompt> "); // On destruction the history will be written to the file \endcode @section References - GNU Readline */ // ************************************************************************** #include "Readline.h" #include #include #include #include #include "tools.h" using namespace std; Readline *Readline::This = 0; // -------------------------------------------------------------------------- // //! Construct a Readline object. The constructor reads the history from a //! history file. The filename is compiled by adding ".his" to the //! supplied argument. The name oif the history file is stored in fName. //! //! Since readline has a global namespace, the creation of only one //! Readline instance is allowed. //! //! The provided program name is supplied to readline by means of //! rl_readline_name. //! //! Readlines default callback frunction for completions is redirected //! to CompletionImp which in turn will call Completion, which can be //! overwritten by the user. //! //! Bind some default key sequences like Page-up/-down for searching forward //! and backward in history. //! //! @param prgname //! The prefix of the history filename. Usually the program name, which //! can be initialized by argv[0]. // Readline::Readline(const char *prgname) : fMaxLines(500), fLine(0), fCompletion(0) { if (This) { cout << "ERROR - Readline can only be instatiated once!" << endl; exit(-1); } This = this; // Alternative completion function rl_attempted_completion_function = rl_ncurses_completion_function; // Program name rl_readline_name = prgname; // Compile filename for history file fName = string(prgname)+".his"; // Read history file if (read_history(fName.c_str())) cout << "WARNING - Reading " << fName << ": " << strerror(errno) << endl; // Setup the readline callback which are needed to redirect // the otuput properly to our ncurses panel rl_getc_function = rl_ncurses_getc; rl_startup_hook = rl_ncurses_startup; rl_redisplay_function = rl_ncurses_redisplay; rl_event_hook = rl_ncurses_event_hook; rl_completion_display_matches_hook = rl_ncurses_completion_display; // Bind delete, page up, page down rl_bind_keyseq("\e[3~", rl_named_function("delete-char")); rl_bind_keyseq("\e[5~", rl_named_function("history-search-backward")); rl_bind_keyseq("\e[6~", rl_named_function("history-search-forward")); rl_bind_keyseq("\033[1;3F", rl_named_function("kill-line")); rl_bind_keyseq("\033[1;5D", rl_named_function("backward-word")); rl_bind_keyseq("\033[1;5C", rl_named_function("forward-word")); rl_bind_key(25, rl_named_function("kill-whole-line")); //for (int i=0; i<10; i++) cout << (int)getchar() << endl; } // -------------------------------------------------------------------------- // //! Writes the current history to the file with the name stored in fName. //! In addition the written file is truncated to fMaxLines to keep the //! file of a reasonable size. The number of lines fMaxLines can be set //! by SetMaxLines before the destructor is called. Setting fMaxLines //! to 0 or a negative value switches automatic truncation off. // Readline::~Readline() { // Write current history to file if (write_history(fName.c_str())) cout << "WARNING - Write " << fName.c_str() << ": " << strerror(errno) << endl; // Truncate file if (fMaxLines>0 && history_truncate_file(fName.c_str(), fMaxLines)) cout << "WARNING - Truncate " << fName.c_str() << ": " << strerror(errno) << endl; } // -------------------------------------------------------------------------- // //! This is a static helper to remove leading and trailing whitespaces. //! //! @param buf //! a pointer to the char array from which the whitespaces should be //! removed //! //! @returns //! a std::string with the whitespaces removed from buf // string Readline::TrimSpaces(const char *buf) { const string str(buf); // Trim Both leading and trailing spaces const size_t start = str.find_first_not_of(" "); // Find the first character position after excluding leading blank spaces const size_t end = str.find_last_not_of(" "); // Find the first character position from reverse af // if all spaces or empty return an empty string if (string::npos==start || string::npos==end) return ""; return str.substr(start, end-start+1); } // -------------------------------------------------------------------------- // //! Redirected from rl_getc_function, calls Getc // int Readline::rl_ncurses_getc(FILE *f) { return This->Getc(f); } // -------------------------------------------------------------------------- // //! Redirected from rl_startup_hook, calls Startup. //! A function called just before readline prints the first prompt. // int Readline::rl_ncurses_startup() { This->Startup(); return 0; // What is this for? } // -------------------------------------------------------------------------- // //! Redirected from rl_redisplay_function, calls Redisplay. //! Readline will call indirectly to update the display with the current //! contents of the editing buffer. // void Readline::rl_ncurses_redisplay() { This->Redisplay(); } // -------------------------------------------------------------------------- // //! Redirected from rl_event_hook, calls Update(). //! A function called periodically when readline is waiting for //! terminal input. //! int Readline::rl_ncurses_event_hook() { This->EventHook(); return 0; } // -------------------------------------------------------------------------- // //! Redirected from rl_completion_display_matches_hook, //! calls CompletionDisplayImp //! //! A function to be called when completing a word would normally display //! the list of possible matches. This function is called in lieu of //! Readline displaying the list. It takes three arguments: //! (char **matches, int num_matches, int max_length) where matches is //! the array of matching strings, num_matches is the number of strings //! in that array, and max_length is the length of the longest string in //! that array. Readline provides a convenience function, //! rl_display_match_list, that takes care of doing the display to //! Readline's output stream. // void Readline::rl_ncurses_completion_display(char **matches, int num, int max) { This->CompletionDisplay(matches, num, max); } char **Readline::rl_ncurses_completion_function(const char *text, int start, int end) { return This->Completion(text, start, end); } // -------------------------------------------------------------------------- // //! Calls the default rl_getc function. // int Readline::Getc(FILE *f) { return rl_getc(f); } // -------------------------------------------------------------------------- // //! Default: Do nothing. // void Readline::Startup() { } // -------------------------------------------------------------------------- // //! The default is to redisplay the prompt which is gotten from //! GetUpdatePrompt(). If GetUpdatePrompt() returns an empty string the //! prompt is kept untouched. This can be used to keep a prompt updated //! with some information (e.g. time) just by overwriting GetUpdatePrompt() //! void Readline::EventHook() { const string p = GetUpdatePrompt(); if (p.empty()) return; UpdatePrompt(""); Redisplay(); UpdatePrompt(p); Redisplay(); } // -------------------------------------------------------------------------- // //! Called from Prompt and PromptEOF after readline has returned. It is //! meant as the opposite of Startup (called after readline finsihes) //! The default is to do nothing. //! //! @param buf //! A pointer to the buffer returned by readline // void Readline::Shutdown(const char *) { } // -------------------------------------------------------------------------- // //! Default: call rl_redisplay() // void Readline::Redisplay() { rl_redisplay(); } // -------------------------------------------------------------------------- // //! Default: call rl_completion_display_matches() // void Readline::CompletionDisplay(char **matches, int num, int max) { rl_display_match_list(matches, num, max); } // -------------------------------------------------------------------------- // //! This is a static helper for the compilation of a completion-list. //! It compares the two inputs (str and txt) to a maximum of the size of //! txt. If they match, memory is allocated with malloc and a pointer to //! the null-terminated version of str is returned. //! //! @param str //! A reference to the string which is checked (e.g. "Makefile.am") //! //! @param txt //! A reference to the part of the string the user has already typed, //! e.g. "Makef" //! //! @returns //! A pointer to memory allocated with malloc containing the string str // char *Readline::Compare(const string &str, const string &txt) { return str.substr(0, txt.length())==txt ? strndup(str.c_str(), str.length()) : 0; } char **Readline::CompletionMatches(const char *text, char *(*func)(const char*, int)) { return rl_completion_matches(text, func); } // -------------------------------------------------------------------------- // //! The given vector should be a reference to a vector of strings //! containing all possible matches. The actual match-making is then //! done in Complete(const char *, int) //! //! The pointer fCompletion is redirected to the vector for the run time //! of the function, but restored afterwards. So by this you can set a //! default completion list in case Complete is not called or Completion //! not overloaded. //! //! @param v //! reference to a vector of strings with all possible matches //! //! @param text //! the text which should be matched (it is just propagated to //! Readline::Completion) //! char **Readline::Complete(const vector &v, const char *text) { const vector *save = fCompletion; fCompletion = &v; char **rc = rl_completion_matches(const_cast(text), CompleteImp); fCompletion = save; return rc; } // -------------------------------------------------------------------------- // //! If fCompletion==0 the default is to call readline's //! rl_filename_completion_function. Otherwise the contents of fCompletion //! are returned. To change fCompletion either initialize it via //! SetCompletion() (in this case you must ensure the life time of the //! object) or call //! Complete(const vector&, const char*) //! from //! Completion(const char * int, int) //! //! This is the so called generator function, the readline manual says //! about this function: //! //! The generator function is called repeatedly from //! rl_completion_matches(), returning a string each time. The arguments //! to the generator function are text and state. text is the partial word //! to be completed. state is zero the first time the function is called, //! allowing the generator to perform any necessary initialization, and a //! positive non-zero integer for each subsequent call. The generator //! function returns (char *)NULL to inform rl_completion_matches() that //! there are no more possibilities left. Usually the generator function //! computes the list of possible completions when state is zero, and //! returns them one at a time on subsequent calls. Each string the //! generator function returns as a match must be allocated with malloc(); //! Readline frees the strings when it has finished with them. // char *Readline::Complete(const char* text, int state) { if (fCompletion==0) return rl_filename_completion_function(text, state); static vector::const_iterator pos; if (state==0) pos = fCompletion->begin(); while (pos!=fCompletion->end()) { char *rc = Compare(*pos++, text); if (rc) return rc; } return 0; } // -------------------------------------------------------------------------- // //! Calls Complete() // char *Readline::CompleteImp(const char* text, int state) { return This->Complete(text, state); } // -------------------------------------------------------------------------- // //! The readline manual says about this function: //! //! A pointer to an alternative function to create matches. The //! function is called with text, start, and end. start and end are //! indices in rl_line_buffer saying what the boundaries of text are. //! If this function exists and returns NULL, or if this variable is //! set to NULL, then rl_complete() will call the value of //! rl_completion_entry_function to generate matches, otherwise the //! array of strings returned will be used. //! //! This function is virtual and can be overwritten. It defaults to //! a call to rl_completion_matches with CompleteImp as an argument //! which defaults to filename completion, but can also be overwritten. //! //! It is suggested that you call //! Complete(const vector&, const char*) //! from here. //! //! @param text //! A pointer to a char array conatining the text which should be //! completed. The text is null-terminated. //! //! @param start //! The start index within readline's line buffer rl_line_buffer, //! at which the text starts which presumably should be completed. //! //! @param end //! The end index within readline's line buffer rl_line_buffer, //! at which the text ends which presumably should be completed. //! //! @returns //! An array of strings which were allocated with malloc and which //! will be freed by readline with the possible matches. // char **Readline::Completion(const char *text, int /*start*/, int /*end*/) { // To do filename completion call return rl_completion_matches((char*)text, CompleteImp); } // -------------------------------------------------------------------------- // //! Adds the given string to the history buffer of readline's history by //! calling add_history. If skip is set to true str will be compared //! with the last line added to the history and only be added if it difers. //! //! @param str //! A reference to a string which should be added to readline's //! history. //! //! @param skip //! If skip is true do not add a new entry if it is identical to //! the last one. // void Readline::AddToHistory(const string &str, bool skip) { if (skip && fLastLine==str) return; if (str.empty()) return; add_history(str.c_str()); fLastLine = str; } string Readline::GetLinePrompt() const { return Form("[%d]", fLine); } // -------------------------------------------------------------------------- // //! Calls rl_set_prompt. This can be used from readline's callback function //! to change the prompt while a call to the readline function is in //! progress. //! //! @param prompt //! The new prompt to be shown // void Readline::UpdatePrompt(const string &prompt) const { rl_set_prompt(prompt.c_str()); } // -------------------------------------------------------------------------- // //! This function is used to bind a key sequence via a call to //! rl_bind_keyseq. //! //! Readline's manual says about this function: //! //! Bind the key sequence represented by the string keyseq to the //! function function, beginning in the current keymap. This makes //! new keymaps as necessary. The return value is non-zero if keyseq //! is invalid. //! //! Key sequences are escaped sequences of characters read from an input //! stream when a special key is pressed. This is necessary because //! there are usually more keys and possible combinations than ascii codes. //! //! Possible key sequences are for example: //! "\033OP" F1 //! "\033[1;5A" Ctrl+up //! "\033[1;5B" Ctrl+down //! "\033[1;3A" Alt+up //! "\033[1;3B" Alt+down //! "\033[5;3~" Alt+page up //! "\033[6;3~" Alt+page down //! "\033+" Alt++ //! "\033-" Alt+- //! "\033\t" Alt+tab //! "\033[1~" Alt+tab //! //! @param seq //! The key sequence to be bound //! //! @param func //! A function of type "int func(int, int) // void Readline::BindKeySequence(const char *seq, int (*func)(int, int)) { rl_bind_keyseq(seq, func); } // -------------------------------------------------------------------------- // //! Calls rl_variable_dumper(1) //! //! Print the readline variable names and their current values //! to rl_outstream. If readable is non-zero, the list is formatted //! in such a way that it can be made part of an inputrc file and //! re-read. //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // bool Readline::DumpVariables() { rl_variable_dumper(1); return true; } // -------------------------------------------------------------------------- // //! Calls rl_function_dumper(1) //! //! Print the readline function names and the key sequences currently //! bound to them to rl_outstream. If readable is non-zero, the list //! is formatted in such a way that it can be made part of an inputrc //! file and re-read. //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // bool Readline::DumpFunctions() { rl_function_dumper(1); return true; } // -------------------------------------------------------------------------- // //! Calls rl_list_funmap_names() //! //! Print the names of all bindable Readline functions to rl_outstream. //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // bool Readline::DumpFunmap() { rl_list_funmap_names(); return true; } // -------------------------------------------------------------------------- // //! Sets rl_outstream (the stdio stream to which Readline performs output) //! to the new stream. //! //! @param f //! The new stdio stream to which readline should perform its output //! //! @return //! The old stream to which readline was performing output // FILE *Readline::SetStreamOut(FILE *f) { FILE *rc = rl_outstream; rl_outstream = f; return rc; } // -------------------------------------------------------------------------- // //! Sets rl_instream (the stdio stream from which Readline reads input) //! to the new stream. //! //! @param f //! The new stdio stream from which readline should read its input //! //! @return //! The old stream from which readline was reading it input // FILE *Readline::SetStreamIn(FILE *f) { FILE *rc = rl_instream; rl_instream = f; return rc; } // -------------------------------------------------------------------------- // //! return rl_display_prompt (the prompt which should currently be //! displayed on the screen) while a readline command is in progress // string Readline::GetPrompt() { return rl_display_prompt; } // -------------------------------------------------------------------------- // //! return rl_line_buffer (the current input line which should currently be //! displayed on the screen) while a readline command is in progress //! //! The length of the current line buffer (rl_end) is available as //! GetLineBuffer().size() //! //! Note that after readline has returned the contents of rl_end might //! not reflect the correct buffer length anymore, hence, the returned buffer //! might be truncated. // string Readline::GetBuffer() { return string(rl_line_buffer, rl_end); } // -------------------------------------------------------------------------- // //! return rl_point (the current cursor position within the line buffer) // int Readline::GetCursor() { return rl_point; } // -------------------------------------------------------------------------- // //! return strlen(rl_display_prompt) + rl_point // int Readline::GetAbsCursor() { return strlen(rl_display_prompt) + rl_point; } // -------------------------------------------------------------------------- // //! return rl_end (the current total length of the line buffer) //! Note that after readline has returned the contents of rl_end might //! not reflect the correct buffer length anymore. // int Readline::GetBufferLength() { return rl_end; } // -------------------------------------------------------------------------- // //! return the length of the prompt plus the length of the line buffer // int Readline::GetLineLength() { return strlen(rl_display_prompt) + rl_end; } // -------------------------------------------------------------------------- // //! Calls: Function: void rl_resize_terminal() //! Update Readline's internal screen size by reading values from the kernel. // void Readline::Resize() { rl_resize_terminal(); } // -------------------------------------------------------------------------- // //! Calls: Function: void rl_set_screen_size (int rows, int cols) //! Set Readline's idea of the terminal size to rows rows and cols columns. //! //! @param width //! Number of columns //! //! @param height //! Number of rows // void Readline::Resize(int width, int height) { rl_set_screen_size(height, width); } // -------------------------------------------------------------------------- // //! Get the number of cols readline assumes the screen size to be // int Readline::GetCols() const { int rows, cols; rl_get_screen_size(&rows, &cols); return cols; } // -------------------------------------------------------------------------- // //! Get the number of rows readline assumes the screen size to be // int Readline::GetRows() const { int rows, cols; rl_get_screen_size(&rows, &cols); return rows; } // -------------------------------------------------------------------------- // //! Return a list of pointer to the history contents // vector Readline::GetHistory() const { HIST_ENTRY **next = history_list(); vector v; for (; *next; next++) v.push_back((*next)->line); return v; } // -------------------------------------------------------------------------- // //! Clear readline history (calls clear_history()) //! //! @returns //! always true // bool Readline::ClearHistory() { clear_history(); return true; } // -------------------------------------------------------------------------- // //! Displays the current history on rl_outstream //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // bool Readline::DumpHistory() { HIST_ENTRY **next = history_list(); for (; *next; next++) fprintf(rl_outstream, "%s\n", (*next)->line); return true; } // -------------------------------------------------------------------------- // //! Print the available commands. This is intended for being overwritten //! by deriving classes. //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // // bool Readline::PrintCommands() { fprintf(rl_outstream, "\n"); fprintf(rl_outstream, " Commands:\n"); fprintf(rl_outstream, " No application specific commands defined.\n"); fprintf(rl_outstream, "\n"); return true; } // -------------------------------------------------------------------------- // //! Print a general help message. This is intended for being overwritten //! by deriving classes. //! //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // // bool Readline::PrintGeneralHelp() { fprintf(rl_outstream, "\n"); fprintf(rl_outstream, " General help:\n"); fprintf(rl_outstream, " h,help Print this help message\n"); fprintf(rl_outstream, " clear Clear history buffer\n"); fprintf(rl_outstream, " lh,history Dump the history buffer to the screen\n"); fprintf(rl_outstream, " v,variables Dump readline variables\n"); fprintf(rl_outstream, " f,functions Dump readline functions\n"); fprintf(rl_outstream, " m,funmap Dump readline funmap\n"); fprintf(rl_outstream, " c,commands Dump available commands\n"); fprintf(rl_outstream, " k,keylist Dump key bindings\n"); fprintf(rl_outstream, " .q,quit Quit\n"); fprintf(rl_outstream, "\n"); fprintf(rl_outstream, " The command history is automatically loaded and saves to\n"); fprintf(rl_outstream, " and from %s.\n", GetName().c_str()); fprintf(rl_outstream, "\n"); return true; } // -------------------------------------------------------------------------- // //! Print a help text about key bindings. This is intended for being //! overwritten by deriving classes. //! //! //! rl_outstream can be redirected using SetStreamOut() //! //! @returns //! always true // // bool Readline::PrintKeyBindings() { fprintf(rl_outstream, "\n"); fprintf(rl_outstream, " Key bindings:\n"); fprintf(rl_outstream, " Page-up Search backward in history\n"); fprintf(rl_outstream, " Page-dn Search forward in history\n"); fprintf(rl_outstream, " Ctrl-left One word backward\n"); fprintf(rl_outstream, " Ctrl-right One word forward\n"); fprintf(rl_outstream, " Ctrl-y Delete line\n"); fprintf(rl_outstream, " Alt-end/Ctrl-k Delete until the end of the line\n"); fprintf(rl_outstream, " F1 Toggle visibility of upper panel\n"); fprintf(rl_outstream, "\n"); fprintf(rl_outstream, " Default key-bindings are identical with your bash.\n"); fprintf(rl_outstream, "\n"); return true; } // -------------------------------------------------------------------------- // //! // bool Readline::Process(const string &str) { // ----------- Readline static ------------- if (str=="clear") return ClearHistory(); if (str=="lh" || str=="history") return DumpHistory(); if (str=="v" || str=="variables") return DumpVariables(); if (str=="f" || str=="functions") return DumpFunctions(); if (str=="m" || str=="funmap") return DumpFunmap(); // ---------- Readline virtual ------------- if (str=="h" || str=="help") return PrintGeneralHelp(); if (str=="c" || str=="commands") return PrintCommands(); if (str=="k" || str=="keylist") return PrintKeyBindings(); return false; } // -------------------------------------------------------------------------- // //! This function is a wrapper around the call to readline. It encapsultes //! the return buffer into a std::string and deletes the memory allocated //! by readline. Furthermore, it removes leading and trailing whitespaces //! before return the result. The result is returned in the given //! argument containing the prompt. Before the function returns Shutdown() //! is called (as opposed to Startup when readline starts) //! //! @param str //! The prompt which is to be shown by the readline libarary. it is //! directly given to the call to readline. The result of the //! readline call is returned in this string. //! //! @returns //! true if the call succeeded as usual, false if EOF was detected //! by the readline call. // bool Readline::PromptEOF(string &str) { char *buf = readline(str.c_str()); Shutdown(buf); // Happens when EOF is encountered if (!buf) return false; str = TrimSpaces(buf); free(buf); return true; } // -------------------------------------------------------------------------- // //! This function is a wrapper around the call to readline. It encapsultes //! the return buffer into a std::string and deletes the memory allocated //! by readline. Furthermore, it removes leading and trailing whitespaces //! before return the result. Before the function returns Shutdown() is //! called (as opposed to Startup when readline starts) //! //! @param prompt //! The prompt which is to be shown by the readline libarary. it is //! directly given to the call to readline. //! //! @returns //! The result of the readline call // string Readline::Prompt(const string &prompt) { char *buf = readline(prompt.c_str()); Shutdown(buf ? buf : ""); const string str = buf ? TrimSpaces(buf) : ".q"; free(buf); return str; } // -------------------------------------------------------------------------- // //! This implements a loop over readline calls. A prompt to issue can be //! given. If it is NULL, the prompt is retrieved from GetUpdatePrompt(). //! It is updated regularly by means of calls to GetUpdatePrompt() from //! EventHook(). If Prompt() returns with "quit" or ".q" the loop is //! exited. If ".qqq" is entered exit(-1) is called. In case of ".qqqqqq" //! abort(). Both ways to exit the program are not recommended. Empty //! inputs are ignored. After that Process() with the returned string //! is called. If Process returns true the input is not counted and not //! added to the history, otherwise the line counter is increased //! and the input is added to the history. //! //! @param prompt //! The prompt to be issued or NULL if GetUPdatePrompt should be used //! instead. //! void Readline::Run(const char *prompt) { fLine = 0; while (1) { // Before we start we have to make sure that the // screen looks like and is ordered like expected. const string str = Prompt(prompt?prompt:GetUpdatePrompt()); if (str.empty()) continue; if (str=="quit" || str==".q") break; if (str==".qqq") exit(-1); if (str==".qqqqqq") abort(); if (Process(str)) continue; fLine++; AddToHistory(str); } }