11acd27e7Smillert /*
21acd27e7Smillert From: Jeff Solomon <jsolomon@stanford.edu>
31acd27e7Smillert Date: Fri, 9 Apr 1999 10:13:27 -0700 (PDT)
41acd27e7Smillert To: chet@po.cwru.edu
51acd27e7Smillert Subject: new readline example
61acd27e7Smillert Message-ID: <14094.12094.527305.199695@mrclean.Stanford.EDU>
71acd27e7Smillert
81acd27e7Smillert Chet,
91acd27e7Smillert
101acd27e7Smillert I've been using readline 4.0. Specifically, I've been using the perl
111acd27e7Smillert version Term::ReadLine::Gnu. It works great.
121acd27e7Smillert
131acd27e7Smillert Anyway, I've been playing around the alternate interface and I wanted
141acd27e7Smillert to contribute a little C program, callback.c, to you that you could
151acd27e7Smillert use as an example of the alternate interface in the /examples
161acd27e7Smillert directory of the readline distribution.
171acd27e7Smillert
181acd27e7Smillert My example shows how, using the alternate interface, you can
191acd27e7Smillert interactively change the prompt (which is very nice imo). Also, I
201acd27e7Smillert point out that you must roll your own terminal setting when using the
211acd27e7Smillert alternate interface because readline depreps (using your parlance) the
221acd27e7Smillert terminal while in the user callback. I try to demostrate what I mean
231acd27e7Smillert with an example. I've included the program below.
241acd27e7Smillert
251acd27e7Smillert To compile, I just put the program in the examples directory and made
261acd27e7Smillert the appropriate changes to the EXECUTABLES and OBJECTS line and added
271acd27e7Smillert an additional target 'callback'.
281acd27e7Smillert
291acd27e7Smillert I compiled on my Sun Solaris2.6 box using Sun's cc.
301acd27e7Smillert
311acd27e7Smillert Let me know what you think.
321acd27e7Smillert
331acd27e7Smillert Jeff
341acd27e7Smillert */
351acd27e7Smillert
361acd27e7Smillert #if defined (HAVE_CONFIG_H)
371acd27e7Smillert #include <config.h>
381acd27e7Smillert #endif
391acd27e7Smillert
401acd27e7Smillert #include <stdio.h>
411acd27e7Smillert #include <sys/types.h>
421acd27e7Smillert
431acd27e7Smillert #ifdef HAVE_UNISTD_H
441acd27e7Smillert #include <unistd.h>
451acd27e7Smillert #endif
461acd27e7Smillert
471acd27e7Smillert #include <termios.h> /* xxx - should make this more general */
481acd27e7Smillert
491acd27e7Smillert #ifdef READLINE_LIBRARY
501acd27e7Smillert # include "readline.h"
511acd27e7Smillert #else
521acd27e7Smillert # include <readline/readline.h>
531acd27e7Smillert #endif
541acd27e7Smillert
551acd27e7Smillert /* This little examples demonstrates the alternate interface to using readline.
561acd27e7Smillert * In the alternate interface, the user maintains control over program flow and
571acd27e7Smillert * only calls readline when STDIN is readable. Using the alternate interface,
581acd27e7Smillert * you can do anything else while still using readline (like talking to a
591acd27e7Smillert * network or another program) without blocking.
601acd27e7Smillert *
611acd27e7Smillert * Specifically, this program highlights two importants features of the
621acd27e7Smillert * alternate interface. The first is the ability to interactively change the
631acd27e7Smillert * prompt, which can't be done using the regular interface since rl_prompt is
641acd27e7Smillert * read-only.
651acd27e7Smillert *
661acd27e7Smillert * The second feature really highlights a subtle point when using the alternate
671acd27e7Smillert * interface. That is, readline will not alter the terminal when inside your
681acd27e7Smillert * callback handler. So let's so, your callback executes a user command that
691acd27e7Smillert * takes a non-trivial amount of time to complete (seconds). While your
701acd27e7Smillert * executing the command, the user continues to type keystrokes and expects them
711acd27e7Smillert * to be re-echoed on the new prompt when it returns. Unfortunately, the default
721acd27e7Smillert * terminal configuration doesn't do this. After the prompt returns, the user
731acd27e7Smillert * must hit one additional keystroke and then will see all of his previous
741acd27e7Smillert * keystrokes. To illustrate this, compile and run this program. Type "sleep" at
751acd27e7Smillert * the prompt and then type "bar" before the prompt returns (you have 3
761acd27e7Smillert * seconds). Notice how "bar" is re-echoed on the prompt after the prompt
771acd27e7Smillert * returns? This is what you expect to happen. Now comment out the 4 lines below
781acd27e7Smillert * the line that says COMMENT LINE BELOW. Recompile and rerun the program and do
791acd27e7Smillert * the same thing. When the prompt returns, you should not see "bar". Now type
801acd27e7Smillert * "f", see how "barf" magically appears? This behavior is un-expected and not
811acd27e7Smillert * desired.
821acd27e7Smillert */
831acd27e7Smillert
841acd27e7Smillert void process_line(char *line);
851acd27e7Smillert int change_prompt(void);
861acd27e7Smillert char *get_prompt(void);
871acd27e7Smillert
881acd27e7Smillert int prompt = 1;
891acd27e7Smillert char prompt_buf[40], line_buf[256];
901acd27e7Smillert tcflag_t old_lflag;
911acd27e7Smillert cc_t old_vtime;
921acd27e7Smillert struct termios term;
931acd27e7Smillert
941acd27e7Smillert int
main()951acd27e7Smillert main()
961acd27e7Smillert {
971acd27e7Smillert fd_set fds;
981acd27e7Smillert
991acd27e7Smillert /* Adjust the terminal slightly before the handler is installed. Disable
1001acd27e7Smillert * canonical mode processing and set the input character time flag to be
1011acd27e7Smillert * non-blocking.
1021acd27e7Smillert */
1031acd27e7Smillert if( tcgetattr(STDIN_FILENO, &term) < 0 ) {
1041acd27e7Smillert perror("tcgetattr");
1051acd27e7Smillert exit(1);
1061acd27e7Smillert }
1071acd27e7Smillert old_lflag = term.c_lflag;
1081acd27e7Smillert old_vtime = term.c_cc[VTIME];
1091acd27e7Smillert term.c_lflag &= ~ICANON;
1101acd27e7Smillert term.c_cc[VTIME] = 1;
1111acd27e7Smillert /* COMMENT LINE BELOW - see above */
1121acd27e7Smillert if( tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0 ) {
1131acd27e7Smillert perror("tcsetattr");
1141acd27e7Smillert exit(1);
1151acd27e7Smillert }
1161acd27e7Smillert
1171acd27e7Smillert rl_add_defun("change-prompt", change_prompt, CTRL('t'));
1181acd27e7Smillert rl_callback_handler_install(get_prompt(), process_line);
1191acd27e7Smillert
1201acd27e7Smillert while(1) {
1211acd27e7Smillert FD_ZERO(&fds);
1221acd27e7Smillert FD_SET(fileno(stdin), &fds);
1231acd27e7Smillert
1242f6ac5a1Smillert if( select(fileno(stdin) + 1, &fds, NULL, NULL, NULL) < 0) {
1251acd27e7Smillert perror("select");
1261acd27e7Smillert exit(1);
1271acd27e7Smillert }
1281acd27e7Smillert
1291acd27e7Smillert if( FD_ISSET(fileno(stdin), &fds) ) {
1301acd27e7Smillert rl_callback_read_char();
1311acd27e7Smillert }
1321acd27e7Smillert }
1331acd27e7Smillert }
1341acd27e7Smillert
1351acd27e7Smillert void
process_line(char * line)1361acd27e7Smillert process_line(char *line)
1371acd27e7Smillert {
1381acd27e7Smillert if( line == NULL ) {
1391acd27e7Smillert fprintf(stderr, "\n", line);
1401acd27e7Smillert
1411acd27e7Smillert /* reset the old terminal setting before exiting */
1421acd27e7Smillert term.c_lflag = old_lflag;
1431acd27e7Smillert term.c_cc[VTIME] = old_vtime;
1441acd27e7Smillert if( tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0 ) {
1451acd27e7Smillert perror("tcsetattr");
1461acd27e7Smillert exit(1);
1471acd27e7Smillert }
1481acd27e7Smillert exit(0);
1491acd27e7Smillert }
1501acd27e7Smillert
1511acd27e7Smillert if( strcmp(line, "sleep") == 0 ) {
1521acd27e7Smillert sleep(3);
1531acd27e7Smillert } else {
1541acd27e7Smillert fprintf(stderr, "|%s|\n", line);
1551acd27e7Smillert }
156*af70c2dfSkettenis
157*af70c2dfSkettenis free (line);
1581acd27e7Smillert }
1591acd27e7Smillert
1601acd27e7Smillert int
change_prompt(void)1611acd27e7Smillert change_prompt(void)
1621acd27e7Smillert {
1631acd27e7Smillert /* toggle the prompt variable */
1641acd27e7Smillert prompt = !prompt;
1651acd27e7Smillert
1661acd27e7Smillert /* save away the current contents of the line */
1671acd27e7Smillert strcpy(line_buf, rl_line_buffer);
1681acd27e7Smillert
1691acd27e7Smillert /* install a new handler which will change the prompt and erase the current line */
1701acd27e7Smillert rl_callback_handler_install(get_prompt(), process_line);
1711acd27e7Smillert
1721acd27e7Smillert /* insert the old text on the new line */
1731acd27e7Smillert rl_insert_text(line_buf);
1741acd27e7Smillert
1751acd27e7Smillert /* redraw the current line - this is an undocumented function. It invokes the
1761acd27e7Smillert * redraw-current-line command.
1771acd27e7Smillert */
1781acd27e7Smillert rl_refresh_line(0, 0);
1791acd27e7Smillert }
1801acd27e7Smillert
1811acd27e7Smillert char *
get_prompt(void)1821acd27e7Smillert get_prompt(void)
1831acd27e7Smillert {
1841acd27e7Smillert /* The prompts can even be different lengths! */
1851acd27e7Smillert sprintf(prompt_buf, "%s",
1861acd27e7Smillert prompt ? "Hit ctrl-t to toggle prompt> " : "Pretty cool huh?> ");
1871acd27e7Smillert return prompt_buf;
1881acd27e7Smillert }
189