From 3a85e0bc9c72935ca9d154b3aa3093c31ed1836e Mon Sep 17 00:00:00 2001 From: Ken Hornstein Date: Wed, 21 Mar 2012 15:27:15 -0400 Subject: [PATCH] Support for readline command history, editing, and completion at the WhatNow? prompt. Based on work by Steve Rader. --- Makefile.am | 12 +-- configure.ac | 2 + docs/pending-release-notes | 2 + h/prototypes.h | 3 + m4/readline.m4 | 25 ++++++ sbr/getansreadline.c | 212 ++++++++++++++++++++++++++++++++++++++++++++ uip/whatnowsbr.c | 4 + 7 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 m4/readline.m4 create mode 100644 sbr/getansreadline.c diff --git a/Makefile.am b/Makefile.am index 0727fea..14d1dd1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -230,11 +230,11 @@ uip_burst_SOURCES = uip/burst.c uip_comp_SOURCES = uip/comp.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \ uip/annosbr.c uip/distsbr.c -uip_comp_LDADD = $(LDADD) $(ICONVLIB) +uip_comp_LDADD = $(LDADD) $(ICONVLIB) $(READLINELIB) uip_dist_SOURCES = uip/dist.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \ uip/annosbr.c uip/distsbr.c uip/forwsbr.c -uip_dist_LDADD = $(LDADD) $(ICONVLIB) +uip_dist_LDADD = $(LDADD) $(ICONVLIB) $(READLINELIB) uip_flist_SOURCES = uip/flist.c @@ -242,7 +242,7 @@ uip_folder_SOURCES = uip/folder.c uip_forw_SOURCES = uip/forw.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \ uip/annosbr.c uip/distsbr.c uip/forwsbr.c -uip_forw_LDADD = $(LDADD) $(ICONVLIB) +uip_forw_LDADD = $(LDADD) $(ICONVLIB) $(READLINELIB) uip_inc_SOURCES = uip/inc.c uip/scansbr.c uip/dropsbr.c uip/termsbr.c \ uip/popsbr.c @@ -302,7 +302,7 @@ uip_refile_SOURCES = uip/refile.c uip_repl_SOURCES = uip/repl.c uip/replsbr.c uip/whatnowproc.c \ uip/whatnowsbr.c uip/sendsbr.c uip/annosbr.c uip/distsbr.c -uip_repl_LDADD = $(LDADD) $(ICONVLIB) +uip_repl_LDADD = $(LDADD) $(ICONVLIB) $(READLINELIB) uip_rmf_SOURCES = uip/rmf.c @@ -320,6 +320,7 @@ uip_sortm_SOURCES = uip/sortm.c uip_whatnow_SOURCES = uip/whatnow.c uip/whatnowsbr.c uip/sendsbr.c \ uip/annosbr.c uip/distsbr.c +uip_whatnow_LDADD = $(LDADD) $(READLINELIB) uip_whom_SOURCES = uip/whom.c uip/distsbr.c @@ -476,7 +477,8 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c sbr/atooi.c sbr/brkstring.c \ sbr/snprintb.c sbr/ssequal.c sbr/strcasecmp.c \ sbr/strindex.c sbr/trimcpy.c sbr/uprf.c sbr/vfgets.c \ sbr/fmt_def.c sbr/m_msgdef.c sbr/mf.c sbr/utils.c \ - sbr/m_mktemp.c config/config.c config/version.c + sbr/m_mktemp.c sbr/getansreadline.c config/config.c \ + config/version.c sbr_libmh_a_CPPFLAGS = -I./sbr -DNMHETCDIR='"$(sysconfdir)"' \ -DMAILSPOOL='"$(mailspool)"' \ diff --git a/configure.ac b/configure.ac index 0b512b3..480c8be 100644 --- a/configure.ac +++ b/configure.ac @@ -113,6 +113,8 @@ AS_IF([test x"$with_mts" = x"smtp"], [MTS="smtp"], [MTS="smtp"]) AC_SUBST([MTS])dnl +NMH_READLINE + dnl What should be the default pager? AC_ARG_WITH([pager], AS_HELP_STRING([--with-pager=PAGER],[specify the default pager])) diff --git a/docs/pending-release-notes b/docs/pending-release-notes index a867299..fa8147c 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -57,3 +57,5 @@ Things to add to the release notes for the next full release: obsolete/deprecated: -noatfile will become the default in the next nmh release. If there are no requests to maintain -atfile, it will be removed in the future. +- Added support for readline editing and command/filename completion at + the WhatNow? prompt diff --git a/h/prototypes.h b/h/prototypes.h index 935c593..38357a8 100644 --- a/h/prototypes.h +++ b/h/prototypes.h @@ -57,6 +57,9 @@ struct msgs *folder_read (char *); struct msgs *folder_realloc (struct msgs *, int, int); int gans (char *, struct swit *); char **getans (char *, struct swit *); +#ifdef READLINE_SUPPORT +char **getans_via_readline (char *, struct swit *); +#endif /* READLINE_SUPPORT */ int getanswer (char *); char **getarguments (char *, int, char **, int); char *get_charset(void); diff --git a/m4/readline.m4 b/m4/readline.m4 new file mode 100644 index 0000000..7a2710d --- /dev/null +++ b/m4/readline.m4 @@ -0,0 +1,25 @@ +dnl +dnl Our readline heuristic. If we haven't been asked about readline, then +dnl try to compile with it. If we've been asked for it, then we fail +dnl if we cannot use it. If we were explicitly NOT asked for it, then +dnl don't even try to use it. +dnl + +AC_DEFUN([NMH_READLINE], +[AC_ARG_WITH([readline], + AS_HELP_STRING([--with-readline], + [enable readline editing for whatnow (default=maybe)]), + [], [with_readline=maybe]) +AS_IF([test x"$with_readline" = xyes -o x"$with_readline" = xmaybe], + [save_LIBS="$LIBS" + LIBS= + AC_SEARCH_LIBS([readline], [readline editline], + [READLINELIB="$LIBS" + AC_DEFINE([READLINE_SUPPORT], [1], + [Support for using readline() in whatnow])], + [AS_IF([test x"$with_readline" = xyes], + [AC_MSG_ERROR([Unable to find a readline library])])]) + LIBS="$save_LIBS"]) +]) + +AC_SUBST([READLINELIB]) diff --git a/sbr/getansreadline.c b/sbr/getansreadline.c new file mode 100644 index 0000000..0daaa49 --- /dev/null +++ b/sbr/getansreadline.c @@ -0,0 +1,212 @@ + +/* + * getansreadline.c -- get an answer from the user, with readline + * + * This code is Copyright (c) 2012, by the authors of nmh. See the + * COPYRIGHT file in the root directory of the nmh distribution for + * complete copyright information. + */ + +#include +#include +#include +#include +#include + +#ifdef READLINE_SUPPORT +#include +#include + +static struct swit *rl_cmds; + +static char *nmh_command_generator(const char *, int); +static char **nmh_completion(const char *, int, int); +static void initialize_readline(void); + +static char ansbuf[BUFSIZ]; +static sigjmp_buf sigenv; + +#if 0 +/* + * static prototypes + */ +static void intrser (int); + + +char ** +getans (char *prompt, struct swit *ansp) +{ + int i; + SIGNAL_HANDLER istat = NULL; + char *cp, **cpp; + + if (!(sigsetjmp(sigenv, 1))) { + istat = SIGNAL (SIGINT, intrser); + } else { + SIGNAL (SIGINT, istat); + return NULL; + } + + for (;;) { + printf ("%s", prompt); + fflush (stdout); + cp = ansbuf; + while ((i = getchar ()) != '\n') { + if (i == EOF) { + /* + * If we get an EOF, return + */ + if (feof(stdin)) + siglongjmp (sigenv, 1); + + /* + * For errors, if we get an EINTR that means that we got + * a signal and we should retry. If we get another error, + * then just return. + */ + + else if (ferror(stdin)) { + if (errno == EINTR) { + clearerr(stdin); + continue; + } + fprintf(stderr, "\nError %s during read\n", + strerror(errno)); + siglongjmp (sigenv, 1); + } else { + /* + * Just for completeness's sake ... + */ + + fprintf(stderr, "\nUnknown problem in getchar()\n"); + siglongjmp (sigenv, 1); + } + } + if (cp < &ansbuf[sizeof ansbuf - 1]) + *cp++ = i; + } + *cp = '\0'; + if (ansbuf[0] == '?' || cp == ansbuf) { + printf ("Options are:\n"); + print_sw (ALL, ansp, "", stdout); + continue; + } + cpp = brkstring (ansbuf, " ", NULL); + switch (smatch (*cpp, ansp)) { + case AMBIGSW: + ambigsw (*cpp, ansp); + continue; + case UNKWNSW: + printf (" -%s unknown. Hit for help.\n", *cpp); + continue; + default: + SIGNAL (SIGINT, istat); + return cpp; + } + } +} + + +static void +intrser (int i) +{ + NMH_UNUSED (i); + + /* + * should this be siglongjmp? + */ + siglongjmp (sigenv, 1); +} +#endif + +/* + * getans, but with readline support + */ + +char ** +getans_via_readline(char *prompt, struct swit *ansp) +{ + char *ans, **cpp; + + initialize_readline(); + rl_cmds = ansp; + + for (;;) { + ans = readline(prompt); + /* + * If we get an EOF, return + */ + + if (ans == NULL) + return NULL; + + if (ans[0] == '?' || ans[0] == '\0') { + printf("Options are:\n"); + print_sw(ALL, ansp, "", stdout); + free(ans); + continue; + } + add_history(ans); + strncpy(ansbuf, ans, sizeof(ansbuf)); + ansbuf[sizeof(ansbuf) - 1] = '\0'; + cpp = brkstring(ansbuf, " ", NULL); + switch (smatch(*cpp, ansp)) { + case AMBIGSW: + ambigsw(*cpp, ansp); + continue; + case UNKWNSW: + printf(" -%s unknown. Hit for help.\n", *cpp); + continue; + default: + free(ans); + return cpp; + } + free(ans); + } +} + +static void +initialize_readline(void) +{ + rl_readline_name = "Nmh"; + rl_attempted_completion_function = nmh_completion; +} + +static char ** +nmh_completion(const char *text, int start, int end) +{ + char **matches; + + matches = (char **) NULL; + + if (start == 0) + matches = rl_completion_matches(text, nmh_command_generator); + + return matches; +} + +static char * +nmh_command_generator(const char *text, int state) +{ + static int list_index, len; + char *name, *p; + char buf[256]; + + if (!state) { + list_index = 0; + len = strlen(text); + } + + while (name = rl_cmds[list_index].sw) { + list_index++; + strncpy(buf, name, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + p = *brkstring(buf, " ", NULL); + if (strncmp(p, text, len) == 0) + return strdup(p); + } + + return NULL; +} +#endif /* READLINE_SUPPORT */ + diff --git a/uip/whatnowsbr.c b/uip/whatnowsbr.c index 7b63762..6830bfb 100644 --- a/uip/whatnowsbr.c +++ b/uip/whatnowsbr.c @@ -249,7 +249,11 @@ WhatNow (int argc, char **argv) snprintf (prompt, sizeof(prompt), myprompt, invo_name); for (;;) { +#ifdef READLINE_SUPPORT + if (!(argp = getans_via_readline (prompt, aleqs))) { +#else /* ! READLINE_SUPPORT */ if (!(argp = getans (prompt, aleqs))) { +#endif /* READLINE_SUPPORT */ unlink (LINK); done (1); } -- 1.7.10.4