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