mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 20:00:19 +02:00
As the Autoconf documentation says, "These days portable programs [...] should not rely on `HAVE_RESTARTABLE_SYSCALLS', since nowadays whether a system call is restartable is a dynamic issue, not a configuration-time issue." In other words, if we ever rely on HAVE_RESTARTABLE_SYSCALLS, we are at the mercy of any code that Guile happens to be linked with, because that code could install a signal handler without the SA_RESTART flag, and then a Guile system call could unexpectedly return EINTR. The readline part of this goes back to this problem report: http://sources.redhat.com/ml/guile/2000-05/msg00177.html; and is an excellent example of the above paragraph. It was noted during the discussion that undefining HAVE_RESTARTABLE_SYSCALLS would fix the problem, but that solution wasn't adopted - I guess because Guile was still using cooperative threads then (not pthreads) and so there was a significant concern (whether founded or not) that not using restartable syscalls (where available) could lead to a loss of performance. Now Guile's default mode of operation is with pthreads, where we already don't assume that HAVE_RESTARTABLE_SYSCALLS is reliable, so there is no possible further performance loss. And in any case we really have no choice, if we want correct operation. Thanks to Sylvain Beucler for reporting this and suggesting the fix. * configure.in (AC_SYS_RESTARTABLE_SYSCALLS): Removed. * doc/ref/posix.texi (Signals): Remove statement that Guile always sets SA_RESTART flag. * guile-readline/configure.in (GUILE_SIGWINCH_SA_RESTART_CLEARED): Remove this setting, together with its test code. (HAVE_RL_PRE_INPUT_HOOK): Remove this setting and its code, as no longer needed. * guile-readline/readline.c (sigwinch_enable_restart): Removed. (scm_init_readline): Remove setting of rl_pre_input_hook. * libguile/_scm.h (SCM_SYSCALL): Remove the definition that relies on HAVE_RESTARTABLE_SYSCALLS. * libguile/scmsigs.c (scm_sigaction_for_thread): Don't always set the SA_RESTART flag if available. Update docstring accordingly. (scm_init_scmsigs): Remove code that sets SA_RESTART flag for all signals. * THANKS: Add Sylvain.
567 lines
12 KiB
C
567 lines
12 KiB
C
/* readline.c --- line editing support for Guile */
|
||
|
||
/* Copyright (C) 1997,1999,2000,2001, 2002, 2003, 2006, 2007, 2008 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
|
||
* the Free Software Foundation; either version 3, or (at your option)
|
||
* any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this software; see the file COPYING. If not, write to
|
||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
* Boston, MA 02110-1301 USA
|
||
*
|
||
*/
|
||
|
||
|
||
|
||
|
||
/* Include private, configure generated header (i.e. config.h). */
|
||
#include "guile-readline-config.h"
|
||
|
||
#ifdef HAVE_RL_GETC_FUNCTION
|
||
#include "libguile.h"
|
||
#include "libguile/iselect.h"
|
||
|
||
#include <stdio.h>
|
||
#ifdef HAVE_UNISTD_H
|
||
#include <unistd.h>
|
||
#endif
|
||
#include <readline/readline.h>
|
||
#include <readline/history.h>
|
||
#ifndef __MINGW32__
|
||
#include <sys/time.h>
|
||
#else
|
||
#include <io.h>
|
||
#endif
|
||
#include <signal.h>
|
||
|
||
#include "libguile/validate.h"
|
||
#include "guile-readline/readline.h"
|
||
|
||
scm_t_option scm_readline_opts[] = {
|
||
{ SCM_OPTION_BOOLEAN, "history-file", 1,
|
||
"Use history file." },
|
||
{ SCM_OPTION_INTEGER, "history-length", 200,
|
||
"History length." },
|
||
{ SCM_OPTION_INTEGER, "bounce-parens", 500,
|
||
"Time (ms) to show matching opening parenthesis (0 = off)."},
|
||
{ 0 }
|
||
};
|
||
|
||
extern void stifle_history (int max);
|
||
|
||
SCM_DEFINE (scm_readline_options, "readline-options-interface", 0, 1, 0,
|
||
(SCM setting),
|
||
"")
|
||
#define FUNC_NAME s_scm_readline_options
|
||
{
|
||
SCM ans = scm_options (setting,
|
||
scm_readline_opts,
|
||
FUNC_NAME);
|
||
stifle_history (SCM_HISTORY_LENGTH);
|
||
return ans;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
#ifndef HAVE_STRDUP
|
||
static char *
|
||
strdup (char *s)
|
||
{
|
||
size_t len = strlen (s);
|
||
char *new = malloc (len + 1);
|
||
strcpy (new, s);
|
||
return new;
|
||
}
|
||
#endif /* HAVE_STRDUP */
|
||
|
||
#ifndef HAVE_RL_CLEANUP_AFTER_SIGNAL
|
||
|
||
/* These are readline functions added in release 2.3. They will work
|
||
* together with readline-2.1 and 2.2. (The readline interface is
|
||
* disabled for earlier releases.)
|
||
* They are declared static; if we want to use them elsewhere, then
|
||
* we need external declarations for them, but at the moment, I don't
|
||
* think anything else in Guile ought to use these.
|
||
*/
|
||
|
||
extern void _rl_clean_up_for_exit ();
|
||
extern void _rl_kill_kbd_macro ();
|
||
extern int _rl_init_argument ();
|
||
|
||
void
|
||
rl_cleanup_after_signal ()
|
||
{
|
||
#ifdef HAVE_RL_CLEAR_SIGNALS
|
||
_rl_clean_up_for_exit ();
|
||
#endif
|
||
(*rl_deprep_term_function) ();
|
||
#ifdef HAVE_RL_CLEAR_SIGNALS
|
||
rl_clear_signals ();
|
||
#endif
|
||
rl_pending_input = 0;
|
||
}
|
||
|
||
void
|
||
rl_free_line_state ()
|
||
{
|
||
register HIST_ENTRY *entry;
|
||
|
||
free_undo_list ();
|
||
|
||
entry = current_history ();
|
||
if (entry)
|
||
entry->data = (char *)NULL;
|
||
|
||
_rl_kill_kbd_macro ();
|
||
rl_clear_message ();
|
||
_rl_init_argument ();
|
||
}
|
||
|
||
#endif /* !HAVE_RL_CLEANUP_AFTER_SIGNAL */
|
||
|
||
static int promptp;
|
||
static SCM input_port;
|
||
static SCM before_read;
|
||
|
||
static int
|
||
current_input_getc (FILE *in SCM_UNUSED)
|
||
{
|
||
if (promptp && scm_is_true (before_read))
|
||
{
|
||
scm_apply (before_read, SCM_EOL, SCM_EOL);
|
||
promptp = 0;
|
||
}
|
||
return scm_getc (input_port);
|
||
}
|
||
|
||
static int in_readline = 0;
|
||
static SCM reentry_barrier_mutex;
|
||
|
||
static SCM internal_readline (SCM text);
|
||
static SCM handle_error (void *data, SCM tag, SCM args);
|
||
static void reentry_barrier (void);
|
||
|
||
|
||
SCM_DEFINE (scm_readline, "%readline", 0, 4, 0,
|
||
(SCM text, SCM inp, SCM outp, SCM read_hook),
|
||
"")
|
||
#define FUNC_NAME s_scm_readline
|
||
{
|
||
SCM ans;
|
||
|
||
reentry_barrier ();
|
||
|
||
before_read = SCM_BOOL_F;
|
||
|
||
if (!SCM_UNBNDP (text))
|
||
{
|
||
if (!scm_is_string (text))
|
||
{
|
||
--in_readline;
|
||
scm_wrong_type_arg (s_scm_readline, SCM_ARG1, text);
|
||
}
|
||
}
|
||
|
||
if (!((SCM_UNBNDP (inp) && SCM_OPINFPORTP (scm_current_input_port ()))
|
||
|| SCM_OPINFPORTP (inp)))
|
||
{
|
||
--in_readline;
|
||
scm_misc_error (s_scm_readline,
|
||
"Input port is not open or not a file port",
|
||
SCM_EOL);
|
||
}
|
||
|
||
if (!((SCM_UNBNDP (outp) && SCM_OPOUTFPORTP (scm_current_output_port ()))
|
||
|| SCM_OPOUTFPORTP (outp)))
|
||
{
|
||
--in_readline;
|
||
scm_misc_error (s_scm_readline,
|
||
"Output port is not open or not a file port",
|
||
SCM_EOL);
|
||
}
|
||
|
||
if (!(SCM_UNBNDP (read_hook) || scm_is_false (read_hook)))
|
||
{
|
||
if (scm_is_false (scm_thunk_p (read_hook)))
|
||
{
|
||
--in_readline;
|
||
scm_wrong_type_arg (s_scm_readline, SCM_ARG4, read_hook);
|
||
}
|
||
before_read = read_hook;
|
||
}
|
||
|
||
scm_readline_init_ports (inp, outp);
|
||
|
||
ans = scm_internal_catch (SCM_BOOL_T,
|
||
(scm_t_catch_body) internal_readline,
|
||
(void *) SCM_UNPACK (text),
|
||
handle_error, 0);
|
||
|
||
#ifndef __MINGW32__
|
||
fclose (rl_instream);
|
||
fclose (rl_outstream);
|
||
#endif
|
||
|
||
--in_readline;
|
||
return ans;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
|
||
static void
|
||
reentry_barrier ()
|
||
{
|
||
int reentryp = 0;
|
||
/* We should rather use scm_try_mutex when it becomes available */
|
||
scm_lock_mutex (reentry_barrier_mutex);
|
||
if (in_readline)
|
||
reentryp = 1;
|
||
else
|
||
++in_readline;
|
||
scm_unlock_mutex (reentry_barrier_mutex);
|
||
if (reentryp)
|
||
scm_misc_error (s_scm_readline, "readline is not reentrant", SCM_EOL);
|
||
}
|
||
|
||
static SCM
|
||
handle_error (void *data, SCM tag, SCM args)
|
||
{
|
||
rl_free_line_state ();
|
||
rl_cleanup_after_signal ();
|
||
fputc ('\n', rl_outstream); /* We don't want next output on this line */
|
||
#ifndef __MINGW32__
|
||
fclose (rl_instream);
|
||
fclose (rl_outstream);
|
||
#endif
|
||
--in_readline;
|
||
scm_handle_by_throw (data, tag, args);
|
||
return SCM_UNSPECIFIED; /* never reached */
|
||
}
|
||
|
||
static SCM
|
||
internal_readline (SCM text)
|
||
{
|
||
SCM ret;
|
||
char *s;
|
||
char *prompt = SCM_UNBNDP (text) ? "" : scm_to_locale_string (text);
|
||
|
||
promptp = 1;
|
||
s = readline (prompt);
|
||
if (s)
|
||
ret = scm_from_locale_string (s);
|
||
else
|
||
ret = SCM_EOF_VAL;
|
||
|
||
if (!SCM_UNBNDP (text))
|
||
free (prompt);
|
||
free (s);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static FILE *
|
||
stream_from_fport (SCM port, char *mode, const char *subr)
|
||
{
|
||
int fd;
|
||
FILE *f;
|
||
|
||
fd = dup (((struct scm_t_fport *) SCM_STREAM (port))->fdes);
|
||
if (fd == -1)
|
||
{
|
||
--in_readline;
|
||
scm_syserror (subr);
|
||
}
|
||
|
||
f = fdopen (fd, mode);
|
||
if (f == NULL)
|
||
{
|
||
--in_readline;
|
||
scm_syserror (subr);
|
||
}
|
||
|
||
return f;
|
||
}
|
||
|
||
void
|
||
scm_readline_init_ports (SCM inp, SCM outp)
|
||
{
|
||
if (SCM_UNBNDP (inp))
|
||
inp = scm_current_input_port ();
|
||
|
||
if (SCM_UNBNDP (outp))
|
||
outp = scm_current_output_port ();
|
||
|
||
if (!SCM_OPINFPORTP (inp)) {
|
||
scm_misc_error (0,
|
||
"Input port is not open or not a file port",
|
||
SCM_EOL);
|
||
}
|
||
|
||
if (!SCM_OPOUTFPORTP (outp)) {
|
||
scm_misc_error (0,
|
||
"Output port is not open or not a file port",
|
||
SCM_EOL);
|
||
}
|
||
|
||
input_port = inp;
|
||
#ifndef __MINGW32__
|
||
rl_instream = stream_from_fport (inp, "r", s_scm_readline);
|
||
rl_outstream = stream_from_fport (outp, "w", s_scm_readline);
|
||
#endif
|
||
}
|
||
|
||
|
||
|
||
SCM_DEFINE (scm_add_history, "add-history", 1, 0, 0,
|
||
(SCM text),
|
||
"")
|
||
#define FUNC_NAME s_scm_add_history
|
||
{
|
||
char* s;
|
||
|
||
s = scm_to_locale_string (text);
|
||
add_history (s);
|
||
free (s);
|
||
|
||
return SCM_UNSPECIFIED;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
|
||
SCM_DEFINE (scm_read_history, "read-history", 1, 0, 0,
|
||
(SCM file),
|
||
"")
|
||
#define FUNC_NAME s_scm_read_history
|
||
{
|
||
char *filename;
|
||
SCM ret;
|
||
|
||
filename = scm_to_locale_string (file);
|
||
ret = scm_from_bool (!read_history (filename));
|
||
free (filename);
|
||
return ret;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
|
||
SCM_DEFINE (scm_write_history, "write-history", 1, 0, 0,
|
||
(SCM file),
|
||
"")
|
||
#define FUNC_NAME s_scm_write_history
|
||
{
|
||
char *filename;
|
||
SCM ret;
|
||
|
||
filename = scm_to_locale_string (file);
|
||
ret = scm_from_bool (!write_history (filename));
|
||
free (filename);
|
||
return ret;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
SCM_DEFINE (scm_clear_history, "clear-history", 0, 0, 0,
|
||
(),
|
||
"Clear the history buffer of the readline machinery.")
|
||
#define FUNC_NAME s_scm_clear_history
|
||
{
|
||
clear_history();
|
||
return SCM_UNSPECIFIED;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
|
||
SCM_DEFINE (scm_filename_completion_function, "filename-completion-function", 2, 0, 0,
|
||
(SCM text, SCM continuep),
|
||
"")
|
||
#define FUNC_NAME s_scm_filename_completion_function
|
||
{
|
||
char *s;
|
||
SCM ans;
|
||
char *c_text = scm_to_locale_string (text);
|
||
#ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
|
||
s = rl_filename_completion_function (c_text, scm_is_true (continuep));
|
||
#else
|
||
s = filename_completion_function (c_text, scm_is_true (continuep));
|
||
#endif
|
||
ans = scm_take_locale_string (s);
|
||
free (c_text);
|
||
return ans;
|
||
}
|
||
#undef FUNC_NAME
|
||
|
||
/*
|
||
* The following has been modified from code contributed by
|
||
* Andrew Archibald <aarchiba@undergrad.math.uwaterloo.ca>
|
||
*/
|
||
|
||
SCM scm_readline_completion_function_var;
|
||
|
||
static char *
|
||
completion_function (char *text, int continuep)
|
||
{
|
||
SCM compfunc = SCM_VARIABLE_REF (scm_readline_completion_function_var);
|
||
SCM res;
|
||
|
||
if (scm_is_false (compfunc))
|
||
return NULL; /* #f => completion disabled */
|
||
else
|
||
{
|
||
SCM t = scm_from_locale_string (text);
|
||
SCM c = scm_from_bool (continuep);
|
||
res = scm_apply (compfunc, scm_list_2 (t, c), SCM_EOL);
|
||
|
||
if (scm_is_false (res))
|
||
return NULL;
|
||
|
||
return scm_to_locale_string (res);
|
||
}
|
||
}
|
||
|
||
#if HAVE_RL_GET_KEYMAP
|
||
/*Bouncing parenthesis (reimplemented by GH, 11/23/98, since readline is strict gpl)*/
|
||
|
||
static int match_paren (int x, int k);
|
||
static int find_matching_paren (int k);
|
||
static void init_bouncing_parens ();
|
||
|
||
static void
|
||
init_bouncing_parens ()
|
||
{
|
||
if (strncmp (rl_get_keymap_name (rl_get_keymap ()), "vi", 2))
|
||
{
|
||
rl_bind_key (')', match_paren);
|
||
rl_bind_key (']', match_paren);
|
||
rl_bind_key ('}', match_paren);
|
||
}
|
||
}
|
||
|
||
static int
|
||
find_matching_paren(int k)
|
||
{
|
||
register int i;
|
||
register char c = 0;
|
||
int end_parens_found = 0;
|
||
|
||
/* Choose the corresponding opening bracket. */
|
||
if (k == ')') c = '(';
|
||
else if (k == ']') c = '[';
|
||
else if (k == '}') c = '{';
|
||
|
||
for (i=rl_point-2; i>=0; i--)
|
||
{
|
||
/* Is the current character part of a character literal? */
|
||
if (i - 2 >= 0
|
||
&& rl_line_buffer[i - 1] == '\\'
|
||
&& rl_line_buffer[i - 2] == '#')
|
||
;
|
||
else if (rl_line_buffer[i] == k)
|
||
end_parens_found++;
|
||
else if (rl_line_buffer[i] == '"')
|
||
{
|
||
/* Skip over a string literal. */
|
||
for (i--; i >= 0; i--)
|
||
if (rl_line_buffer[i] == '"'
|
||
&& ! (i - 1 >= 0
|
||
&& rl_line_buffer[i - 1] == '\\'))
|
||
break;
|
||
}
|
||
else if (rl_line_buffer[i] == c)
|
||
{
|
||
if (end_parens_found==0)
|
||
return i;
|
||
else --end_parens_found;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static int
|
||
match_paren (int x, int k)
|
||
{
|
||
int tmp;
|
||
#ifndef __MINGW32__
|
||
int fno;
|
||
SELECT_TYPE readset;
|
||
struct timeval timeout;
|
||
#endif
|
||
|
||
rl_insert (x, k);
|
||
if (!SCM_READLINE_BOUNCE_PARENS)
|
||
return 0;
|
||
|
||
/* Did we just insert a quoted paren? If so, then don't bounce. */
|
||
if (rl_point - 1 >= 1
|
||
&& rl_line_buffer[rl_point - 2] == '\\')
|
||
return 0;
|
||
|
||
#ifndef __MINGW32__
|
||
tmp = 1000 * SCM_READLINE_BOUNCE_PARENS;
|
||
timeout.tv_sec = tmp / 1000000;
|
||
timeout.tv_usec = tmp % 1000000;
|
||
FD_ZERO (&readset);
|
||
fno = fileno (rl_instream);
|
||
FD_SET (fno, &readset);
|
||
#endif
|
||
|
||
if (rl_point > 1)
|
||
{
|
||
tmp = rl_point;
|
||
rl_point = find_matching_paren (k);
|
||
if (rl_point > -1)
|
||
{
|
||
rl_redisplay ();
|
||
#ifndef __MINGW32__
|
||
scm_std_select (fno + 1, &readset, NULL, NULL, &timeout);
|
||
#else
|
||
WaitForSingleObject (GetStdHandle(STD_INPUT_HANDLE),
|
||
SCM_READLINE_BOUNCE_PARENS);
|
||
#endif
|
||
}
|
||
rl_point = tmp;
|
||
}
|
||
return 0;
|
||
}
|
||
#endif /* HAVE_RL_GET_KEYMAP */
|
||
|
||
#endif /* HAVE_RL_GETC_FUNCTION */
|
||
|
||
void
|
||
scm_init_readline ()
|
||
{
|
||
#ifdef HAVE_RL_GETC_FUNCTION
|
||
#include "guile-readline/readline.x"
|
||
scm_readline_completion_function_var
|
||
= scm_c_define ("*readline-completion-function*", SCM_BOOL_F);
|
||
#ifndef __MINGW32__
|
||
rl_getc_function = current_input_getc;
|
||
#endif
|
||
#if defined (_RL_FUNCTION_TYPEDEF)
|
||
rl_completion_entry_function = (rl_compentry_func_t*) completion_function;
|
||
#else
|
||
rl_completion_entry_function = (Function*) completion_function;
|
||
#endif
|
||
rl_basic_word_break_characters = "\t\n\"'`;()";
|
||
rl_readline_name = "Guile";
|
||
|
||
reentry_barrier_mutex = scm_permanent_object (scm_make_mutex ());
|
||
scm_init_opts (scm_readline_options,
|
||
scm_readline_opts);
|
||
#if HAVE_RL_GET_KEYMAP
|
||
init_bouncing_parens();
|
||
#endif
|
||
scm_add_feature ("readline");
|
||
#endif /* HAVE_RL_GETC_FUNCTION */
|
||
}
|
||
|
||
/*
|
||
Local Variables:
|
||
c-file-style: "gnu"
|
||
End:
|
||
*/
|