b8c71447ce5c7563c6bde942f7bf9bde18767723
[pspp] / src / ui / terminal / terminal-reader.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <signal.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <stdbool.h>
23 #include <stdio.h>
24
25
26 #if HAVE_READLINE
27 #include <readline/readline.h>
28 #include <readline/history.h>
29 #include <termios.h>
30
31 static char *history_file;
32
33 static char **complete_command_name (const char *, int, int);
34 static char **dont_complete (const char *, int, int);
35 static char *command_generator (const char *text, int state);
36 #endif
37
38
39 #include "ui/terminal/terminal-reader.h"
40 #include <sys/select.h>
41 #include <sys/time.h>
42 #include <sys/types.h>
43
44 #include <assert.h>
45 #include <errno.h>
46 #include <stdint.h>
47 #include <stdlib.h>
48
49 #include "data/settings.h"
50 #include "language/command.h"
51 #include "language/lexer/lexer.h"
52 #include "libpspp/assertion.h"
53 #include "libpspp/cast.h"
54 #include "libpspp/message.h"
55 #include "libpspp/prompt.h"
56 #include "libpspp/str.h"
57 #include "libpspp/version.h"
58 #include "output/driver.h"
59 #include "output/journal.h"
60 #include "ui/terminal/terminal.h"
61
62 #include "gl/minmax.h"
63 #include "gl/xalloc.h"
64
65 #include "gettext.h"
66 #define _(msgid) gettext (msgid)
67
68 struct terminal_reader
69   {
70     struct lex_reader reader;
71     struct substring s;
72     size_t offset;
73     bool eof;
74   };
75
76 static int n_terminal_readers;
77
78 static void readline_init (void);
79 static void readline_done (void);
80 static bool readline_read (struct substring *, enum prompt_style);
81
82 /* Display a welcoming message. */
83 static void
84 welcome (void)
85 {
86   static bool welcomed = false;
87   if (welcomed)
88     return;
89   welcomed = true;
90   fputs ("PSPP is free software and you are welcome to distribute copies of "
91          "it\nunder certain conditions; type \"show copying.\" to see the "
92          "conditions.\nThere is ABSOLUTELY NO WARRANTY for PSPP; type \"show "
93          "warranty.\" for details.\n", stdout);
94   puts (announced_version);
95   journal_init ();
96 }
97
98 static struct terminal_reader *
99 terminal_reader_cast (struct lex_reader *r)
100 {
101   return UP_CAST (r, struct terminal_reader, reader);
102 }
103
104
105 /* Older libreadline versions do not provide rl_outstream.
106    However, it is almost always going to be the same as stdout. */
107 #if ! HAVE_RL_OUTSTREAM
108 # define rl_outstream stdout
109 #endif
110
111
112 #if HAVE_READLINE
113 /* Similarly, rl_echo_signal_char is fairly recent.
114    We provide our own crude version if it is not present. */
115 #if ! HAVE_RL_ECHO_SIGNAL_CHAR
116 static void
117 rl_echo_signal_char (int sig)
118 {
119 #if HAVE_TERMIOS_H
120   struct termios t;
121   if (0 == tcgetattr (0, &t))
122     {
123       cc_t c = t.c_cc[VINTR];
124
125       if (c >= 0  && c <= 'Z' - 'A')
126         fprintf (rl_outstream, "^%c", 'A' + c - 1);
127       else
128         fprintf (rl_outstream, "%c", c);
129     }
130   else
131 #endif
132     fprintf (rl_outstream, "^C");
133
134   fflush (rl_outstream);
135 }
136 #endif
137 #endif
138
139
140 static size_t
141 terminal_reader_read (struct lex_reader *r_, char *buf, size_t n,
142                       enum prompt_style prompt_style)
143 {
144   struct terminal_reader *r = terminal_reader_cast (r_);
145   size_t chunk;
146
147   if (r->offset >= r->s.length && !r->eof)
148     {
149       welcome ();
150       msg_ui_reset_counts ();
151       output_flush ();
152
153       ss_dealloc (&r->s);
154       if (! readline_read (&r->s, prompt_style))
155         {
156           *buf = '\n';
157           fprintf (rl_outstream, "\n");
158           return 1;
159         }
160       r->offset = 0;
161       r->eof = ss_is_empty (r->s);
162
163       /* Check whether the size of the window has changed, so that
164          the output drivers can adjust their settings as needed.  We
165          only do this for the first line of a command, as it's
166          possible that the output drivers are actually in use
167          afterward, and we don't want to confuse them in the middle
168          of output. */
169       if (prompt_style == PROMPT_FIRST)
170         terminal_check_size ();
171     }
172
173   chunk = MIN (n, r->s.length - r->offset);
174   memcpy (buf, r->s.string + r->offset, chunk);
175   r->offset += chunk;
176   return chunk;
177 }
178
179 static void
180 terminal_reader_close (struct lex_reader *r_)
181 {
182   struct terminal_reader *r = terminal_reader_cast (r_);
183
184   ss_dealloc (&r->s);
185   free (r->reader.file_name);
186   free (r);
187
188   if (!--n_terminal_readers)
189     readline_done ();
190 }
191
192 static struct lex_reader_class terminal_reader_class =
193   {
194     terminal_reader_read,
195     terminal_reader_close
196   };
197
198 /* Creates a source which uses readln to get its line */
199 struct lex_reader *
200 terminal_reader_create (void)
201 {
202   struct terminal_reader *r;
203
204   if (!n_terminal_readers++)
205     readline_init ();
206
207   r = xzalloc (sizeof *r);
208   r->reader.class = &terminal_reader_class;
209   r->reader.syntax = LEX_SYNTAX_INTERACTIVE;
210   r->reader.error = LEX_ERROR_TERMINAL;
211   r->reader.file_name = NULL;
212   r->s = ss_empty ();
213   r->offset = 0;
214   r->eof = false;
215   return &r->reader;
216 }
217 \f
218
219
220 static const char *
221 readline_prompt (enum prompt_style style)
222 {
223   switch (style)
224     {
225     case PROMPT_FIRST:
226       return "PSPP> ";
227
228     case PROMPT_LATER:
229       return "    > ";
230
231     case PROMPT_DATA:
232       return "data> ";
233
234     case PROMPT_COMMENT:
235       return "comment> ";
236
237     case PROMPT_DOCUMENT:
238       return "document> ";
239
240     case PROMPT_DO_REPEAT:
241       return "DO REPEAT> ";
242     }
243
244   NOT_REACHED ();
245 }
246
247
248 #if HAVE_READLINE
249
250 static int pfd[2];
251 static bool sigint_received ;
252
253 /*
254    A function similar to getc from stdio.
255    However this one may be interrupted by SIGINT.
256    If that happens it will return EOF and the global variable
257    sigint_received will be set to true.
258  */
259 static int
260 interruptible_getc (FILE *fp)
261 {
262   int c  = 0;
263   int ret = -1;
264   do
265     {
266       struct timeval timeout = {0, 50000};
267       fd_set what;
268       int max_fd = 0;
269       int fd ;
270       FD_ZERO (&what);
271       fd = fileno (fp);
272       max_fd = (max_fd > fd) ? max_fd : fd;
273       FD_SET (fd, &what);
274       fd = pfd[0];
275       max_fd = (max_fd > fd) ? max_fd : fd;
276       FD_SET (fd, &what);
277       ret = select (max_fd + 1, &what, NULL, NULL, &timeout);
278       if ( ret == -1 && errno != EINTR)
279         {
280           perror ("Select failed");
281           continue;
282         }
283
284       if (ret > 0 )
285         {
286           if (FD_ISSET (pfd[0], &what))
287             {
288               char dummy[1];
289               read (pfd[0], dummy, 1);
290               sigint_received = true;
291               return EOF;
292             }
293         }
294     }
295   while (ret <= 0);
296
297   /* This will not block! */
298   read (fileno (fp), &c, 1);
299
300   return c;
301 }
302
303 static void
304 handler (int sig)
305 {
306   rl_end = 0;
307
308   write (pfd[1], "x", 1);
309   rl_echo_signal_char (sig);
310 }
311
312
313 static void
314 readline_init (void)
315 {
316   if ( 0 != pipe2 (pfd, O_NONBLOCK))
317     perror ("Cannot create pipe");
318
319   if ( SIG_ERR == signal (SIGINT, handler))
320     perror ("Cannot add signal handler");
321
322   rl_catch_signals = 0;
323   rl_getc_function = interruptible_getc;
324   rl_basic_word_break_characters = "\n";
325   using_history ();
326   stifle_history (500);
327   if (history_file == NULL)
328     {
329       const char *home_dir = getenv ("HOME");
330       if (home_dir != NULL)
331         {
332           history_file = xasprintf ("%s/.pspp_history", home_dir);
333           read_history (history_file);
334         }
335     }
336 }
337
338 static void
339 readline_done (void)
340 {
341   if (history_file != NULL && false == settings_get_testing_mode () )
342     write_history (history_file);
343   clear_history ();
344   free (history_file);
345 }
346
347 /* Prompt the user for a line of input and return it in LINE.
348    Returns true if the LINE should be considered valid, false otherwise.
349  */
350 static bool
351 readline_read (struct substring *line, enum prompt_style style)
352 {
353   char *string;
354
355   rl_attempted_completion_function = (style == PROMPT_FIRST
356                                       ? complete_command_name
357                                       : dont_complete);
358   sigint_received = false;
359   string = readline (readline_prompt (style));
360   if (sigint_received)
361     {
362       *line = ss_empty ();
363       return false;
364     }
365
366   if (string != NULL)
367     {
368       char *end;
369
370       if (string[0])
371         add_history (string);
372
373       end = strchr (string, '\0');
374       *end = '\n';
375       *line = ss_buffer (string, end - string + 1);
376     }
377   else
378     *line = ss_empty ();
379
380   return true;
381 }
382
383 /* Returns a set of command name completions for TEXT.
384    This is of the proper form for assigning to
385    rl_attempted_completion_function. */
386 static char **
387 complete_command_name (const char *text, int start, int end UNUSED)
388 {
389   if (start == 0)
390     {
391       /* Complete command name at start of line. */
392       return rl_completion_matches (text, command_generator);
393     }
394   else
395     {
396       /* Otherwise don't do any completion. */
397       rl_attempted_completion_over = 1;
398       return NULL;
399     }
400 }
401
402 /* Do not do any completion for TEXT. */
403 static char **
404 dont_complete (const char *text UNUSED, int start UNUSED, int end UNUSED)
405 {
406   rl_attempted_completion_over = 1;
407   return NULL;
408 }
409
410 /* If STATE is 0, returns the first command name matching TEXT.
411    Otherwise, returns the next command name matching TEXT.
412    Returns a null pointer when no matches are left. */
413 static char *
414 command_generator (const char *text, int state)
415 {
416   static const struct command *cmd;
417   const char *name;
418
419   if (state == 0)
420     cmd = NULL;
421   name = cmd_complete (text, &cmd);
422   return name ? xstrdup (name) : NULL;
423 }
424
425 #else  /* !HAVE_READLINE */
426
427 static void
428 readline_init (void)
429 {
430 }
431
432 static void
433 readline_done (void)
434 {
435 }
436
437 static bool
438 readline_read (struct substring *line, enum prompt_style style)
439 {
440   struct string string;
441   const char *prompt = readline_prompt (style);
442
443   fputs (prompt, stdout);
444   fflush (stdout);
445   ds_init_empty (&string);
446   ds_read_line (&string, stdin, SIZE_MAX);
447
448   *line = string.ss;
449
450   return false;
451 }
452 #endif /* !HAVE_READLINE */