/* PSPP - a program for statistical analysis.
- Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+ Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <config.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "libpspp/str.h"
+
+#if HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <termios.h>
+
+static char *history_file;
+
+static char **complete_command_name (const char *, int, int);
+static char **dont_complete (const char *, int, int);
+static char *command_generator (const char *text, int state);
+#endif
+
+
#include "ui/terminal/terminal-reader.h"
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
-#include <assert.h>
#include <errno.h>
-#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include "data/file-name.h"
#include "data/settings.h"
#include "language/command.h"
#include "language/lexer/lexer.h"
#include "libpspp/version.h"
#include "output/driver.h"
#include "output/journal.h"
-#include "ui/terminal/terminal.h"
#include "gl/minmax.h"
#include "gl/xalloc.h"
static void readline_init (void);
static void readline_done (void);
-static struct substring readline_read (enum prompt_style);
+static bool readline_read (struct substring *, enum prompt_style);
/* Display a welcoming message. */
static void
"it\nunder certain conditions; type \"show copying.\" to see the "
"conditions.\nThere is ABSOLUTELY NO WARRANTY for PSPP; type \"show "
"warranty.\" for details.\n", stdout);
- puts (stat_version);
- journal_enable ();
+ puts (announced_version);
+ journal_init ();
}
static struct terminal_reader *
return UP_CAST (r, struct terminal_reader, reader);
}
+
+/* Older libreadline versions do not provide rl_outstream.
+ However, it is almost always going to be the same as stdout. */
+#if ! HAVE_RL_OUTSTREAM
+# define rl_outstream stdout
+#endif
+
+
+#if HAVE_READLINE
+/* Similarly, rl_echo_signal_char is fairly recent.
+ We provide our own crude version if it is not present. */
+#if ! HAVE_RL_ECHO_SIGNAL_CHAR
+static void
+rl_echo_signal_char (int sig)
+{
+#if HAVE_TERMIOS_H
+ struct termios t;
+ if (0 == tcgetattr (0, &t))
+ {
+ cc_t c = t.c_cc[VINTR];
+
+ if (c >= 0 && c <= 'Z' - 'A')
+ fprintf (rl_outstream, "^%c", 'A' + c - 1);
+ else
+ fprintf (rl_outstream, "%c", c);
+ }
+ else
+#endif
+ fprintf (rl_outstream, "^C");
+
+ fflush (rl_outstream);
+}
+#endif
+#endif
+
+
static size_t
terminal_reader_read (struct lex_reader *r_, char *buf, size_t n,
enum prompt_style prompt_style)
output_flush ();
ss_dealloc (&r->s);
- r->s = readline_read (prompt_style);
+ if (! readline_read (&r->s, prompt_style))
+ {
+ *buf = '\n';
+ fprintf (rl_outstream, "\n");
+ return 1;
+ }
r->offset = 0;
r->eof = ss_is_empty (r->s);
-
- /* Check whether the size of the window has changed, so that
- the output drivers can adjust their settings as needed. We
- only do this for the first line of a command, as it's
- possible that the output drivers are actually in use
- afterward, and we don't want to confuse them in the middle
- of output. */
- if (prompt_style == PROMPT_FIRST)
- terminal_check_size ();
}
chunk = MIN (n, r->s.length - r->offset);
r = xzalloc (sizeof *r);
r->reader.class = &terminal_reader_class;
r->reader.syntax = LEX_SYNTAX_INTERACTIVE;
- r->reader.error = LEX_ERROR_INTERACTIVE;
+ r->reader.error = LEX_ERROR_TERMINAL;
r->reader.file_name = NULL;
r->s = ss_empty ();
r->offset = 0;
return &r->reader;
}
\f
-#if HAVE_READLINE
-#include <readline/readline.h>
-#include <readline/history.h>
-
-static char *history_file;
-
-static char **complete_command_name (const char *, int, int);
-static char **dont_complete (const char *, int, int);
-static char *command_generator (const char *text, int state);
-
-static void
-readline_init (void)
-{
- rl_basic_word_break_characters = "\n";
- using_history ();
- stifle_history (500);
- if (history_file == NULL)
- {
- const char *home_dir = getenv ("HOME");
- if (home_dir != NULL)
- {
- history_file = xasprintf ("%s/.pspp_history", home_dir);
- read_history (history_file);
- }
- }
-}
-static void
-readline_done (void)
-{
- if (history_file != NULL && false == settings_get_testing_mode () )
- write_history (history_file);
- clear_history ();
- free (history_file);
-}
static const char *
readline_prompt (enum prompt_style style)
NOT_REACHED ();
}
-static struct substring
-readline_read (enum prompt_style style)
+
+#if HAVE_READLINE
+
+static int pfd[2];
+static bool sigint_received ;
+
+/*
+ A function similar to getc from stdio.
+ However this one may be interrupted by SIGINT.
+ If that happens it will return EOF and the global variable
+ sigint_received will be set to true.
+ */
+static int
+interruptible_getc (FILE *fp)
+{
+ int c = 0;
+ int ret = -1;
+ do
+ {
+ struct timeval timeout = {0, 50000};
+ fd_set what;
+ int max_fd = 0;
+ int fd ;
+ FD_ZERO (&what);
+ fd = fileno (fp);
+ max_fd = (max_fd > fd) ? max_fd : fd;
+ FD_SET (fd, &what);
+ fd = pfd[0];
+ max_fd = (max_fd > fd) ? max_fd : fd;
+ FD_SET (fd, &what);
+ ret = select (max_fd + 1, &what, NULL, NULL, &timeout);
+ if (ret == -1 && errno != EINTR)
+ {
+ perror ("Select failed");
+ continue;
+ }
+
+ if (ret > 0)
+ {
+ if (FD_ISSET (pfd[0], &what))
+ {
+ char dummy[1];
+ read (pfd[0], dummy, 1);
+ sigint_received = true;
+ return EOF;
+ }
+ }
+ }
+ while (ret <= 0);
+
+ /* This will not block! */
+ read (fileno (fp), &c, 1);
+
+ return c;
+}
+
+static void
+handler (int sig)
+{
+ rl_end = 0;
+
+ write (pfd[1], "x", 1);
+ rl_echo_signal_char (sig);
+}
+
+
+static void
+readline_init (void)
+{
+ if (0 != pipe2 (pfd, O_NONBLOCK))
+ perror ("Cannot create pipe");
+
+ if (SIG_ERR == signal (SIGINT, handler))
+ perror ("Cannot add signal handler");
+
+ rl_catch_signals = 0;
+ rl_getc_function = interruptible_getc;
+ rl_basic_word_break_characters = "\n";
+ using_history ();
+ stifle_history (500);
+ if (history_file == NULL)
+ {
+ const char *home_dir = getenv ("HOME");
+ if (home_dir != NULL)
+ {
+ history_file = xasprintf ("%s/.pspp_history", home_dir);
+ read_history (history_file);
+ }
+ }
+}
+
+static void
+readline_done (void)
+{
+ if (history_file != NULL && false == settings_get_testing_mode ())
+ write_history (history_file);
+ clear_history ();
+ free (history_file);
+}
+
+/* Prompt the user for a line of input and return it in LINE.
+ Returns true if the LINE should be considered valid, false otherwise.
+ */
+static bool
+readline_read (struct substring *line, enum prompt_style style)
{
char *string;
rl_attempted_completion_function = (style == PROMPT_FIRST
? complete_command_name
: dont_complete);
+ sigint_received = false;
string = readline (readline_prompt (style));
+ if (sigint_received)
+ {
+ *line = ss_empty ();
+ return false;
+ }
+
if (string != NULL)
{
char *end;
end = strchr (string, '\0');
*end = '\n';
- return ss_buffer (string, end - string + 1);
+ *line = ss_buffer (string, end - string + 1);
}
else
- return ss_empty ();
+ *line = ss_empty ();
+
+ return true;
}
/* Returns a set of command name completions for TEXT.
if (state == 0)
cmd = NULL;
name = cmd_complete (text, &cmd);
- return name ? xstrdup (name) : NULL;
+ return xstrdup_if_nonnull (name);
}
+
#else /* !HAVE_READLINE */
+
+static const char * the_prompt;
+
+static void
+handler (int sig)
+{
+ if (the_prompt)
+ fputs (the_prompt, stdout);
+ fflush (stdout);
+}
+
static void
readline_init (void)
{
+ if (SIG_ERR == signal (SIGINT, handler))
+ perror ("Cannot add signal handler");
}
static void
{
}
-static struct substring
-readline_read (enum prompt_style style)
+/* Prompt the user for a line of input and return it in LINE.
+ Returns true if the LINE should be considered valid, false otherwise.
+ */
+static bool
+readline_read (struct substring *line, enum prompt_style style)
{
- const char *prompt = prompt_get (style);
- struct string line;
+ struct string string;
+ the_prompt = readline_prompt (style);
- fputs (prompt, stdout);
+ fputs (the_prompt, stdout);
fflush (stdout);
- ds_init_empty (&line);
- ds_read_line (&line, stdin, SIZE_MAX);
+ ds_init_empty (&string);
+ ds_read_line (&string, stdin, SIZE_MAX);
+
+ *line = string.ss;
- return line.ss;
+ return true;
}
#endif /* !HAVE_READLINE */