1/*
2From: Jeff Solomon <jsolomon@stanford.edu>
3Date: Fri,  9 Apr 1999 10:13:27 -0700 (PDT)
4To: chet@po.cwru.edu
5Subject: new readline example
6Message-ID: <14094.12094.527305.199695@mrclean.Stanford.EDU>
7
8Chet,
9
10I've been using readline 4.0. Specifically, I've been using the perl
11version Term::ReadLine::Gnu. It works great.
12
13Anyway, I've been playing around the alternate interface and I wanted
14to contribute a little C program, callback.c, to you that you could
15use as an example of the alternate interface in the /examples
16directory of the readline distribution.
17
18My example shows how, using the alternate interface, you can
19interactively change the prompt (which is very nice imo). Also, I
20point out that you must roll your own terminal setting when using the
21alternate interface because readline depreps (using your parlance) the
22terminal while in the user callback. I try to demostrate what I mean
23with an example. I've included the program below.
24
25To compile, I just put the program in the examples directory and made
26the appropriate changes to the EXECUTABLES and OBJECTS line and added
27an additional target 'callback'.
28
29I compiled on my Sun Solaris2.6 box using Sun's cc.
30
31Let me know what you think.
32
33Jeff
34*/
35
36#if defined (HAVE_CONFIG_H)
37#include <config.h>
38#endif
39
40#include <stdio.h>
41#include <sys/types.h>
42
43#ifdef HAVE_UNISTD_H
44#include <unistd.h>
45#endif
46
47#include <termios.h>	/* xxx - should make this more general */
48
49#ifdef READLINE_LIBRARY
50#  include "readline.h"
51#else
52#  include <readline/readline.h>
53#endif
54
55/* This little examples demonstrates the alternate interface to using readline.
56 * In the alternate interface, the user maintains control over program flow and
57 * only calls readline when STDIN is readable. Using the alternate interface,
58 * you can do anything else while still using readline (like talking to a
59 * network or another program) without blocking.
60 *
61 * Specifically, this program highlights two importants features of the
62 * alternate interface. The first is the ability to interactively change the
63 * prompt, which can't be done using the regular interface since rl_prompt is
64 * read-only.
65 *
66 * The second feature really highlights a subtle point when using the alternate
67 * interface. That is, readline will not alter the terminal when inside your
68 * callback handler. So let's so, your callback executes a user command that
69 * takes a non-trivial amount of time to complete (seconds). While your
70 * executing the command, the user continues to type keystrokes and expects them
71 * to be re-echoed on the new prompt when it returns. Unfortunately, the default
72 * terminal configuration doesn't do this. After the prompt returns, the user
73 * must hit one additional keystroke and then will see all of his previous
74 * keystrokes. To illustrate this, compile and run this program. Type "sleep" at
75 * the prompt and then type "bar" before the prompt returns (you have 3
76 * seconds). Notice how "bar" is re-echoed on the prompt after the prompt
77 * returns? This is what you expect to happen. Now comment out the 4 lines below
78 * the line that says COMMENT LINE BELOW. Recompile and rerun the program and do
79 * the same thing. When the prompt returns, you should not see "bar". Now type
80 * "f", see how "barf" magically appears? This behavior is un-expected and not
81 * desired.
82 */
83
84void process_line(char *line);
85int  change_prompt(void);
86char *get_prompt(void);
87
88int prompt = 1;
89char prompt_buf[40], line_buf[256];
90tcflag_t old_lflag;
91cc_t     old_vtime;
92struct termios term;
93
94int
95main()
96{
97    fd_set fds;
98
99    /* Adjust the terminal slightly before the handler is installed. Disable
100     * canonical mode processing and set the input character time flag to be
101     * non-blocking.
102     */
103    if( tcgetattr(STDIN_FILENO, &term) < 0 ) {
104        perror("tcgetattr");
105        exit(1);
106    }
107    old_lflag = term.c_lflag;
108    old_vtime = term.c_cc[VTIME];
109    term.c_lflag &= ~ICANON;
110    term.c_cc[VTIME] = 1;
111    /* COMMENT LINE BELOW - see above */
112    if( tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0 ) {
113        perror("tcsetattr");
114        exit(1);
115    }
116
117    rl_add_defun("change-prompt", change_prompt, CTRL('t'));
118    rl_callback_handler_install(get_prompt(), process_line);
119
120    while(1) {
121      FD_ZERO(&fds);
122      FD_SET(fileno(stdin), &fds);
123
124      if( select(FD_SETSIZE, &fds, NULL, NULL, NULL) < 0) {
125        perror("select");
126        exit(1);
127      }
128
129      if( FD_ISSET(fileno(stdin), &fds) ) {
130        rl_callback_read_char();
131      }
132    }
133}
134
135void
136process_line(char *line)
137{
138  if( line == NULL ) {
139    fprintf(stderr, "\n", line);
140
141    /* reset the old terminal setting before exiting */
142    term.c_lflag     = old_lflag;
143    term.c_cc[VTIME] = old_vtime;
144    if( tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0 ) {
145        perror("tcsetattr");
146        exit(1);
147    }
148    exit(0);
149  }
150
151  if( strcmp(line, "sleep") == 0 ) {
152    sleep(3);
153  } else {
154    fprintf(stderr, "|%s|\n", line);
155  }
156
157  free (line);
158}
159
160int
161change_prompt(void)
162{
163  /* toggle the prompt variable */
164  prompt = !prompt;
165
166  /* save away the current contents of the line */
167  strcpy(line_buf, rl_line_buffer);
168
169  /* install a new handler which will change the prompt and erase the current line */
170  rl_callback_handler_install(get_prompt(), process_line);
171
172  /* insert the old text on the new line */
173  rl_insert_text(line_buf);
174
175  /* redraw the current line - this is an undocumented function. It invokes the
176   * redraw-current-line command.
177   */
178  rl_refresh_line(0, 0);
179}
180
181char *
182get_prompt(void)
183{
184  /* The prompts can even be different lengths! */
185  sprintf(prompt_buf, "%s",
186    prompt ? "Hit ctrl-t to toggle prompt> " : "Pretty cool huh?> ");
187  return prompt_buf;
188}
189