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/*
36Copyright (C) 1999 Jeff Solomon
37*/
38
39#if defined (HAVE_CONFIG_H)
40#include <config.h>
41#endif
42
43#include <stdio.h>
44#include <sys/types.h>
45
46#ifdef HAVE_UNISTD_H
47#include <unistd.h>
48#endif
49
50#include <termios.h>	/* xxx - should make this more general */
51
52#ifdef READLINE_LIBRARY
53#  include "readline.h"
54#else
55#  include <readline/readline.h>
56#endif
57
58/* This little examples demonstrates the alternate interface to using readline.
59 * In the alternate interface, the user maintains control over program flow and
60 * only calls readline when STDIN is readable. Using the alternate interface,
61 * you can do anything else while still using readline (like talking to a
62 * network or another program) without blocking.
63 *
64 * Specifically, this program highlights two importants features of the
65 * alternate interface. The first is the ability to interactively change the
66 * prompt, which can't be done using the regular interface since rl_prompt is
67 * read-only.
68 *
69 * The second feature really highlights a subtle point when using the alternate
70 * interface. That is, readline will not alter the terminal when inside your
71 * callback handler. So let's so, your callback executes a user command that
72 * takes a non-trivial amount of time to complete (seconds). While your
73 * executing the command, the user continues to type keystrokes and expects them
74 * to be re-echoed on the new prompt when it returns. Unfortunately, the default
75 * terminal configuration doesn't do this. After the prompt returns, the user
76 * must hit one additional keystroke and then will see all of his previous
77 * keystrokes. To illustrate this, compile and run this program. Type "sleep" at
78 * the prompt and then type "bar" before the prompt returns (you have 3
79 * seconds). Notice how "bar" is re-echoed on the prompt after the prompt
80 * returns? This is what you expect to happen. Now comment out the 4 lines below
81 * the line that says COMMENT LINE BELOW. Recompile and rerun the program and do
82 * the same thing. When the prompt returns, you should not see "bar". Now type
83 * "f", see how "barf" magically appears? This behavior is un-expected and not
84 * desired.
85 */
86
87void process_line(char *line);
88int  change_prompt(void);
89char *get_prompt(void);
90
91int prompt = 1;
92char prompt_buf[40], line_buf[256];
93tcflag_t old_lflag;
94cc_t     old_vtime;
95struct termios term;
96
97int
98main()
99{
100    fd_set fds;
101
102    /* Adjust the terminal slightly before the handler is installed. Disable
103     * canonical mode processing and set the input character time flag to be
104     * non-blocking.
105     */
106    if( tcgetattr(STDIN_FILENO, &term) < 0 ) {
107        perror("tcgetattr");
108        exit(1);
109    }
110    old_lflag = term.c_lflag;
111    old_vtime = term.c_cc[VTIME];
112    term.c_lflag &= ~ICANON;
113    term.c_cc[VTIME] = 1;
114    /* COMMENT LINE BELOW - see above */
115    if( tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0 ) {
116        perror("tcsetattr");
117        exit(1);
118    }
119
120    rl_add_defun("change-prompt", change_prompt, CTRL('t'));
121    rl_callback_handler_install(get_prompt(), process_line);
122
123    while(1) {
124      FD_ZERO(&fds);
125      FD_SET(fileno(stdin), &fds);
126
127      if( select(FD_SETSIZE, &fds, NULL, NULL, NULL) < 0) {
128        perror("select");
129        exit(1);
130      }
131
132      if( FD_ISSET(fileno(stdin), &fds) ) {
133        rl_callback_read_char();
134      }
135    }
136}
137
138void
139process_line(char *line)
140{
141  if( line == NULL ) {
142    fprintf(stderr, "\n", line);
143
144    /* reset the old terminal setting before exiting */
145    term.c_lflag     = old_lflag;
146    term.c_cc[VTIME] = old_vtime;
147    if( tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0 ) {
148        perror("tcsetattr");
149        exit(1);
150    }
151    exit(0);
152  }
153
154  if( strcmp(line, "sleep") == 0 ) {
155    sleep(3);
156  } else {
157    fprintf(stderr, "|%s|\n", line);
158  }
159
160  free (line);
161}
162
163int
164change_prompt(void)
165{
166  /* toggle the prompt variable */
167  prompt = !prompt;
168
169  /* save away the current contents of the line */
170  strcpy(line_buf, rl_line_buffer);
171
172  /* install a new handler which will change the prompt and erase the current line */
173  rl_callback_handler_install(get_prompt(), process_line);
174
175  /* insert the old text on the new line */
176  rl_insert_text(line_buf);
177
178  /* redraw the current line - this is an undocumented function. It invokes the
179   * redraw-current-line command.
180   */
181  rl_refresh_line(0, 0);
182}
183
184char *
185get_prompt(void)
186{
187  /* The prompts can even be different lengths! */
188  sprintf(prompt_buf, "%s",
189    prompt ? "Hit ctrl-t to toggle prompt> " : "Pretty cool huh?> ");
190  return prompt_buf;
191}
192