1 /* add readline support to any command
2 Copyright 2002 Andrew Tridgell <tridge@samba.org>, June 2002
4 released under the GNU General Public License version 2 or later
15 #include <readline/readline.h>
16 #include <readline/history.h>
18 static void usage(void)
22 Copyright 2002 Andrew Tridgell <tridge@samba.org>
23 Released under the GNU General Public License v2 or later
28 This add readline support to any command line driven program.
30 rline will try to load a completions file from one of the following locations
32 $RLINE_COMPLETIONS_FILE
33 $RLINE_COMPLETIONS_DIR/COMMAND
35 /usr/share/rline/COMMAND
37 A completion file consists of one completion per line, with multi-part completions
38 separated by spaces. You can use the special word [FILE] to mean filename completion.
42 /* list of command completions */
43 static char **cmd_list;
44 static int num_commands;
45 static int cmd_offset;
48 load completions from the specified filename
50 static int load_completions_file(const char *fname)
55 f = fopen(fname, "r");
60 /* don't bother parsing multi-part completions here, that is done at runtime */
61 while (fgets(line, sizeof(line), f)) {
62 int len = strlen(line);
63 if (len == 0) continue;
65 cmd_list = (char **)realloc(cmd_list, sizeof(char *)*(num_commands+1));
68 cmd_list[num_commands++] = strdup(line);
76 /* try loading a completions list from varios places */
77 static void load_completions(const char *command)
82 /* take only the last part of the command */
83 if ((p = strrchr(command, '/'))) {
87 /* try $RLINE_COMPLETIONS_FILE */
88 if ((p = getenv("RLINE_COMPLETIONS_FILE"))) {
89 if (load_completions_file(p)) return;
92 /* try $RLINE_COMPLETIONS_DIR */
93 if ((p = getenv("RLINE_COMPLETIONS_DIR"))) {
94 asprintf(&fname, "%s/%s", p, command);
95 if (load_completions_file(fname)) {
102 /* try $HOME/.rline/ */
103 if ((p=getenv("HOME"))) {
104 asprintf(&fname, "%s/.rline/%s", p, command);
105 if (load_completions_file(fname)) {
112 /* try /usr/share/rline/ */
113 asprintf(&fname, "/usr/share/rline/%s", command);
114 if (load_completions_file(fname)) {
124 command completion generator, including multi-part completions
126 static char *command_generator(const char *line, int state)
136 /* find the next command that matches both the line so far and
137 the next part of the command */
138 for (;idx<num_commands;idx++) {
139 if (strncmp(rl_line_buffer, cmd_list[idx], cmd_offset) == 0) {
140 if (strcmp(cmd_list[idx] + cmd_offset, "[FILE]") == 0) {
141 /* we want filename completion for this one. This must
142 be the last completion */
143 rl_filename_completion_desired = 1;
144 rl_filename_quoting_desired = 1;
148 if (strncmp(line, cmd_list[idx] + cmd_offset, len) == 0) {
149 char *p = cmd_list[idx++] + cmd_offset;
150 /* return only the current part */
151 return strndup(p, strcspn(p, " "));
156 /* we don't want the filename completer */
157 rl_attempted_completion_over = 1;
164 our completion function, just an interface to our command generator
166 static char **completion_function(const char *line, int start, int end)
170 return (char **)completion_matches(line, command_generator);
173 /* used by line_handler */
176 /* callback function when readline has a whole line */
177 void line_handler(char *line)
180 /* send the line down the pipe to the command */
181 dprintf(child_fd, "%s\n", line);
183 /* only add non-empty lines to the history */
189 /* mirror echo mode from a slave terminal to our terminal */
190 static void mirror_echo_mode(int fd)
192 struct termios pterm1, pterm2;
194 if (tcgetattr(fd, &pterm1) != 0) return;
195 if (tcgetattr(0, &pterm2) != 0) return;
197 pterm2.c_lflag &= ~ICANON;
198 pterm2.c_lflag &= ~ECHO;
199 pterm2.c_lflag |= ISIG;
200 pterm2.c_cc[VMIN] = 1;
201 pterm2.c_cc[VTIME]=0;
202 pterm2.c_lflag &= ~(ICANON|ISIG|ECHO|ECHONL|ECHOCTL|
205 if (pterm1.c_lflag) {
206 pterm2.c_lflag |= ECHO;
208 pterm2.c_lflag &= ~ECHO;
210 tcsetattr(0, TCSANOW, &pterm2);
214 /* setup the slave side of a pty appropriately */
215 static void setup_terminal(int fd)
219 /* fetch the old settings */
220 if (tcgetattr(fd, &term) != 0) return;
223 term.c_cc[VTIME] = 0;
224 /* we don't want things like echo or other processing */
225 term.c_iflag |= IGNBRK;
226 term.c_lflag &= ~(ICANON|ISIG|ECHO|ECHONL|ECHOCTL|
229 tcsetattr(fd, TCSANOW, &term);
235 int main(int argc, char *argv[])
239 struct termios term, *pterm=NULL;
241 char slave_name[100];
243 if (argc < 2 || argv[1][0] == '-') {
248 /* load the completions list */
249 load_completions(argv[1]);
251 if (tcgetattr(0, &term) == 0) {
255 /* by using forkpty we give a true pty to the child, which means it should
256 behave the same as if run from a terminal */
257 pid = forkpty(&child_fd, slave_name, pterm, NULL);
259 if (pid == (pid_t)-1) {
264 /* the child just sets up its pty then executes the command */
267 execvp(argv[1], argv+1);
268 /* it failed?? maybe command not found */
273 slave_fd = open(slave_name, O_RDWR);
275 /* initial blank prompt */
278 /* install completion handling */
279 rl_already_prompted = 1;
280 rl_attempted_completion_function = completion_function;
281 rl_callback_handler_install(prompt, line_handler);
290 FD_SET(child_fd, &fds);
292 /* wait for some activity */
293 ret = select(child_fd+1, &fds, NULL, NULL, NULL);
294 if (ret == -1 && errno == EINTR) continue;
297 /* give any stdin to readline */
298 if (FD_ISSET(0, &fds)) {
299 rl_callback_read_char();
302 /* data from the program is used to intuit the
303 prompt. This works surprisingly well */
304 if (FD_ISSET(child_fd, &fds)) {
307 int n = read(child_fd, buf, sizeof(buf)-1);
311 /* send to standard output */
314 /* work out the next prompt */
315 p = strrchr(buf, '\n');
322 /* tell readline about the new prompt */
326 rl_set_prompt(prompt);
327 rl_on_new_line_with_prompt();
328 if (slave_fd != -1) {
329 mirror_echo_mode(slave_fd);
334 /* cleanup the terminal */
335 rl_callback_handler_remove();