Support for readline command history, editing, and completion at the
authorKen Hornstein <kenh@pobox.com>
Wed, 21 Mar 2012 19:27:15 +0000 (15:27 -0400)
committerKen Hornstein <kenh@pobox.com>
Wed, 21 Mar 2012 19:27:15 +0000 (15:27 -0400)
WhatNow? prompt.  Based on work by Steve Rader.

Makefile.am
configure.ac
docs/pending-release-notes
h/prototypes.h
m4/readline.m4 [new file with mode: 0644]
sbr/getansreadline.c [new file with mode: 0644]
uip/whatnowsbr.c

index 0727fea..14d1dd1 100644 (file)
@@ -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)"' \
index 0b512b3..480c8be 100644 (file)
@@ -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]))
index a867299..fa8147c 100644 (file)
@@ -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
index 935c593..38357a8 100644 (file)
@@ -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 (file)
index 0000000..7a2710d
--- /dev/null
@@ -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 (file)
index 0000000..0daaa49
--- /dev/null
@@ -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 <h/mh.h>
+#include <h/signals.h>
+#include <h/m_setjmp.h>
+#include <signal.h>
+#include <errno.h>
+
+#ifdef READLINE_SUPPORT
+#include <readline/readline.h>
+#include <readline/history.h>
+
+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 <CR> 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 <CR> 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 */
+
index 7b63762..6830bfb 100644 (file)
@@ -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);
        }